Skip to main content

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.

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-login CLI 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)

Example:

import asyncio

from appie import AHClient


async def main() -> None:
    async with AHClient() as client:
        product = await client.products.get(1525)
        print(product)


asyncio.run(main())

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 MockAHClient for 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 development
  • client.mock.calls and client.mock.last_call capture what your code did
  • client.mock.next_response(operation, value) seeds a one-shot result
  • client.mock.next_error(operation, exc) seeds a one-shot failure
  • client.mock.set_scenario(operation, delay_ms=..., error=...) applies persistent delay/error behavior
  • appie.pytest_plugin provides 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().mock
  • await 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.calls
  • client.mock.last_call
  • client.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

python_appie-0.2.0.tar.gz (97.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

python_appie-0.2.0-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

Details for the file python_appie-0.2.0.tar.gz.

File metadata

  • Download URL: python_appie-0.2.0.tar.gz
  • Upload date:
  • Size: 97.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for python_appie-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8edb2d5df9598ddba5907c395ab833bd79296b59fa0a0df4aea6a56f1b6bf40f
MD5 57bbf2c33a0a675ef348c399a86f18ef
BLAKE2b-256 6b59a0796b64d56ba415a764353bae08b5e880895248c6eec7166794ab5f0e52

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_appie-0.2.0.tar.gz:

Publisher: pypi.yml on tijnschouten/appie

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file python_appie-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: python_appie-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 17.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for python_appie-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4519c7d66f87f37bb33f01bee357cb5f41dfcbbd39f78516b7163c39773bc943
MD5 0044d5e612de5e8bed917d83d50c431b
BLAKE2b-256 be16b4f2c1438f31e0a5d335e4192c6329e1258d4c651189ed4ff5e4631eca30

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_appie-0.2.0-py3-none-any.whl:

Publisher: pypi.yml on tijnschouten/appie

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page