API documentation

Nectar API documentation

Eight endpoints on the Nectar nutrition database: food search, barcode lookup, AI meal analysis from a photo or a sentence, and nutritional calculations.

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

Foods10k+, of which 8k+ branded products
Nutrients per food~100
Household portions24k
Scannable products7k+
NOVA score coverage98%
Reference-intake brackets772

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

CallTokensWhat you get
food/search1Up to 50 foods: identity, translations, calories, macronutrients
portions/convert1A household measure converted to grams
nutrition/reference-intakes1Reference intakes for one person
food/details/{id}1 to 81 for the base, +1 per nutrient block, +1 for portions
food/barcode2The product carrying that barcode
nutrition/compute2 +The total of a meal. +1 per nutrient block.
meal/parse10A meal described in text, turned into foods and quantities
vision/analyze25A 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.

ParameterRequiredDescription
searchYes2 characters minimum. Matching is fuzzy: tommate finds Tomate.
typeNobase (generic) or commercial (branded). Both by default.
limitNoResults per page, 50 maximum and by default.
offsetNoResults 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.

BlockContentsCost
fatty-acidFatty acids and lipids, cholesterol included+1
vitaminVitamins+1
mineralMinerals and trace elements+1
amino-acidEssential amino acids+1
sugarSugars and polyols+1
novaNOVA processing score, 1 to 4+1
withPortions: trueThe 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.

ParameterRequiredDescription
photoYesBase64 data URL. JPEG, PNG, GIF or WebP, up to 10 MB decoded.
noteNoWhat 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
}
FieldWhat it is for
dishThe 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[].gramsThe weight to log. Not always the model's raw estimate: a food counted in units is weighed by our portion.
foods[].unitCountWhole units counted. 0 for anything measured in bulk.
needsReviewtrue below 0.7 confidence. Surface these for confirmation.
suggestionsIngredients 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.

ParameterRequiredDescription
textYesThe 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:

FieldWhat it is
totalWhat the meal actually delivers. The figure a food diary logs.
per100gThe 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.
totalGramsCombined weight.
unknownFoodIdsIds 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.

ParameterRequiredDescription
foodIdYesThe food in question
labelNoThe portion name. Case- and accent-insensitive, and matches on a fragment. Omit it to list every portion.
quantityNoHow 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.

ParameterRequiredValues
ageYesIn years, 0 to 130
genderYesmale or female
pregnancyStatusNonot_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

FieldDescription
idStable identifier, usable everywhere else
typebase (generic) or commercial (branded)
labelOur internal name for the food
brandBrand, for commercial products
trueCaloriesCalories actually metabolised per 100 g, per the Nectar formula
rawCaloriesGross calories per 100 g, standard Atwater method
translationsFrench and English labels, keyed by language code
nutrientsValues per 100 g, with USDA id, unit and source
portionsHousehold portions in grams, on the details endpoint
novaScoreProcessing degree 1 to 4, when the block was requested

Display translations, not label. label is our internal name and its language varies by record — one response can mix "Purée de pomme de terre" and "Salmon, wild". translations.fr and translations.en are 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

CodeMeaningWhat to do
400Missing or invalid parameterThe message says which
401Key missing, malformed or unknownCheck the header and the nec_ prefix
402Quota exhausted on an AI endpointWait for the reset or move up a plan. Not charged.
403Key disabledContact your Nectar representative
404Resource not foundUnknown id, barcode or foods. Not charged.
413Photo too largeOver 10 MB decoded
429Rate limit reachedRetry with exponential backoff
500Server errorRetry; 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