Quickstart
Go from nothing to a nutrition result with a test key. Test mode runs the real workflow — statuses, confirmation, webhooks, usage — but returns deterministic fixtures instead of calling the live engine, and it is free.
1. Get a test key
- Sign in to the console with Google and create an organization.
- Open API keys, choose Create key, pick your project and the Test mode.
- Copy the secret (
opis_test_…). It is shown once. Store it in your secrets manager or an environment variable:
export OPIS_API_KEY="opis_test_…"New organizations start as pending: test keys work straight away, and live keys are enabled after opis activates your organization.
2. Upload a photo
Send the image as multipart/form-data (up to 15 MB; JPEG, PNG or WebP). The file is sanitised
asynchronously — EXIF metadata is stripped — and becomes ready a moment later. You can reference
it immediately: an analysis waits in queued until its files are ready.
curl https://platform.opis.health/v1/files \
-H "Authorization: Bearer $OPIS_API_KEY" \
-F file=@lunch.jpg3. Create a meal analysis
Send an Idempotency-Key so a retried request can never create a second analysis. The response is
202 Accepted with the new resource, a Location header and a Retry-After hint.
description is an optional text hint about the meal. In test mode, values such as
test:no_food or test:failed_identify select a scenario — see Test mode.
4. Poll until it settles
An analysis is settled when its status is succeeded, failed, canceled or
requires_confirmation (manual mode only). Honour Retry-After between polls; production
integrations should use webhooks instead.
curl https://platform.opis.health/v1/meal-analyses/mna_01j9… \
-H "Authorization: Bearer $OPIS_API_KEY"Or skip the loop: send Prefer: wait=25 on the create or on a GET, and the API holds the response
until the analysis settles (then 200 with Preference-Applied: wait) or 25 seconds pass (202).
5. Read the result
{
"id": "mna_01j9…",
"object": "meal_analysis",
"status": "succeeded",
"mode": "test",
"confirmation": {
"mode": "automatic",
"method": "automatic",
"action": "accept"
},
"identification": {
"version": 1,
"items": [
{
"id": "itm_01j9…",
"name": "Grilled chicken breast",
"item_type": "food",
"confidence": 0.86,
"portion": {
"grams": 150,
"source": "vision_estimate",
"confidence": 0.7
}
}
],
"flags": []
},
"nutrition": {
"items": [
{
"id": "itm_01j9…",
"origin": "identified",
"name": "Grilled chicken breast",
"grams": 150,
"portion_source": "vision_estimate",
"match": {
"source": "cofid",
"food_code": "13-123",
"approximate": false,
"reason": "matched"
},
"nutrients": {
"energy_kcal": {
"value": 248.1,
"unit": "kcal",
"derivation": "analysed"
},
"vitamin_d": null
}
}
],
"totals": {
"nutrients": { "energy_kcal": { "value": 248.1, "unit": "kcal" } },
"completeness": { "energy_kcal": 1.0, "vitamin_d": 0 },
"items_unresolved": 0
},
"flags": []
},
"error": null
}Nutrient values are portion totals at the item's grams; null means "not measured", never zero.
See Nutrients for the dictionary.
Next steps
- Decide whether a person should review items before nutrition: Confirmation.
- Receive results by webhook: Webhooks.
- Already have item names and grams? Use standalone nutrition estimates.
- When you go live, create a live key (
opis_live_…) — nothing else in your code changes.