The Mouve API is in active development and not yet live. Follow the changelog for the launch announcement.

Mouve Developers
Guides

Food scan

Send a food photo, get back the identified products and their nutrition — the same unified scan Mouve uses in-app.

Send one photo of food or a packaged product and get back the identified items — ranked product candidates, portion estimate, and full macro + micronutrient breakdown. This is the same unified recognition pipeline the Mouve app uses (on-device signals, a vision model, OpenFoodFacts resolution, and confidence-weighted fusion), exposed as a single synchronous call.

Scope

Requires the nutrition.read scope on your token — a scan reads an image and returns nutrition; it does not write to your account. This is a health-data scope, so it needs the explicit acknowledgement described in Authentication.

Request

curl -X POST https://api.mouve.it/public/v1/public/food/scan \
  -H "Authorization: Bearer $MOUVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "image": {
      "base64": "<base64 JPEG or PNG>",
      "mime": "image/jpeg",
      "meta": { "width": 1280, "height": 960, "capturedAt": "2026-08-30T12:00:00Z" }
    },
    "context": { "country": "EE", "locale": "en", "mealType": "lunch" }
  }'

context is optional. country is a ranking boost, never a filter — a scan resolves worldwide regardless of it. Pass idempotencyKey to make a retried request return the same scan instead of running the model twice.

Response

{
  "sessionId": "986df63c-c9d3-4dcb-9170-fc7442c9d70e",
  "status": "complete",
  "items": [
    {
      "name": "Coca-Cola",
      "category": "drink",
      "status": "resolved",
      "chosenCandidateId": "off:5449000000996",
      "candidates": [ { "ref": { "source": "off", "id": "5449000000996", "name": "Coca-Cola" }, "score": 0.71 } ],
      "portion": { "value": 330, "unit": "ml", "method": "net_quantity" },
      "nutrients": { "calories": 139, "carbs_g": 35, "sugars_g": 35 },
      "confidence": { "identity": 0.71, "portion": 0.9 }
    }
  ],
  "totals": { "calories": 139, "carbs_g": 35, "sugars_g": 35 },
  "telemetry": { "latencyMs": { "total": 5506, "identify": 3917 }, "costUsd": 0.0023 }
}
  • status — complete when the scan finished, processing if it exceeded the 45s synchronous bound (rare; poll again with the same idempotencyKey).
  • items[].status — resolved whenever the item is priced: the scan always takes its best answer, so a resolved item can be logged as-is. When several candidates were plausible, the best one is chosen and the others stay in candidates; offering them is optional. needs_confirmation means nothing could price the item (no nutrients); leave it out or let the user look it up. Scans made before 2026-09-27 could also report needs_confirmation for a priced item; treat any item with nutrients as loggable.
  • candidates — ranked product references with a per-source score. Absent or empty means nothing in the catalogue matched; the item still carries the model's name and a portion estimate.
  • nutrients / totals — per the chosen candidate, per portion. Absent when nothing resolved.

Rate limits

30 scans per minute per token by default. See Rate limits. Each scan runs a vision model; telemetry.costUsd reports the model spend for that call.