Skip to content

Instantly share code, notes, and snippets.

@jabbink
Last active June 11, 2024 09:12
Show Gist options
  • Save jabbink/8bfa44bdfc535d696b340c46d228fdd1 to your computer and use it in GitHub Desktop.
Save jabbink/8bfa44bdfc535d696b340c46d228fdd1 to your computer and use it in GitHub Desktop.
Interact with the Albert Heijn mobile app API to retrieve receipt data, and other things

AH API

Always use User-Agent: Appie/8.22.3 and Content-Type: application/json
Technically there is more information about your device and user ID after it, but the server does not seem to care

Token

If you have a valid access_token, add it as a header in request
Authorization: Bearer access_token

Get token

Anonymous token

Get a token:
POST https://api.ah.nl/mobile-auth/v1/auth/token/anonymous

{
  "clientId": "appie"
}

Returns:

{
  "access_token": "USERID_ACCESSTOKEN",
  "refresh_token": "REFRESHTOKEN",
  "expires_in": 7199
}

User specific token

Sign in via browser (set User-Agent)
Visit https://login.ah.nl/secure/oauth/authorize?client_id=appie&redirect_uri=appie://login-exit&response_type=code
Login, page should reply with 303 See Other and something like Location: appie://login-exit?code=CODE

Take CODE and

POST https://api.ah.nl/mobile-auth/v1/auth/token

{
  "clientId": "appie",
  "code": "CODE"
}

Returns:

{
  "access_token": "USERID_ACCESSTOKEN",
  "refresh_token": "REFRESHTOKEN",
  "expires_in": 7199
}

Refresh token

POST https://api.ah.nl/mobile-auth/v1/auth/token/refresh

{
  "clientId": "appie",
  "refreshToken": "REFRESHTOKEN"
}

Returns:

{
  "access_token": "USERID_ACCESSTOKEN",
  "refresh_token": "REFRESHTOKEN",
  "expires_in": 7199
}

Find products

GET https://api.ah.nl/mobile-services/product/search/v2?query=QUERY&sortOn=RELEVANCE
See reply example in search.json

Get receipts (signed in)

GET https://api.ah.nl/mobile-services/v1/receipts
See reply example in receipts.json

Get specific receipt (signed in)

GET https://api.ah.nl/mobile-services/v2/receipts/TRANSACTIONID
See reply example in receipt.json

If you want to use this to crossmatch transactions from your bank statement, look for the text in "first": "Authorization code" (and for old receipts it's "third":"Autorisatiecode")

{
"receiptUiItems": [
{
"type": "ah-logo",
"style": "AH"
},
{
"type": "text",
"value": "Albert Heijn REMOVED",
"alignment": "CENTER",
"isBold": false
},
{
"type": "text",
"value": "REMOVED",
"alignment": "CENTER",
"isBold": false
},
{
"type": "text",
"value": "Tel: REMOVED",
"alignment": "CENTER",
"isBold": false
},
{
"type": "spacer"
},
{
"type": "products-header"
},
{
"type": "divider"
},
{
"type": "product",
"description": "BONUSKAART",
"amount": "REMOVED"
},
{
"type": "product",
"quantity": "1",
"description": "REMOVED REGULAR",
"amount": "1,00",
"indicator": ""
},
{
"type": "product",
"quantity": "1",
"description": "REMOVED BONUS PRODUCT",
"amount": "0,35",
"indicator": "B"
},
{
"type": "divider"
},
{
"type": "subtotal",
"quantity": "1",
"text": "SUBTOTAAL",
"amount": "1,35"
},
{
"type": "spacer"
},
{
"type": "product",
"quantity": "BONUS",
"description": "REMOVED BONUS PRODUCT",
"amount": "-0,12"
},
{
"type": "spacer"
},
{
"type": "total",
"label": "UW VOORDEEL",
"price": "0,12"
},
{
"type": "product",
"description": "Waarvan"
},
{
"type": "product",
"description": "BONUS BOX",
"amount": "0,00"
},
{
"type": "divider"
},
{
"type": "spacer"
},
{
"type": "total",
"label": "TOTAAL",
"price": "1,23"
},
{
"type": "text",
"value": "SPAARACTIES:",
"alignment": "LEFT",
"isBold": true
},
{
"type": "product",
"quantity": "123",
"description": "eSPAARZEGELS"
},
{
"type": "spacer"
},
{
"type": "text",
"value": "BETAALD MET:",
"alignment": "LEFT",
"isBold": false
},
{
"type": "product",
"description": "PINNEN",
"amount": "1,23"
},
{
"type": "spacer"
},
{
"type": "four-text-column",
"first": "POI: REMOVED",
"third": ""
},
{
"type": "four-text-column",
"first": "CLIENT TICKET",
"third": "Terminal",
"fourth": "REMOVED"
},
{
"type": "four-text-column",
"first": "Merchant",
"second": "1",
"third": "Period",
"fourth": "1"
},
{
"type": "four-text-column",
"first": "Transaction",
"second": "REMOVED",
"third": "MAESTRO"
},
{
"type": "four-text-column",
"first": "(REMOVED)",
"third": "MAESTRO"
},
{
"type": "four-text-column",
"first": "Card",
"second": "REMOVED",
"third": "Card sequence number",
"fourth": "0"
},
{
"type": "four-text-column",
"first": "PAYMENT",
"third": "Date",
"fourth": "01/01/1970 00:00"
},
{
"type": "four-text-column",
"first": "Authorization code",
"second": "CODE MATCHED ON YOUR BANK STATEMENT",
"third": "Total",
"fourth": "1,23 EUR"
},
{
"type": "four-text-column",
"first": "Contactless",
"third": "Read Method CHIP"
},
{
"type": "spacer"
},
{
"type": "vat",
"left": "BTW",
"center": "OVER",
"right": "EUR"
},
{
"type": "vat",
"left": "9%",
"center": "1,23",
"right": "0,12"
},
{
"type": "vat",
"left": "TOTAAL",
"center": "1,23",
"right": "0,12"
},
{
"type": "spacer"
},
{
"type": "tech-info",
"store": 1,
"lane": 1,
"transaction": 1,
"dateTime": "1970-01-01T00:00:00Z"
},
{
"type": "spacer"
},
{
"type": "text",
"value": "",
"alignment": "CENTER",
"isBold": false
},
{
"type": "text",
"value": "",
"alignment": "CENTER",
"isBold": false
},
{
"type": "text",
"value": "",
"alignment": "CENTER",
"isBold": false
},
{
"type": "spacer"
}
],
"storeId": 1,
"transactionMoment": "1970-01-01T00:00:00Z"
}
[
{
"transactionId": "TRANSACTIONID",
"transactionMoment": "1970-01-0T00:0:00Z",
"total": {
"amount": {
"amount": 1.23,
"currency": "EUR"
}
},
"totalDiscount": {
"amount": 0.12,
"currency": "EUR"
}
}
]
{
"page": {
"size": 30,
"totalElements": 180,
"totalPages": 6,
"number": 0
},
"products": [
{
"webshopId": 132597,
"hqId": 788626,
"title": "Lipton Ice tea green",
"salesUnitSize": "1,5 l",
"unitPriceDescription": "normale prijs per liter €1.77",
"images": [
{
"width": 48,
"height": 48,
"url": "https://static.ah.nl/dam/product/AHI_43545239383733303439?revLabel=1&rendition=48x48_GIF&fileType=binary"
},
{
"width": 80,
"height": 80,
"url": "https://static.ah.nl/dam/product/AHI_43545239383733303439?revLabel=1&rendition=80x80_JPG&fileType=binary"
},
{
"width": 708,
"height": 708,
"url": "https://static.ah.nl/dam/product/AHI_43545239383733303439?revLabel=1&rendition=LowRes_JPG&fileType=binary"
},
{
"width": 200,
"height": 200,
"url": "https://static.ah.nl/dam/product/AHI_43545239383733303439?revLabel=1&rendition=200x200_JPG_Q85&fileType=binary"
},
{
"width": 400,
"height": 400,
"url": "https://static.ah.nl/dam/product/AHI_43545239383733303439?revLabel=1&rendition=400x400_JPG_Q85&fileType=binary"
},
{
"width": 800,
"height": 800,
"url": "https://static.ah.nl/dam/product/AHI_43545239383733303439?revLabel=1&rendition=800x800_JPG_Q90&fileType=binary"
}
],
"bonusStartDate": "2022-08-15",
"bonusEndDate": "2022-08-28",
"discountType": "AHOO",
"segmentType": "AHOO",
"promotionType": "AHONLINE",
"bonusMechanism": "KEUZEDEAL 10% KORTING",
"currentPrice": 2.38,
"priceBeforeBonus": 2.65,
"orderAvailabilityStatus": "IN_ASSORTMENT",
"mainCategory": "Frisdrank, sappen, koffie, thee",
"subCategory": "Ice tea koolzuurvrij (pakken)",
"brand": "Lipton",
"shopType": "AH",
"availableOnline": true,
"isPreviouslyBought": false,
"descriptionHighlights": "<p>Fris en licht, laag in suiker met Lipton ice tea green original.</p><p><ul><li>Fris en licht, laag in suiker met Lipton ice tea green original</li><li>Heerlijk verfrissende, koolzuurvrije ijsthee, laag in suiker</li><li>Deze frisdrank is het lekkerst om ijskoud en samen met vrienden te drinken</li><li>Alle Lipton ice tea is 100% rainforest alliance gecertificeerd</li></ul></p>",
"propertyIcons": [
"vegan"
],
"nix18": false,
"isStapelBonus": false,
"extraDescriptions": [
"Alleen geldig voor online bestellingen.",
"10% korting op deze Keuze Deal."
],
"bonusSegmentId": -13879,
"bonusSegmentDescription": "Ola, Lipton & Lay's",
"isBonus": true,
"hasListPrice": false,
"descriptionFull": "Lipton ice tea green original is een heerlijk verfrissende drank met de lichte smaak van groene thee: dat heb je soms gewoon even nodig! De groene ijsthee is koolzuurvrij. Lipton ice tea green is laag in suiker en daardoor de perfecte verfrissing voor tussendoor. Het maken van Lipton ice tea begint natuurlijk bij de thee. De green variant is gemaakt op basis van groene thee-extracten. Fris en licht, laag in suiker met Lipton ice tea green. Lipton gebruikt 100% rainforest alliance gecertificeerde thee. Dit houdt in dat alle thee die wij gebruiken, afkomstig is van theeplantages waar wordt gewerkt met oog voor mens en milieu. Vanaf 2020 zijn alle 1,5 liter pakken van Lipton gemaakt van volledig recyclebaar materiaal. Onze pakken zijn namelijk ingekocht van 95% plantaardig materiaal en 5% gecertificeerd aluminium op basis van mass balance. Hiermee dragen wij bij aan de vermindering van de co2 uitstoot. Gooi deze verpakking inclusief de dop op de juiste Manier weg! Al meer dan 25 jaar zorgt Lipton ice tea voor verfrissing op de meest gezellige plekken van Nederland: op een zonnig terrasje, een zomers festival of gewoon bij jou thuis in de woonkamer.",
"isOrderable": true,
"isInfiniteBonus": false,
"isSample": false,
"isBonusPrice": true,
"isSponsored": false,
"isVirtualBundle": false,
"productCount": 14,
"multipleItemPromotion": true
},
],
"links": {
"first": {
"href": "https://ms.ah.nl:8080/mobile-services/product/search/v2?application=AHWEBSHOP&availableOnline=true&bonus=NONE&orderable=any&query=QUERY&sortBy=RELEVANCE&page=0&size=30"
},
"current": {
"href": "https://ms.ah.nl:8080/mobile-services/product/search/v2?application=AHWEBSHOP&availableOnline=true&bonus=NONE&orderable=any&query=QUERY&sortBy=RELEVANCE&page=0&size=30"
},
"next": {
"href": "https://ms.ah.nl:8080/mobile-services/product/search/v2?application=AHWEBSHOP&availableOnline=true&bonus=NONE&orderable=any&query=QUERY&sortBy=RELEVANCE&page=1&size=30"
},
"last": {
"href": "https://ms.ah.nl:8080/mobile-services/product/search/v2?application=AHWEBSHOP&availableOnline=true&bonus=NONE&orderable=any&query=QUERY&sortBy=RELEVANCE&page=5&size=30"
}
},
"filters": [
{
"id": "brand",
"label": "Merk",
"options": [
{
"id": "AH",
"label": "AH",
"count": 18,
"display": true
},
{
"id": "AH Excellent",
"label": "AH Excellent",
"count": 5,
"display": true
},
{
"id": "Ambi Pur",
"label": "Ambi Pur",
"count": 1,
"display": true
},
{
"id": "Arizona",
"label": "Arizona",
"count": 13,
"display": true
},
{
"id": "Batu",
"label": "Batu",
"count": 2,
"display": true
},
{
"id": "Bozu",
"label": "Bozu",
"count": 2,
"display": true
},
{
"id": "Care",
"label": "Care",
"count": 3,
"display": true
},
{
"id": "Celestial Seasonings",
"label": "Celestial Seasonings",
"count": 5,
"display": true
},
{
"id": "ChariTea",
"label": "ChariTea",
"count": 2,
"display": true
},
{
"id": "Clipper",
"label": "Clipper",
"count": 16,
"display": true
},
{
"id": "Dove",
"label": "Dove",
"count": 2,
"display": true
},
{
"id": "Etos",
"label": "Etos",
"count": 1,
"display": true
},
{
"id": "Fuze Tea",
"label": "Fuze Tea",
"count": 27,
"display": true
},
{
"id": "Glorix",
"label": "Glorix",
"count": 1,
"display": true
},
{
"id": "Karvan Cévitam",
"label": "Karvan Cévitam",
"count": 1,
"display": true
},
{
"id": "Lipton",
"label": "Lipton",
"count": 49,
"display": true
},
{
"id": "Lux",
"label": "Lux",
"count": 1,
"display": true
},
{
"id": "Nestea",
"label": "Nestea",
"count": 2,
"display": true
},
{
"id": "Pickwick",
"label": "Pickwick",
"count": 19,
"display": true
},
{
"id": "The Gutsy Captain",
"label": "The Gutsy Captain",
"count": 3,
"display": true
},
{
"id": "Thursday Plantation",
"label": "Thursday Plantation",
"count": 1,
"display": true
},
{
"id": "Trésor Chinois",
"label": "Trésor Chinois",
"count": 1,
"display": true
},
{
"id": "Uggo",
"label": "Uggo",
"count": 3,
"display": true
},
{
"id": "Zonnatura",
"label": "Zonnatura",
"count": 1,
"display": true
}
],
"type": "BRAND",
"booleanFilter": false
},
{
"id": "taxonomy",
"label": "Soort",
"options": [
{
"id": "6421",
"label": "Deodorant",
"count": 2,
"display": true
},
{
"id": "11676",
"label": "Deodorant vrouwen",
"count": 2,
"display": true
},
{
"id": "1465",
"label": "Deoroller vrouwen",
"count": 1,
"display": true
},
{
"id": "1585",
"label": "Deospray vrouwen",
"count": 1,
"display": true
},
{
"id": "1964",
"label": "Douche",
"count": 4,
"display": true
},
{
"id": "11541",
"label": "Douche olie",
"count": 1,
"display": true
},
{
"id": "4858",
"label": "Douchegel",
"count": 1,
"display": true
},
{
"id": "1965",
"label": "Douchegel alle huidtypes",
"count": 1,
"display": true
},
{
"id": "4208",
"label": "Doucheschuim",
"count": 2,
"display": true
},
{
"id": "1083",
"label": "Frisdrank",
"count": 107,
"display": true
},
{
"id": "8316",
"label": "Frisse fruitdrank",
"count": 3,
"display": true
},
{
"id": "2395",
"label": "Frisse fruitdrank (pakken)",
"count": 3,
"display": true
},
{
"id": "2124",
"label": "Groene thee",
"count": 21,
"display": true
},
{
"id": "11524",
"label": "Handverzorging",
"count": 1,
"display": true
},
{
"id": "5779",
"label": "Handzeep",
"count": 1,
"display": true
},
{
"id": "18019",
"label": "Hard seltzer",
"count": 2,
"display": true
},
{
"id": "4293",
"label": "Huid",
"count": 1,
"display": true
},
{
"id": "1512",
"label": "IJskoffie",
"count": 1,
"display": true
},
{
"id": "4124",
"label": "Ice tea",
"count": 107,
"display": true
},
{
"id": "1174",
"label": "Ice tea koolzuurhoudend",
"count": 20,
"display": true
},
{
"id": "8909",
"label": "Ice tea koolzuurvrij",
"count": 87,
"display": true
},
{
"id": "8910",
"label": "Ice tea koolzuurvrij (blikjes)",
"count": 14,
"display": true
},
{
"id": "8314",
"label": "Ice tea koolzuurvrij (drinkpakjes)",
"count": 3,
"display": true
},
{
"id": "11389",
"label": "Ice tea koolzuurvrij (flessen)",
"count": 45,
"display": true
},
{
"id": "4125",
"label": "Ice tea koolzuurvrij (pakken)",
"count": 25,
"display": true
},
{
"id": "2763",
"label": "Koffie",
"count": 1,
"display": true
},
{
"id": "5113",
"label": "Kruidenthee",
"count": 9,
"display": true
},
{
"id": "2126",
"label": "Kruidenthee eenkops",
"count": 9,
"display": true
},
{
"id": "11540",
"label": "Lichaamsverzorging",
"count": 4,
"display": true
},
{
"id": "1523",
"label": "Limonadesiroop",
"count": 1,
"display": true
},
{
"id": "2125",
"label": "Losse thee",
"count": 4,
"display": true
},
{
"id": "1319",
"label": "Melk",
"count": 1,
"display": true
},
{
"id": "10170",
"label": "Melk (drinkpakjes)",
"count": 1,
"display": true
},
{
"id": "18401",
"label": "Mixdranken en seltzers",
"count": 2,
"display": true
},
{
"id": "8304",
"label": "Pakjes drinken",
"count": 4,
"display": true
},
{
"id": "4130",
"label": "Rooibosthee",
"count": 1,
"display": true
},
{
"id": "2223",
"label": "Siroop",
"count": 1,
"display": true
},
{
"id": "2120",
"label": "Thee",
"count": 55,
"display": true
},
{
"id": "1997",
"label": "Toiletblokken - vloeibaar",
"count": 2,
"display": true
},
{
"id": "1994",
"label": "Toiletreinigers & verfrissers",
"count": 2,
"display": true
},
{
"id": "11716",
"label": "Verzorging",
"count": 7,
"display": true
},
{
"id": "1966",
"label": "Vloeibare zeep",
"count": 1,
"display": true
},
{
"id": "1677",
"label": "Vruchtensappen & drank",
"count": 3,
"display": true
},
{
"id": "2122",
"label": "Vruchtenthee",
"count": 12,
"display": true
},
{
"id": "2129",
"label": "Witte thee",
"count": 1,
"display": true
},
{
"id": "2361",
"label": "Zelfzorg",
"count": 1,
"display": true
},
{
"id": "18281",
"label": "Zuivel drinkpakjes",
"count": 1,
"display": true
},
{
"id": "17859",
"label": "Zuivel tussendoortjes (voor thuis en onderweg)",
"count": 2,
"display": true
},
{
"id": "5158",
"label": "Zwarte thee",
"count": 7,
"display": true
},
{
"id": "2128",
"label": "Zwarte thee eenkops",
"count": 5,
"display": true
},
{
"id": "2123",
"label": "Zwarte thee meerkops",
"count": 2,
"display": true
}
],
"type": "TAXONOMY",
"booleanFilter": false
},
{
"id": "intolerance",
"label": "Allergie",
"options": [
{
"id": "sp_include_intolerance_geen_eieren",
"label": "eieren",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_gluten",
"label": "gluten",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_lactose",
"label": "lactose",
"count": 156,
"display": true
},
{
"id": "sp_include_intolerance_geen_lupine",
"label": "lupine",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_melk",
"label": "melk",
"count": 156,
"display": true
},
{
"id": "sp_include_intolerance_geen_mosterd",
"label": "mosterd",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_noten",
"label": "noten",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_pindas",
"label": "pinda's",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_schelpdieren",
"label": "schelpdieren",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_selderij",
"label": "selderij",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_sesam",
"label": "sesam",
"count": 159,
"display": true
},
{
"id": "sp_include_intolerance_geen_soja",
"label": "soja",
"count": 157,
"display": true
},
{
"id": "sp_include_intolerance_geen_sulfiet",
"label": "sulfiet",
"count": 145,
"display": true
},
{
"id": "sp_include_intolerance_geen_vis",
"label": "vis",
"count": 159,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": false
},
{
"id": "diet",
"label": "Dieet",
"options": [
{
"id": "sp_include_dieet_halal",
"label": "Halal",
"count": 5,
"display": true
},
{
"id": "sp_include_dieet_laag_suiker",
"label": "Laag suiker",
"count": 52,
"display": true
},
{
"id": "sp_include_dieet_laag_vet",
"label": "Laag vet",
"count": 116,
"display": true
},
{
"id": "sp_include_dieet_laag_zout",
"label": "Laag zout",
"count": 111,
"display": true
},
{
"id": "sp_include_dieet_veganistisch",
"label": "Veganistisch",
"count": 141,
"display": true
},
{
"id": "sp_include_dieet_vegetarisch",
"label": "Vegetarisch",
"count": 154,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": false
},
{
"id": "nutriscore",
"label": "Nutri-Score",
"options": [
{
"id": "b",
"label": "Score B",
"count": 9,
"display": true
},
{
"id": "c",
"label": "Score C",
"count": 8,
"display": true
},
{
"id": "d",
"label": "Score D",
"count": 3,
"display": true
}
],
"type": "NUTRISCORE",
"booleanFilter": false
},
{
"id": "bonus",
"label": "Bonus",
"options": [
{
"id": "Bonus",
"label": "Bonus",
"count": 20,
"display": true
}
],
"type": "BONUS",
"booleanFilter": true
},
{
"id": "sp_include_dieet_vegetarisch",
"label": "Vegetarisch",
"options": [
{
"id": "sp_include_dieet_vegetarisch",
"label": "Vegetarisch",
"count": 154,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": true
},
{
"id": "sp_include_dieet_veganistisch",
"label": "Veganistisch",
"options": [
{
"id": "sp_include_dieet_veganistisch",
"label": "Veganistisch",
"count": 141,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": true
},
{
"id": "np_biologisch",
"label": "Biologisch",
"options": [
{
"id": "np_biologisch",
"label": "Biologisch",
"count": 30,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": true
},
{
"id": "np_goedkoopje",
"label": "Prijsfavoriet",
"options": [
{
"id": "np_goedkoopje",
"label": "Prijsfavoriet",
"count": 3,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": true
},
{
"id": "np_voordeel",
"label": "Voordeelpakken",
"options": [
{
"id": "np_voordeel",
"label": "Voordeelpakken",
"count": 3,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": true
},
{
"id": "np_nieuw",
"label": "Nieuw",
"options": [
{
"id": "np_nieuw",
"label": "Nieuw",
"count": 2,
"display": true
}
],
"type": "PROPERTY",
"booleanFilter": true
}
],
"sortOn": [
"RELEVANCE",
"PRICEHIGHLOW",
"PRICELOWHIGH",
"TAXONOMY",
"PURCHASE_FREQUENCY",
"PURCHASE_DATE",
"PURCHASE_DEPARTMENT",
"NUTRISCORE"
],
"configuration": {
"googleBanners": {
"adUnitMainPath": "REMOVED",
"adUnitSecondaryPath": "REMOVED",
"customTemplateId": "REMOVED",
"divGptAd": "REMOVED"
}
},
"ads": [],
"taxonomyNodes": []
}
@janwytze
Copy link

janwytze commented Feb 2, 2023

Thanks for the research! 🚀

@ojkaas
Copy link

ojkaas commented Feb 15, 2023

Hi, thanks for the research! I am able to pull my grocery list items but when I try to search using: GET https://api.ah.nl/mobile-services/product/search/v2?query=QUERY&sortOn=RELEVANCE with a valid token.

I get the following back:
{
"status": 500,
"message": "Can not find application: 'null'",
"correlationId": "valid-uuid",
"timestamp": 1676460044.498667677
}

Any idea why it is not working for me?

@jabbink
Copy link
Author

jabbink commented Mar 1, 2023

Are you using the custom user agent all the way through, and not just for the token retrieval @ojkaas ?

@coduinix
Copy link

coduinix commented Mar 4, 2023

Great instructions on how to "login" 👌

Maybe another interesting resource to look at, some reverse-engineered OpenAPI specification of parts of the AH API: https://github.com/nickbouwhuis/Albert-Heijn-OpenAPI

@MarcelWeidum
Copy link

If you get the "Can not find application: 'null'" error add another header:

X-Application: AHWEBSHOP

@survivorbat
Copy link

This is really cool! I was thinking of creating a dashboard of sorts to see how much money I spend on products from my receipts. I am curious though, is usage of this api allowed by the terms of service?

@PimDoos
Copy link

PimDoos commented Oct 31, 2023

I am curious though, is usage of this api allowed by the terms of service?

The ToS specify that you cannot 'network' the AH App and don't reverse engineer it. Although a bit vague, it sounds like a no to me. Which is a shame, as I would love to integrate this into a python library for Home Assistant.

U mag de AH app niet distribueren en/of beschikbaar stellen op welke wijze dan ook en u mag de AH app niet aansluiten op een netwerk waardoor andere apparaten van de AH app gebruik zouden kunnen maken. U mag de AH app niet deassembleren, decompileren of onderwerpen aan reverse engineering.

@Teejay-first
Copy link

Could this be used to extract product data from Albert Heijn app? Or would that be against the terms of service? Everything is so vague

@jabbink
Copy link
Author

jabbink commented Dec 11, 2023

You can probably scrape out the entire product catalog, but that is against their terms of service.

@Teejay-first
Copy link

You can probably scrape out the entire product catalog, but that is against their terms of service.

I see. But scraping isn't what i'm after. I'm looking for live data, but that's probably not possible.

@JeffreyNijland
Copy link

Is there a way to get the 'Voedingswaarden' from the api?

@Gerben321
Copy link

How do you get your grocery list? (Mijn lijst) Searching works but I'd like to see my current list.

Also, is it possible to search by barcode? Want to integrate with Grocy.

@Astronautomator
Copy link

Anyone here knows how to retrieve a AH product with all its information with the bar code? This one 5410013116646 is the code from SPA sparkling water. Would like to get all product details of it using iOS shortcuts and the bar code scan step.

@jabbink
Copy link
Author

jabbink commented Apr 11, 2024

@Gerben321

Also, is it possible to search by barcode? Want to integrate with Grocy.

GET https://api.ah.nl/mobile-services/product/search/v1/gtin/[BARCODE] searches products based on the GTIN/barcode

Some information might be hidden behind their own webshopId (returned by the GTIN search) and can be queried here: GET https://api.ah.nl/mobile-services/product/detail/v4/fir/WEBSHOPID?includeActivatableDiscount=true/false

@JeffreyNijland

Is there a way to get the 'Voedingswaarden' from the api?

the search by webshopId returns nutrients

@Bennie337
Copy link

Anyone know how to search per category or sub category ID?

Querying for a specific product does return an ID but it's not clear yet how you could list products of one specific category as you do on the site.

@jabbink
Copy link
Author

jabbink commented Apr 13, 2024

@Bennie337

Categories: GET https://api.ah.nl/mobile-services/v1/product-shelves/categories

Sub categories: GET https://api.ah.nl/mobile-services/v1/product-shelves/categories/6401/sub-categories (this one is for "Aardappel, groente, fruit")

Sub category with products:

GET https://api.ah.nl/mobile-services/v1/product-shelves/categories/1789/sub-categories (has many potato products)
GET https://api.ah.nl/mobile-services/product/search/v2?sortOn=RELEVANCE&size=30&page=0&taxonomyId=1789&adType=TAXONOMY Searches on the category ID and directly returns the potato products

I tried this search query with 6401 as well (the app does not show it as having products), and it still works, returning a bunch of potato/vegetable/fruit options)

@Bennie337
Copy link

Thanks! I'm attempting to drill down as much as possible into the sub-categories to reduce the amount of products that need to be checked per page. I've noticed that iterating on pages where you've set the page size to 1000 products will return a 400 error at around the 3rd page, smaller page sizes also seem to return a 400 error around that same page count. I'm assuming this is a limitation of the API but please correct me if I'm wrong here.

@JorgeAnzola
Copy link

Is there a way to add items to the basket? I tried with the website API, but of course that's restricted:

curl --request POST \
  --url https://www.ah.nl/common/api/basket/v2/add \
  --header 'Authorization: Bearer 61852100_2e62-4bcb-b9a4-xxx' \
  --header 'Content-Type: application/json' \
  --header 'User-Agent: Appie/8.22.3' \
  --header 'X-Application: AHWEBSHOP' \
  --data '{"items":[{"quantity":1,"id":450534}]}'

@jabbink
Copy link
Author

jabbink commented Apr 17, 2024

@JorgeAnzola

Is there a way to add items to the basket?

curl --request PATCH --url 'https://api.ah.nl/mobile-services/shoppinglist/v2/items?orderBy=userInput&orderByParam=0' -H 'Authorization: Bearer TOKEN' -H 'X-Application: AHWEBSHOP' -H 'Content-Type: application/json; charset=UTF-8' -H 'User-Agent: Appie/8.63 Android/12-API31' --data '{"items":[{"originCode":"PRD","productId":450534,"quantity":1,"type":"SHOPPABLE"}]}'

Leaving out either originCode or type will result in some generic error response.

position may be optionally specified to change the ordering. Similarly, there is strikeThrough true/false to have it checked off

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment