For app developers
Food photo in.
Nutrition out.
The meal scan behind the NutriScan app, as one HTTPS call. Send a photo, a pack label or a line of text. Get the dish, every item with its portion, 21 nutrients and a NutriScore from A to E.
- 3input types
- 21nutrients per item
- 12languages
Grilled chicken with rice and salad
Lean protein and vegetables balance the rice; moderate sodium from the marinade.
- Grilled chicken breast150 g248 kcal
- Steamed white rice1 cup (158 g)205 kcal
- Green salad with olive oil80 g62 kcal
Three ways in, one result
Every input returns the same JSON: meal name, NutriScore with its reason, totals, and each item with portion, grams, macros and micros.
"type": "meal"Meal photo
A plate, a bowl, a takeaway box. Items and portions come from the photo.
"type": "label"Pack label
A photo of the nutrition table on a packet, read per pack or per serving.
"type": "text"Text
"2 eggs and a slice of toast". Useful for voice input or a search box.
Pricing
Monthly plans in US dollars. Each plan is a credit balance (1 credit = $0.001). A scan uses credits for what it really took: the AI work, web lookups, and items matched from our food database.
Starter
$49 / month
49,000 credits
About 380 to 2,400 scans
- 30 requests a minute
- Test key included
Growth
$199 / month
199,000 credits
About 1,500 to 9,900 scans
- 60 requests a minute
- Test key included
Scale
$599 / month
599,000 credits
About 4,600 to 29,900 scans
- 120 requests a minute
- Test key included
- What a scan uses: 20 credits minimum. A photo with no food about 20, a short text log about 60, a normal plate about 65, a big plate with 9 items about 130. Every result says
credits_used. - No credits: failed scans and the test key.
- Starting a scan needs 300 credits free. With less left, live scans return
402until you upgrade or the next month starts. Unused credits don't carry over. - Upgrade any time: the new balance applies straight away.
Docs
Base URL https://server-api.nutriscan.app/platform/v1. JSON in, JSON out.
Keys
You get two keys by email. ns_test_… returns a fixed sample result for free, so you can build your integration first. ns_live_… runs real scans that use your plan's credits. Send the key as a bearer token.
Call the API from your server. A key inside a mobile app can be pulled out of the binary and used by anyone.
Authorization: Bearer ns_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Create a scan
POST /scans waits for the result and returns it in the same response. Send image_base64 or image_url for meal and label, and text for text.
curl https://server-api.nutriscan.app/platform/v1/scans \
-H "Authorization: Bearer $NUTRISCAN_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7b1c5e0a-order-4411" \
-d '{
"type": "meal",
"image_url": "https://example.com/lunch.jpg",
"language": "en",
"context": { "meal_time": "lunch", "cooking_method": ["grilled"] }
}'
| Field | Type | Notes |
|---|---|---|
type | string | meal (default), label or text |
image_base64 | string | JPEG, PNG or WebP, up to 10 MB. Send this or image_url, not both. |
image_url | string | Public https URL. We fetch it once. |
text | string | Up to 2,000 characters. Only with type: text. |
language | string | Language of names and the reason: en, de, fr, es, it, pt, nl, pl, ru, tr, hi, ar. Default en. |
context | object | Optional hints that improve accuracy: meal_time, food_type, cooking_method (list), oil_quantity, consumed_quantity, location, goal, food_preferences. |
Send an Idempotency-Key header to retry safely. The same key with the same body returns the first result and uses credits once. If that first try failed (402, 500), the same key runs a fresh scan. A scan can take up to 95 seconds, so set your client timeout to at least 100.
The result
Units are in the field names. Each item says where its numbers came from: food_db (our food database), web or estimate.
{
"id": "scn_Q2mX8rT4vK1pLw9eZb3nYh6A",
"status": "succeeded",
"engine_version": "2026.11.0",
"language": "en",
"meal": {
"name": "Grilled chicken with rice and salad",
"nutri_score": "B",
"nutri_score_reason": "Lean protein and vegetables balance the rice; moderate sodium from the marinade.",
"totals": {
"calories": 515, "protein_g": 51.9, "carbs_g": 47.7, "fat_g": 11.1,
"fiber_g": 2.2, "sugar_g": 1.6, "saturated_fat_g": 2.3, "trans_fat_g": 0,
"micros": { "sodium_mg": 457, "potassium_mg": 645, "calcium_mg": 69, "iron_mg": 4.2,
"vitamin_c_mg": 14, "vitamin_a_mcg": 204, "omega_3_mg": 100, "…": "13 in all" }
}
},
"items": [
{
"name": "Grilled chicken breast",
"portion": { "value": 150, "unit": "g", "display": "150 g" },
"grams": 150,
"calories": 248, "protein_g": 46.5, "carbs_g": 0, "fat_g": 5.4,
"micros": { "sodium_mg": 410, "…": "…" },
"source": "food_db"
}
]
}
A live result also has "credits_used": 64 and ends with "usage": {"credits_used": 18240, "credits_left": 30760}. Test keys return the same shape with "test_mode": true and "credits_used": 0.
Other endpoints
| Endpoint | Returns |
|---|---|
GET /scans/{id} | A past scan by its id, for scans made with your account. |
GET /usage | Your plan, status (active, or expired once the period has ended), period dates, credits_included, credits_used, credits_left and scans_charged. |
Errors
Every error has the same body: {"error": {"code": "plan_limit_reached", "message": "…"}}. Branch on code; the message is for people.
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_request, invalid_image, image_too_large, image_url_unreachable | Fix the request. The message names the field. |
| 401 | invalid_api_key | Check the key and the Bearer prefix. |
| 402 | plan_limit_reached, subscription_inactive | Upgrade or renew. Test keys keep working. |
| 403 | key_revoked, account_suspended | Email support@nutriscan.app. |
| 409 | idempotency_conflict, idempotency_mode_conflict, idempotency_in_progress | Use a new Idempotency-Key for a new body or for the other key mode, or retry shortly. |
| 413 | request_too_large | Send an image of 10 MB or less. |
| 422 | no_food_detected | Nothing to scan. Uses the credits it took (usually 20); the message says how many. |
| 429 | rate_limited, platform_busy | Wait for the Retry-After seconds, then retry. |
| 500 | scan_failed | Retry. No credits used. |
| 503 | platform_unavailable | Retry later. |
Limits
| Image | JPEG, PNG or WebP, up to 10 MB. HEIC is not supported yet; convert it on the device. |
| Text | 2,000 characters |
| Requests | 30, 60 or 120 a minute per account, by plan (all keys together) |
| Scan time | Up to 95 seconds |
| Keys | Up to 10 active keys per account |
Your data
We keep the photos and results you send and use them to improve our food database. Do not send photos of people or anything that identifies your users. Results are not medical advice.
Request access
Tell us what you are building. We set up every account by hand for now.
- You send the formWe read every request.
- We email a payment linkFor the plan that fits your volume, billed monthly.
- You get your keysA test key and a live key, by email, once the payment goes through.
Request sent
We will reply to your email.