L'app
Contratto dell'API del database alimentare (sperimentale)
Sperimentale. Le richieste e le risposte che un database alimentare deve fornire, così che FOOD_DB_API_URL possa puntare a un server diverso da lowcarbcheck.org
Questa pagina è tradotta automaticamente dalla documentazione in inglese.
Sperimentale. Questa pagina descrive cosa richiede oggi il server dell'app a un database alimentare. Non è un'API stabile. Una release minore può modificarla, e il changelog lo segnalerà. L'unico server che la implementa è lowcarbcheck.org. Non esiste ancora un server open source e non c'è alcun test di conformità. Se ne crei uno, segnalacelo in una issue, così che il contratto possa evolversi con un secondo utente.
openplate cerca gli alimenti per nome tramite il proprio server. Per impostazione predefinita, quel server interroga lowcarbcheck.org. FOOD_DB_API_URL lo indirizza verso un altro server che risponde alle stesse richieste. configuration.md spiega la chiave e la quota. Questa pagina illustra le richieste e le risposte.
Cosa contiene il contratto
Il server dell'app effettua quattro tipi di richiesta. Solo la prima è obbligatoria.
| Richiesta | Scopo | Se manca sul tuo server |
|---|---|---|
POST /api/v1/foods/search | Ricerca degli alimenti, e vitamine e minerali per una nuova voce | La ricerca degli alimenti è vuota. Le scansioni mantengono i valori dell'IA. |
GET /api/v1/foods/{slug} | Vitamine per le vecchie voci del diario (il backfill) | Le vecchie voci restano senza vitamine. |
GET /api/v1/nutrients | Assunzioni di riferimento, così che l'app possa mostrare le carenze | L'app mostra cosa contiene una giornata, ma nessuna carenza. |
GET /api/v1/nutrients/{slug}/foods | "Alimenti ricchi di questo nutriente" nella schermata dei nutrienti | La lista è vuota. |
Il server dell'app tratta ogni errore come una risposta vuota. Un endpoint mancante, un timeout, uno stato diverso da 2xx o un corpo non corrispondente non bloccano mai una scansione o una pagina del diario. Questo comportamento si chiama fail-open.
Regole per ogni richiesta
- L'indirizzo di base è
FOOD_DB_API_URL. I percorsi indicati sopra vengono aggiunti in coda. - Solo JSON. Le richieste inviano
accept: application/json. Le risposte sono in JSON UTF-8. - La chiave. Quando
FOOD_DB_API_KEYè impostata, ogni richiesta includeauthorization: Bearer <key>. È il tuo server a stabilire il significato della chiave. Se non è impostata alcuna chiave, l'intestazione è assente. - Il timeout è di 4 secondi per richiesta. Una risposta più lenta viene considerata un errore.
- I campi sconosciuti vengono ignorati. Puoi aggiungere campi liberamente. Un campo facoltativo assente viene interpretato come "nessun valore".
- Per 100 g. Tutti i valori nutrizionali si riferiscono a 100 g di alimento. L'energia è in kcal. I macronutrienti sono in grammi.
nullnon è0.nullsignifica "questa fonte non ha un valore".0significa "misurato, ed è pari a zero". L'app conta solo i valori misurati, quindi non inviare mai0per un valore sconosciuto.- Privacy. La richiesta di ricerca trasmette il nome dell'alimento nel corpo di un
POST, non nell'indirizzo, così che il nome non finisca nei registri di accesso. Mantieni questa proprietà: non registrare i corpi delle richieste. L'app non invia alcun account, nessun diario e nessun dato corporeo. Vedi architecture.md.
Cerca
POST /api/v1/foods/search
Corpo della richiesta:
{ "q": "hähnchenbrust", "locale": "de", "limit": 10 }qè il nome dell'alimento digitato dalla persona o identificato dall'IA.localeè uno traen,de,fr,it,es,tr. Restituisci i titoli in quella lingua se ne disponi.limitoggi vale 10.
Risposta:
{
"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 | Significato |
|---|---|---|
slug | stringa, obbligatorio | Un identificatore stabile per l'alimento. L'app lo memorizza e lo richiede in seguito nella richiesta di dettaglio. |
locale | stringa, obbligatorio | La lingua di title. |
title | stringa, obbligatorio | Il nome da mostrare. |
canonicalName | stringa, obbligatorio | Un nome in inglese, usato come etichetta stabile. |
url | stringa o null | Una pagina dedicata all'alimento. null se non esiste. Se assente, viene interpretato come null. |
imageUrl | stringa o null, obbligatorio | Un'immagine, oppure null. |
macrosPer100g | oggetto, obbligatorio | kcal, protein, fat, carbs, fiber, sugars, polyols. Ciascuno è un numero o null. Tutte e sette le chiavi devono essere presenti. |
netCarbsPer100g | numero o null, obbligatorio | Carboidrati senza fibre. Vedi Carboidrati. |
attribution | stringa o null | L'attribuzione della licenza per i dati importati. L'app la mostra. |
score | numero, obbligatorio | Rilevanza, da 0 a 1. Vedi Ordinamento. |
origin | stringa o null | Origine dell'alimento. Determina il modo in cui l'app legge carbs. Vedi Carboidrati. |
portionSize | numero o null | Una porzione tipica in grammi. |
vitamins | oggetto o null | Vedi Vitamine e minerali. Omettilo se l'alimento non contiene alcun dato sulle vitamine. |
minerals | oggetto o null | Lo stesso, per i minerali. |
Carboidrati
Due fonti diverse riportano i carboidrati in due modi differenti, e l'app deve sapere quale usa un alimento. Il campo origin indica:
origin | Base | Cosa significa macrosPer100g.carbs |
|---|---|---|
bls, curated | disponibili (UE) | Carboidrati senza fibre. Le fibre sono una voce a parte. |
fdc, user, proposal | totali (USA) | Carboidrati con fibre. I carboidrati netti corrispondono ai carboidrati meno le fibre. |
| assente o qualsiasi altro valore | sconosciuto | L'app lo considera come totali, e l'utente può modificarlo a mano. |
Se i tuoi dati seguono il formato UE, invia origin: "curated". Se seguono il formato USA, invia origin: "fdc". Un valore errato altera i valori di carboidrati e calorie per ogni alimento coinvolto, quindi verificalo con un alimento noto.
netCarbsPer100g corrisponde al valore di carboidrati netti per 100 g che fornisci tu, e l'app lo preferisce rispetto al proprio calcolo. null indica "sconosciuto per questo alimento". In questo caso l'app calcola i carboidrati netti a partire dai macronutrienti e dalla base indicata sopra. Non inviare mai 0 per un valore sconosciuto.
Ordinamento
Ordina results per rilevanza, mettendo prima i risultati migliori. L'app mantiene il tuo ordine. Scarta le corrispondenze deboli in base a score:
- Un valore di
scorepari o superiore a 0.4 deve indicare una corrispondenza testuale reale (esatta, prefisso, parola o sottostringa). L'app lo scarta sotto lo 0.45. - Un valore di
scoreinferiore a 0.4 deve indicare una corrispondenza dovuta solo a errori di battitura. L'app lo scarta sotto lo 0.3. - Una corrispondenza per errore di battitura non deve mai ottenere un punteggio pari o superiore a 0.4, altrimenti supera una corrispondenza reale.
Vitamine e minerali
Ogni oggetto alimento include due blocchi opzionali, vitamins e minerals. Un blocco contiene queste chiavi e nessun'altra letta dall'app:
| Blocco | Chiavi | 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 |
Ogni valore è un numero o null. Una chiave omessa viene letta come null. Il blocco stesso ha tre stati, e non sono equivalenti:
- Blocco assente (o
null): questo alimento non ha alcun dato su vitamine o minerali. - La chiave è
null: il blocco esiste e questo singolo valore non è noto. - La chiave è un numero: un valore misurato.
0indica dati reali.
L'app considera un nutriente coperto per la giornata solo se una quota sufficiente di cibo in peso riporta un valore misurato (il 60 percento attualmente). Degli zeri errati fanno sembrare che la persona non abbia assunto affatto una vitamina, quindi invia null quando non hai certezze.
La vitamina A è espressa in equivalenti di attività del retinolo (RAE), che include già i carotenoidi. betaCarotene serve da contesto, non è un'aggiunta. Se la tua fonte fornisce equivalenti di retinolo (RE) o unità internazionali, convertili prima dell'invio, altrimenti i totali saranno errati.
Dettaglio dell'alimento
GET /api/v1/foods/{slug}?locale=de
L'app richiede le vitamine di un alimento registrato prima che esistesse la funzionalità delle vitamine. Invia lo slug proveniente da un vecchio risultato di ricerca.
- Trovato: stato 200 e un oggetto alimento con
slug,vitaminseminerals. Gli stessi blocchi presenti nella ricerca. Gli altri campi vengono ignorati. - Non trovato in quella lingua: stato 404 esattamente con questo corpo:
{ "error": "not_found" }. L'app prova quindi le altre lingue (de,en,fr,it,es, partendo da quella della persona), così che un alimento noto in una sola lingua venga comunque trovato. Qualsiasi altro corpo per il 404 viene considerato un errore, non "non trovato". - Trovato, ma senza blocchi: 200 con entrambi i blocchi assenti. L'app registra "verificato, nessun dato" e non lo richiede più.
Ogni ricerca può costare un credito dal tuo lato, fino a cinque per uno slug che nessuna lingua riconosce. L'app effettua la richiesta una sola volta per alimento distinto su ciascun dispositivo.
Assunzioni di riferimento
GET /api/v1/nutrients
I valori seguenti servono solo a illustrare la struttura. Non sono assunzioni di riferimento reali.
{
"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
}
]
}slugdeve essere uno degli slug indicati sotto. L'app ignora gli altri.kindètarget("ne hai assunto abbastanza?") oppureceiling("sei rimasto sotto?"). Attualmente solo il sodio èceiling. Un valore sconosciuto o mancante viene letto cometarget.rdaDe,rdaEuerdaUssono tre documenti pubblicati distinti: DGE, EFSA e Stati Uniti (NASEM). Invia quelli di cui disponi enullper il resto. L'app mostra una sola base alla volta, selezionata dall'impostazione dell'istanza, e non ne combina mai due.- Le fasce di età sono esattamente
14-18,19-30,31-50,51-70eover_70. Sono necessarie tutte e cinque per entrambi i sessi. Non esistono fasce per età inferiori. L'app non mostra alcun riferimento per chi ha meno di 14 anni. pregnancyelactationsono numeri facoltativi. L'app li usa solo localmente. Nessun dato relativo a una persona ti viene mai inviato.
Slug dei nutrienti:
| Chiave dell'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 non ha un valore di riferimento proprio, perché fa parte della vitamina A.
Alimenti ricchi di 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 }
]
}Ordina foods partendo dal più ricco. value è la quantità per 100 g e non è mai null. Ometti gli alimenti privi di valore anziché inviarli come 0.
Errori
| Stato | Significato | Cosa fa l'app |
|---|---|---|
| 401 | La chiave è errata o mancante | Registra invalid_key, mostra un avviso, continua a funzionare con i numeri dell'IA |
| 429 | Superata la quota o la frequenza limite | Registra rate_limited, oppure allowance_exhausted quando il corpo della risposta lo indica |
| qualsiasi altra configurazione | Errore | Trattato come "nessuna risposta", senza alcun avviso |
Per i codici 401 e 429, puoi inviare un corpo nella forma { "error": { "code": "allowance_exhausted" } }. I codici riconosciuti sono invalid_key, rate_limited e allowance_exhausted. Un codice sconosciuto ripiega sul motivo implicito nello stato. L'app non memorizza mai un rifiuto nella cache.
Prova il tuo server
Esegui queste richieste verso il tuo server. Ciascuna deve restituire un JSON conforme alle sezioni precedenti.
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"Imposta quindi FOOD_DB_API_URL sul server dell'app, riavvialo e cerca un alimento nell'app. Un alimento registrato con un blocco vitamins viene quindi conteggiato nella scheda delle vitamine e dei minerali del rispettivo giorno del diario. In caso contrario, controlla il registro del server dell'app: un rifiuto viene registrato con livello warning indicando i primi 16 caratteri della chiave e mai la chiave completa.
Cosa non è coperto
- Nessun server fornito. Ne crei uno tu, oppure usi lowcarbcheck.org.
- Proposte di alimenti (
POST /api/v1/foods/proposals,FOOD_DB_BACKFILL) sono una funzionalità separata e specifica di LowCarbCheck, e non fanno parte di questo contratto. - Nessun versionamento per ora. I percorsi contengono
/v1, ma la struttura può ancora cambiare finché questa pagina riporta la dicitura "sperimentale". - Gli schemi di trasmissione si trovano nel codice. La fonte normativa attuale è
app/services/food-resolution/schema.ts(ricerca e dettagli),app/lib/nutrient-reference.ts(assunzioni di riferimento e cibi ricchi) eapp/services/food-db/micronutrients-wire.ts(la route di backfill dell'app stessa, che non fa parte di questo contratto).