Die Foodlex-API

Ein Rezept als JSON hinein, die gerechneten Werte als JSON heraus: Nährwerte, Allergene und die fertige Zutatenliste. Gedacht für die Warenwirtschaft, den Shop oder ein eigenes Werkzeug — überall dort, wo die Zahlen gebraucht werden, ohne dass jemand sie in der App abtippt.

Drei Endpunkte

Alle unter https://app.foodlex.app/api/v1/, alle POST.

EndpunktAntwort
/v1/nutrition Nährwerte je 100 g, je Portion und roh — ungerundet zum Weiterrechnen, dazu die gerundeten Etikettwerte
/v1/allergens Die vierzehn Allergengruppen nach Anhang II der Verordnung (EU) Nr. 1169/2011, mit der Fundstelle im Text
/v1/ingredient-list Die Zutatenliste, absteigend nach Gewichtsanteil, mit Allergen-Hervorhebung und Mengenkennzeichnung (QUID)

Was sie nicht tut

Die API ist zustandslos: Sie legt kein Rezept an, liest keines und ändert nichts an Deinen Daten. Jeder Aufruf steht für sich — was hineingeht, wird gerechnet, und danach bleibt nichts zurück.

Sie schlägt auch keine Lebensmittel nach. Die Nährwerte jeder Zutat gibst Du mit. Das ist Absicht: Eine Zahl, die wir still aus einer Datenbank ergänzen, steht am Ende auf einem Etikett, für das jemand anderes geradesteht. Wer die Datenbank sucht, nimmt die App.

Anmeldung

Ein Schlüssel, eine Kopfzeile:

Authorization: Bearer flx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Der Schlüssel hat die Form flx_live_ plus 32 Zeichen. Er wird genau einmal gezeigt, beim Anlegen — gespeichert ist nur sein SHA-256, aus der Datenbank ist er nicht wiederherstellbar. Verloren heißt: sperren und neu vergeben.

Der Schlüssel gehört auf einen Server, nicht in eine Webseite. Wer ihn in JavaScript im Browser einträgt, verschenkt ihn an jeden Besucher. Und es gibt bewusst keinen Query-Parameter: ein Schlüssel in der URL landet in jedem Zugriffslog und in jedem Referrer.

Ein Aufruf, vollständig

curl -sS https://app.foodlex.app/api/v1/nutrition \ -H "Authorization: Bearer $FOODLEX_KEY" \ -H 'Content-Type: application/json' \ -d '{ "market": "DE", "language": "de", "preparationLossPercent": 12, "portions": 8, "ingredients": [ { "name": "Weizenmehl Type 405", "amountGrams": 500, "nutrients": { "energyKj": 1443, "energyKcal": 341, "fat": 1.1, "fatSaturated": 0.2, "carbs": 71.8, "sugar": 1.2, "protein": 10.6, "salt": 0.01 } }, { "name": "Butter", "amountGrams": 250, "nutrients": { "energyKj": 3059, "energyKcal": 741, "fat": 82.0, "fatSaturated": 52.0, "carbs": 0.6, "sugar": 0.6, "protein": 0.7, "salt": 0.02 } } ] }'

Die Antwort, gekürzt:

{ "apiVersion": "1", "market": "DE", "language": "de", "weights": { "rawGrams": 750, "finalGrams": 660, "portionGrams": 82.5 }, "per100g": { "energyKj": 2251.8939393939395, "energyKcal": 539.0151515151515, … }, "perPortion":{ "energyKj": 1857.8125, … }, "labelPer100g": { "energyKj": "2.252 kJ", "energyKcal": "539 kcal", "fat": "32 g", "fatSaturated": "20 g", "carbs": "55 g", "sugar": "1,1 g", "protein": "8,3 g", "salt": "0,02 g" }, "warnings": [] }
per100g ist ungerundet, mit allen Stellen. Das ist Absicht: Wer weiterrechnet — auf eine andere Portionsgröße, in eine eigene Tabelle — soll nicht mit einem gerundeten Zwischenwert anfangen. Gerundet wird einmal, am Ende, und das steht in labelPer100g.
Zwei Stellen, an denen sich ein fremdes Programm verschluckt. Zwischen Zahl und Einheit steht ein geschütztes Leerzeichen (U+00A0; der französische Tausendertrenner ist U+202F) — wer "32 g" an einem gewöhnlichen Leerzeichen trennt, findet nichts. Und labelPer100g gibt es nur für DE, FR und GB, die drei unter EU-Recht: Australien rundet auf drei signifikante Stellen, die USA stufenweise auf eine Portionsgröße nach RACC. Dort fehlt das Feld, und labelNote sagt in Ihrer Sprache warum — eine falsch gerundete Zahl sieht richtig aus. Die Werte in per100g stimmen für jeden Markt.

Märkte und Sprachen

Beides gibt die Anfrage mit, und beides deckt den vollen Umfang der App ab:

market DE · FR · AU · US · GB — entscheidet über das Regelwerk. Vorgabe DE
language de · en · fr — wirkt auf Meldungen, Bezeichnungen und das Zahlenformat. Vorgabe de

Die Kennungen der Allergene bleiben dabei englisch, auch in einer deutschen Antwort: Sie sind Schlüssel, keine Anzeige. Der Klartext steht je Markt und Sprache daneben.

Grenzen und Kontingent

Rumpf1 MB
Zutaten je Anfrage200
Text bei /v1/allergens20.000 Zeichen
Aufrufe je MonatFree 0 · Starter 0 · Pro 2.000 · Team 20.000

Gezählt wird je Workspace, nicht je Schlüssel: Das Kontingent hängt am Tarif, und zwei Schlüssel desselben Betriebs sollen sich nicht gegenseitig Luft verschaffen.

Fehler

Eine Form, auf allen Endpunkten, bei jedem Status:

{ "apiVersion": "1", "error": { "code": "bad_request", "message": "Die Anfrage passt nicht zum Schema: …", "field": "ingredients[2].amountGrams" } }
StatuscodeWann
400bad_requestRumpf ist kein JSON oder passt nicht zum Schema
401unauthorizedKein Schlüssel, unbekannt, gesperrt oder abgelaufen
403forbiddenDer Tarif dieses Workspaces schließt die API aus
405method_not_allowedAlle Endpunkte sind POST
413payload_too_largeRumpf größer als 1 MB
429quota_exceededMonatskontingent aufgebraucht
500internal_errorUnerwartet
503unavailableDie Schlüsselprüfung ist nicht erreichbar

Versionierung

Die Fassung steht im Pfad, nicht in einer Kopfzeile — so erscheint sie in jeder Fehlermeldung, in jedem Log und in jeder Zeile Beispielcode.

ZugesagtEin Feld, das in v1 dokumentiert ist, verschwindet nicht und ändert seine Bedeutung nicht
ZugesagtEin unbekanntes Feld in der Anfrage wird ignoriert, nicht abgewiesen
ErlaubtNeue Felder in der Antwort — ein Client muss unbekannte Felder überlesen
Nicht zugesagtDie Reihenfolge der Schlüssel im JSON. Und die Zahlen selbst: Korrigiert sich eine Rundungsregel oder ein Allergenmuster, ändert sich das Ergebnis — das ist eine Fehlerbehebung, kein Vertragsbruch

Soll sich ein Feld in seiner Bedeutung ändern, kommt /api/v2/… daneben.

Einen Schlüssel bekommen

Die API ist im Pro-Tarif enthalten und kostet nichts extra. Schreib mir, wofür Du sie einsetzen willst, dann lege ich den Schlüssel an und schicke ihn Dir über einen Weg, der ihn nicht im Postfach liegen lässt.

Die Rechnung bleibt Deine: Foodlex rechnet und setzt, und es sagt, woher jede Zahl kommt. Die fachliche und rechtliche Prüfung vor dem Druck bleibt bei dem, der das Etikett in Verkehr bringt.