API de recherche alimentaire
Recherche plein texte dans 6,1 millions de produits alimentaires par nom, marque ou catégorie — classée par pertinence, paginée et rapide. Cherchez dans la langue imprimée sur l'étiquette (cyrillique compris) et passez lang=en|fr|es|de|it|bg pour obtenir les noms, catégories et allergènes localisés. Une seule requête GET renvoie un JSON propre que votre application peut afficher immédiatement.
curl -H "X-API-Key: food_•••" \
"https://foodbase.dev/v1/foods/search?q=banana&limit=20"
{
"data": [
{
"id": "034becf0-9aa7-c748-41aa-b71dd4df2bae",
"name_default": "Bananes",
"name_localized": "Bananas",
"brand": "Bio Village, Marque Repère",
"barcode": "3564707141710",
"category": "...,Fruits,Fruits tropicaux,Bananes",
"nutriscore": "a"
}
],
"total": 1000,
"limit": 20,
"offset": 0
} Vous préférez cliquer ? Essayez l'explorateur de produits ou le bac à sable photo.
Ce que vous obtenez
id
Identifiant stable de l'aliment — à réutiliser avec les autres endpoints.
name_default
Nom du produit dans sa langue principale.
name_localized / category_localized
Traductions dans la langue demandée (en, fr, es, de, it, bg) quand elles existent — sinon le texte d'origine.
brand
Marque / fabricant quand il est connu.
barcode
Code-barres EAN-13 / UPC quand il est disponible.
category
Catégorie alimentaire de premier niveau.
total / limit / offset
Métadonnées de pagination pour l'ensemble des résultats.
Inclus dans le forfait gratuit — 100 requêtes/jour, sans carte bancaire. Voir les forfaits →
Exemples de code
La même requête en curl, JavaScript et Python. Remplacez YOUR_API_KEY par une clé de votre tableau de bord.
curl "https://foodbase.dev/v1/foods/search?q=banana&limit=20" \
-H "X-API-Key: YOUR_API_KEY"
const res = await fetch("https://foodbase.dev/v1/foods/search?q=banana&limit=20", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
import requests
res = requests.get(
"https://foodbase.dev/v1/foods/search?q=banana&limit=20",
headers={"X-API-Key": "YOUR_API_KEY"},
timeout=30,
)
res.raise_for_status()
data = res.json()
Comment ça marche
Ce que fait une requête de recherche
Une recherche est un simple GET avec le texte dans q. Les mots sont comparés au nom du produit et à la marque ensemble : q=olympus milk trouve des produits dont le nom ne mentionne jamais Olympus, et le dernier mot est traité comme un préfixe, si bien que milk olym trouve déjà des résultats pendant que l'utilisateur tape. Tous les mots doivent correspondre ; il n'y a pas de OU. q doit faire de 1 à 200 caractères ; un q absent ou vide est rejeté avant toute recherche, donc protégez le champ de recherche vide de votre côté.
Les produits dont le nom correspond exactement à votre saisie arrivent en premier : banana liste les bananes avant les yaourts à la banane, et greek yogurt liste les yaourts grecs nature. Ensuite, les résultats sont classés par pertinence, et à pertinence égale le produit le plus populaire passe devant. Une requête de marque comme pringles saute l'étape du nom exact et reste triée par popularité ; ajouter un deuxième mot (une marque, un parfum) est le moyen le plus rapide de faire remonter un produit précis.
Les noms de catégorie servent de repli, ils ne font pas partie de la correspondance principale : c'est seulement quand la recherche nom-et-marque ne trouve rien que l'API cherche les produits dont la catégorie contient les mots, quelle que soit la langue dans laquelle la catégorie est stockée. C'est ce qui permet à une requête comme Seeds & Related Snacks ou Fruits tropicaux de renvoyer une liste, triée par popularité, au lieu d'un tableau vide.
- L'ordre n'est ni alphabétique ni par id. Ne triez pas à nouveau le tableau data côté client.
- Si la saisie est un GTIN composé uniquement de chiffres, envoyez-la plutôt à l'API de recherche par code-barres : elle résout les variantes avec zéros de remplissage (0000080042556 trouve 80042556), ce que la recherche textuelle ne fait pas.
Lire une ligne de résultat
Chaque élément de data est un FoodListItem, une forme plus légère que la fiche complète : id, barcode, source, name_default, brand, category, image_url, nutriscore et nova_group, plus name_localized (toujours présent) et category_localized quand une lang autre que l'anglais est passée. Aucun nutriment ici : la liste sert à afficher des lignes, pas à faire des calculs.
Plusieurs champs peuvent être null, et l'exemple le montre. Le bonbon Roshen 'Banana land' (4f995c13-c0df-bfcc-b6ec-449d312def75) a image_url null et nova_group null ; brand et barcode peuvent aussi être null. Les valeurs de nutriscore hors de a à e, comme les 'n' et 'u' de l'exemple, signifient qu'aucune note n'a été calculée : affichez « non noté » plutôt que la lettre brute.
category est le chemin de catégorie complet d'Open Food Facts, séparé par des virgules du plus général au plus précis ('...,Fruits tropicaux,Bananes' pour les bananes Bio Village). Affichez le dernier segment comme libellé, ou le premier pour un regroupement grossier, et retirez les préfixes de langue comme pt: avant l'affichage. image_url est une photo de contributeur sur images.openfoodfacts.org : prévoyez une image par défaut pour le cas null.
Pagination, total et identifiants stables
limit va de 1 à 100 (20 par défaut) et offset de 0 à 10000. total est un compte plafonné : l'API arrête de compter à 1 000 correspondances, donc le total de 1000 de l'exemple pour q=banana avec limit=3 signifie au moins 1 000. Affichez « 1 000+ » et ne calculez pas de numéro de dernière page à partir de lui.
Paginez en ajoutant limit à offset jusqu'à ce que data revienne avec moins de limit éléments. Comme offset plafonne à 10000, une requête avec davantage de correspondances demande un q plus précis, pas une page plus profonde.
- Les id sont stables d'une réimportation FoodBase à l'autre : stockez l'id d'une ligne de recherche et résolvez-le plus tard avec l'API de base de données alimentaire sans relancer de recherche.
- Le classement est calculé à chaque requête et la popularité peut évoluer : un offset n'est donc pas un curseur durable. Conservez des id, pas des positions de page.
Chercher dans une autre langue
lang vaut en par défaut. Chaque langue cherche dans l'entrée de cette langue du tableau name du produit, quand un bénévole ou la traduction IA de FoodBase en a fourni une, et se replie sur name_default, le nom tel qu'imprimé sur l'étiquette, pour les produits qui n'en ont pas ; la marque fait toujours partie de la correspondance. Ainsi lang=bg atteint tous les produits tout en privilégiant les noms bulgares quand ils existent, et une étiquette en cyrillique est trouvée quelle que soit la lang.
La lang passée façonne aussi la réponse. name_localized contient le nom traduit : l'exemple, demandé avec lang=en, transforme 'Puré de plátano y fresa' en 'Banana and Strawberry Puree', et se replie sur name_default quand il n'y a rien à traduire, comme le montre 'Banana land'. Affichez name_localized s'il n'est pas null, sinon name_default. category_localized n'est pas un champ obligatoire et n'apparaît pas dans l'exemple : lisez-le de façon défensive.
Autocomplétion et passage aux fiches complètes
La recherche est le bon endpoint pour un champ de saisie avec suggestions. Temporisez les frappes (debounce), demandez limit=8 à 10 et annulez la requête en cours quand l'utilisateur tape à nouveau, pour qu'une réponse lente et plus ancienne n'écrase pas une plus récente. Chaque frappe qui atteint l'API compte comme une requête dans votre quota quotidien : le debounce est un levier de coût autant qu'un détail d'ergonomie.
Quand l'utilisateur choisit une ligne, appelez l'API de base de données alimentaire avec l'id pour obtenir la fiche complète, ou l'API de nutrition si vous n'avez besoin que du tableau des nutriments pour 100 g. Une recherche plus une requête de détail coûte moins cher que de récupérer la fiche complète de chaque suggestion.
La réponse de recherche capturée ne contient pas d'en-tête Cache-Control, contrairement aux fiches alimentaires, qui peuvent être mises en cache 300 secondes. Mettez vous-même les résultats en cache, avec pour clé exacte q, lang, limit et offset ; les mêmes préfixes courts reviennent sans cesse dans un champ à suggestions. X-RateLimit-Second-Remaining indique la marge de rafale restante, et en cas de 429 l'en-tête Retry-After donne le nombre de secondes à attendre.
Ce que vous pouvez créer
Sélecteurs d'aliments et autocomplétion
Alimentez le champ de recherche d'un compteur de calories ou d'une appli de recettes — des suggestions instantanées parmi 6,1 M de produits.
Enrichissement de catalogue
Rapprochez vos propres références des fiches alimentaires canoniques par nom ou par marque.
Flux de découverte
Parcourez par catégorie ou mot-clé pour composer des listes d'aliments.
Limites par forfait
| Free | 100 requêtes/jour partagées entre les clés du compte ; 1 requête/s par clé, rafale de 5. Le plafond de 25 détails nutritionnels/jour ne s'applique pas à la recherche. |
|---|---|
| Starter | 5 000 requêtes/jour ; 5 requêtes/s par clé, rafale de 25. |
| Pro | 25 000 requêtes/jour ; 20 requêtes/s par clé, rafale de 100. |
| Enterprise | 100 000 requêtes/jour ; 50 requêtes/s par clé, rafale de 250. |
Chaque réponse contient des en-têtes X-RateLimit-* indiquant le budget restant ; chaque 429 contient Retry-After . Comparer les forfaits →
Erreurs et comment les gérer
401 — L'en-tête X-API-Key est absent, mal formé ou appartient à une clé révoquée.
Vérifiez le nom de l'en-tête et la clé dans votre tableau de bord. Ne réessayez pas avant d'avoir corrigé.
422 — q est absent ou vide, dépasse 200 caractères, limit est hors de 1 à 100, offset hors de 0 à 10000, ou lang ne fait pas partie de en, fr, es, de, it, bg.
Lisez le tableau details du corps Validation Error pour savoir quel paramètre a échoué et corrigez la requête côté client. Ne renvoyez jamais un 422 à l'identique.
429 — La rafale par seconde de la clé est épuisée ou le quota quotidien du compte est consommé.
Attendez le nombre de secondes indiqué dans Retry-After et réessayez une fois. Dans un flux d'autocomplétion, abandonnez la frappe périmée au lieu de la mettre en file.
Comparaison avec d'autres API
Un moteur de recherche généraliste ou un catalogue e-commerce indexe du texte ; la recherche FoodBase indexe des produits alimentaires, si bien qu'un résultat arrive avec le code-barres, le chemin de catégorie, le Nutri-Score et le groupe NOVA, et un id qui mène au tableau des nutriments en une requête de plus. La contrepartie, c'est le périmètre : elle compare les noms de produits et les marques, avec les noms de catégorie seulement en repli, plutôt que tous les champs de la fiche, et une lang autre que l'anglais cherche les noms localisés quand ils existent et les noms d'étiquette ailleurs, au lieu de traduire votre requête. Pas de bibliothèque cliente à installer ni d'index à construire de votre côté : un GET et un objet JSON. Si vous comparez des fournisseurs de données alimentaires, /compare/food-database-apis détaille ce que chacun couvre.
Voir la comparaison complète des API de données alimentaires →
Questions fréquentes
Pourquoi total vaut-il exactement 1000 ? +
Le compte est plafonné. Il s'arrête à 1 000 correspondances, donc 1000 signifie au moins autant. Affichez « 1 000+ » et paginez avec offset jusqu'à ce que data renvoie moins de limit lignes, plutôt que de diviser total par limit.
Que renvoie l'API quand rien ne correspond ? +
Un 200 avec un tableau data vide, pas un 404. Traitez le tableau vide comme votre état « aucun résultat ». Comme chaque mot de q doit correspondre, retirer le mot le moins précis ou celui qui risque une faute de frappe est la parade habituelle avant de l'afficher.
Puis-je chercher uniquement par marque ou par catégorie ? +
Par marque, oui : la marque fait partie de ce à quoi chaque mot de q est comparé, donc q=Hacendado liste les produits de cette marque, et ajouter un mot de produit affine la liste. Les noms de catégorie ne sont essayés que si la recherche nom-et-marque ne renvoie rien : q=Fruits tropicaux renvoie donc les produits classés dans cette catégorie, triés par popularité plutôt que par pertinence.
Un résultat de recherche contient-il les nutriments ? +
Non. Les lignes de recherche sont la forme allégée FoodListItem, sans champs de nutriments. Passez l'id de la ligne à l'API de nutrition au moment de la sélection plutôt que pour chaque suggestion, car le forfait Free autorise 25 détails nutritionnels par jour.
Quelle lang une appli bulgare doit-elle passer ? +
lang=bg cherche dans les noms localisés en bulgare et renvoie name_localized en bulgare quand une traduction existe. Les étiquettes saisies en cyrillique comme nom par défaut sont trouvées avec lang=en, car en cherche dans le nom par défaut quel que soit l'alphabet. Interrogez d'abord bg et repliez-vous sur en si le résultat est vide.
Le classement est-il le même que sur le site FoodBase ? +
Oui. L'explorateur de produits sur /products et les pages de catégorie sur /categories/ utilisent le même endpoint : vous pouvez tester une requête sur le site et attendre le même ordre dans votre appli pour les mêmes q et lang. La recherche ne consomme aucun crédit IA ; chaque appel compte comme une requête dans le quota quotidien.
Découvrez d'autres endpoints
API de recherche par code-barres
Scannez ou tapez un code-barres, obtenez le produit. Résolvez n'importe quel code EAN-13 ou UPC en fiche alimentaire — marque, catégorie, Nutri-Score et résumé nutritionnel — en une seule requête. Conçue pour les applis de scan.
En savoir plus →API de base de données alimentaire
Une source unique pour 6,1 millions d'aliments. Récupérez une fiche produit complète par ID — nom, marque, ingrédients, allergènes, Nutri-Score, groupe NOVA, images, et plus encore.
En savoir plus →API de nutrition
Allez au-delà des macros principales. Obtenez le détail de plus de 40 nutriments par produit — énergie, protéines, glucides, lipides, sucres, fibres, sodium, vitamines et minéraux — tous pour 100 g, pour n'importe quel aliment par ID.
En savoir plus →Commencez à développer avec l'API de recherche alimentaire
Gratuit pour démarrer. Sans carte bancaire.
Obtenir une clé API Aller au tableau de bord