python-appie
python-appie is an unofficial async Python client for the Albert Heijn API.
Full documentation is available in English and Dutch. The source for the docs lives in docs/ and is built from mkdocs.yml.
Changelog tracks release history.
Releases are intended to publish to PyPI as python-appie from version tags via GitHub Actions.
Install
uv add python-appie
Or:
pip install python-appie
For local development in this repository:
uv sync --extra dev
pre-commit install
Quick start
Authenticate once:
uv run appie-login
This opens Chrome for an interactive AH login and captures the OAuth redirect code automatically. If automatic capture cannot start, the CLI falls back to asking for the redirect URL or raw code manually.
Then use the client:
import asyncio
from appie import AHClient
async def main() -> None:
async with AHClient() as client:
products = await client.products.search("melk", limit=3)
for product in products:
print(product)
asyncio.run(main())
Tokens are stored in ~/.config/appie/tokens.json and refreshed automatically when they are close to expiring.
Features
Authentication
appie-loginCLI for browser-based login- automatic code capture from the AH redirect flow
- token persistence in
~/.config/appie/tokens.json - automatic token refresh using the stored refresh token
Products
- search products via
client.products.search(query, limit=10) - fetch a single product via
client.products.get(product_id) - product results include current price, original price, bonus metadata, and biologisch-related markers when available
Example:
import asyncio
from appie import AHClient
async def main() -> None:
async with AHClient() as client:
product = await client.products.get(1525)
print(
product.title,
product.price,
product.original_price,
product.is_bonus,
product.bonus_label,
product.is_organic,
)
asyncio.run(main())
Expected outcome:
AH Halfvolle melk 1.29 1.49 True 2e halve prijs True
Receipts
- list in-store POS receipt summaries via
client.receipts.list_all(limit=50) - fetch a receipt with line items via
client.receipts.get_pos_receipt(receipt_id)
Important:
list_all() and list_pos_receipts() return receipt summaries. In those results, products is intentionally empty.
To retrieve line items, call get_pos_receipt() with a receipt ID from the summary list.
Example:
import asyncio
from appie import AHClient
async def main() -> None:
async with AHClient() as client:
receipts = await client.receipts.list_all(limit=5)
detailed = await client.receipts.get_pos_receipt(receipts[0].id)
print(detailed)
asyncio.run(main())
Shopping lists
- add an item via
client.lists.add_item(description, quantity=1, product_id=None) - read the current shopping list via
client.lists.get_list() - remove one item via
client.lists.remove_item(item_id) - clear the entire list via
client.lists.clear() - use
MockAHClientfor local development and tests without touching AH
Example:
import asyncio
from appie import AHClient
async def main() -> None:
async with AHClient() as client:
await client.lists.add_item("Halfvolle melk", quantity=2)
items = await client.lists.get_list()
print(items)
asyncio.run(main())
Note:
the ShoppingListItem.id returned by get_list() is an opaque removal key designed for remove_item(item_id). It should be treated as an implementation detail rather than a stable AH server identifier.
Mocking and downstream tests
MockAHClient()provides an in-memory drop-in client for local developmentclient.mock.callsandclient.mock.last_callcapture what your code didclient.mock.next_response(operation, value)seeds a one-shot resultclient.mock.next_error(operation, exc)seeds a one-shot failureclient.mock.set_scenario(operation, delay_ms=..., error=...)applies persistent delay/error behaviorappie.pytest_pluginprovides pytest fixtures for downstream packages
Example:
import asyncio
from appie import MockAHClient
async def main() -> None:
async with MockAHClient() as client:
client.mock.next_response("products.search", [])
products = await client.products.search("melk")
print(products)
print(client.mock.last_call)
asyncio.run(main())
Expected outcome:
[]
AppieMockCall(operation='products.search', params={'query': 'melk', 'limit': 10}, result=[], error=None)
Pytest plugin example:
# tests/conftest.py
pytest_plugins = ["appie.pytest_plugin"]
import pytest
@pytest.mark.asyncio
async def test_checkout_uses_expected_query(appie_mock):
await appie_mock.products.search("melk", limit=3)
assert appie_mock.mock.last_call is not None
assert appie_mock.mock.last_call.params == {"query": "melk", "limit": 3}
Expected outcome:
- the test runs without touching AH
- the recorded call proves what your code sent into
python-appie
API overview
Main client
AHClient()MockAHClient()MockAHClient().mockawait client.login()await client.graphql(query, variables=None)
Auth client
AHAuthClient.get_anonymous_token()AHAuthClient.login_with_code(code)AHAuthClient.refresh_token(refresh_token)
Sub-APIs
client.products.search(query, limit=10)client.products.get(product_id)client.receipts.list_pos_receipts(limit=50)client.receipts.list_all(limit=50)client.receipts.get_pos_receipt(receipt_id)client.lists.add_item(description, quantity=1, product_id=None)client.lists.get_list()client.lists.remove_item(item_id)client.lists.clear()
Mock helpers
client.mock.callsclient.mock.last_callclient.mock.clear_calls()client.mock.next_response(operation, value)client.mock.next_error(operation, exc)client.mock.clear_seeded_responses()client.mock.set_scenario(operation, delay_ms=0, error=None)client.mock.clear_scenarios()
Development
Run checks locally:
uv run ruff format .
uv run --extra dev ruff check .
uv run --extra dev pyright
uv run --extra dev pytest
uv run --extra dev mkdocs build --strict
Notes
- This client is unofficial and may break when Albert Heijn changes its backend.
- Receipt support currently covers in-store POS receipts.
- Shopping-list read, add, remove, and clear are implemented against the live main-list endpoint.
- Receipt summaries do not include line items; call
get_pos_receipt()for a detailed receipt. - Endpoint discovery for this package is inspired by gwillem/appie-go.
Metadata
Release files for python-appie 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| python_appie-0.2.2.tar.gz | 99.3 kB | With provenance | Details for python_appie-0.2.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| python_appie-0.2.2-py3-none-any.whl | Python 3 | none | any | Details for python_appie-0.2.2-py3-none-any.whl |
Total release size: 118.1 kB
Release files / python_appie-0.2.2.tar.gz
| Download URL | python_appie-0.2.2.tar.gz |
|---|---|
| Size | 99.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2a8065784fa5e20cc493ea61c34edb265eacf7942e4c3d341fabcacfa2555917
|
|
BLAKE2b-256 checksum How to use checksums |
fd1394cc6e95d10cc2d4a35e951f7b859665f6a7903b82274ed470f24a1ade23
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency logRelease files / python_appie-0.2.2-py3-none-any.whl
| Download URL | python_appie-0.2.2-py3-none-any.whl |
|---|---|
| Size | 18.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4179b601c22167e03996d501b0e1ac30cb7ea4bc0eab78b417689f39d274f662
|
|
BLAKE2b-256 checksum How to use checksums |
e8080634bbfb8c13e896b589708e0b387be14a54dba9fac31668e1660dc0df75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency logSecurity for python-appie 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for python_appie-0.2.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.