All endpoints are GET against /aldi.php with an ?action=… query param. Responses are JSON and include Access-Control-Allow-Origin: *.
Base URL: https://grocery.freeze2k.net/aldi.php
categories
{
"success": true,
"action": "categories",
"count": 25,
"data": ["fresh-produce", "dairy-and-eggs", "meat-seafood", …]
}
stores
GET/aldi.php?action=stores&zip=NNNNN[&limit=N]
Find ALDI stores near a US ZIP, distance-sorted (haversine). Backed by Uberall's public storefinder +
zippopotam.us for ZIP geocoding.| Param | Required | Default | Notes |
|---|---|---|---|
| zip | yes | — | 5-digit US ZIP |
| limit | no | 10 | 1–50 |
{
"success": true,
"action": "stores",
"count": 5,
"data": [
{
"id": 5766226,
"name": "ALDI",
"street": "3632 Frankford Rd Ste 200C",
"city": "Dallas", "state": "Texas", "zip": "75287",
"lat": 32.99597, "lng": -96.85424,
"phone": "+1 855-955-2534",
"openNow": false,
"hours": [{ "dayOfWeek": 1, "from1": "09:00", "to1": "20:00" }, …],
"distanceMi": 2.65
}
],
"meta": { "zip": "75001", "totalKnown": 2690 }
}
browse
GET/aldi.php?action=browse&zip=NNNNN&category=SLUG[&limit=N]
All products and prices for one category at one store (determined by ZIP). Source is the ALDI/Instacart whitelabel SSR HTML, parsed server-side.
| Param | Required | Default | Notes |
|---|---|---|---|
| zip | yes | — | 5-digit US ZIP |
| category | yes | — | slug from action=categories |
| limit | no | 50 | 1–200 |
| nocache | no | — | set 1 to force upstream refetch |
| cache_ttl | no | 86400 | seconds |
| max_tries | no | 4 | upstream retry budget on empty SSR |
{
"success": true,
"action": "browse",
"count": 5,
"data": [
{
"itemId": "16902710",
"name": "Friendly Farms Whole Milk",
"price": 4.05,
"priceFormatted": "$4.05",
"imageUrl": "https://www.instacart.com/image-server/…/large_….jpg",
"productUrl": "https://www.aldi.us/store/aldi/products/16902710-…",
"stockStatus": "Many in stock"
},
{
"itemId": "22014511",
"name": "Friendly Farms Greek Yogurt, Vanilla",
"price": 0.85,
"priceFormatted": "$0.85",
"originalPrice": 1.19,
"originalPriceFormatted": "$1.19",
"onSale": true,
"savings": 0.34,
…
}
],
"meta": {
"zip": "75001", "category": "dairy-and-eggs",
"totalFound": 25, "cached": true, "stale": false,
"tries": 0, "bodySize": 1450607
}
}
search
GET/aldi.php?action=search&zip=NNNNN&q=TERM[&limit=N]
Search across a fixed set of high-coverage categories (
fresh-produce, dairy-and-eggs, meat-seafood, frozen-foods, bakery-bread, beverages, breakfast-cereals, household-essentials, prepared-foods, deli) and return matching products. Scoring: exact substring (100), all tokens (70), any token (30); deduped by item id.| Param | Required | Default | Notes |
|---|---|---|---|
| zip | yes | — | 5-digit US ZIP |
| q | yes | — | free-text query |
| limit | no | 20 | 1–100 |
{
"success": true,
"action": "search",
"count": 5,
"data": [
{
"itemId": "16902710",
"name": "Friendly Farms Whole Milk",
"price": 4.05, "priceFormatted": "$4.05",
"imageUrl": "…", "productUrl": "…",
"matchScore": 100,
"matchedInCategory": "dairy-and-eggs"
}
],
"meta": {
"zip": "75001", "query": "milk",
"source": "fallback_categories",
"categoriesScanned": ["fresh-produce", "dairy-and-eggs", …],
"cacheHits": 9
}
}
Response schema
Every response is one of:
{ "success": true, "action": "…", "count": N, "data": […], "meta": { … } }
{ "success": false, "error": "code", "message": "human-readable explanation" }
HTTP status codes mirror common REST conventions: 200 on success, 400 on bad input, 404 for unknown category, 502 when an upstream provider (Uberall, ALDI, zippopotam.us) fails or returns nothing parseable.
Caching & freshness
ALDI/Instacart serves their category SSR non-deterministically — the same URL sometimes returns the full page with products, sometimes an empty shell. To work around this:
- Successful (product-bearing) responses are cached on disk for 24 hours (
/var/cache/aldi/). - A cron job runs at 04:17 daily and warms the cache for every category at the default ZIP, retrying each one up to 30 times to defeat the SSR flakiness.
- Live requests that miss cache retry up to
max_triesupstream fetches; if all fail, the API falls back to any prior cached response (flagged with"stale": true) so callers still get useful data. - Per-ZIP pricing varies, so only the warmed ZIP (currently 75001) is reliably fast — other ZIPs will trigger live fetches and may return empty if upstream is unlucky.
Examples
Command line
curl -s 'https://grocery.freeze2k.net/aldi.php?action=search&zip=75001&q=eggs&limit=5' | jq
JavaScript (browser)
const r = await fetch('https://grocery.freeze2k.net/aldi.php?' +
new URLSearchParams({ action: 'search', zip: '75001', q: 'milk' }));
const { data } = await r.json();
for (const p of data) console.log(p.priceFormatted, p.name);
Python
import requests
r = requests.get('https://grocery.freeze2k.net/aldi.php',
params={'action': 'search', 'zip': '75001', 'q': 'pizza'})
for p in r.json()['data']:
print(p['priceFormatted'], p['name'])