Unofficial Python client for the Albert Heijn API.
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_appie-0.2.2.tar.gz.
File metadata
- Download URL: python_appie-0.2.2.tar.gz
- Upload date:
- Size: 99.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a8065784fa5e20cc493ea61c34edb265eacf7942e4c3d341fabcacfa2555917
|
|
| MD5 |
17a4f1bd16a81478a2ffadbfad3897b4
|
|
| BLAKE2b-256 |
fd1394cc6e95d10cc2d4a35e951f7b859665f6a7903b82274ed470f24a1ade23
|
Provenance
The following attestation bundles were made for python_appie-0.2.2.tar.gz:
Publisher:
pypi.yml on tijnschouten/appie
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_appie-0.2.2.tar.gz -
Subject digest:
2a8065784fa5e20cc493ea61c34edb265eacf7942e4c3d341fabcacfa2555917 - Sigstore transparency entry: 1155379332
- Sigstore integration time:
-
Permalink:
tijnschouten/appie@d2044ff530c734057233cf16a58869aceea43ede -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/tijnschouten
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@d2044ff530c734057233cf16a58869aceea43ede -
Trigger Event:
push
-
Statement type:
File details
Details for the file python_appie-0.2.2-py3-none-any.whl.
File metadata
- Download URL: python_appie-0.2.2-py3-none-any.whl
- Upload date:
- Size: 18.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4179b601c22167e03996d501b0e1ac30cb7ea4bc0eab78b417689f39d274f662
|
|
| MD5 |
cff870770d6aeeac52b15e0000806dc8
|
|
| BLAKE2b-256 |
e8080634bbfb8c13e896b589708e0b387be14a54dba9fac31668e1660dc0df75
|
Provenance
The following attestation bundles were made for python_appie-0.2.2-py3-none-any.whl:
Publisher:
pypi.yml on tijnschouten/appie
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_appie-0.2.2-py3-none-any.whl -
Subject digest:
4179b601c22167e03996d501b0e1ac30cb7ea4bc0eab78b417689f39d274f662 - Sigstore transparency entry: 1155379333
- Sigstore integration time:
-
Permalink:
tijnschouten/appie@d2044ff530c734057233cf16a58869aceea43ede -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/tijnschouten
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@d2044ff530c734057233cf16a58869aceea43ede -
Trigger Event:
push
-
Statement type: