L’API Foodlex
Une recette entre en JSON, les valeurs calculées en ressortent en JSON : valeurs nutritionnelles, allergènes et liste d’ingrédients terminée. Pensée pour votre ERP, votre boutique ou votre propre outil — partout où les chiffres sont nécessaires sans que personne ait à les ressaisir dans l’application.
Trois points d’entrée
Tous sous https://app.foodlex.app/api/v1/, tous en POST.
| Point d’entrée | Réponse |
|---|---|
/v1/nutrition |
Valeurs nutritionnelles pour 100 g, par portion et à cru — non arrondies pour poursuivre le calcul, avec en plus les valeurs arrondies de l’étiquette |
/v1/allergens |
Les quatorze groupes d’allergènes de l’annexe II du règlement (UE) n° 1169/2011, avec leur position dans le texte |
/v1/ingredient-list |
La liste des ingrédients, par ordre pondéral décroissant, avec la mise en évidence des allergènes et le QUID |
Ce qu’elle ne fait pas
L’API est sans état : elle ne crée aucune recette, n’en lit aucune et ne modifie rien dans vos données. Chaque appel se suffit à lui-même — ce qui entre est calculé, et rien ne subsiste ensuite.
Authentification
Une clé, un en-tête :
Authorization: Bearer flx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
La clé se compose de flx_live_ suivi de 32 caractères. Elle est
affichée une seule fois, à sa création — seul son SHA-256 est
conservé et elle ne peut pas être retrouvée dans la base. Perdue, elle se révoque
et se remplace.
Un appel complet
curl -sS https://app.foodlex.app/api/v1/nutrition \
-H "Authorization: Bearer $FOODLEX_KEY" \
-H 'Content-Type: application/json' \
-d '{
"market": "FR",
"language": "fr",
"preparationLossPercent": 12,
"portions": 8,
"ingredients": [
{ "name": "Farine de blé T55", "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": "Beurre", "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 } }
]
}'La réponse, abrégée :
{
"apiVersion": "1",
"market": "FR",
"language": "fr",
"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 n’est pas arrondi, toutes les décimales y sont.
C’est volontaire : qui poursuit le calcul — vers une autre taille de portion, vers
son propre tableau — ne doit pas partir d’une valeur intermédiaire arrondie.
L’arrondi n’a lieu qu’une fois, à la fin, et c’est ce que contient
labelPer100g.
"32 g" sur une espace ordinaire ne donne rien. Et
labelPer100g n’existe que pour DE, FR et
GB, les trois sous droit de l’UE : l’Australie arrondit à trois
chiffres significatifs, les États-Unis par paliers sur une portion RACC. Le
champ y est absent et labelNote explique pourquoi, dans votre
langue — une valeur mal arrondie a l’air juste. Les valeurs de
per100g sont exactes pour chaque marché.
Marchés et langues
La requête transmet les deux, et les deux couvrent tout ce que couvre l’application :
market |
DE · FR · AU · US · GB — détermine le corpus de règles. Valeur par défaut DE |
|---|---|
language |
de · en · fr — agit sur les messages, les dénominations et le format des nombres. Valeur par défaut de |
Les identifiants des allergènes restent en anglais, même dans une réponse en français : ce sont des clés, pas un affichage. Le libellé lisible figure à côté, selon le marché et la langue.
Limites et quota
| Corps de la requête | 1 Mo |
|---|---|
| Ingrédients par requête | 200 |
Texte pour /v1/allergens | 20 000 caractères |
| Appels par mois | Free 0 · Starter 0 · Pro 2 000 · Team 20 000 |
Le décompte se fait par espace de travail, et non par clé : le quota tient à l’offre, et deux clés d’une même entreprise ne doivent pas se dégager mutuellement de la marge.
Erreurs
Une seule forme, sur tous les points d’entrée, quel que soit le statut :
{ "apiVersion": "1",
"error": { "code": "bad_request",
"message": "La requête ne correspond pas au schéma : …",
"field": "ingredients[2].amountGrams" } }| Statut | code | Quand |
|---|---|---|
| 400 | bad_request | Le corps n’est pas du JSON ou ne correspond pas au schéma |
| 401 | unauthorized | Clé absente, inconnue, révoquée ou expirée |
| 403 | forbidden | L’offre de cet espace de travail exclut l’API |
| 405 | method_not_allowed | Tous les points d’entrée sont en POST |
| 413 | payload_too_large | Corps supérieur à 1 Mo |
| 429 | quota_exceeded | Quota mensuel épuisé |
| 500 | internal_error | Imprévu |
| 503 | unavailable | La vérification de la clé est injoignable |
Gestion des versions
La version figure dans le chemin, et non dans un en-tête — elle apparaît ainsi dans chaque message d’erreur, dans chaque journal et dans chaque ligne d’exemple.
| Garanti | Un champ documenté en v1 ne disparaît pas et ne change pas de sens |
|---|---|
| Garanti | Un champ inconnu dans la requête est ignoré, non rejeté |
| Possible | De nouveaux champs dans la réponse — un client doit passer outre les champs qu’il ne connaît pas |
| Non garanti | L’ordre des clés dans le JSON. Ni les chiffres eux-mêmes : si une règle d’arrondi ou un motif d’allergène est corrigé, le résultat change — c’est une correction, pas une rupture de contrat |
Si un champ doit changer de sens, /api/v2/… apparaîtra à côté.
Obtenir une clé
L’API est comprise dans l’offre Pro et ne coûte rien de plus. Écrivez-moi ce que vous souhaitez en faire : je crée la clé et vous l’envoie par un canal qui ne la laisse pas traîner dans une boîte aux lettres.
La responsabilité reste la vôtre : Foodlex calcule et met en forme, et indique d’où vient chaque chiffre. La vérification technique et juridique avant impression incombe à celui qui met l’étiquette sur le marché.