L'application
Contrat d'API de la base de données alimentaires (expérimental)
Expérimental. Les requêtes et réponses qu'une base de données alimentaires doit fournir, pour que FOOD_DB_API_URL puisse pointer vers un serveur autre que lowcarbcheck.org
Cette page est traduite automatiquement à partir de la documentation en anglais.
Expérimental. Cette page décrit ce que le serveur applicatif demande aujourd'hui à une base de données alimentaires. Ce n'est pas une API stable. Une version mineure peut la modifier, et le journal des modifications l'indiquera. Le seul serveur qui l'implémente est lowcarbcheck.org. Aucun serveur open source n'existe encore, et il n'y a pas de test de conformité. Si tu en crées un, dis-le-nous dans un ticket, pour que le contrat puisse évoluer avec un deuxième utilisateur.
openplate recherche les aliments par leur nom via son propre serveur. Par défaut, ce serveur interroge lowcarbcheck.org. FOOD_DB_API_URL le fait pointer vers un autre serveur qui répond aux mêmes requêtes. configuration.md explique la clé et le quota. Cette page détaille les requêtes et les réponses.
Contenu du contrat
Le serveur applicatif effectue quatre types de requêtes. Seule la première est obligatoire.
| Requête | Utilisation | Si ton serveur ne l'a pas |
|---|---|---|
POST /api/v1/foods/search | Recherche d'aliments, et vitamines et minéraux d'une nouvelle entrée | La recherche d'aliments est vide. Les analyses conservent les chiffres de l'IA. |
GET /api/v1/foods/{slug} | Vitamines pour les anciennes entrées du journal (le rattrapage) | Les anciennes entrées restent sans vitamines. |
GET /api/v1/nutrients | Apports de référence, pour que l'application affiche les déficits | L'application montre ce qu'une journée contient, sans les déficits. |
GET /api/v1/nutrients/{slug}/foods | « Aliments riches en ce nutriment » sur l'écran du nutriment | La liste est vide. |
Le serveur applicatif traite chaque échec comme une réponse vide. Un point de terminaison manquant, un délai dépassé, un statut non-2xx ou un corps non conforme ne bloque jamais une analyse ou une page du journal. C'est le principe du fail-open.
Règles pour chaque requête
- L'adresse de base correspond à
FOOD_DB_API_URL. Les chemins ci-dessus y sont ajoutés. - JSON uniquement. Les requêtes envoient
accept: application/json. Les réponses sont en JSON UTF-8. - La clé. Quand
FOOD_DB_API_KEYest défini, chaque requête porteauthorization: Bearer <key>. Ton serveur décide de la signification d'une clé. Sans clé définie, l'en-tête est absent. - Le délai d'expiration est de 4 secondes par requête. Une réponse plus lente compte comme un échec.
- Les champs inconnus sont ignorés. Tu peux ajouter des champs librement. Un champ optionnel manquant signifie « aucun chiffre ».
- Pour 100 g. Tous les chiffres nutritionnels sont donnés pour 100 g d'aliment. L'énergie est en kcal. Les macronutriments sont en grammes.
nulln'est pas0.nullsignifie « cette source n'a aucun chiffre ».0signifie « mesuré, et la valeur est zéro ». L'application ne compte que les chiffres mesurés, donc n'envoie jamais0pour une valeur inconnue.- Vie privée. La requête de recherche transporte le nom de l'aliment dans un corps
POST, pas dans l'adresse, afin qu'aucun nom ne se retrouve dans un journal d'accès. Conserve cette propriété : ne journalise pas les corps de requête. L'application n'envoie aucun compte, aucun journal et aucune donnée corporelle. Voir architecture.md.
Recherche
POST /api/v1/foods/search
Corps de la requête :
{ "q": "hähnchenbrust", "locale": "de", "limit": 10 }qest le nom de l'aliment que la personne a saisi ou que l'IA a identifié.localeest l'un des éléments suivants :en,de,fr,it,es,tr. Renvoie les titres dans cette langue quand tu en disposes.limitvaut 10 aujourd'hui.
Réponse :
{
"results": [
{
"slug": "haehnchenbrust-roh",
"locale": "de",
"title": "Hähnchenbrust, roh",
"canonicalName": "Chicken breast, raw",
"url": null,
"imageUrl": null,
"macrosPer100g": {
"kcal": 106, "protein": 23.1, "fat": 1.2,
"carbs": 0, "fiber": 0, "sugars": 0, "polyols": 0
},
"netCarbsPer100g": 0,
"attribution": "BLS 4.0, CC BY 4.0",
"score": 0.92,
"origin": "bls",
"portionSize": null,
"vitamins": { "vitaminA": 9, "vitaminC": 0, "vitaminB12": 0.3 },
"minerals": { "sodium": 60, "potassium": 370 }
}
]
}| Champ | Type | Signification |
|---|---|---|
slug | string, requis | Un identifiant stable pour l'aliment. L'application le stocke et le redemande plus tard dans la requête de détail. |
locale | string, requis | La langue de title. |
title | string, requis | Le nom à afficher. |
canonicalName | string, requis | Un nom en anglais, utilisé comme libellé stable. |
url | string ou null | Une page pour l'aliment. null lorsqu'aucune n'existe. Une absence vaut null. |
imageUrl | string ou null, requis | Une image, ou null. |
macrosPer100g | object, requis | kcal, protein, fat, carbs, fiber, sugars, polyols. Chacun est un nombre ou null. Les sept clés doivent être présentes. |
netCarbsPer100g | number ou null, requis | Glucides sans les fibres. Voir Glucides. |
attribution | string ou null | La mention de licence pour les données importées. L'application l'affiche. |
score | nombre, obligatoire | Pertinence, de 0 à 1. Voir Classement. |
origin | string ou null | La provenance de l'aliment. Elle détermine la façon dont l'application lit carbs. Voir Glucides. |
portionSize | nombre ou null | Une portion type en grammes. |
vitamins | objet ou null | Voir Vitamines et minéraux. Omets-le quand l'aliment n'a aucune donnée de vitamines. |
minerals | objet ou null | La même chose, pour les minéraux. |
Glucides
Deux sources affichent les glucides de deux manières différentes, et l'application doit savoir laquelle un aliment utilise. Le champ origin l'indique :
origin | Base | Ce que macrosPer100g.carbs signifie |
|---|---|---|
bls, curated | disponible (UE) | Glucides sans les fibres. Les fibres forment une ligne distincte. |
fdc, user, proposal | total (États-Unis) | Glucides avec les fibres. Les glucides nets correspondent aux glucides moins les fibres. |
| absent ou toute autre valeur | inconnu | L'application le traite comme un total, et la personne peut le modifier manuellement. |
Si tes données sont au format UE, envoie origin: "curated". Si elles sont au format américain, envoie origin: "fdc". Une mauvaise valeur ici fausse les valeurs de glucides et de calories de chaque aliment concerné, vérifie donc avec un aliment que tu connais.
netCarbsPer100g est ta propre valeur de glucides nets pour 100 g, et l'application lui fait confiance plutôt qu'à son propre calcul. null signifie « inconnu pour cet aliment ». L'application calcule alors les glucides nets à partir des macronutriments et de la base ci-dessus. N'envoie jamais 0 pour une valeur inconnue.
Classement
Trie results par pertinence, le meilleur en premier. L'application conserve ton ordre. Elle ignore les correspondances faibles selon score :
- Un
scorede 0,4 ou plus doit correspondre à une vraie correspondance textuelle (exacte, préfixe, mot ou sous-chaîne). L'application l'ignore en dessous de 0,45. - Un
scoreinférieur à 0,4 doit correspondre uniquement à une faute de frappe. L'application l'ignore en dessous de 0,3. - Une correspondance par faute de frappe ne doit jamais atteindre ou dépasser 0,4, sinon elle passe devant une vraie correspondance.
Vitamines et minéraux
Chaque objet d'aliment contient deux blocs facultatifs, vitamins et minerals. Un bloc comporte ces clés et aucune autre que l'application ne lise :
| Bloc | Clés | Unité |
|---|---|---|
vitamins | betaCarotene, vitaminA, vitaminD, vitaminB9, vitaminB12 | µg |
vitamins | vitaminC, vitaminE, vitaminB1, vitaminB2, vitaminB6 | mg |
minerals | nacl, potassium, sodium, calcium, magnesium, zinc, phosphorus, iron | mg |
Chaque valeur est un nombre ou null. Une clé omise vaut null. Le bloc lui-même a trois états distincts :
- Bloc absent (ou
null) : cet aliment n'a aucune donnée de vitamines ou de minéraux. - La clé vaut
null: le bloc existe et cette valeur précise est inconnue. - La clé est un nombre : une valeur mesurée.
0est une vraie donnée.
L'application considère qu'un nutriment est couvert pour une journée seulement si une part suffisante du poids des aliments comporte une valeur mesurée (60 % aujourd'hui). De faux zéros donnent l'impression que la personne n'a consommé aucune part d'une vitamine, envoie donc null si tu n'es pas sûr.
La vitamine A est exprimée en équivalents d'activité rétinol (RAE), qui comprend déjà les caroténoïdes. betaCarotene sert de contexte, ce n'est pas un ajout. Si ta source fournit des équivalents rétinol (RE) ou des unités internationales, convertis avant d'envoyer, sinon les totaux seront faux.
Détail de l'aliment
GET /api/v1/foods/{slug}?locale=de
L'application demande les vitamines d'un aliment consigné avant que la fonction des vitamines n'existe. Elle envoie le slug issu d'un ancien résultat de recherche.
- Trouvé : statut 200 et un objet d'aliment avec
slug,vitaminsetminerals. Les mêmes blocs que pour la recherche. Les autres champs sont ignorés. - Introuvable dans cette région : statut 404 avec exactement ce corps :
{ "error": "not_found" }. L'application teste ensuite les autres régions (de,en,fr,it,es, avec la langue de la personne en priorité), de sorte qu'un aliment connu dans une seule région reste trouvé. Tout autre corps 404 est considéré comme un échec, pas comme « introuvable ». - Trouvé, et aucun bloc : 200 avec les deux blocs absents. L'application enregistre « vérifié, sans données » et ne redemande plus.
Chaque recherche peut te coûter un crédit, jusqu'à cinq pour un slug qu'aucune région ne connaît. L'application interroge une fois par aliment distinct et par appareil.
Apports de référence
GET /api/v1/nutrients
Les valeurs ci-dessous ne servent que d'exemple de structure. Ce ne sont pas de vrais apports de référence.
{
"nutrients": [
{
"slug": "vitamin-c",
"unit": "mg",
"kind": "target",
"rdaDe": {
"source": "DGE 2024",
"male": { "14-18": 105, "19-30": 110, "31-50": 110, "51-70": 110, "over_70": 110 },
"female": { "14-18": 90, "19-30": 95, "31-50": 95, "51-70": 95, "over_70": 95 },
"pregnancy": 105,
"lactation": 125
},
"rdaEu": null,
"rdaUs": null
}
]
}slugdoit être l'un des slugs ci-dessous. L'application ignore les autres.kindvauttarget(« en as-tu assez pris ? ») ouceiling(« es-tu resté en dessous ? »). Aujourd'hui, seul le sodium est unceiling. Une valeur inconnue ou manquante est interprétée commetarget.rdaDe,rdaEuetrdaUssont trois publications distinctes : la DGE, l'EFSA et les États-Unis (NASEM). Envoie celles que tu as etnullpour les autres. L'application affiche une seule référence à la fois, selon le réglage de l'instance, sans jamais en mélanger deux.- Les tranches d'âge sont exactement
14-18,19-30,31-50,51-70etover_70. Les cinq sont requises pour les deux sexes. Il n'y a pas de tranche plus jeune. L'application n'affiche aucune référence pour une personne de moins de 14 ans. pregnancyetlactationsont des nombres optionnels. L'application les utilise uniquement en local. Rien sur une personne ne t'est jamais envoyé.
Slugs de nutriments :
| Clé de l'application | Slug |
|---|---|
vitaminA | vitamin-a-retinol |
vitaminC | vitamin-c |
vitaminD | vitamin-d |
vitaminE | vitamin-e-tocopherole |
vitaminB1 | vitamin-b-1-thiamine |
vitaminB2 | vitamin-b-2-riboflavin |
vitaminB6 | vitamin-b-6-pyridoxine |
vitaminB9 | folic-acid-folate |
vitaminB12 | vitamin-b-12-cobalamin |
potassium | potassium |
sodium | sodium |
calcium | calcium |
magnesium | magnesium |
zinc | zinc |
phosphorus | phosphorus |
iron | iron |
betaCarotene n'a pas de référence propre, car il fait partie de la vitamine A.
Aliments riches en un nutriment
GET /api/v1/nutrients/{slug}/foods?locale=en&limit=8
{
"nutrient": "vitamin-c",
"unit": "mg",
"foods": [
{ "slug": "acerola-raw", "title": "Acerola, raw", "url": null, "imageUrl": null,
"netCarbsPer100g": 7.7, "attribution": null, "value": 1677.6 }
]
}Trie foods du plus riche au moins riche. value est la quantité pour 100 g et n'est jamais null. Omets les aliments sans valeur plutôt que de les envoyer avec 0.
Erreurs
| Statut | Signification | Ce que fait l'application |
|---|---|---|
| 401 | La clé est incorrecte ou manquante | Enregistre invalid_key, affiche un message, continue de fonctionner avec les chiffres de l'IA |
| 429 | Dépassement du quota ou de la limite de requêtes | Enregistre rate_limited, ou allowance_exhausted si le corps l'indique |
| autre chose | Échec | Traité comme "aucune réponse", sans avertissement |
Pour 401 et 429, tu peux envoyer un corps sous la forme { "error": { "code": "allowance_exhausted" } }. Les codes reconnus sont invalid_key, rate_limited et allowance_exhausted. Un code inconnu se rabat sur la raison sous-entendue par le statut. L'application ne met jamais un refus en cache.
Teste ton serveur
Exécute ces requêtes sur ton serveur. Chacune doit renvoyer du JSON conforme aux sections ci-dessus.
BASE=https://food.example.org
curl -s -X POST "$BASE/api/v1/foods/search" -H 'content-type: application/json' \
-d '{"q":"apple","locale":"en","limit":3}'
curl -s "$BASE/api/v1/foods/apple-raw?locale=en"
curl -s "$BASE/api/v1/foods/does-not-exist?locale=en" -w '\n%{http_code}\n' # 404, {"error":"not_found"}
curl -s "$BASE/api/v1/nutrients"Définis ensuite FOOD_DB_API_URL sur le serveur de l'application, redémarre-le et recherche un aliment dans l'application. Un aliment consigné avec un bloc vitamins compte alors dans la carte des vitamines et minéraux de sa journée du journal. Si ce n'est pas le cas, consulte le journal du serveur de l'application : un refus est consigné au niveau warning avec les 16 premiers caractères de la clé et jamais la clé entière.
Ce qui n'est pas couvert
- Aucun serveur n'est fourni. Tu en crées un, ou tu utilises lowcarbcheck.org.
- Propositions d'aliments (
POST /api/v1/foods/proposals,FOOD_DB_BACKFILL) sont une fonctionnalité distincte, propre à LowCarbCheck, et ne font pas partie de ce contrat. - Aucun versionnement pour le moment. Les chemins portent
/v1, mais la structure peut encore changer tant que cette page indique "expérimental". - Les schémas réseau résident dans le code. La source normative actuelle est
app/services/food-resolution/schema.ts(recherche et détail),app/lib/nutrient-reference.ts(apports de référence et aliments riches) etapp/services/food-db/micronutrients-wire.ts(la route de rétroremplissage propre à l'application, qui ne fait pas partie de ce contrat).