API reference

kcal is five endpoints under https://kcal.elbantli.com/v1. You send an API key with each request, and every response is JSON.

Only successful queries are charged: a response with a 2xx status. Each endpoint's price is below and on the pricing table.

The example responses are shortened. An … marks what is left out.

For a coding assistant, the same reference is one Markdown file: llms-full.txt.

Authentication

Create an API key in the console settings and send it as a Bearer token. The full key is shown once, when you create it.

Keep keys on your server. Anyone who holds a key can spend your credits, so revoke a key you think is exposed.

An account can run 60 queries per minute across all its keys. Past that, requests return a 429 until the minute is over.

Request header
Authorization: Bearer $KCAL_API_KEY

POST /v1/image-transcribe · $0.055 per query

Image search

Identifies up to 12 foods in a photo, estimates each visible portion, and matches each food to the catalog.

Multipart form fields

imagefile
A JPEG or PNG of at most 25 million pixels. The whole request can be at most 10 MiB.
image_urlstring
A public HTTP or HTTPS image URL without credentials, at most 2,048 bytes.

Send exactly one of image and image_url. The response has the same shape as smart search.

A portion estimated from the photo comes back in grams or millilitres with serving.estimated set to true. Show it to your user for review.

Only visible foods are reported. Hidden ingredients are not guessed.

Each item has a status. Only matched items carry a food. The others have no nutrition, so never count them as zero.

Nutrients are not scaled to the serving. Use a portion of the matched food to convert the serving to grams or millilitres first.

Request
curl "https://kcal.elbantli.com/v1/image-transcribe" \
    -H "Authorization: Bearer $KCAL_API_KEY" \
    -F image=@lunch.jpg

GET /v1/barcode · $0.002 per query

Barcode lookup

Returns the packaged food with exactly this barcode.

Query parameters

barcodestring, required
The barcode as printed. Leading zeros count.

When the catalog has no food for a barcode, the newest food submitted for it is returned. Its id starts with user: and its values are as they were submitted.

A barcode with no food at all returns a 404 with the code not_found, and is not charged.

Request
curl "https://kcal.elbantli.com/v1/barcode?barcode=3017620422003" \
    -H "Authorization: Bearer $KCAL_API_KEY"
Response
{
  "food": {
    "kind": "branded",
    "name": "Nutella",
    "brand": "Nutella, Ferrero",
    "barcode": "3017620422003",
    "relaxed": false,
    "relevance": 1,
    "nutrition": [ … ],
    "portions": [ … ]
  }
}

POST /v1/submit-food · $0.002 per query

Food submission

Stores a packaged food for a barcode the catalog does not have, so that the next lookup of that barcode returns it.

JSON body

idstring, required
A UUID that identifies this submission. The whole body can be at most 16 KiB.
barcodestring, required
The barcode as printed, 8 to 14 digits. Leading zeros count.
namestring, required
The product's name. 1 to 200 characters.
brandstring
The brand. At most 200 characters.
basisobject, required
The amount the nutrients are measured on: a quantity above 0 and a unit of g, ml or portion. With portion, the quantity counts servings and is normally 1.
nutrientsobject, required
kcal, proteinG, carbsG and fatG are required. sugarsG, fiberG and saltG can be left out or null. Energy is in kcal and everything else in grams, as numbers of 0 or more.
servingobject or null
The size of one serving: grams, millilitres or both, each a number above 0 or null.
linkedIdstring
The id of the catalog food these values were copied from, when there is one. At most 64 characters.

Generate the id yourself. Sending an id again stores nothing and returns 200 instead of 201, so a request that timed out is safe to repeat.

A food in the catalog is never replaced: barcode lookup returns a submitted food only when the catalog has none for that barcode. Basic search does not return submitted foods.

Values no food can have are refused with invalid_request: more than 10 kcal per gram, more of a nutrient than the basis weighs, or more sugars than carbohydrates.

An account can submit 1,000 foods in 24 hours. Past that, requests return a 429.

Request
curl "https://kcal.elbantli.com/v1/submit-food" \
    -H "Authorization: Bearer $KCAL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
  "id": "5e0c1c1e-6f0e-4d53-9d0b-0f3a2a0f8a11",
  "barcode": "3017620422003",
  "name": "Nutella",
  "brand": "Ferrero",
  "basis": { "quantity": 100, "unit": "g" },
  "nutrients": {
    "kcal": 539, "proteinG": 6.3, "carbsG": 57.5, "fatG": 30.9,
    "sugarsG": 56.3, "fiberG": null, "saltG": 0.107
  },
  "serving": { "grams": 15 }
}'
Response
{
  "id": "user:3017620422003"
}

Food object

Basic search, barcode lookup and matched meal items return foods in this shape.

Food

idstring
The food's catalog ID. It stays the same between queries.
kindstring
branded for a packaged product. foundation, survey or legacy for a generic food.
namestring
The food's name.
brandstring
The brand, or an empty string for a generic food.
barcodestring
The barcode, or an empty string when the record has none.
relaxedboolean
true when the food was found by leaving out one of your search terms. Treat it as a suggestion.
relevancenumber
How well the food fits the query, from 0 to 1. It ranks results; it is not a probability.
nutritionarray
Every nutrition profile the source has, for example per 100 g and per serving. Empty when it has none.
portionsarray
The food's serving sizes. Empty when the source has none.

Nutrition profile

keystring
Identifies the profile within the food.
preparationstring
as_sold, prepared or unknown.
basisobject
The amount the nutrients are measured on: a quantity and a unit of g, ml, portion or unknown. A portion basis refers to the profile's portion.
nutrientsobject
kcal, proteinG, carbsG, fatG, sugarsG, saltG and fiberG. Each is a number, or null when the source does not list it. micronutrients maps names that include the unit, such as vitamin_c_mg, to numbers.
portionobject or null
The serving this profile was reported for, when it has one.
estimatedboolean
true when the values are the source's estimate, not a label or measurement.
issuesarray of strings
Problems found in the source record, such as a nutrient with an unsupported unit.

Portion

keystring
Identifies the portion within the food.
preparationstring
as_sold, prepared or unknown.
labelstring
The serving as the source wrote it, such as 1 cup.
quantity, unitnumber or null, string
The serving as a number and a unit, when the source gives them or the label could be parsed.
grams, millilitresnumber or null
The portion's weight or volume. One is never derived from the other.

Nutrients always apply to the profile's basis. To get the calories in two slices, find a portion that gives the weight of a slice, then scale a per-gram profile yourself.

Meal items

Smart search and image search return one item for each food, in the order the foods appear.

Meal item

textstring
The words of your query this item comes from, or a description of the food seen in the photo.
foodstring
The food name that was searched in the catalog.
servingobject
The amount you asked for: quantity and unit, both null when you gave none. A whole food is counted with the unit count. estimated is true only for a portion judged from a photo.
referencesarray of strings
English names used to find a misspelled, translated or regional food. Left out when there are none.
statusstring
matched, ambiguous, serving_unavailable or no_match.

Status

matched
One food was selected. The item includes all its fields.
ambiguous
Foods were found, but none fits confidently enough to choose for you. Ask your user to pick.
serving_unavailable
A food fits, but its records cannot express the unit you asked for. Ask your user for a weight.
no_match
Nothing in the catalog fits.

Errors

An error has a 4xx or 5xx status and the body below. Branch on code. The message is written for people and can change.

Error response
{
  "error": {
    "code": "insufficient_credits",
    "message": "Your free queries or balance are used up. Add credits in Billing."
  }
}

Codes

invalid_query, invalid_limit, invalid_barcode, invalid_request, invalid_image, invalid_image_url400
The request is malformed. The message names the field to fix.
unauthorized401
The API key is missing or not valid.
insufficient_credits402
Your free queries and balance are used up. Add credits in the console.
account_suspended403
The account is suspended.
not_found404
No food has this barcode, or the endpoint does not exist.
method_not_allowed405
The endpoint does not accept this HTTP method. The Allow header names the one it does.
request_too_large, image_too_large413
The body or image upload is over the size limit.
unsupported_image415
The image is not a JPEG or PNG.
not_food_query, not_food_image, too_many_foods, unverified_food_query422
The text or photo is not about food, names more than 12 foods, or produced foods and amounts that could not be verified against it.
rate_limit_exceeded, search_capacity_exceeded429
You sent more than 60 queries in a minute or submitted more than 1,000 foods in 24 hours, or search is busy. Wait for the number of seconds in the Retry-After header.
internal_error and codes ending in _failed, _unavailable or _timeout500 to 504
A temporary fault on our side. Retry after a short wait.

Once a query is accepted, its response carries an X-Request-ID header. The same ID appears in your console logs. Include it when you report a problem.

https://kcal.elbantli.com/mcp

MCP server

An assistant that supports the Model Context Protocol, such as Claude or Cursor, can query kcal itself. Add the server by its URL. The first time it connects, your browser opens so you can sign in to kcal and allow access.

Claude Code
claude mcp add --transport http kcal https://kcal.elbantli.com/mcp

In the Claude app, add a custom connector with the same URL. You can disconnect an app at any time in the console settings.

A client can also send an API key as the Bearer header, and then nobody has to sign in.

With an API key
{
  "mcpServers": {
    "kcal": {
      "url": "https://kcal.elbantli.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

The server has three tools: basic-search, smart-search and barcode. Each takes the parameters of the endpoint it is named after and returns that endpoint's JSON. Image search and food submission are not available as tools.

A tool call is one query. It is charged, counted toward your rate limit and shown in your logs like the HTTP request, under the app's name or the key's. A call that fails is not charged.