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. 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-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)
  • 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 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.2.tar.gz (99.3 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.2-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

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

Hashes for python_appie-0.2.2.tar.gz
Algorithm Hash digest
SHA256 2a8065784fa5e20cc493ea61c34edb265eacf7942e4c3d341fabcacfa2555917
MD5 17a4f1bd16a81478a2ffadbfad3897b4
BLAKE2b-256 fd1394cc6e95d10cc2d4a35e951f7b859665f6a7903b82274ed470f24a1ade23

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_appie-0.2.2.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.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

Hashes for python_appie-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4179b601c22167e03996d501b0e1ac30cb7ea4bc0eab78b417689f39d274f662
MD5 cff870770d6aeeac52b15e0000806dc8
BLAKE2b-256 e8080634bbfb8c13e896b589708e0b387be14a54dba9fac31668e1660dc0df75

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_appie-0.2.2-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