Bulk-Lebensmittelabfrage API
Bis zu 100 Lebensmittel in einer einzigen Anfrage abrufen. Sende ein Array aus IDs oder Barcodes und erhalte die vollständigen Datensätze plus eine Liste dessen, was nicht gefunden wurde – ideal, um einen Essensplan, einen Warenkorb, das Ernährungstagebuch eines Tages oder einen Katalog gescannter Produkte ohne hundert Roundtrips zu befüllen.
curl -X POST -H "X-API-Key: food_•••" \
-H "Content-Type: application/json" \
-d '{"ids":["62ea12b8...","2fc13a3b..."]}' \
"https://foodbase.dev/v1/foods/bulk"
{
"data": [
{ "id": "62ea12b8...", "name_default": "Original Taste", "brand": "Coca-Cola" },
{ "id": "2fc13a3b...", "name_default": "Nutella", "brand": "Ferrero" }
],
"not_found": []
} Lieber klicken? Probier den Produkt-Explorer oder die Foto-Spielwiese aus.
Was du zurückbekommst
ids[]
1–100 Lebensmittel-IDs im Request-Body – oder:
barcodes[]
1–100 Barcodes (GTIN-8/12/13/14), jeder aufgelöst wie bei der Barcode-Lookup-API, einschließlich Varianten mit führenden Nullen.
data[]
Vollständige Datensätze in der angefragten Reihenfolge; Barcode-Abfragen ergänzen requested_barcode (den gesendeten Code).
not_found[]
Die IDs oder Barcodes ohne Produkt – kein Fehler für den Batch.
?lang=
Optional en/fr/es/de/it/bg, wie bei den Einzelabfragen.
Ab dem Starter-Tarif verfügbar. Tarife ansehen →
Codebeispiele
Dieselbe Anfrage in curl, JavaScript und Python. Ersetze YOUR_API_KEY durch einen Schlüssel aus deinem Dashboard.
curl "https://foodbase.dev/v1/foods/bulk" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ids":["62ea12b8-b260-c288-5c38-3a4906f5d6dd","2fc13a3b-1913-5ab6-d2c7-8c09b56f93bb"]}'
const res = await fetch("https://foodbase.dev/v1/foods/bulk", {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
"ids": [
"62ea12b8-b260-c288-5c38-3a4906f5d6dd",
"2fc13a3b-1913-5ab6-d2c7-8c09b56f93bb"
]
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
import requests
res = requests.post(
"https://foodbase.dev/v1/foods/bulk",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"ids": [
"62ea12b8-b260-c288-5c38-3a4906f5d6dd",
"2fc13a3b-1913-5ab6-d2c7-8c09b56f93bb"
]
},
timeout=30,
)
res.raise_for_status()
data = res.json()
So funktioniert's
Die Anfrage, Schritt für Schritt
POST /v1/foods/bulk mit Content-Type: application/json, deinem X-API-Key-Header und einem Body der Form {"ids": [...]} oder {"barcodes": [...]} – genau eines von beiden, 1 bis 100 Einträge. ids sind UUID-Strings (die in den Codebeispielen oben sind echte Datensätze zum Testen); Barcodes werden genau wie bei GET /v1/foods/barcode/{code} aufgelöst, sodass ein EAN-13 auch ein Produkt findet, das in seiner UPC-12- oder GTIN-14-Form gespeichert ist, und das Feld barcode des Datensatzes zeigt die gespeicherte Form neben requested_barcode.
Die Validierung läuft, bevor die Datenbank berührt wird, und gilt ganz oder gar nicht. Ein Barcode in ids, beide Felder oder keines, ein leeres Array oder 101 Einträge lehnen den ganzen Batch mit 422 {"error": "Validation Error", "details": [{"field": "ids.3", "message": ...}]} ab. Ein optionales ?lang= (en, fr, es, de, it, bg) lokalisiert Kategorie- und Allergenbezeichnungen wie bei den Einzelabfragen; Namen und Zutaten in anderen Sprachen stehen in den Arrays name und ingredients.
Die Antwort lesen
data ist ein Array von FoodItem-Objekten, aufgebaut vom selben Formatter wie die Lebensmittel-Datenbank-API, sodass ein für den Einzeldatensatz geschriebener Parser unverändert funktioniert — von id, barcode und source bis categories_tags, image_url und allergens. Fehlende Quelldaten sind null, nie ein fehlender Schlüssel.
nutrition_summary enthält energy_kcal, proteins_g, carbs_g, fat_g, sugars_g und fiber_g pro 100 g. Es ist nur dann als Ganzes null, wenn das Produkt gar keine Nährwertzeile hat; fehlt einer Zeile ein Wert, zeigt nur dieser Schlüssel null. Etikettentext wie quantity und serving_size_raw kommt genau so an, wie Mitwirkende ihn getippt haben — parse also defensiv.
Fehlende Einträge, Duplikate und Drift
Die Antwort kann kürzer sein als die Anfrage: Es gibt kein 404 pro Eintrag, aber jede ID und jeder Barcode ohne Produkt steht in not_found, in der Reihenfolge der Anfrage – du musst also nie selbst abgleichen. Eine doppelt gesendete ID oder ein doppelter Barcode kommt als ein Datensatz zurück; dedupliziere vor dem Senden, damit jeder der 100 Plätze zählt.
IDs sind deterministisch, aus der Quelle und deren eigener Produktkennung abgeleitet, sodass ein Produkt über Reimporte hinweg seine id behält. Löst sich eine gespeicherte id nicht mehr auf, ist das Produkt unter dieser Identität nicht mehr im Katalog: Markiere die Zeile als veraltet und löse sie über die Barcode-Abfrage-API oder die Lebensmittel-Suche-API neu auf, statt sie zu löschen.
Kontingent und Rate-Limit-Rechnung
Ein Bulk-Aufruf ist eine Anfrage gegen das Tageskontingent und ein Token aus dem Sekunden-Bucket, egal wie viele IDs. Die Reihenfolge der Middleware ist Authentifizierung, Sekundenlimit, Tarif-Schranke, dann Tageskontingent, sodass ein 401, ein Rate-Limit-429 oder das Starter-403 nie eine Tagesanfrage verbraucht. Ein 422 schon, weil die Validierung nach der Zählung des Kontingents läuft; prüfe UUIDs also auf dem Client.
Lies die Header bei jedem 200: X-RateLimit-Second-Limit, X-RateLimit-Burst-Capacity und X-RateLimit-Second-Remaining für den Bucket pro Schlüssel, X-RateLimit-Account-Limit und X-RateLimit-Account-Remaining für das Sekunden-Aggregat über alle Schlüssel des Kontos sowie X-RateLimit-Daily-Limit, X-RateLimit-Daily-Remaining und X-RateLimit-Daily-Reset (bei normalen Konten) für das gemeinsame Tagesbudget und seinen Reset um Mitternacht UTC. Das POST trägt keinen Cache-Control-Header, am Edge wird also nichts für dich gecacht: Speichere, was du abrufst.
Einen Katalog nachts synchronisieren
Für das Sortiment eines Händlers sende die Barcodes direkt, 100 pro Aufruf, oder speichere die zurückgegebene ID neben deiner SKU und synchronisiere danach per ID. So oder so sucht der nächtliche Job nie: Er liest die gespeicherten Codes, dedupliziert sie, teilt sie in Gruppen zu 100, sendet jede Gruppe hierher und setzt nach jeder fertigen Gruppe einen Checkpoint, damit ein Katalog, der größer ist als das Budget einer Nacht, dort weitermacht, wo er aufgehört hat. Vergleiche updated_at mit deiner Kopie, um zu sehen, was sich geändert hat.
Takte anhand der Header statt mit einem festen Sleep und beobachte X-RateLimit-Daily-Remaining, damit der Sync nicht den Tagesverkehr desselben Kontos aushungert. Braucht eine Ansicht die 40+ Nährstoffe, rufe die Nährwert-API für diese id bei Bedarf auf; sie nimmt eine id pro Anfrage und zählt ebenfalls als Anfrage.
- IDs deduplizieren, in 100er-Blöcke teilen, pro Block einen Checkpoint setzen.
- Alles in not_found als veraltet markieren; später erneut prüfen.
- /nutrients lazy abrufen, pro Produkt, nicht im Sync.
Was du damit baust
Mahlzeitenpläne & Lebensmittelprotokolle
Löse jedes Element des Tagesplans mit einem Aufruf auf.
Warenkorb befüllen
Mach aus einer Liste gespeicherter Lebensmittel-IDs effizient vollständige Datensätze.
Sync-Jobs
Aktualisiere viele gecachte Lebensmittel in Stapeln nach Zeitplan.
Limits pro Tarif
| Free | Nicht verfügbar: 403 mit required_plan starter (zählt nicht gegen die 100 Anfragen/Tag) |
|---|---|
| Starter | 5.000 Anfragen/Tag, 5/s dauerhaft, Burst 25; bis zu 100 IDs oder Barcodes pro Anfrage |
| Pro | 25.000 Anfragen/Tag, 20/s dauerhaft, Burst 100; bis zu 100 IDs oder Barcodes pro Anfrage |
| Enterprise | 100.000 Anfragen/Tag, 50/s dauerhaft, Burst 250; bis zu 100 IDs oder Barcodes pro Anfrage |
Jede Antwort enthält Header X-RateLimit-* mit dem verbleibenden Budget; jede 429 enthält Retry-After . Tarife vergleichen →
Fehler und wie du sie behandelst
403 — Free-Konto; der Body enthält error Plan upgrade required, required_plan starter, current_plan free und eine upgrade_url
Upgrade über die upgrade_url, oder nutze im Free-Tarif GET /v1/foods/{id}
422 — Keines oder beide der Felder ids und barcodes; ein leeres Array oder mehr als 100 Einträge; eine Nicht-UUID in ids oder ein leerer Barcode; details[] nennt die fehlerhafte Position
Clientseitig validieren und deduplizieren; Barcodes in barcodes[] senden, nicht in ids[]
429 — Bucket pro Schlüssel oder Konto leer, oder X-RateLimit-Daily-Remaining hat 0 erreicht (Body: Daily quota exceeded)
Pausiere für die Sekunden aus Retry-After; im Tagesfall ist das die Zeit bis Mitternacht UTC
401 — X-API-Key-Header fehlt, oder der Schlüssel ist ungültig oder widerrufen
Prüfe den Header-Namen und den Schlüssel im Dashboard
503 — Der Speicher für Kontingent oder Rate-Limit ist nicht erreichbar; die API schlägt geschlossen fehl (fail closed)
Mit Backoff wiederholen; die Anfrage wurde nicht gezählt
Im Vergleich zu anderen APIs
Gegenüber dem GET für einen einzelnen Datensatz liegt der Gewinn bei Roundtrips und Kontingent: 100 Datensätze für eine Anfrage statt 100 Anfragen. Das GET gewinnt, wenn viele Clients dasselbe beliebte Produkt lesen, weil seine Antworten von jedem HTTP-Cache 300 Sekunden lang gecacht werden können und es lang akzeptiert; Bulk ist ungecacht und füllt deinen eigenen Speicher. Die Lebensmittel-Suche-API und die Barcode-Abfrage-API machen aus Text oder einer GTIN eine id; dieser Endpunkt macht aus IDs, die du schon hast, Datensätze. Die Nährwert-API geht den umgekehrten Weg: die vollständige Tabelle für ein Produkt statt sechs Zusammenfassungswerte für bis zu 100.
Häufige Fragen
Kann ich Barcodes oder Suchbegriffe statt IDs senden? +
Nein. Jeder Eintrag muss eine UUID sein, sonst wird der ganze Stapel mit 422 abgelehnt. Löse Barcodes über die Barcode-Abfrage-API und Namen über die Lebensmittel-Suche-API auf, speichere die IDs und verwende sie dann hier.
Kostet ein Aufruf mit 100 IDs 100 Anfragen? +
Nein, er kostet eine. Der Tageszähler und der Sekunden-Bucket bewegen sich je Anfrage um eins, unabhängig davon, wie viele IDs darin stecken.
Warum gibt es keinen lang-Parameter? +
Bulk liefert den rohen Datensatz ohne die lokalisierten Hilfsfelder. Die name- und ingredients-Arrays enthalten jede Sprache, die Mitwirkende geliefert haben, wähle den Eintrag also auf dem Client; brauchst du category_localized oder allergens_localized, nutze für dieses Produkt GET /v1/foods/{id}?lang=.
Was sieht ein Free-Konto? +
Ein 403 mit error Plan upgrade required, required_plan starter und einer upgrade_url, die auf die Abrechnungsseite zeigt. Die Schranke läuft vor dem Tageskontingent, diese 403er fressen also nicht die 100 Anfragen pro Tag, und GET /v1/foods/{id} bleibt verfügbar.
Kann ich die vollständige Nährwert-Aufschlüsselung im Bulk bekommen? +
Nein. Bulk-Datensätze tragen nur nutrition_summary. Die Tabelle mit 40+ Nährstoffen kommt von der Nährwert-API, eine id pro Anfrage; ab Starter hat sie kein separates Tageslimit, im Free-Tarif ist sie auf 25 pro Tag begrenzt.
Weitere Endpoints entdecken
Lebensmittel-Suche API
Volltextsuche über 6,1 Millionen Lebensmittelprodukte nach Name, Marke oder Kategorie — nach Relevanz sortiert, paginiert und schnell. Suche in jeder Sprache, in der das Etikett gedruckt wurde (auch kyrillisch), und übergib lang=en|fr|es|de|it|bg für lokalisierte Namen, Kategorien und Allergene. Eine GET-Anfrage liefert sauberes JSON, das deine App sofort darstellen kann.
Mehr erfahren →Nährwert-API
Geh über die Makro-Eckdaten hinaus. Hole eine detaillierte Aufschlüsselung von 40+ Nährstoffen pro Produkt — Energie, Eiweiß, Kohlenhydrate, Fette, Zucker, Ballaststoffe, Natrium, Vitamine und Mineralstoffe — alles pro 100 g, für jedes Lebensmittel per ID.
Mehr erfahren →Lebensmittel-Datenbank API
Eine einzige Quelle für 6,1 Millionen Lebensmittel. Hole einen vollständigen Produktdatensatz per ID — Name, Marke, Zutaten, Allergene, Nutri-Score, NOVA-Gruppe, Bilder und mehr.
Mehr erfahren →Leg jetzt los mit der Bulk-Lebensmittelabfrage API
Kostenlos starten. Keine Kreditkarte nötig.
API-Schlüssel holen Zum Dashboard