Saltar al contenido

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ónPara qué se usaSi tu servidor no la tiene
POST /api/v1/foods/searchBúsqueda de alimentos, y las vitaminas y minerales de una entrada nuevaLa 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/nutrientsIngestas de referencia, para que la aplicación pueda mostrar carenciasLa 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 nutrientesLa 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 lleva authorization: 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.
  • null no es 0. null significa "esta fuente no tiene el dato". 0 significa "se midió, y es cero". La app solo contabiliza datos medidos, así que nunca envíes 0 para 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.

POST /api/v1/foods/search

Cuerpo de la solicitud:

json
{ "q": "hähnchenbrust", "locale": "de", "limit": 10 }
  • q es el nombre del alimento que la persona escribió o que la IA identificó.
  • locale es uno de estos valores: en, de, fr, it, es, tr. Devuelve los títulos en ese idioma cuando dispongas de ellos.
  • limit vale 10 hoy.

Respuesta:

json
{
  "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 }
    }
  ]
}
CampoTipoSignificado
slugstring, obligatorioUn identificador estable para el alimento. La app lo guarda y vuelve a solicitarlo después en la petición de detalle.
localestring, obligatorioEl idioma de title.
titlestring, obligatorioEl nombre que se mostrará.
canonicalNamestring, obligatorioUn nombre en inglés, usado como etiqueta estable.
urlstring o nullUna página para el alimento. null cuando no exista ninguna. Su ausencia se interpreta como null.
imageUrlstring o null, obligatorioUna imagen, o null.
macrosPer100gobject, obligatoriokcal, protein, fat, carbs, fiber, sugars, polyols. Cada uno es un número o null. Las siete claves deben estar presentes.
netCarbsPer100gnumber o null, obligatorioCarbohidratos sin fibra. Consulta Carbohidratos.
attributionstring o nullEl crédito de licencia para los datos importados. La app lo muestra.
scorenúmero, obligatorioRelevancia, de 0 a 1. Consulta Clasificación.
originstring o nullEl origen del alimento. Determina cómo lee la app carbs. Consulta Carbohidratos.
portionSizenúmero o nullUna porción típica en gramos.
vitaminsobjeto o nullConsulta Vitaminas y minerales. Omítelo si el alimento no incluye datos de vitaminas.
mineralsobjeto o nullLo 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:

originBaseQué significa macrosPer100g.carbs
bls, curateddisponibles (UE)Carbohidratos sin fibra. La fibra va en una fila aparte.
fdc, user, proposaltotales (EE. UU.)Carbohidratos con fibra. Los carbohidratos netos son los carbohidratos menos la fibra.
ausente o cualquier otro valordesconocidoLa 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 score de 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 score inferior 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:

BloqueClavesUnidad
vitaminsbetaCarotene, vitaminA, vitaminD, vitaminB9, vitaminB12µg
vitaminsvitaminC, vitaminE, vitaminB1, vitaminB2, vitaminB6mg
mineralsnacl, potassium, sodium, calcium, magnesium, zinc, phosphorus, ironmg

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:

  1. Bloque ausente (o null): este alimento no tiene ningún dato de vitaminas o minerales.
  2. La clave es null: el bloque existe y este dato concreto es desconocido.
  3. La clave es un número: un dato medido. 0 son 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, vitamins y minerals. 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.

json
{
  "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
    }
  ]
}
  • slug debe ser uno de los slugs indicados abajo. La app ignora los demás.
  • kind es target ("¿has tomado suficiente?") o ceiling ("¿te has mantenido por debajo?"). A día de hoy, solo el sodio es un ceiling. Un valor desconocido o ausente se interpreta como target.
  • rdaDe, rdaEu y rdaUs son tres documentos publicados independientes: DGE, EFSA y Estados Unidos (NASEM). Envía los que tengas y null para 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-70 y over_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.
  • pregnancy y lactation son 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 appSlug
vitaminAvitamin-a-retinol
vitaminCvitamin-c
vitaminDvitamin-d
vitaminEvitamin-e-tocopherole
vitaminB1vitamin-b-1-thiamine
vitaminB2vitamin-b-2-riboflavin
vitaminB6vitamin-b-6-pyridoxine
vitaminB9folic-acid-folate
vitaminB12vitamin-b-12-cobalamin
potassiumpotassium
sodiumsodium
calciumcalcium
magnesiummagnesium
zinczinc
phosphorusphosphorus
ironiron

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

json
{
  "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

EstadoSignificadoQué hace la app
401Falta la clave o no es correctaRegistra invalid_key, muestra un aviso y sigue funcionando con los valores de la IA
429Se ha superado la cuota o el límite de peticionesRegistra rate_limited, o allowance_exhausted cuando el cuerpo de la respuesta así lo indique
cualquier otra opciónFalloSe 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.

bash
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) y app/services/food-db/micronutrients-wire.ts (la ruta interna de relleno de la app, que no forma parte de este contrato).

Edita esta página en GitHub