La aplicación
Contrato de la API de la base de datos de alimentos (experimental)
Experimental. Las peticiones y respuestas que debe ofrecer una base de datos de alimentos para que FOOD_DB_API_URL pueda apuntar a un servidor distinto de lowcarbcheck.org
Esta página es una traducción automática de la documentación en inglés.
Experimental. Esta página describe lo que el servidor de la aplicación le pide hoy a una base de datos de alimentos. No es una API estable. Una versión menor puede cambiarla, y el registro de cambios lo indicará. El único servidor que la implementa es lowcarbcheck.org. Todavía no existe ningún servidor de código abierto ni hay pruebas de conformidad. Si creas uno, cuéntanoslo en una incidencia para que el contrato pueda evolucionar con un segundo usuario.
openplate busca alimentos por nombre a través de su propio servidor. Por defecto, ese servidor consulta a lowcarbcheck.org. FOOD_DB_API_URL hace que apunte a otro servidor que responda a las mismas peticiones. configuration.md explica la clave y la cuota. Esta página explica las peticiones y las respuestas.
Qué contiene el contrato
El servidor de la aplicación realiza cuatro tipos de peticiones. Solo la primera es obligatoria.
| Petición | Para qué se usa | Si tu servidor no la tiene |
|---|---|---|
POST /api/v1/foods/search | Búsqueda de alimentos, y las vitaminas y minerales de una entrada nueva | La búsqueda de alimentos queda vacía. Los escaneos conservan los propios números de la IA. |
GET /api/v1/foods/{slug} | Vitaminas para entradas antiguas del diario (el rellenado retroactivo) | Las entradas antiguas se quedan sin vitaminas. |
GET /api/v1/nutrients | Ingestas de referencia, para que la aplicación pueda mostrar carencias | La aplicación muestra lo que contiene un día y ninguna carencia. |
GET /api/v1/nutrients/{slug}/foods | "Alimentos ricos en este nutriente" en la pantalla de nutrientes | La lista está vacía. |
El servidor de la aplicación trata cualquier fallo como una respuesta vacía. Si falta un endpoint, si se agota el tiempo de espera, si devuelve un estado distinto de 2xx o si el cuerpo no coincide, nunca se detiene un escaneo ni una página del diario. Esto se conoce como fail-open.
Reglas para cada petición
- La dirección base es
FOOD_DB_API_URL. Las rutas anteriores se añaden a continuación. - Solo JSON. Las peticiones envían
accept: application/json. Las respuestas son JSON en UTF-8. - La clave. Cuando se define
FOOD_DB_API_KEY, cada petición llevaauthorization: Bearer <key>. Tu servidor decide qué significa una clave. Si no se define ninguna clave, la cabecera no se envía. - El tiempo de espera es de 4 segundos por petición. Una respuesta más lenta cuenta como un fallo.
- Los campos desconocidos se ignoran. Puedes añadir campos con libertad. Si falta un campo opcional, se interpreta como "sin dato".
- Por cada 100 g. Todas las cifras de los alimentos son por cada 100 g de alimento. La energía va en kcal. Los macronutrientes van en gramos.
nullno es0.nullsignifica "esta fuente no tiene el dato".0significa "se midió, y es cero". La app solo contabiliza datos medidos, así que nunca envíes0para un valor desconocido.- Privacidad. La solicitud de búsqueda lleva el nombre del alimento en un cuerpo
POST, no en la dirección, para que el nombre no acabe en un registro de acceso. Conserva esa propiedad: no registres los cuerpos de las solicitudes. La app no envía cuentas, diarios ni datos corporales. Consulta architecture.md.
Búsqueda
POST /api/v1/foods/search
Cuerpo de la solicitud:
{ "q": "hähnchenbrust", "locale": "de", "limit": 10 }qes el nombre del alimento que la persona escribió o que la IA identificó.localees uno de estos valores:en,de,fr,it,es,tr. Devuelve los títulos en ese idioma cuando dispongas de ellos.limitvale 10 hoy.
Respuesta:
{
"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 }
}
]
}| Campo | Tipo | Significado |
|---|---|---|
slug | string, obligatorio | Un identificador estable para el alimento. La app lo guarda y vuelve a solicitarlo después en la petición de detalle. |
locale | string, obligatorio | El idioma de title. |
title | string, obligatorio | El nombre que se mostrará. |
canonicalName | string, obligatorio | Un nombre en inglés, usado como etiqueta estable. |
url | string o null | Una página para el alimento. null cuando no exista ninguna. Su ausencia se interpreta como null. |
imageUrl | string o null, obligatorio | Una imagen, o null. |
macrosPer100g | object, obligatorio | kcal, protein, fat, carbs, fiber, sugars, polyols. Cada uno es un número o null. Las siete claves deben estar presentes. |
netCarbsPer100g | number o null, obligatorio | Carbohidratos sin fibra. Consulta Carbohidratos. |
attribution | string o null | El crédito de licencia para los datos importados. La app lo muestra. |
score | número, obligatorio | Relevancia, de 0 a 1. Consulta Clasificación. |
origin | string o null | El origen del alimento. Determina cómo lee la app carbs. Consulta Carbohidratos. |
portionSize | número o null | Una porción típica en gramos. |
vitamins | objeto o null | Consulta Vitaminas y minerales. Omítelo si el alimento no incluye datos de vitaminas. |
minerals | objeto o null | Lo mismo, para los minerales. |
Carbohidratos
Las fuentes muestran los carbohidratos de dos formas distintas, y la app necesita saber cuál usa cada alimento. Lo indica el campo origin:
origin | Base | Qué significa macrosPer100g.carbs |
|---|---|---|
bls, curated | disponibles (UE) | Carbohidratos sin fibra. La fibra va en una fila aparte. |
fdc, user, proposal | totales (EE. UU.) | Carbohidratos con fibra. Los carbohidratos netos son los carbohidratos menos la fibra. |
| ausente o cualquier otro valor | desconocido | La app lo procesa como totales, y el usuario puede cambiarlo a mano. |
Si tus datos siguen el formato de la UE, envía origin: "curated". Si siguen el formato de EE. UU., envía origin: "fdc". Un valor incorrecto aquí altera los carbohidratos y las calorías de cada alimento afectado, así que compruébalo con un alimento que conozcas.
netCarbsPer100g es tu propio valor de carbohidratos netos por cada 100 g, y la app le da prioridad frente a su propio cálculo. null significa "desconocido para este alimento". La app calcula entonces los carbohidratos netos a partir de los macronutrientes y la base anterior. No envíes nunca 0 para un valor desconocido.
Clasificación
Ordena results por relevancia, los mejores primero. La app conserva tu orden. Descarta las coincidencias débiles según score:
- Un
scorede 0,4 o más debe indicar una coincidencia de texto real (exacta, prefijo, palabra o subcadena). La app lo descarta por debajo de 0,45. - Un
scoreinferior a 0,4 debe indicar una coincidencia solo por errata. La app lo descarta por debajo de 0,3. - Una coincidencia por errata nunca debe puntuar 0,4 o más, o superará a una coincidencia real.
Vitaminas y minerales
Cada objeto de alimento incluye dos bloques opcionales, vitamins y minerals. Un bloque contiene estas claves y ninguna otra que lea la app:
| Bloque | Claves | Unidad |
|---|---|---|
vitamins | betaCarotene, vitaminA, vitaminD, vitaminB9, vitaminB12 | µg |
vitamins | vitaminC, vitaminE, vitaminB1, vitaminB2, vitaminB6 | mg |
minerals | nacl, potassium, sodium, calcium, magnesium, zinc, phosphorus, iron | mg |
Cada valor es un número o null. Si omites una clave, se interpreta como null. El bloque en sí tiene tres estados, y no son equivalentes:
- Bloque ausente (o
null): este alimento no tiene ningún dato de vitaminas o minerales. - La clave es
null: el bloque existe y este dato concreto es desconocido. - La clave es un número: un dato medido.
0son datos reales.
La app solo considera cubierto un nutriente en un día cuando una cantidad suficiente de comida en peso incluye un dato medido (el 60 por ciento actualmente). Los ceros incorrectos hacen que parezca que la persona no tomó nada de una vitamina, así que envía null cuando no tengas certeza.
La vitamina A se expresa en equivalentes de actividad de retinol (RAE), que ya incluye los carotenoides. betaCarotene aporta contexto, no es una suma. Si tu fuente proporciona equivalentes de retinol (RE) o unidades internacionales, conviértelos antes de enviar los datos, o los totales serán erróneos.
Detalle del alimento
GET /api/v1/foods/{slug}?locale=de
La app solicita las vitaminas de un alimento que la persona registró antes de que existiera la función de vitaminas. Envía el slug de un resultado de búsqueda antiguo.
- Encontrado: estado 200 y un objeto de alimento con
slug,vitaminsyminerals. Los mismos bloques que en la búsqueda. Los demás campos se ignoran. - No encontrado en esa configuración regional: estado 404 con exactamente este cuerpo:
{ "error": "not_found" }. A continuación, la app prueba con las otras configuraciones regionales (de,en,fr,it,es, priorizando el idioma de la persona), de modo que un alimento conocido solo en una configuración regional se sigue encontrando. Cualquier otro cuerpo en un 404 se considera un error, no un "no encontrado". - Encontrado y sin bloques: 200 con ambos bloques ausentes. La app registra "comprobado, sin datos" y no vuelve a preguntar.
Cada consulta puede costar un crédito en tu lado, hasta cinco en el caso de un slug que ninguna configuración regional conozca. La app pregunta una sola vez por cada alimento distinto y por dispositivo.
Ingestas de referencia
GET /api/v1/nutrients
Los valores siguientes solo muestran la estructura. No son ingestas de referencia reales.
{
"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
}
]
}slugdebe ser uno de los slugs indicados abajo. La app ignora los demás.kindestarget("¿has tomado suficiente?") oceiling("¿te has mantenido por debajo?"). A día de hoy, solo el sodio es unceiling. Un valor desconocido o ausente se interpreta comotarget.rdaDe,rdaEuyrdaUsson tres documentos publicados independientes: DGE, EFSA y Estados Unidos (NASEM). Envía los que tengas ynullpara el resto. La app muestra una única base de referencia a la vez, definida por el ajuste de la instancia, y nunca mezcla dos.- Los tramos de edad son exactamente
14-18,19-30,31-50,51-70yover_70. Se necesitan los cinco para ambos sexos. No existe un tramo inferior. La app no muestra ninguna referencia para menores de 14 años. pregnancyylactationson números opcionales. La app solo los utiliza a nivel local. Nunca se te envía ningún dato sobre la persona.
Slugs de nutrientes:
| Clave de la app | 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 no tiene una referencia propia, ya que forma parte de la vitamina A.
Alimentos ricos en un nutriente
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 }
]
}Ordena foods de mayor a menor contenido. value es la cantidad por cada 100 g y nunca es null. Omite los alimentos sin datos en vez de enviarlos con valor 0.
Errores
| Estado | Significado | Qué hace la app |
|---|---|---|
| 401 | Falta la clave o no es correcta | Registra invalid_key, muestra un aviso y sigue funcionando con los valores de la IA |
| 429 | Se ha superado la cuota o el límite de peticiones | Registra rate_limited, o allowance_exhausted cuando el cuerpo de la respuesta así lo indique |
| cualquier otra opción | Fallo | Se procesa como "sin respuesta", sin mostrar avisos |
En las respuestas 401 y 429, puedes enviar un cuerpo con el formato { "error": { "code": "allowance_exhausted" } }. Los códigos reconocidos son invalid_key, rate_limited y allowance_exhausted. Si el código no se reconoce, se recurre al motivo que implica el propio estado HTTP. La app nunca guarda en caché un rechazo.
Prueba tu servidor
Ejecuta estas peticiones contra tu servidor. Cada una debe devolver un JSON que coincida con las secciones anteriores.
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"A continuación, define FOOD_DB_API_URL en el servidor de la app, reinícialo y busca un alimento en la app. Un alimento registrado con un bloque vitamins pasa a computar en la tarjeta de vitaminas y minerales de su día del diario. Si no lo hace, consulta el registro del servidor de la app: los rechazos se registran con nivel de advertencia, mostrando los primeros 16 caracteres de la clave y nunca la clave completa.
Qué queda fuera de este contrato
- No se incluye ningún servidor. Tienes que crear uno tú, o usar lowcarbcheck.org.
- Propuestas de alimentos (
POST /api/v1/foods/proposals,FOOD_DB_BACKFILL) son una función independiente y específica de LowCarbCheck, por lo que no forman parte de este contrato. - Aún no hay control de versiones. Las rutas llevan
/v1, pero la estructura todavía puede cambiar mientras esta página indique "experimental". - Los esquemas de transmisión están definidos en el código. Las fuentes normativas actuales son
app/services/food-resolution/schema.ts(búsqueda y detalle),app/lib/nutrient-reference.ts(ingestas de referencia y alimentos ricos) yapp/services/food-db/micronutrients-wire.ts(la ruta interna de relleno de la app, que no forma parte de este contrato).