The Nectar API gives you our nutrition database and our analysis engines. Eight endpoints, one key, one meter.
Base URL: https://app.mynectarapp.com/api/public/v1
| Foods | 10k+, of which 8k+ branded products |
| Nutrients per food | ~100 |
| Household portions | 24k |
| Scannable products | 7k+ |
| NOVA score coverage | 98% |
| Reference-intake brackets | 772 |
Authentication
Every request carries your key in the x-api-key header. Keys start with nec_ and are issued by your Nectar contact. All endpoints are POST, with a JSON body.
curl -X POST https://app.mynectarapp.com/api/public/v1/food/search \
-H "x-api-key: nec_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"search": "apple"}'
Never expose your key in client code — a web page, a mobile app, a public repository. It calls the API in your name and spends your quota. Always go through your own server.
Understanding tokens
One meter for everything. Each call spends a number of tokens you know upfront, echoed in the response as tokensCharged. The meter resets on the 1st of each month.
The rate card
| Call | Tokens | What you get |
|---|---|---|
food/search | 1 | Up to 50 foods: identity, translations, calories, macronutrients |
portions/convert | 1 | A household measure converted to grams |
nutrition/reference-intakes | 1 | Reference intakes for one person |
food/details/{id} | 1 to 8 | 1 for the base, +1 per nutrient block, +1 for portions |
food/barcode | 2 | The product carrying that barcode |
nutrition/compute | 2 + | The total of a meal. +1 per nutrient block. |
meal/parse | 10 | A meal described in text, turned into foods and quantities |
vision/analyze | 25 | A meal photo, turned into foods and quantities |
Serving one more food row costs us almost nothing: database and computation endpoints sit at 1 or 2 tokens. An AI analysis runs a model on every call: it costs 10 or 25. That is the whole of the difference.
Going over: two behaviours
On data and computation endpoints, going over never interrupts anything. Requests keep succeeding, the overage is recorded and settled in conversation.
On the two AI endpoints, the quota is hard. Once your tokens are spent they return 402 until the reset — and the model is never invoked, so a refused call is never charged.
Tracking your usage
Every response carries the state of your quota:
X-Token-Quota-Month: 15000
X-Token-Used-Month: 11240
X-Token-Remaining: 3760
Alert on X-Token-Remaining: that is what stops you discovering a 402 mid-day.
Rate limits
Independent of the monthly quota. 60 requests per minute and 1,000 per hour by default, adjustable per key. Past that the API returns 429 with the X-RateLimit-Limit-Minute and X-RateLimit-Limit-Hour headers. Implement exponential backoff.
Search for a food
POST /food/search — 1 token
Fuzzy, accent-insensitive search across the whole catalogue. Returns a light list with a detailsUrl per food. One token whatever the result count.
| Parameter | Required | Description |
|---|---|---|
search | Yes | 2 characters minimum. Matching is fuzzy: tommate finds Tomate. |
type | No | base (generic) or commercial (branded). Both by default. |
limit | No | Results per page, 50 maximum and by default. |
offset | No | Results to skip. 0 by default. |
A search resolves at most 50 foods. To reach a specific product, use its barcode or a result's detailsUrl rather than deep pagination.
{
"data": [
{
"id": "53bff494-e435-5cfd-b8e4-529668939d5d",
"type": "base",
"label": "Apple, raw",
"brand": null,
"trueCalories": 52,
"rawCalories": 54,
"translations": { "fr": { "label": "Pomme, crue" }, "en": { "label": "Apple, raw" } },
"nutrients": [],
"detailsUrl": "https://.../food/details/53bff494-..."
}
],
"total": 1,
"limit": 50,
"offset": 0,
"tokensCharged": 1
}
Scan a barcode
POST /food/barcode — 2 tokens
Exact barcode lookup to its Nectar record. 7k+ catalogue products carry one. The response has exactly the shape of a search result, so the same handling code works for both.
curl -X POST https://app.mynectarapp.com/api/public/v1/food/barcode \
-H "x-api-key: nec_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"barcode": "3017620422003"}'
The lookup covers branded products only. A barcode absent from the catalogue returns 404 and is not charged.
Food details
POST /food/details/{id} — 1 to 8 tokens
The complete record for one food. You pay only for what you ask: one token for the base, then one per nutrient block and one for portions.
| Block | Contents | Cost |
|---|---|---|
fatty-acid | Fatty acids and lipids, cholesterol included | +1 |
vitamin | Vitamins | +1 |
mineral | Minerals and trace elements | +1 |
amino-acid | Essential amino acids | +1 |
sugar | Sugars and polyols | +1 |
nova | NOVA processing score, 1 to 4 | +1 |
withPortions: true | The food's household portions | +1 |
# Vitamins + minerals + NOVA + portions = 1 + 3 + 1 = 5 tokens
curl -X POST https://app.mynectarapp.com/api/public/v1/food/details/53bff494-... \
-H "x-api-key: nec_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"blocks": ["vitamin", "mineral", "nova"], "withPortions": true}'
One search to find, then details only on the food the user actually picked. Asking for every block across a whole result set multiplies the bill for nothing: most of those foods are never displayed.
Analyse a meal photo
POST /vision/analyze — 25 tokens · hard quota
A photo in, the foods it shows out — matched against the database, each with an estimated weight. The same engine the dietitians use inside Nectar.
| Parameter | Required | Description |
|---|---|---|
photo | Yes | Base64 data URL. JPEG, PNG, GIF or WebP, up to 10 MB decoded. |
note | No | What the meal was, in free text, 500 characters maximum. |
The note outranks the photo. Counter-intuitive but verified: a photo cannot tell semolina from bulgur, veal from pork, or a 0% yogurt from a whole-milk one. When the note names a food, it wins. The photo stays authoritative on what only it can give: quantities, foods the note forgot, and the cooking method.
{
"data": {
"dish": { "query": "pad thai", "confidence": 0.92, "grams": 320, "candidates": [] },
"foods": [
{
"query": "salmon fillet",
"grams": 120,
"unitCount": 0,
"confidence": 0.9,
"needsReview": false,
"candidates": []
}
],
"suggestions": [{ "query": "olive oil", "reason": "Glossy appearance", "grams": 10 }],
"cookingMethod": "grill"
},
"tokensCharged": 25
}
| Field | What it is for |
|---|---|
dish | The whole plate, when the model recognises a recipe we carry. Lets a meal be logged in one step instead of confirming seven ingredients. null is the normal case. |
foods[].grams | The weight to log. Not always the model's raw estimate: a food counted in units is weighed by our portion. |
foods[].unitCount | Whole units counted. 0 for anything measured in bulk. |
needsReview | true below 0.7 confidence. Surface these for confirmation. |
suggestions | Ingredients the photo implies without showing: oil, butter, salt. Offered with a reason, never added on your behalf. The same food may appear twice, for two different reasons. |
Confirm before logging. Every food comes with a confidence and up to three candidates precisely so a human can correct the pick. That is how the feature is used inside Nectar.
No photo is kept. The image is analysed then discarded, never written to our disks. Only the result comes back to you.
Analyse a written meal
POST /meal/parse — 10 tokens · hard quota
"2 eggs and 150 g of rice" becomes the same structured list a photo produces. Use it wherever your users type rather than photograph: a quick-add field, a voice transcript, a chat agent.
| Parameter | Required | Description |
|---|---|---|
text | Yes | The meal in free text, 1,000 characters maximum. French gives the best results. |
The response shape is identical to the photo endpoint — one parser handles both.
A stated quantity is taken at face value. This is the one behavioural difference from the photo, and it matters when you compare results. 150 g of rice returns 150 g, not re-estimated. two eggs returns 2 units, and the grams come from our portion. a tablespoon of oil is converted. With no quantity, the portion is standard and the confidence drops.
curl -X POST https://app.mynectarapp.com/api/public/v1/meal/parse \
-H "x-api-key: nec_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"text": "2 œufs brouillés et 150 g de riz complet"}'
Compute a meal
POST /nutrition/compute — 2 tokens + 1 per block
A list of foods and weights, the full nutritional total. The same blocks as the details endpoint apply, at the same rate.
{
"items": [
{ "foodId": "53bff494-...", "grams": 150 },
{ "foodId": "a7c21e08-...", "grams": 120 }
],
"blocks": ["vitamin"]
}
The response holds three views of the same computation:
| Field | What it is |
|---|---|
total | What the meal actually delivers. The figure a food diary logs. |
per100g | The density of the mixture. What a label would print, and how two dishes get compared. |
items[] | Each food's own contribution, for a breakdown or a recompute on your side. |
totalGrams | Combined weight. |
unknownFoodIds | Ids we do not carry. The rest is computed anyway. |
50 items maximum. If every id is unknown, the call returns 404 and is not charged.
Convert a portion
POST /portions/convert — 1 token
"1 bowl of rice" becomes 200 g. Nectar holds 24k of these, food by food — because a bowl of rice and a bowl of soup do not weigh the same, and no generic table can say so.
| Parameter | Required | Description |
|---|---|---|
foodId | Yes | The food in question |
label | No | The portion name. Case- and accent-insensitive, and matches on a fragment. Omit it to list every portion. |
quantity | No | How many portions. 1 by default. |
The full portion list comes back even on a label that does not match, so you can show what the food does offer. The call is charged either way: the lookup ran.
curl -X POST https://app.mynectarapp.com/api/public/v1/portions/convert \
-H "x-api-key: nec_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"foodId": "f14b1be0-...", "label": "filet", "quantity": 2}'
Reference intakes
POST /nutrition/reference-intakes — 1 token
How much of each nutrient a given person should get. This is what turns a number into a judgement: showing "12 mg of iron" says nothing, showing "150% of the reference for a 30-year-old woman" is the feature.
| Parameter | Required | Values |
|---|---|---|
age | Yes | In years, 0 to 130 |
gender | Yes | male or female |
pregnancyStatus | No | not_pregnant (default), pregnant, lactation. Only valid with female. |
A recommendation is a range, not a target. min is the intake to reach, max the ceiling not to exceed, and either can be null: plenty of nutrients have a floor and no known upper limit, a few the reverse.
Pregnancy status genuinely changes the figures: 23 nutrients differ between a pregnant and a non-pregnant woman. Caffeine drops from 400 to 200 mg, iodine rises from 150 to 220 µg. It is the same resolution the Nectar app itself uses, so your figures match what a dietitian would see.
The food object
| Field | Description |
|---|---|
id | Stable identifier, usable everywhere else |
type | base (generic) or commercial (branded) |
label | Our internal name for the food |
brand | Brand, for commercial products |
trueCalories | Calories actually metabolised per 100 g, per the Nectar formula |
rawCalories | Gross calories per 100 g, standard Atwater method |
translations | French and English labels, keyed by language code |
nutrients | Values per 100 g, with USDA id, unit and source |
portions | Household portions in grams, on the details endpoint |
novaScore | Processing degree 1 to 4, when the block was requested |
Display
translations, notlabel.labelis our internal name and its language varies by record — one response can mix "Purée de pomme de terre" and "Salmon, wild".translations.frandtranslations.enare filled for every food: those are what your users should see.
Two calorie measures. trueCalories is Nectar's own figure: it accounts for what the body actually absorbs, where rawCalories gives the gross calorie measure, using the standard Atwater calculation. The gap is significant on fibre and on absorption and metabolisation parameters (digestibility coefficient and diet-induced thermogenesis). Display whichever matches what you promise your users.
Error codes
| Code | Meaning | What to do |
|---|---|---|
400 | Missing or invalid parameter | The message says which |
401 | Key missing, malformed or unknown | Check the header and the nec_ prefix |
402 | Quota exhausted on an AI endpoint | Wait for the reset or move up a plan. Not charged. |
403 | Key disabled | Contact your Nectar representative |
404 | Resource not found | Unknown id, barcode or foods. Not charged. |
413 | Photo too large | Over 10 MB decoded |
429 | Rate limit reached | Retry with exponential backoff |
500 | Server error | Retry; tell us if it persists |
Version 1 of the API. Any breaking change will be announced ahead of time under a new version; v1 will keep being served.
Last updated: September 11, 2026