Back to Skills
    šŸ¦ž

    supermarkt-prijzen

    Albert Heijn bonuses, product search, multi-store price

    By @cgnl
    View on GitHub
    SKILL.md
    ---
    name: supermarkt-prijzen
    description: Albert Heijn bonuses, product search, multi-store price comparison (12 supermarkets), recipe search by ingredients, and fridge scanner with vision AI.
    homepage: https://www.ah.nl
    metadata: {"openclaw":{"emoji":"šŸ›’","requires":{"bins":["python3","curl"]}}}
    ---
    
    # Albert Heijn API Skill
    
    Complete AH bonussen + producten + recepten via GraphQL (web) en OAuth (mobile).
    
    ## Features
    
    āœ… **Bonussen ophalen** (GraphQL, 200+ items, **geen login**)  
    āœ… **Producten zoeken** (REST API, 20k+ items, **geen login**)  
    āœ… **Recepten zoeken** (GraphQL, **geen login**)  
    āœ… **Multi-supermarkt prijsvergelijking** (Checkjebon.nl - 12 supermarkten, 107k producten)  
    āœ… **OAuth token flow** (mobile API access - alleen voor persoonlijke data)  
    āœ… **Fridge scanner** (vision AI → recepten → shopping list)
    
    ## Quick Start
    
    ### 1. Bonussen + Producten (GEEN LOGIN NODIG!)
    
    **Bonussen ophalen (200+ items):**
    ```bash
    ./ah-api.py bonuses --filter WEB_BONUS_PAGE --pretty
    ```
    
    **Producten zoeken (20.000+ items):**
    ```bash
    ./ah-api.py search --query "melk" --limit 10 --pretty
    ```
    
    **Recepten zoeken:**
    ```bash
    ./ah-recipes.py search --query "pasta carbonara" --pretty
    ```
    
    **Recept ophalen via URL:**
    ```bash
    ./ah-recipes.py url --url "https://www.ah.nl/allerhande/recept/R-R1187649/zoete-tortillachips" --pretty
    ```
    
    ✨ **Alles werkt zonder cookies!** Gebruikt `curl-cffi` met Chrome fingerprint.
    
    ### 2. OAuth Token Flow (mobile API)
    
    **Get initial token:**
    1. Open Appie app
    2. Tap profiel → instellingen → scroll down → "Developer" (hidden option)
    3. Tap "OAuth Code" → copy code
    4. Run binnen 30 seconden:
    ```bash
    curl -X POST 'https://api.ah.nl/mobile-auth/v1/auth/token' \
      -H 'Content-Type: application/json' \
      -H 'User-Agent: Appie/8.22.3' \
      -d '{"clientId":"appie","code":"PASTE_CODE_HERE"}'
    ```
    
    **Response:**
    ```json
    {
      "access_token": "USERID_TOKEN",
      "refresh_token": "REFRESH_TOKEN",
      "expires_in": 604798
    }
    ```
    
    **Save to ~/.ah_tokens.json:**
    ```bash
    echo '{"access_token":"...","refresh_token":"...","expires_in":604798}' > ~/.ah_tokens.json
    ```
    
    **Refresh token (na 7 dagen):**
    ```bash
    ./refresh-token.py
    ```
    
    ### 3. Multi-store price comparison
    
    **Search across 12 supermarkets:**
    ```bash
    ./checkjebon-search.py --compare "melk" --top 10
    ```
    
    **Stores:** AH, Jumbo, Lidl, Plus, Dekamarkt, Spar, Dirk, Hoogvliet, Poiesz, Aldi, Vomar, Ekoplaza
    
    ## Tools
    
    | Tool | Purpose |
    |------|---------|
    | `ah-api.py` | Cookie-based bonussen + producten (GraphQL + REST) |
    | `ah-recipes.py` | **NEW!** Recipe search by text or ingredients |
    | `fridge-scan.sh` | **NEW!** Scan fridge with camera/peekaboo |
    | `smart-cook.sh` | **NEW!** Complete workflow: scan → recipes → shopping |
    | `get-bonuses.py` | Legacy bonus tool (GraphQL only) |
    | `checkjebon-search.py` | Multi-store prijsvergelijking |
    | `refresh-token.py` | OAuth token vernieuwen |
    | `setup-cookies.sh` | Cookie setup helper |
    
    ## Technical Details
    
    ### Authentication (NONE NEEDED!)
    
    **Previous:** Required session cookies from browser  
    **Now:** Uses `curl-cffi` with `impersonate='chrome120'`
    
    **How it works:**
    - curl-cffi sends real Chrome TLS fingerprints
    - AH's bot detection sees it as a normal browser
    - No cookies, no login, no setup required! šŸŽ‰
    
    **Only needed for:**
    - OAuth mobile API (receipts, personal data) - requires app login
    
    ### GraphQL Bonuses API
    
    **Endpoint:** `https://www.ah.nl/gql`
    
    **Query:**
    ```graphql
    query FetchBonusPromotions($periodStart: String, $periodEnd: String) {
      bonusPromotions(
        filterSet: WEB_BONUS_PAGE
        input: {
          periodStart: "2026-02-01"
          periodEnd: "2026-02-08"
          filterUnavailableProducts: false
          forcePromotionVisibility: true
        }
      ) {
        id title promotionType
        price { now { amount } }
        product { title category }
      }
    }
    ```
    
    **Available filters:**
    - `WEB_BONUS_PAGE` - Alle bonussen (326 items!)
    - `APP_PERSONAL` - Persoonlijke aanbiedingen
    - `APP_BONUS_BOX` - Bonus box
    - `COUPON` - Kortingsbonnen
    - `FREE_DELIVERY` - Gratis bezorging
    - `SPOTLIGHT` - Spotlight aanbiedingen
    
    ### REST Product Search
    
    **Endpoint:** `https://www.ah.nl/zoeken/api/products/search`
    
    **Example:**
    ```bash
    curl 'https://www.ah.nl/zoeken/api/products/search?query=melk' \
      -H 'Cookie: SSOC=...; jsessionid_myah=...' \
      --user-agent 'Mozilla/5.0 (compatible; AH-Bot/1.0)'
    ```
    
    **Response:**
    ```json
    {
      "cards": [
        {
          "products": [
            {
              "id": 441199,
              "title": "Campina Halfvolle melk",
              "price": { "now": 1.99, "unitSize": "1,5 l" }
            }
          ]
        }
      ]
    }
    ```
    
    ### OAuth Mobile API
    
    **Authorization:** `https://login.ah.nl/secure/oauth/authorize`  
    **Token exchange:** `https://api.ah.nl/mobile-auth/v1/auth/token`  
    **Token refresh:** `https://api.ah.nl/mobile-auth/v1/auth/token/refresh`
    
    **Token lifetime:** 7 days (604798 seconds)
    
    **Known endpoints (from gist):**
    - `/mobile-services/v1/receipts` - All receipts
    - `/mobile-services/v2/receipts/{id}` - Specific receipt
    - `/mobile-services/product/search/v2` - Product search
    
    **Note:** Some mobile endpoints currently return 500 errors (infrastructure issues).
    
    ### Why curl-cffi?
    
    AH uses **Cloudflare + Akamai bot detection**. Normal `requests` → 403 Access Denied.
    
    **curl-cffi** uses real Chrome TLS fingerprints:
    ```python
    from curl_cffi import requests
    response = requests.get(url, impersonate="chrome120")  # ← Magic!
    ```
    
    ## Checkjebon Multi-Store Data
    
    **Source:** `https://raw.githubusercontent.com/supermarkt/checkjebon/main/data/supermarkets.json`
    
    **Stats:**
    - File size: 10.3MB
    - Total products: 106,991
    - Daily updates
    - 24h local cache
    
    **Usage:**
    ```bash
    # Find cheapest
    ./checkjebon-search.py --compare "bier" --top 5
    
    # Specific store
    ./checkjebon-search.py --query "campina" --store jumbo
    
    # Show stats
    ./checkjebon-search.py --stats
    ```
    
    ## Recipe Features (NEW!) šŸ³
    
    ### Scan Fridge → Find Recipes → Shopping List
    
    **1. Scan your fridge:**
    ```bash
    ./fridge-scan.sh
    # Opens camera, captures fridge contents
    # Output: /tmp/fridge-scan.jpg
    ```
    
    **2. Extract ingredients (via OpenClaw image tool):**
    ```bash
    # Ask assistant:
    # "Analyze /tmp/fridge-scan.jpg and list all food items as comma-separated"
    # → melk, eieren, tomaten, kaas, broccoli
    ```
    
    **3. Find recipes:**
    ```bash
    ./ah-recipes.py ingredients --ingredients "melk,eieren,kaas,broccoli" --pretty
    ```
    
    **4. Get recipe details (by ID):**
    ```bash
    ./ah-recipes.py details --recipe-id 1187649 --pretty
    ```
    
    **Or get recipe from URL directly:**
    ```bash
    ./ah-recipes.py url --url "https://www.ah.nl/allerhande/recept/R-R1187649/zoete-tortillachips" --pretty
    ```
    
    **5. Complete workflow:**
    ```bash
    ./smart-cook.sh
    # Interactive: scan → analyze → find recipes → shopping list
    ```
    
    ### Recipe ID Resolution
    
    **How to get recipe IDs:**
    
    1. **From search results:** The `search` action returns titles only. To get full details, you need the recipe ID.
    2. **From URL:** Recipe URLs contain the ID in format `R-R{ID}`:
       - Example: `https://www.ah.nl/allerhande/recept/R-R1187649/zoete-tortillachips`
       - Extract: `R-R1187649` → ID = `1187649`
    3. **Direct lookup:** Use the `url` action to automatically extract ID and fetch details
    
    **Workflow:**
    ```bash
    # Step 1: Search for recipes (returns titles only)
    ./ah-recipes.py search --query "pasta carbonara" --pretty
    
    # Step 2: If you have the recipe URL (e.g., from browser or website), extract ID
    ./ah-recipes.py url --url "https://www.ah.nl/allerhande/recept/R-R{ID}/{slug}" --pretty
    
    # Note: Search results don't include recipe IDs (client-side rendered)
    # To get full details, you need either:
    #   - The direct recipe URL (contains R-R{ID})
    #   - The recipe ID number
    ```
    
    ### Recipe Search Examples
    
    **Search by text (returns IDs + titles):**
    ```bash
    ./ah-recipes.py search --query "pasta carbonara" --size 10 --pretty
    # Output: {"recipes": [{"id": 1200422, "title": "Klassieke spaghetti carbonara"}, ...], "total": 49, "hasMore": true}
    ```
    
    **Search with detailed info (cook time, ratings, images, servings):**
    ```bash
    ./ah-recipes.py search --query "pasta carbonara" --size 5 --detailed --pretty
    # Output: Full recipe summaries with time, ratings, images, servings
    ```
    
    **Search by ingredients:**
    ```bash
    ./ah-recipes.py ingredients --ingredients "tomaat,ui,knoflook" --size 5 --pretty
    ```
    
    **Get recipe from URL:**
    ```bash
    ./ah-recipes.py url --url "https://www.ah.nl/allerhande/recept/R-R1187649/zoete-tortillachips" --pretty
    # Extracts recipe ID from URL (R-R1187649 → 1187649) and fetches full details
    ```
    
    ## Examples
    
    **Cheapest melk across all stores:**
    ```bash
    ./checkjebon-search.py --compare "melk" --top 5
    ```
    
    **AH bonussen vandaag:**
    ```bash
    ./ah-api.py bonuses --filter WEB_BONUS_PAGE --pretty | \
      jq '.bonuses[] | select(.title | contains("Campina"))'
    ```
    
    **Search AH products:**
    ```bash
    ./ah-api.py search --query "bier" --limit 20 --pretty
    ```
    
    ## Troubleshooting
    
    **"Access Denied" errors:**
    - Use curl-cffi (not standard requests)
    - Check User-Agent header
    - Refresh cookies (run `./setup-cookies.sh`)
    
    **OAuth code expired:**
    - Codes valid for only 30 seconds!
    - Use curl command immediately after generating code
    - Or use refresh_token to extend session
    
    **GraphQL errors:**
    - Check date format (YYYY-MM-DD)
    - Verify filterSet value (case-sensitive)
    - Ensure cookies are fresh
    
    ## Files
    
    ```
    ah-bonuses/
    ā”œā”€ā”€ SKILL.md              # This file
    ā”œā”€ā”€ README.md             # Quick start
    ā”œā”€ā”€ ah-api.py             # Main CLI tool (bonuses + search)
    ā”œā”€ā”€ get-bonuses.py        # Legacy bonus tool
    ā”œā”€ā”€ checkjebon-search.py  # Multi-store search
    ā”œā”€ā”€ refresh-token.py      # OAuth token refresh
    ā”œā”€ā”€ setup-cookies.sh      # Cookie extractor
    └── ~/.ah_cookies.json    # Session cookies (gitignored)
    └── ~/.ah_tokens.json     # OAuth tokens (gitignored)
    ```
    
    ## Credits
    
    - **AlbertPWN** (userlandkernel) - Original mobile API research
    - **TommasoAmici/ah-bonus-bot** - Rust bot with product search endpoint
    - **jabbink** - Comprehensive API documentation gist
    - **curl-cffi** - Chrome fingerprinting 
    
    ... (truncated)