{"openapi":"3.0.0","paths":{"/v1.2/chat/completions":{"post":{"description":"**API key only.**\n\nNon-streaming OpenAI Chat Completions subset: model january, text messages, and optional stream false only. Uses supplied history and clinical literature when useful, with citations in the answer. No patient account or persistent memory. Accepted transcripts are retained for debugging and evaluation. One successful completion costs one credit. Five concurrent requests per customer by default.","operationId":"createChatCompletion","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"},"examples":{"question":{"summary":"A single question","value":{"model":"january","messages":[{"role":"user","content":"What evidence supports dietary fiber for heart health?"}]}},"follow_up":{"summary":"Instructions and a follow-up","description":"Each request carries its whole history: instructions first, then the earlier turns, ending with the new user message.","value":{"model":"january","messages":[{"role":"system","content":"Answer in plain language for a general audience, in a few sentences."},{"role":"user","content":"What evidence supports dietary fiber for heart health?"},{"role":"assistant","content":"Large cohort studies and meta-analyses consistently link higher fiber intake with lower risk of coronary heart disease and stroke."},{"role":"user","content":"How much should I aim for each day?"}]}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatError"}}}},"default":{"description":"OpenAI-shaped error: { error: { message, type, param, code } }.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Create a health chat completion","tags":["chat"],"x-january-credential-types":["api_key"]}},"/v1.2/literature/search":{"post":{"description":"**API key only.**\n\nSearch English journal articles and clinical guidelines. Returns ordered passages with citation metadata and an optional summary. Null options use their defaults. Summary failure preserves search results; no matches is a successful empty search. One successful search costs one credit regardless of summary or result count. Five concurrent literature requests per customer by default. Accepted queries and complete results are retained under the shared retention policy. No patient identity is used.","operationId":"searchLiterature","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiteratureSearchRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiteratureSearchResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Search clinical literature","tags":["literature"],"x-january-credential-types":["api_key"]}},"/v1.2/auth/client-tokens":{"post":{"description":"**API key only.**\n\nExchanges your partner API key for a short-lived token bound to one of your end users, so your mobile app can call the v1.2 API directly instead of through a proxy that holds your key.\n\n**This endpoint requires your API key (`sk-…`), so always call it from your backend** — behind whatever login already protects your own APIs. Never ship your API key in a mobile app in order to call this from the device: that puts a credential for your whole account in every copy of your app, which is the problem client tokens exist to solve.\n\nThe end user is identified by `end_user_id` in the body, not the `January-End-User-ID` header: the token is bound to it, and requests made with the token act only on that user, whatever headers they carry.\n\nTokens last 300–7200 seconds and the raw value is returned exactly once — it is stored only as a hash, so it can never be retrieved again. Relay it to the device and let the device refresh when it expires: a `401` with code `token_expired` is the signal to mint a new one and retry the original request once.","operationId":"createClientToken","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientTokenRequest"}}}},"responses":{"201":{"description":"Token minted. The raw `token` is returned exactly once — relay it to the device and store nothing; only its hash is kept. The response also echoes `expires_in`/`expires_at`, the bound `end_user_id`, and the exact `scopes` granted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientToken"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"`forbidden` — client tokens have been switched off for your account (turn them back on in the [Developer Dashboard](https://dashboard.january.ai)), or the key is issued for the other API version.\n\n`client_token_not_allowed` — this request was made with a client token, and minting requires your `sk-` API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Minting is capped per partner (`code: rate_limited`) — the only limit that applies here. It is `@NotBillable` and outside the shared v1.2 request ceiling, so this 429 never means credit exhaustion or the monthly ceiling. Honor `Retry-After` when present, otherwise back off a few seconds; mint one token per user session and reuse it until it expires rather than minting per request.","headers":{"Retry-After":{"description":"Seconds to wait before retrying; present when the window is known.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Client tokens are not configured on this environment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Mint a client token","tags":["authentication"],"x-january-credential-types":["api_key"]}},"/v1.2/auth/client-token-revocations":{"post":{"description":"**API key only.**\n\nRevokes every outstanding client token for one end user. Safe to call repeatedly: it reports how many tokens it actually stopped, so an immediate second call reports 0.\n\n**This endpoint requires your API key (`sk-…`), so always call it from your backend.** A client token cannot revoke anything, its own included.\n\nRevocation takes effect within 60 seconds, the authentication cache window. For an immediate cut-off, stop trusting the user in your own app as well — and note that a token minted in the same instant as the revoke may survive it, bounded by its own expiry.\n\nIf some tokens cannot be revoked, the call answers `503` with code `client_token_revocation_incomplete`, naming how many of them succeeded rather than reporting a smaller count as though it were the whole story — **retry until it succeeds**, which only picks up the remainder.\n\nOne call stops at most 500 tokens so it cannot time out, so **repeat the call until `revoked_count` is 0** if a user is somehow holding more than that (which means your app is minting per request rather than per session).","operationId":"revokeClientTokens","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeClientTokensRequest"}}}},"responses":{"200":{"description":"Revocation processed. `revoked_count` is how many live tokens this call actually stopped — repeat the call until it reports 0, since already-revoked and already-expired tokens are not counted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientTokenRevocationResult"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"`forbidden` — the key is issued for the other API version. Unlike minting, revocation is **not** gated on your account’s client-token switch ([Developer Dashboard](https://dashboard.january.ai)): switching minting off must never take away the ability to revoke what is already out there.\n\n`client_token_not_allowed` — this request was made with a client token, and revocation requires your `sk-` API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Revocation is capped per partner (`code: rate_limited`) — the only limit that applies here. It is `@NotBillable` and outside the shared v1.2 request ceiling, so this 429 never means credit exhaustion or the monthly ceiling, and a partner mid-incident can still cut a device off. Honor `Retry-After` when present, otherwise back off a few seconds, and retry; the revoke bucket is separate from minting, so a security sweep cannot spend your sign-in allowance.","headers":{"Retry-After":{"description":"Seconds to wait before retrying; present when the window is known.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"`service_unavailable` — client tokens are not configured on this environment. `client_token_revocation_incomplete` — some tokens could not be revoked; the message names how many succeeded, and repeating the request is safe and picks up the remainder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Revoke an end user’s client tokens","tags":["authentication"],"x-january-credential-types":["api_key"]}},"/v1.2/credits":{"get":{"description":"**API key only.**\n\nYour API credit allowance and consumption for the current calendar month (UTC). Each successful billable data operation consumes credits — how many depends on the operation and your plan — while requests that fail cost nothing and v1.1 calls are not counted. Checking your balance, creating client tokens, and revoking client tokens never consume credits. Reading your balance is also exempt from the request limits that bound the rest of the API, so it keeps answering once your allowance is spent or your request limit is reached — it carries only a cap of its own, 60 reads per minute unless we have agreed a different one with you. Read your balance when a request is rejected or on a schedule rather than before every call, and treat that balance — not a fixed per-call price — as the source of truth. When credits run out, v1.2 endpoints return `429` with code `credit_limit_exceeded` until the allowance resets — retrying does not help before then.","operationId":"getCredits","parameters":[],"responses":{"200":{"description":"Your credit balance for the current billing period: the `plan`, the period bounds and reset instant, `used_credits`, and `included_credits`/`remaining_credits` — always present, and `null` for an uncapped partner whose plan has no ceiling.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditBalance"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Balance reads are capped at 60 per minute by default (`code: rate_limited`), and that is the only limit on your account that applies here — neither the monthly credit allowance nor the shared request limit does, so this 429 is never `credit_limit_exceeded`. Honor `Retry-After` when present, otherwise back off a few seconds; the window reopens one minute after your first read in it, so a retry shortly afterwards succeeds. A burst of traffic from a single IP can also be refused by the service-wide throttle, likewise with `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before retrying; present when the window is known.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Get your credit balance","tags":["credits"],"x-january-credential-types":["api_key"]}},"/v1.2/foods":{"get":{"description":"**API key or client token.**\n\nFull-text search over the January food database, returning up to 50 ranked matches per call. Generic foods, branded products and recipes are searched together unless `type` narrows it to one; page deeper with `offset`. To look up a scanned barcode, use `GET /v1.2/foods/barcode/{barcode}` instead.\n\nCallable with a client token carrying the `foods:read` scope.","operationId":"searchFoods","parameters":[{"name":"query","required":true,"in":"query","description":"The food name to search for.","schema":{"maxLength":256,"example":"greek yogurt","type":"string"}},{"name":"type","required":false,"in":"query","description":"Narrows results to one kind of food. Omitted, all three are searched and returned as one ranked list, so a partner who does not care which kind a match is does not have to ask three times.","schema":{"enum":["generic","branded","recipe"],"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of results to return in one call. Values above 50 are treated as 50, so page by 50 if you asked for more.","schema":{"minimum":1,"maximum":50,"default":10,"type":"integer"}},{"name":"offset","required":false,"in":"query","description":"Number of results to skip, for paging: a page shorter than `limit` is the last one.","schema":{"minimum":0,"default":0,"type":"integer"}}],"responses":{"200":{"description":"Foods matching the query, best match first; empty when nothing matches or `offset` is past the last result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodSearchResponse"}}}},"400":{"description":"A parameter is missing or invalid; the message names the parameter and the accepted values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Search foods by name","tags":["foods"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/foods/autocomplete":{"get":{"description":"**API key or client token.**\n\nLightweight food suggestions for a partial name, built for type-ahead (\"ban\" → banana, banana bread, …): generic foods first, then branded, each with its id, name, brand, a thumbnail and calories. Once the user picks one, fetch `GET /v1.2/foods/{food_id}` for servings and full nutrition. `items` is empty for fewer than 2 letters or digits, no match, or a search-index error (the suggestion service fails open so a typing user is not interrupted); an unreachable service still answers with the standard 502/504.\n\nCallable with a client token carrying the `foods:read` scope.","operationId":"autocompleteFoods","parameters":[{"name":"query","required":true,"in":"query","description":"The characters the user has typed so far. Fewer than 2 letters or digits yield no suggestions.","schema":{"maxLength":64,"example":"ban","type":"string"}},{"name":"type","required":false,"in":"query","description":"Narrows suggestions to one kind of food. Omitted, generic and branded foods are suggested together, generic first.","schema":{"enum":["generic","branded"],"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of suggestions to return.","schema":{"minimum":1,"maximum":20,"default":8,"type":"integer"}}],"responses":{"200":{"description":"Type-ahead suggestions for the partial name, generic before branded; empty when fewer than 2 characters were given or nothing matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodSuggestionsResponse"}}}},"400":{"description":"A parameter is missing or invalid; the message names the parameter and the accepted values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Autocomplete food names","tags":["foods"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/foods/{food_id}/alternatives":{"post":{"description":"**API key or client token.**\n\nReturns healthier alternatives for a food, honoring the given dietary restrictions and preferences. Omit either array (or send `[]`) if it does not apply. An empty `alternatives` result is valid — no suitable alternatives were found.\n\nCallable with a client token carrying the `foods:read` scope.","operationId":"suggestFoodAlternatives","parameters":[{"name":"food_id","required":true,"in":"path","description":"Food id from a search or food-analysis result. The example is brown rice.","schema":{"type":"string","pattern":"^[1-9]\\d{0,9}$","example":"70372230"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestFoodAlternativesRequest"}}}},"responses":{"200":{"description":"Healthier alternatives honoring the restrictions and preferences; may be empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAlternatives"}}}},"400":{"description":"A field is missing or a value is not in the allowed vocabulary; the message names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No food with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Suggest healthier alternatives for a food","tags":["foods"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/foods/barcode/{barcode}":{"get":{"description":"**API key or client token.**\n\nExact lookup of the food a barcode names — one food, not a list. The `barcode` on the returned food is the database's normalized form and may differ from the digits you scanned in leading zeros, so display it rather than comparing it. For free-text search, use `GET /v1.2/foods` instead.\n\nCallable with a client token carrying the `foods:read` scope.","operationId":"getFoodByBarcode","parameters":[{"name":"barcode","required":true,"in":"path","description":"The numeric barcode: 6 to 14 digits (UPC-E, UPC-A, EAN-8, EAN-13 or GTIN-14). The example is a Coca-Cola can.","schema":{"type":"string","pattern":"^[0-9]{6,14}$","example":"049000006346"}}],"responses":{"200":{"description":"The single food the barcode identifies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Food"}}}},"400":{"description":"The barcode is not a 6 to 14 digit number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No food matches this barcode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Look up a food by barcode","tags":["foods"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/foods/{food_id}":{"get":{"description":"**API key or client token.**\n\nOne food's full record — most importantly the **complete list of serving sizes**. Search, barcode, and food-analysis results carry a single default serving; fetch the food here to let an end user pick \"1 cup\" vs \"100 g\" vs \"1 medium\" when logging or predicting. Nutrition is per the default serving, in the shared nutrient vocabulary.\n\nCallable with a client token carrying the `foods:read` scope.","operationId":"getFood","parameters":[{"name":"food_id","required":true,"in":"path","description":"Food id from a search or food-analysis result.","schema":{"type":"string","pattern":"^[1-9]\\d{0,9}$","example":"101963552"}}],"responses":{"200":{"description":"The food's full record, including the complete list of serving sizes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Food"}}}},"400":{"description":"food_id is not a numeric id; the message shows the expected form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No food with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Get a food","tags":["foods"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/restaurants":{"get":{"description":"**API key or client token.**\n\nSearch restaurants matching `query` around (`latitude`, `longitude`), ranked by proximity. Every result is a restaurant — `type` is always `restaurant`. To search the dishes those restaurants serve, use `GET /v1.2/menu-items`.\n\nCallable with a client token carrying the `restaurants:read` scope.","operationId":"searchRestaurants","parameters":[{"name":"radius_meters","required":false,"in":"query","description":"Search radius in meters around (latitude, longitude), e.g. 5000 = 5 kilometers. A fractional value is accepted and rounded. Default 8000 meters (about 5 miles); maximum 50000 (about 31 miles).","schema":{"minimum":1,"maximum":50000,"default":8000,"example":5000,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of results to return.","schema":{"minimum":1,"maximum":100,"default":10,"type":"integer"}},{"name":"query","required":true,"in":"query","description":"Restaurant name to search for.","schema":{"maxLength":256,"example":"sweetgreen","type":"string"}},{"name":"latitude","required":true,"in":"query","description":"Latitude of the search location, e.g. 37.7749 (San Francisco).","schema":{"minimum":-90,"maximum":90,"example":37.7749,"type":"number"}},{"name":"longitude","required":true,"in":"query","description":"Longitude of the search location, e.g. -122.4194 (San Francisco).","schema":{"minimum":-180,"maximum":180,"example":-122.4194,"type":"number"}}],"responses":{"200":{"description":"Restaurants matching the query near the location, ranked by proximity. Empty when nothing matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestaurantSearchResponse"}}}},"400":{"description":"A parameter is missing or invalid; the message names the parameter and the accepted values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Search restaurants near a location","tags":["restaurants"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/restaurants/{restaurant_id}/menu-items":{"get":{"description":"**API key or client token.**\n\nThe menu of one restaurant, by the `id` a `GET /v1.2/restaurants` result carries — a listing, not a search. Items come ordered by name with the nutrition their menu source publishes; each carries one serving, and `GET /v1.2/foods/{food_id}` returns the complete list. Page a long menu with `limit` and `offset`: a page shorter than `limit` is the last one. To find dishes across restaurants near a location, use `GET /v1.2/menu-items`.\n\nCallable with a client token carrying the `restaurants:read` scope.","operationId":"listRestaurantMenuItems","parameters":[{"name":"limit","required":false,"in":"query","description":"Maximum number of menu items to return. Raise it, or page with `offset`, for a long menu.","schema":{"minimum":1,"maximum":500,"default":100,"type":"integer"}},{"name":"offset","required":false,"in":"query","description":"Number of menu items to skip, for paging: a page shorter than `limit` is the last one.","schema":{"minimum":0,"default":0,"type":"integer"}},{"name":"restaurant_id","required":true,"in":"path","description":"Restaurant id from a `GET /v1.2/restaurants` result.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$","example":"94293e99-ee95-40bf-98bd-508402af9f9e"}}],"responses":{"200":{"description":"The restaurant's menu items, by name. Empty when the restaurant has no menu on record or `offset` is past its end.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestaurantMenuItemsResponse"}}}},"400":{"description":"restaurant_id, limit or offset is malformed; the message names the parameter and the accepted values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No restaurant with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"List a restaurant's menu items","tags":["restaurants"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/menu-items":{"get":{"description":"**API key or client token.**\n\nSearch dishes across restaurants near (`latitude`, `longitude`), with the nutrition each menu source publishes. Use `radius_meters` to widen or narrow the search, e.g. `radius_meters=5000` for 5 kilometers; each result reports its own `distance_meters`. To find the restaurants themselves, use `GET /v1.2/restaurants`.\n\nCallable with a client token carrying the `restaurants:read` scope.","operationId":"searchMenuItems","parameters":[{"name":"radius_meters","required":false,"in":"query","description":"Search radius in meters around (latitude, longitude), e.g. 5000 = 5 kilometers. A fractional value is accepted and rounded. Default 8000 meters (about 5 miles); maximum 50000 (about 31 miles).","schema":{"minimum":1,"maximum":50000,"default":8000,"example":5000,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of results to return.","schema":{"minimum":1,"maximum":100,"default":10,"type":"integer"}},{"name":"query","required":true,"in":"query","description":"Dish or restaurant name to search for.","schema":{"maxLength":256,"example":"burger","type":"string"}},{"name":"latitude","required":true,"in":"query","description":"Latitude of the search location, e.g. 37.7749 (San Francisco).","schema":{"minimum":-90,"maximum":90,"example":37.7749,"type":"number"}},{"name":"longitude","required":true,"in":"query","description":"Longitude of the search location, e.g. -122.4194 (San Francisco).","schema":{"minimum":-180,"maximum":180,"example":-122.4194,"type":"number"}}],"responses":{"200":{"description":"Dishes near the location matching the query, ranked by proximity, each with the nutrition its source publishes. Empty when nothing matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuItemSearchResponse"}}}},"400":{"description":"A parameter is missing or invalid; the message names the parameter and the accepted values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Search menu items near a location","tags":["restaurants"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/food-analysis/image":{"post":{"description":"**API key or client token.**\n\nAnalyzes a food photo and returns the detected foods with their nutrition and an aggregated total. The photo can show the food itself or a packaged product — the front of the pack, the ingredient list, or the Nutrition Facts panel all work, and a packaged product comes back as a single detection in the usual result shape. `image` accepts either an http(s) URL or a base64 data URI. Analysis can take tens of seconds for complex meals. Set `reasoning.effort` to `xhigh` to use the reasoning-based analyzer; omit `reasoning` or set its effort to `none` to use the standard analyzer. Both modes return the same response shape and use the same rate-limit bucket and credit cost.\n\n**Beta:** label reading is in testing — returned nutrition can be incomplete or differ from the printed values, so validate results before relying on them. A photo of nothing but a barcode is rejected; use `GET /v1.2/foods/barcode/{barcode}` for those. Best results come from sharp, well-lit photos with the food or the complete panel large in the frame; ~1,024 px on the shorter side is plenty, and downsizing huge images lowers latency.\n\nCallable with a client token carrying the `food_analysis:write` scope.","operationId":"analyzeFoodImage","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageFoodAnalysisRequest"},"examples":{"image_url":{"summary":"Image by URL","description":"Grilled salmon with green beans and roasted potatoes, from January’s public food-scan benchmark dataset.","value":{"image":"https://january-food-image-dataset-public.s3.us-east-1.amazonaws.com/salmon-green-beans-potatoes.jpg","reasoning":{"effort":"xhigh"}}},"base64_data_uri":{"summary":"Image as base64 data URI","description":"The same meal photo as the URL example, re-encoded as a 320×320 JPEG data URI. Keep the data:image/…;base64, prefix around your own image bytes.","value":{"image":"data:image/jpeg;base64,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","reasoning":{"effort":"xhigh"}}},"label_photo":{"summary":"Nutrition label by URL","description":"The back of a chocolate bar — name, ingredients, and Nutrition Facts. The visible barcode does no harm.","value":{"image":"https://upload.wikimedia.org/wikipedia/commons/8/84/HK_Baron_crocolate_product_Nutrition_Facts_mark_Recycling-Code-22_Feb-2014_Best_Before_Date_n_Ingretients_paper_package.jpg","reasoning":{"effort":"xhigh"}}}}}}},"responses":{"200":{"description":"The foods detected in the photo, with per-item and total nutrition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAnalysisResult"}}}},"400":{"description":"`invalid_request`: the image is missing or not an http(s) URL / data URI, or the photo shows nothing but a barcode. `image_unreachable`, `image_corrupt`, `image_format_unsupported`, `image_invalid_base64`: the image itself could not be used — the message says what to fix, and retrying the same image fails the same way.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The request body exceeds 5 MB (a transport cap, not an image-quality limit). Keep raw images under ~3.5 MB before base64-encoding, or send a URL instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The vision model took too long; retry, ideally with a smaller image.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Analyze a food or label photo","tags":["food-analysis"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/food-analysis/text":{"post":{"description":"**API key or client token.**\n\nParses free text like \"a bowl of oatmeal with honey\" into detected foods with quantities and nutrition — the text counterpart of `POST /v1.2/food-analysis/image`. Text analyses return `meal_name: null` (the caller already has the words) and grade nothing, so every detection carries `confidence: null`. For keyword search over the food database, use `GET /v1.2/foods`.\n\nCallable with a client token carrying the `food_analysis:write` scope.","operationId":"analyzeFoodText","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextFoodAnalysisRequest"}}}},"responses":{"200":{"description":"The foods detected in the text, with per-item and total nutrition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAnalysisResult"}}}},"400":{"description":"`invalid_request`: the text is missing or exceeds 512 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Analyze a meal description","tags":["food-analysis"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/food-analysis/corrections":{"post":{"description":"**API key or client token.**\n\nRevises an analysis result. Send back the `analysis` object exactly as `POST /v1.2/food-analysis/image` or `/text` returned it, plus `instruction` describing the correction; the response is a corrected result with recalculated totals. Adjust portions through `instruction` (\"it was about half of that\") rather than editing serving quantities by hand. Nutrient keys a detection omits are filled in as zero automatically, and each detection must carry its selected catalog serving and consumed serving count.\n\nCallable with a client token carrying the `food_analysis:write` scope.","operationId":"correctFoodAnalysis","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CorrectFoodAnalysisRequest"},"examples":{"remove_an_ingredient":{"summary":"Correct an analysis returned by /text or /image","description":"The `analysis` object is what the analysis endpoint returned, sent back unchanged. Only `instruction` is new.","value":{"analysis":{"meal_name":"Breakfast Bowl","total_nutrients":{"calories":{"value":166,"unit":"kcal"},"protein":{"value":5.94,"unit":"g"},"carbohydrates":{"value":28.1,"unit":"g"}},"detections":[{"confidence":"high","food":{"id":"70379835","name":"oatmeal","brand_name":null,"nutrients":{"calories":{"value":166,"unit":"kcal"},"protein":{"value":5.94,"unit":"g"},"carbohydrates":{"value":28.1,"unit":"g"}},"quantity":1,"serving":{"id":"34237662","quantity":1,"unit":"cup","weight_grams":null}}}]},"instruction":"There was no honey, and the oatmeal was about half a cup."}}}}}},"responses":{"200":{"description":"The corrected analysis, with recalculated per-item and total nutrition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodAnalysisResult"}}}},"400":{"description":"A field is missing or malformed; the message names the exact detection index and problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The vision model took too long; retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Correct an analysis in plain English","tags":["food-analysis"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/food-logs":{"post":{"description":"**API key or client token.**\n\nCreates a food log from food + serving ids (from search or food-analysis results). The response echoes the log hydrated with full nutrition — save its `id` to update or delete the log, or fetch it again with GET /v1.2/food-logs/{log_id}. Not idempotent: verify with the list or get endpoint before retrying a timed-out create.\n\nCallable with a client token carrying the `food_logs:write` scope.","operationId":"createFoodLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFoodLogRequest"}}}},"responses":{"201":{"description":"The created log, hydrated with resolved food and nutrition; its id is in the Location header.","headers":{"Location":{"description":"GET path for the created log.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodLog"}}}},"400":{"description":"A field is missing or malformed; the message names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Log foods for a user","tags":["food-logs"],"x-january-credential-types":["api_key","client_token"]},"get":{"description":"**API key or client token.**\n\nReturns the logs between `start_date` and `end_date` (both inclusive local calendar dates in `timezone`; the range spans at most 60 days), ordered by timestamp. An empty list is a valid result.\n\nCallable with a client token carrying the `food_logs:read` scope.","operationId":"listFoodLogs","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"start_date","required":true,"in":"query","description":"First local calendar date in `timezone`, inclusive.","schema":{"format":"date","example":"2024-09-01","type":"string"}},{"name":"end_date","required":true,"in":"query","description":"Last local calendar date in `timezone`, inclusive. May equal start_date for a single day. The inclusive range may not exceed 60 calendar days.","schema":{"format":"date","example":"2024-09-08","type":"string"}},{"name":"timezone","required":true,"in":"query","description":"IANA timezone that defines the local calendar days this range covers. Required: without it the days would be cut in UTC, which silently shifts anything logged near midnight into the wrong day for anyone not on UTC.","schema":{"example":"America/Los_Angeles","type":"string"}}],"responses":{"200":{"description":"The logs in the requested date range, ordered by timestamp; an empty array is a valid result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodLogList"}}}},"400":{"description":"A date is missing, malformed, or the range is inverted or exceeds 60 days; or timezone is missing or not a valid IANA name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"List a user's food logs in a date range","tags":["food-logs"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/food-logs/summary":{"get":{"description":"**API key or client token.**\n\nAggregates the logs between `start_date` and `end_date` (both inclusive local calendar dates in `timezone`) into per-day or per-week buckets, each with summed nutrients, plus totals for the range and an average per logged day. The range spans at most 366 days — wide enough for a year at a time; the 366-day cap bounds the number of buckets. The buckets tile the whole range in chronological order: a day or week with no logs is still returned, with zero counts, and under `group_by=week` the first and last buckets are clipped to the dates you asked for. `nutrients` is sparse, so read `logs_count` to tell a bucket with no logs from one whose logs could not be resolved.\n\nCallable with a client token carrying the `food_logs:read` scope.","operationId":"getFoodLogSummary","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"start_date","required":true,"in":"query","description":"First local calendar date in `timezone`, inclusive.","schema":{"format":"date","example":"2024-09-01","type":"string"}},{"name":"end_date","required":true,"in":"query","description":"Last local calendar date in `timezone`, inclusive. May equal start_date for a single day. The inclusive range may not exceed 366 calendar days.","schema":{"format":"date","example":"2024-09-30","type":"string"}},{"name":"timezone","required":true,"in":"query","description":"IANA timezone that defines the local calendar days this range covers. Required: without it the days would be cut in UTC, which silently shifts anything logged near midnight into the wrong day for anyone not on UTC.","schema":{"example":"America/Los_Angeles","type":"string"}},{"name":"group_by","required":false,"in":"query","description":"Bucket size. `day` is one bucket per local calendar date; `week` is one per week, with the first and last clipped to the range.","schema":{"default":"day","enum":["day","week"],"type":"string"}},{"name":"week_start","required":false,"in":"query","description":"Which weekday a week bucket begins on. Ignored when `group_by=day`, where the response reports `week_start: null`.","schema":{"default":"monday","enum":["monday","sunday"],"type":"string"}}],"responses":{"200":{"description":"The aggregated summary. A range with no logs is a valid result: buckets are still returned, with zero counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodLogSummary"}}}},"400":{"description":"A date is missing, malformed, or the range is inverted or exceeds 366 days; `timezone` is missing or not a valid IANA name; or `group_by`/`week_start` is not one of the documented values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Summarize a user's food logs over a date range","tags":["food-logs"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/food-logs/{log_id}":{"get":{"description":"**API key or client token.**\n\nFetches one food log by the id returned when it was created.\n\nCallable with a client token carrying the `food_logs:read` scope.","operationId":"getFoodLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"log_id","required":true,"in":"path","description":"The log id returned when the log was created.","schema":{"example":"78129823-8ba2-4183-b13b-71f0e963c606","type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}}],"responses":{"200":{"description":"The requested food log, hydrated with resolved food and nutrition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodLog"}}}},"400":{"description":"log_id is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No log with this id for this user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Get a food log","tags":["food-logs"],"x-january-credential-types":["api_key","client_token"]},"patch":{"description":"**API key or client token.**\n\nReplaces any subset of the log: `foods`, `eaten_at`, `name`. Omitted fields are left unchanged.\n\nCallable with a client token carrying the `food_logs:write` scope.","operationId":"updateFoodLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"log_id","required":true,"in":"path","description":"The log id returned when the log was created.","schema":{"example":"78129823-8ba2-4183-b13b-71f0e963c606","type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFoodLogRequest"}}}},"responses":{"200":{"description":"The updated log, hydrated with resolved food and nutrition, reflecting the applied changes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodLog"}}}},"400":{"description":"A field is malformed; the message names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No log with this id for this user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Update a food log","tags":["food-logs"],"x-january-credential-types":["api_key","client_token"]},"delete":{"description":"**API key or client token.**\n\nIdempotent: deleting an unknown or already-deleted log answers the same 204, so it is safe to retry.\n\nCallable with a client token carrying the `food_logs:write` scope.","operationId":"deleteFoodLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"log_id","required":true,"in":"path","description":"The log id returned when the log was created.","schema":{"example":"78129823-8ba2-4183-b13b-71f0e963c606","type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}}],"responses":{"204":{"description":"The log was deleted; empty body. Idempotent — deleting an already-deleted or unknown log also answers 204."},"400":{"description":"log_id is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Delete a food log","tags":["food-logs"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/water-logs":{"post":{"description":"**API key or client token.**\n\nRecords one amount of water, in `fl_oz` or `ml` (1–811.5 fl_oz, 30–24000 ml). An end user's total is capped at 24 L (about 811 fl oz) per day: a log that would take the day of its `consumed_at` past it is refused with `daily_water_limit_exceeded`. Save the returned `id` to delete the log. Not idempotent: verify with the list endpoint before retrying a timed-out create, since a retry records the water twice and counts twice toward the cap.\n\nCallable with a client token carrying the `water_logs:write` scope.","operationId":"createWaterLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWaterLogRequest"}}}},"responses":{"201":{"description":"The logged entry: its id, the amount in the unit it was sent in, and `consumed_at` in UTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaterLog"}}}},"400":{"description":"amount is missing, malformed or out of range, or consumed_at is malformed (`invalid_request`); or the log would take the end user's total for its day past the daily cap (`daily_water_limit_exceeded`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Log water for a user","tags":["water-logs"],"x-january-credential-types":["api_key","client_token"]},"get":{"description":"**API key or client token.**\n\nReturns one total per local calendar day between `start_date` and `end_date` (both inclusive, in `timezone`), oldest first, in the `unit` you ask for, rounded to one decimal place. Days with nothing logged are absent. At most 100 days are returned — when more match, the most recent 100. `start_date` may reach back at most 5 years from today in `timezone`. An empty list is a valid result.\n\nCallable with a client token carrying the `water_logs:read` scope.","operationId":"listWaterLogs","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"start_date","required":true,"in":"query","description":"First local calendar date in `timezone`, inclusive. May be at most 5 years before today in `timezone`.","schema":{"format":"date","example":"2026-09-01","type":"string"}},{"name":"end_date","required":true,"in":"query","description":"Last local calendar date in `timezone`, inclusive. May equal start_date for a single day. When more than 100 days in the range have water logged, the most recent 100 are returned.","schema":{"format":"date","example":"2026-09-10","type":"string"}},{"name":"timezone","required":true,"in":"query","description":"IANA timezone that defines the local calendar days this range covers. Required: without it the days would be cut in UTC, which silently shifts anything logged near midnight into the wrong day for anyone not on UTC.","schema":{"example":"America/Los_Angeles","type":"string"}},{"name":"unit","required":true,"in":"query","description":"The unit every daily total is returned in.","schema":{"example":"fl_oz","enum":["fl_oz","ml"],"type":"string"}}],"responses":{"200":{"description":"One total per local day with water logged, oldest first; an empty array is a valid result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaterLogList"}}}},"400":{"description":"A date is missing, malformed, or the range is inverted; `start_date` is more than 5 years before today in `timezone` (`date_range_too_large`); timezone is missing or not a valid IANA name; or unit is missing or not `fl_oz`/`ml`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"List a user's daily water totals in a date range","tags":["water-logs"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/water-logs/{log_id}":{"delete":{"description":"**API key or client token.**\n\nIdempotent: deleting an unknown or already-deleted log answers the same 204, so it is safe to retry. The amount no longer counts toward its day's cap.\n\nCallable with a client token carrying the `water_logs:write` scope.","operationId":"deleteWaterLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"log_id","required":true,"in":"path","description":"The log id returned when the log was created.","schema":{"example":"78129823-8ba2-4183-b13b-71f0e963c606","type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}}],"responses":{"204":{"description":"The log was deleted; empty body. Idempotent — deleting an already-deleted or unknown log also answers 204."},"400":{"description":"log_id is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Delete a water log","tags":["water-logs"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/weight-logs":{"post":{"description":"**API key or client token.**\n\nRecords one weight measurement, in `lb` (10–1000) or `kg` (4.5–453.6). Every measurement is kept; listing shows one per local day — the latest by `measured_at` — so logging again later the same day replaces what that day shows. Not idempotent: a retried create records the measurement twice, which listing then shows once.\n\nCallable with a client token carrying the `weight_logs:write` scope.","operationId":"createWeightLog","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWeightLogRequest"}}}},"responses":{"201":{"description":"The logged weight, as stored: the unit it was sent in, and `measured_at` in UTC.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WeightLog"}}}},"400":{"description":"weight is missing, malformed or out of range, or measured_at is malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Log a weight for a user","tags":["weight-logs"],"x-january-credential-types":["api_key","client_token"]},"get":{"description":"**API key or client token.**\n\nReturns one weight per day between `start_date` and `end_date` (both inclusive local calendar dates in `timezone`), oldest first. Only days with a logged weight appear; when several were logged on one day, the one with the latest `measured_at` is returned, in the unit it was logged in. At most 100 days are returned — when more match, the most recent 100. `start_date` may reach back at most 5 years from today in `timezone`. An empty list is a valid result.\n\nCallable with a client token carrying the `weight_logs:read` scope.","operationId":"listWeightLogs","parameters":[{"$ref":"#/components/parameters/JanuaryEndUserId"},{"name":"start_date","required":true,"in":"query","description":"First local calendar date in `timezone`, inclusive. May be at most 5 years before today in `timezone`.","schema":{"format":"date","example":"2024-09-01","type":"string"}},{"name":"end_date","required":true,"in":"query","description":"Last local calendar date in `timezone`, inclusive. May equal start_date for a single day. When more than 100 days in the range have a weight, the most recent 100 are returned.","schema":{"format":"date","example":"2024-09-08","type":"string"}},{"name":"timezone","required":true,"in":"query","description":"IANA timezone that defines the local calendar days this range covers. Required: without it the days would be cut in UTC, which silently shifts anything logged near midnight into the wrong day for anyone not on UTC.","schema":{"example":"America/Los_Angeles","type":"string"}}],"responses":{"200":{"description":"One weight per day that has one, oldest first; an empty array is a valid result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WeightLogList"}}}},"400":{"description":"A date is missing, malformed, or the range is inverted; `start_date` is more than 5 years before today in `timezone` (`date_range_too_large`); or timezone is missing or not a valid IANA name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description, and `end_user_id_mismatch` means the `January-End-User-ID` header disagrees with the end user the token is bound to — omit it, or send exactly that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"List a user's daily weights in a date range","tags":["weight-logs"],"x-january-credential-types":["api_key","client_token"]}},"/v1.2/glucose/predictions":{"post":{"description":"**API key or client token.**\n\nPredicts the glucose curve a meal will produce for the given profile and body.timezone (required — the IANA timezone the end user is in; the prediction depends on the meal's local time of day). Optionally personalize by sending cgm_data with the consumed_foods eaten during it (both together; the upstream needs at least five complete days of paired history). Nothing is stored, so there is no end-user identity to send: the request acts as the partner itself.\n\nCallable with a client token carrying the `glucose:read` scope.","operationId":"predictGlucoseResponse","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlucosePredictionRequest"},"examples":{"standard":{"summary":"Standard prediction (no CGM required)","description":"The common case: a profile, the meal, when it is eaten, and the end user’s timezone.","value":{"user_profile":{"age":42,"sex":"female","height":{"value":66,"unit":"in"},"weight":{"value":150,"unit":"lb"},"activity_level":"moderately_active","health_conditions":["prediabetes"]},"foods":[{"food_id":"101963552","serving_id":"68051535","quantity":1.4}],"start_time":"2024-09-13T11:34:56Z","timezone":"America/New_York"}}}}}},"responses":{"200":{"description":"`points`, the predicted glucose curve at 15-minute intervals, plus the meal's overall impact score and suggested chart bounds for rendering it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlucosePrediction"}}}},"400":{"description":"A field is missing or invalid; the message names it and the accepted values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"The request carried no `Authorization` header, or the credential in it was rejected.\n\n`unauthorized` — the header is missing or malformed, or the key is not one we recognise. A valid key belonging to the other API version is `403 forbidden` instead.\n\nA client token is rejected in one of three ways, and only the first should be handled automatically:\n\n- `token_expired` — the token is past its TTL. Mint a fresh one from your backend and retry the request once. This is routine and expected once per TTL window.\n- `token_invalid` — no such token: it was never issued, or it has been purged, which happens shortly after it expires. This code is not an automatic token-refresh signal.\n- `token_revoked` — the token was revoked, by `POST /v1.2/auth/client-token-revocations` or from the dashboard. The end user signs in again in your app, and your backend decides whether to mint another; a device that just re-mints defeats the revocation.","headers":{"WWW-Authenticate":{"description":"RFC 6750 challenge. `Bearer` when the request carried no credential; `Bearer error=\"invalid_token\", error_description=\"token expired|token invalid|token revoked\"` when one was rejected.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but is not allowed to make this request. `forbidden` is the general case — a key issued for the other API version, for example. A client token adds `client_token_not_allowed`, meaning the endpoint takes only an `sk-` API key — its description opens with **API key only.** (in this API: `POST /v1.2/auth/client-tokens`, `POST /v1.2/auth/client-token-revocations`, `POST /v1.2/chat/completions`, `POST /v1.2/literature/search`, and `GET /v1.2/credits`).\n\nOn an endpoint that opens with **API key or client token.**, `scope_insufficient` means the token was minted without the scope named at the end of that endpoint’s description.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Three different failures share this status, and the `code` is what tells them apart — two of them must not be retried.\n\n- `rate_limited` — a window short enough to wait out: a per-endpoint limit configured for your account, or the rolling 24-hour burst guard over the monthly ceiling. `Retry-After` carries the wait whenever it is known, and is at most a day.\n- `request_limit_exceeded` — the monthly request allowance on your account is spent. The message names your plan where the plan set that number, and on the free tier, whose allowance the default *is*. A limit agreed with us, or the default standing in for a **paid** tier the catalog states no ceiling for, is given as a number without naming a plan.\n- `credit_limit_exceeded` — the monthly credit allowance is spent.\n\nThe last two reopen at the start of the next calendar month, the same instant as each other, and neither sends `Retry-After`: a wait of up to four weeks is not something to sleep on, and it does not survive a 32-bit timer. The message names the reset instant, and `GET /v1.2/credits` returns it as `resets_at` along with your balance — that endpoint keeps answering when both of these refuse.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent only with `rate_limited`, and only where the window is known — at most 24 hours. The two monthly refusals never carry it; use the reset instant in the message, or `resets_at` from `GET /v1.2/credits`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The prediction took too long; retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"default":{"description":"Any other error: the HTTP status plus { code, message }. Retry only rate_limited and the transient 5xx codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"JanuaryCredential":[]}],"summary":"Predict the glucose response to a meal","tags":["glucose"],"x-january-credential-types":["api_key","client_token"]}}},"info":{"title":"January AI - Nutrition Intelligence APIs","description":"Build food and metabolic intelligence into your product — one API for understanding what people eat and how food may affect them.\n\n**Clinical-grade precision, consumer-grade experiences.** January builds the infrastructure underneath the product: turning messy health and nutrition data into reliable intelligence, so your team spends its time on the experience instead of the foundation.\n\n**Security & compliance** — **SOC 2 Type II** · **HIPAA-aligned practices** · **BAA** and **Zero Data Retention (ZDR)** available\n\n**What you can build**\n- **Scan food** — photo and text food recognition: detect foods and nutrition, then correct results conversationally\n- **Search the food database** — by name or barcode — and get healthier alternatives for any food\n- **Log food, water and weight** — per-user diaries with day-range queries\n- **Predict glucose response** to any meal — no sensor required\n\n**Getting started**\n1. Create an API key in the [Developer Dashboard](https://dashboard.january.ai) — the full key is shown once, at creation.\n2. Send it as `Authorization: Bearer sk-…` — click **Authorize** here to make every example below a live request.\n3. On the endpoints that read or write an end user's logs, say whose with the `January-End-User-ID` header — each one documents it. No other endpoint takes it: everything else either asks the shared food database a question or works on the body you send, and neither depends on who the food is for.\n\n**Calling from a mobile app** — your `sk-` key must never ship inside an app. Instead, exchange it on your backend for a *client token*: a credential that lasts up to two hours, acts as exactly one of your end users, and carries only the scopes you grant it (see the **authentication** section). Your app then calls these endpoints directly, with no proxy of your own in the request path. Both credentials travel in the same `Authorization: Bearer` header, and every endpoint below opens by saying which it accepts — **API key or client token**, or **API key only**.\n\n**Support** — [support@january.ai](mailto:support@january.ai) · [Discord community](https://discord.gg/cYQeh3UnC) · [docs.january.ai](https://docs.january.ai)","version":"1.2","contact":{"name":"January AI Support","email":"support@january.ai"}},"tags":[{"name":"food-analysis","description":"Food recognition, three ways: interpret food into structured data from a photo (a meal or a packaged food’s label) or a plain-English description, and correct the result conversationally. Every analysis returns the same detection object, so results round-trip into corrections unchanged. Know the food already? Look it up under foods instead."},{"name":"foods","description":"Search the food database by name or barcode, and get healthier alternatives for any food."},{"name":"food-logs","description":"Create and query an end user's food diary."},{"name":"water-logs","description":"Record an end user's water intake and read it back as daily totals."},{"name":"weight-logs","description":"Record an end user's weight and read it back as one value per day."},{"name":"glucose","description":"Glucose intelligence for a meal — a predicted curve and an impact score in one call, no sensor required. CGM personalization rides the same endpoint via cgm_data."},{"name":"restaurants","description":"Find restaurants and menu items near a location."},{"name":"authentication","description":"Two credentials reach this API: your API key (`sk-…`) for calls from your own servers, and a client token (`ct-…`) for calls from a mobile app. Mint a client token here from your API key — on your backend, behind your own login — and it acts as exactly one of your end users for at most 2 hours, so your key never ships inside an app.\n\nFull walkthrough, including the scope and refresh rules: [Client tokens](https://docs.january.ai/rest-api/client-tokens)."},{"name":"credits","description":"Your monthly credit allowance and what you have spent of it."}],"servers":[{"url":"https://partners.january.ai"}],"components":{"securitySchemes":{"JanuaryCredential":{"scheme":"bearer","bearerFormat":"opaque","type":"http","description":"Your credential, sent as `Authorization: Bearer <credential>`. Two kinds reach this API, and the prefix tells them apart.\n\n**API key (`sk-…`)** — created in the [Developer Dashboard](https://dashboard.january.ai), it authenticates your whole account. Use it only where you control the machine — never inside a mobile app or a browser. Every endpoint accepts it.\n\n**Client token (`ct-…`)** — a short-lived credential bound to one of your end users, minted from your API key by `POST /v1.2/auth/client-tokens` on your backend and relayed to the device. Safe to hold on a phone: it expires within 2 hours and acts only as the user it was minted for. Endpoints that open with **API key or client token** accept one and name the scope it must carry; endpoints that open with **API key only** refuse it."}},"schemas":{"ChatErrorDetails":{"type":"object","properties":{"message":{"type":"string","example":"The final message must have role user"},"type":{"type":"string","example":"invalid_request_error"},"param":{"type":"string","nullable":true,"example":"messages"},"code":{"type":"string","example":"invalid_request"}},"required":["message","type","param","code"]},"ChatError":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ChatErrorDetails"}},"required":["error"]},"PublicChatMessage":{"type":"object","properties":{"role":{"type":"string","enum":["system","developer","user","assistant"]},"content":{"type":"string","minLength":1,"description":"Text only. All message content together is limited to 32768 UTF-8 bytes."}},"required":["role","content"]},"ChatCompletionRequest":{"type":"object","properties":{"model":{"type":"string","enum":["january"]},"messages":{"minItems":1,"maxItems":100,"description":"Initial system/developer instructions, then user/assistant history. The final message must be user.","type":"array","items":{"$ref":"#/components/schemas/PublicChatMessage"}},"stream":{"type":"boolean","description":"Omit this field. Streaming is not supported."}},"required":["model","messages"]},"ChatAssistantMessage":{"type":"object","properties":{"role":{"type":"string","enum":["assistant"]},"content":{"type":"string","example":"Most guidelines suggest 25 to 30 g of fiber a day, from whole grains, legumes, fruit and vegetables. Higher intake is consistently associated with lower risk of coronary heart disease and stroke."}},"required":["role","content"]},"ChatCompletionChoice":{"type":"object","properties":{"index":{"type":"number"},"message":{"$ref":"#/components/schemas/ChatAssistantMessage"},"finish_reason":{"type":"string","enum":["stop","length"]},"logprobs":{"type":"object","nullable":true,"example":null}},"required":["index","message","finish_reason","logprobs"]},"ChatCompletionUsage":{"type":"object","properties":{"prompt_tokens":{"type":"number","example":412},"completion_tokens":{"type":"number","example":96},"total_tokens":{"type":"number","example":508}},"required":["prompt_tokens","completion_tokens","total_tokens"]},"ChatCompletionResponse":{"type":"object","properties":{"id":{"type":"string","example":"chatcmpl-8f3a2c1d9e4b7a60"},"object":{"type":"string","enum":["chat.completion"]},"created":{"type":"number","example":1789398240},"model":{"type":"string","enum":["january"]},"choices":{"type":"array","items":{"$ref":"#/components/schemas/ChatCompletionChoice"}},"usage":{"nullable":true,"description":"Primary-model totals across every round; null if any round lacks usage. Excludes literature summarization.","allOf":[{"$ref":"#/components/schemas/ChatCompletionUsage"}]}},"required":["id","object","created","model","choices","usage"]},"ApiError":{"type":"object","properties":{"message":{"type":"string","description":"A developer-facing explanation of what went wrong and how to fix it.","example":"query is required: the food name to search for, e.g. ?query=greek yogurt."},"code":{"type":"string","description":"A stable machine-readable identifier for the class of failure — build retry logic on this, never on message wording.\n\nAny request, each with the status it usually accompanies: `invalid_request` (400), `unauthorized` (401), `forbidden` (403), `not_found` (404), `payload_too_large` (413), `rate_limited` (429), `request_limit_exceeded` (429), `credit_limit_exceeded` (429), `internal_error` (500), `not_implemented` (501), `upstream_error` (502), `service_unavailable` (503), `upstream_timeout` (504). Those pairings are the common case, not a guarantee: a status we do not map falls back to `invalid_request` below 500 and `internal_error` at or above it, so an internal service answering 409 or 422 reaches you with that status and `code: invalid_request`. Branch on the code first and treat the status as the fallback, exactly as for a code you do not recognise.\n\n`cancelled` (499) means the client disconnected before completion. The closed connection may prevent delivery of the error body.\n\nClient tokens add six an API key never produces: `token_expired`, `token_invalid`, `token_revoked` (401), and `client_token_not_allowed`, `scope_insufficient`, `end_user_id_mismatch` (403). Each response documents its own.\n\nFour more are specific to individual endpoints: `end_user_id_required` (400 — an sk- key called an operation that documents the January-End-User-ID header without sending it), `date_range_too_large` (400 — a date range past the operation's documented maximum or lookback), `daily_water_limit_exceeded` (400 — a water log that would take the end user's total for its day past 24 L; not retryable), and `client_token_revocation_incomplete` (503 — a revocation call that only stopped part of its batch; the same request is safe to repeat).\n\n`POST /v1.2/food-analysis/image` adds four 400s about the image itself: `image_unreachable` (the URL could not be fetched), `image_corrupt` (the file could not be decoded), `image_format_unsupported` and `image_invalid_base64`. Each is fixed by the caller; the same image fails the same way again.\n\nRetry only `rate_limited`, `internal_error`, `upstream_error`, `service_unavailable`, `upstream_timeout` and `client_token_revocation_incomplete`, with backoff — `not_implemented` is permanent until the feature ships, so its 5xx status is not a reason to retry it. Three more the status code alone gets wrong. **Two 429s must never be retried**, because both reopen only at the start of the next calendar month: `credit_limit_exceeded` (the monthly credit allowance) and `request_limit_exceeded` (the monthly request allowance). A client that backs off on every 429 will spin until then; neither sends `Retry-After`, and the message names the reset instant — `GET /v1.2/credits` returns it as the resets_at field. `rate_limited` is the 429 that *is* worth retrying: a per-endpoint limit, or the rolling 24-hour burst guard over the monthly ceiling, so its window is at most a day. And `token_expired` is refreshed, not retried — mint a new token, then retry once.\n\nNew codes may be added over time; treat an unknown code according to its HTTP status class.","example":"invalid_request"}},"required":["message","code"]},"LiteratureSearchRequest":{"type":"object","properties":{"query":{"type":"string","description":"Concise English research question. Trimmed, nonblank, no NUL, at most 4096 UTF-8 bytes.","example":"Does soluble fiber lower LDL?"},"scope":{"type":"string","enum":["all","journals","guidelines",null],"default":"all","nullable":true},"limit":{"type":"integer","minimum":1,"maximum":20,"default":5,"nullable":true,"description":"Number of passages, which may include several sections of one publication."},"include_summary":{"type":"boolean","default":false,"nullable":true,"description":"Generate a summary with [n] references to the returned passage order."}},"required":["query"]},"LiteratureResult":{"type":"object","properties":{"id":{"type":"string","description":"Passage identifier; may change when the corpus is reindexed."},"type":{"type":"string","enum":["journal","guideline"]},"text":{"type":"string","description":"Retrieved abstract or guideline passage."},"year":{"type":"integer","nullable":true},"title":{"type":"string","nullable":true},"source":{"type":"string","nullable":true},"section_header":{"type":"string","nullable":true},"pmid":{"type":"string","nullable":true},"doi":{"type":"string","nullable":true},"url":{"type":"string","nullable":true}},"required":["id","type","text","year","title","source","section_header","pmid","doi","url"]},"LiteratureSearchResponse":{"type":"object","properties":{"query":{"type":"string"},"results":{"maxItems":20,"type":"array","items":{"$ref":"#/components/schemas/LiteratureResult"}},"summary":{"type":"string","nullable":true,"description":"Evidence summary with one-based [n] passage references, or null. Different references may name the same publication."},"summary_status":{"type":"string","enum":["not_requested","succeeded","failed","no_results"],"description":"A failed optional summary still returns a successful, billable search. no_results means summary was requested but no evidence matched."}},"required":["query","results","summary","summary_status"]},"CreateClientTokenRequest":{"type":"object","properties":{"end_user_id":{"type":"string","description":"Your stable ID for the end user this token acts as. The token is bound to it; requests made with the token act only on this user.","example":"acme-user-8271","maxLength":64},"scopes":{"type":"array","description":"What the token may do. **Required** — name only the scopes this token needs (least privilege), never the full set out of convenience. A read-only food-lookup screen asks for `[\"foods:read\"]`; a logging screen adds `food_logs:write`. Valid scopes: foods:read, food_analysis:write, food_logs:read, food_logs:write, glucose:read, restaurants:read, water_logs:read, water_logs:write, weight_logs:read, weight_logs:write.","minItems":1,"maxItems":10,"example":["foods:read"],"items":{"type":"string","enum":["foods:read","food_analysis:write","food_logs:read","food_logs:write","glucose:read","restaurants:read","water_logs:read","water_logs:write","weight_logs:read","weight_logs:write"]}},"ttl_seconds":{"type":"integer","description":"How long the token stays valid, in seconds. Between 300 and 7200; defaults to 1800.","minimum":300,"maximum":7200,"default":1800,"example":1800}},"required":["end_user_id","scopes"]},"ClientToken":{"type":"object","properties":{"token":{"type":"string","description":"The credential itself. Shown exactly once — it is stored only as a hash and can never be retrieved again.","example":"ct-4fQr7yNb2KcXm9TvLpZ3wHs6JdRg8AeYuQ1oViB5xCn"},"expires_in":{"type":"integer","description":"Seconds until the token expires, counted from the moment this response was produced. Compute expiry from this rather than from `expires_at` — a device clock that is wrong makes an absolute timestamp wrong with it.","example":1800},"expires_at":{"type":"string","format":"date-time","description":"The same expiry as an absolute UTC instant, for logs and humans.","example":"2026-08-26T18:35:11.000Z"},"end_user_id":{"type":"string","description":"The end user this token is bound to, echoed back so a caller can assert it minted what it meant to.","example":"acme-user-8271"},"scopes":{"type":"array","description":"What this token may do — the exact scopes it was granted, echoed back so a caller can assert it minted what it meant to.","example":["foods:read","food_logs:read"],"items":{"type":"string","enum":["foods:read","food_analysis:write","food_logs:read","food_logs:write","glucose:read","restaurants:read","water_logs:read","water_logs:write","weight_logs:read","weight_logs:write"]}}},"required":["token","expires_in","expires_at","end_user_id","scopes"]},"RevokeClientTokensRequest":{"type":"object","properties":{"end_user_id":{"type":"string","description":"The end user whose outstanding client tokens should all be revoked.","example":"acme-user-8271","maxLength":64}},"required":["end_user_id"]},"ClientTokenRevocationResult":{"type":"object","properties":{"revoked_count":{"type":"integer","description":"How many live tokens this call revoked. Already-revoked and already-expired tokens are not counted, so an immediate second call reports 0 — repeat the call until it does.","example":2}},"required":["revoked_count"]},"CreditBalance":{"type":"object","properties":{"plan":{"type":"string","enum":["free","pro","startup","enterprise","unlimited"],"description":"The plan this allowance comes from. `unlimited` is a partner with no ceiling, for whom both ceiling fields are `null`.","example":"free"},"period_start":{"type":"string","format":"date","description":"First day of the current billing period (UTC), inclusive.","example":"2026-08-01"},"period_end":{"type":"string","format":"date","description":"Last day of the current billing period (UTC), inclusive.","example":"2026-08-31"},"resets_at":{"type":"string","format":"date-time","description":"When the allowance resets and `used_credits` returns to 0.","example":"2026-09-01T00:00:00.000Z"},"included_credits":{"type":"integer","nullable":true,"description":"Credits included in the plan for this period, or `null` when the plan has no ceiling.","example":1000},"used_credits":{"type":"integer","description":"Credits used so far this period. Billable operations consume credits — how many depends on the operation and your plan — while failed calls cost nothing.","example":342},"remaining_credits":{"type":"integer","nullable":true,"description":"Credits left in this period, or `null` when the plan has no ceiling.","example":658}},"required":["plan","period_start","period_end","resets_at","included_credits","used_credits","remaining_credits"]},"NutrientAmount":{"type":"object","properties":{"value":{"type":"number","example":300},"unit":{"type":"string","enum":["g","mg","kcal","IU","mcg"],"example":"g","description":"Canonical across the API: g, mg, kcal, IU, mcg. Read it from each amount rather than assuming a unit per nutrient."}},"required":["value","unit"]},"Nutrients":{"type":"object","properties":{"calories":{"$ref":"#/components/schemas/NutrientAmount"},"protein":{"$ref":"#/components/schemas/NutrientAmount"},"carbohydrates":{"$ref":"#/components/schemas/NutrientAmount"},"net_carbohydrates":{"$ref":"#/components/schemas/NutrientAmount"},"total_fat":{"$ref":"#/components/schemas/NutrientAmount"},"trans_fat":{"$ref":"#/components/schemas/NutrientAmount"},"saturated_fat":{"$ref":"#/components/schemas/NutrientAmount"},"fiber":{"$ref":"#/components/schemas/NutrientAmount"},"total_sugars":{"$ref":"#/components/schemas/NutrientAmount"},"added_sugars":{"$ref":"#/components/schemas/NutrientAmount"},"cholesterol":{"$ref":"#/components/schemas/NutrientAmount"},"calcium":{"$ref":"#/components/schemas/NutrientAmount"},"iron":{"$ref":"#/components/schemas/NutrientAmount"},"potassium":{"$ref":"#/components/schemas/NutrientAmount"},"sodium":{"$ref":"#/components/schemas/NutrientAmount"},"vitamin_d":{"$ref":"#/components/schemas/NutrientAmount"}}},"FoodServing":{"type":"object","properties":{"id":{"type":"string","nullable":true,"example":"68051535","description":"Opaque serving id; may look numeric but is always a string."},"quantity":{"type":"number","nullable":true,"example":1},"unit":{"type":"string","nullable":true,"example":"oz"},"scaling_factor":{"type":"number","nullable":true,"description":"Multiplier applied to the food's nutrition values for this serving.","example":1},"weight_grams":{"type":"number","nullable":true,"example":28},"is_primary":{"type":"boolean","nullable":true,"description":"Whether this is the default serving for the food.","example":true}},"required":["id","quantity","unit","scaling_factor","weight_grams","is_primary"]},"Food":{"type":"object","properties":{"id":{"type":"string","example":"101963552","description":"Opaque id — pass it back verbatim to log, predict, or fetch this food."},"type":{"type":"string","enum":["generic","branded","recipe"],"example":"branded","description":"What kind of food this is: `generic` for a database staple (\"banana\"), `branded` for a packaged product, `recipe` for a multi-ingredient dish."},"name":{"type":"string","nullable":true,"example":"Dipped Banana Bites","description":"Null only when the database has no name for the row."},"brand_name":{"type":"string","nullable":true,"example":"Banana","description":"null for generic foods and recipes, which have no brand."},"nutrients":{"description":"Per-serving nutrition in the shared nutrient vocabulary. Keys are omitted when the database has no value.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"glycemic_index":{"type":"number","nullable":true,"description":"Glycemic index; null when the database has none for this food."},"glycemic_load":{"type":"number","nullable":true,"description":"Glycemic load; null when the database has none for this food."},"image_url":{"type":"string","nullable":true,"description":"URL of a picture of the food; null when the database has none."},"barcode":{"type":"string","nullable":true,"example":"049000006346","description":"The product's barcode; null for foods that have none. Named for the code rather than one of its encodings — the same field carries a UPC, an EAN or a GTIN. It is the database's normalized form, so it may differ from the digits you scanned in leading zeros: display it, do not string-compare it."},"servings":{"description":"Search and barcode results carry the default serving only; `GET /v1.2/foods/{food_id}` returns the complete list to choose from.","type":"array","items":{"$ref":"#/components/schemas/FoodServing"}}},"required":["id","type","name","brand_name","nutrients","glycemic_index","glycemic_load","image_url","barcode","servings"]},"FoodSearchResponse":{"type":"object","properties":{"items":{"description":"Ranked matches, best first. Empty when nothing matches — not an error.","type":"array","items":{"$ref":"#/components/schemas/Food"}}},"required":["items"]},"FoodSuggestion":{"type":"object","properties":{"id":{"type":"string","example":"70376053","description":"Opaque id — the same id `GET /v1.2/foods/{food_id}` takes."},"type":{"type":"string","enum":["generic","branded"],"example":"generic","description":"What kind of food this is: `generic` for a database staple (\"banana\"), `branded` for a packaged product, `recipe` for a multi-ingredient dish."},"name":{"type":"string","nullable":true,"example":"greek yogurt","description":"Generic foods are lowercase; branded foods keep their product name. Null only when the index has no name for the row."},"brand_name":{"type":"string","nullable":true,"example":"Chobani","description":"null for generic (non-branded) foods."},"image_url":{"type":"string","nullable":true,"description":"Thumbnail of the food; null when the database has none."},"nutrients":{"description":"Calories per default serving, in the shared nutrient vocabulary — the one nutrient a suggestion carries. Fetch the food for the full panel. Empty when the index has no value.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]}},"required":["id","type","name","brand_name","image_url","nutrients"]},"FoodSuggestionsResponse":{"type":"object","properties":{"items":{"description":"Ranked suggestions, generic foods before branded. Empty when nothing matches.","type":"array","items":{"$ref":"#/components/schemas/FoodSuggestion"}}},"required":["items"]},"SuggestFoodAlternativesRequest":{"type":"object","properties":{"diet_restrictions":{"type":"array","description":"Allergens/ingredients to avoid. Omit it (or send []) if none apply.","example":["gluten","peanuts"],"maxItems":17,"items":{"type":"string","enum":["gluten","lactose","yeast","tree_nuts","peanuts","dairy","eggs","sulfites","soy","wheat","shellfish","fish","mushrooms","sesame","msg","caffeine","fodmaps"]}},"diet_preferences":{"type":"array","description":"Dietary patterns to match. Omit it (or send []) if none apply.","example":["vegetarian"],"maxItems":9,"items":{"type":"string","enum":["vegetarian","vegan","keto","paleo","pescatarian","low_carbohydrate","high_protein","kosher","halal"]}}}},"ServingSummary":{"type":"object","properties":{"id":{"type":"string","pattern":"^\\d{1,16}$","example":"34237662","description":"Catalog serving id. Pass it back as serving_id when logging this food."},"quantity":{"type":"number","example":1,"minimum":0,"exclusiveMinimum":true,"maximum":10000,"description":"Positive amount of unit represented by this serving definition. In food analysis and food logs this is one catalog serving: consumed amount = food.quantity × food.serving.quantity (4 × 0.5 cup = 2 cups). Food alternatives instead report their recommended portion amount here."},"unit":{"type":"string","nullable":true,"example":"cup","minLength":1,"maxLength":64,"description":"Null only when the producer sent a serving with no unit."},"weight_grams":{"type":"number","nullable":true,"minimum":0,"example":60,"description":"Weight in grams of this serving definition. For food analysis and food logs this is one catalog serving, not the consumed portion: consumed grams = food.quantity × food.serving.weight_grams. Null when unknown."}},"required":["id","quantity","unit","weight_grams"]},"AlternativeFood":{"type":"object","properties":{"id":{"type":"string","nullable":true,"example":"70379835","description":"Catalog food id, or null when the producer matched none."},"name":{"type":"string","nullable":true,"example":"Oatmeal","description":"Null only when the producer sent a food with no name."},"brand_name":{"type":"string","nullable":true,"example":null,"description":"Null for generic (non-branded) foods."},"nutrients":{"$ref":"#/components/schemas/Nutrients"},"servings":{"description":"Servings to read the nutrition against. Empty when the recommender returned none — the key itself is always present.","type":"array","items":{"$ref":"#/components/schemas/ServingSummary"}}},"required":["id","name","brand_name","nutrients","servings"]},"FoodAlternatives":{"type":"object","properties":{"alternatives":{"description":"Healthier alternatives matching the restrictions and preferences. An empty array is a valid result, not an error.","type":"array","items":{"$ref":"#/components/schemas/AlternativeFood"}}},"required":["alternatives"]},"Restaurant":{"type":"object","properties":{"type":{"type":"string","enum":["restaurant"],"example":"restaurant","description":"Always `restaurant`: this endpoint answers places only. For dishes, use `GET /v1.2/menu-items`."},"id":{"type":"string","example":"94293e99-ee95-40bf-98bd-508402af9f9e"},"name":{"type":"string","nullable":true,"example":"McDonald's","description":"Null only when the source has no name for the place."},"is_chain":{"type":"boolean","nullable":true,"example":false,"description":"Whether this location belongs to a chain; null when unknown."},"distance_meters":{"type":"number","nullable":true,"example":124,"description":"Distance from (latitude, longitude) in meters; null when the source cannot place this result."},"city":{"type":"string","nullable":true,"example":"San Francisco"},"address1":{"type":"string","nullable":true,"example":"123 Main Street"},"address2":{"type":"string","nullable":true,"example":"Suite 100","description":"Second address line; null when there is none."}},"required":["type","id","name","is_chain","distance_meters","city","address1","address2"]},"RestaurantSearchResponse":{"type":"object","properties":{"items":{"description":"Matches ranked by proximity. Empty when nothing matches — not an error.","type":"array","items":{"$ref":"#/components/schemas/Restaurant"}}},"required":["items"]},"RestaurantMenuItem":{"type":"object","properties":{"id":{"type":"string","nullable":true,"example":"228990954","description":"Food id of the dish — the same id `GET /v1.2/foods/{food_id}` and `POST /v1.2/food-logs` take. Null only when the menu source carries no id for the row."},"name":{"type":"string","nullable":true,"example":"Cheeseburger","description":"Null only when the menu source has no name for the dish."},"nutrients":{"description":"Per-serving nutrition in the shared nutrient vocabulary. Keys are omitted when the menu source has no value.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"glycemic_index":{"type":"number","nullable":true,"description":"Glycemic index; null when the source has none."},"glycemic_load":{"type":"number","nullable":true,"description":"Glycemic load; null when the source has none."},"servings":{"description":"The serving the nutrition is given for. `GET /v1.2/foods/{food_id}` returns the complete list of servings.","type":"array","items":{"$ref":"#/components/schemas/FoodServing"}}},"required":["id","name","nutrients","glycemic_index","glycemic_load","servings"]},"RestaurantMenuItemsResponse":{"type":"object","properties":{"items":{"description":"The menu items, by name. Empty when the restaurant has no menu on record.","type":"array","items":{"$ref":"#/components/schemas/RestaurantMenuItem"}}},"required":["items"]},"MenuItem":{"type":"object","properties":{"type":{"type":"string","enum":["menu_item"],"example":"menu_item","description":"Always `menu_item`: this endpoint answers dishes only. For the places themselves, use `GET /v1.2/restaurants`."},"id":{"type":"string","example":"228990954"},"name":{"type":"string","nullable":true,"example":"burger","description":"Null only when the menu source has no name for the dish."},"restaurant_name":{"type":"string","nullable":true,"example":"morning due cafe","description":"Null when the menu source names no restaurant."},"is_chain":{"type":"boolean","nullable":true,"example":false,"description":"Whether the restaurant belongs to a chain; null when unknown."},"nutrients":{"description":"Per-dish nutrition in the shared nutrient vocabulary. Keys are omitted when the menu source has no value.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"glycemic_index":{"type":"number","nullable":true,"description":"Glycemic index; null when the source has none."},"glycemic_load":{"type":"number","nullable":true,"description":"Glycemic load; null when the source has none."},"image_url":{"type":"string","nullable":true,"description":"URL of a picture of the dish; null when the source has none."},"distance_meters":{"type":"number","nullable":true,"example":124,"description":"Distance from (latitude, longitude) in meters."},"servings":{"type":"array","items":{"$ref":"#/components/schemas/FoodServing"}}},"required":["type","id","name","restaurant_name","is_chain","nutrients","glycemic_index","glycemic_load","image_url","distance_meters","servings"]},"MenuItemSearchResponse":{"type":"object","properties":{"items":{"description":"Matching dishes ranked by proximity. Empty when nothing matches — not an error.","type":"array","items":{"$ref":"#/components/schemas/MenuItem"}}},"required":["items"]},"Reasoning":{"type":"object","properties":{"effort":{"type":"string","enum":["none","xhigh"],"example":"xhigh","description":"`none` uses the standard analyzer; `xhigh` uses the reasoning-based analyzer."}},"required":["effort"]},"ImageFoodAnalysisRequest":{"type":"object","properties":{"image":{"type":"string","description":"The food photo — the food itself or a packaged product's label — as an http(s) URL or a base64 data URI (data:image/jpeg;base64,…). Formats: JPG, PNG, WEBP, and non-animated GIF. Around 1,024 px on the shorter side is enough for reliable results (a recommendation, not a validation rule). A URL must be publicly fetchable server-side — hosts that block hotlinking or require a login cannot be read — and has no enforced size cap, though very large files slow the analysis and can time out. Base64 must be a complete data URI and fit the 5 MB request-body cap, so keep raw images under ~3.5 MB before encoding (base64 inflates by ~33%). Prefer the URL when the image is already hosted.","example":"https://january-food-image-dataset-public.s3.us-east-1.amazonaws.com/salmon-green-beans-potatoes.jpg"},"reasoning":{"default":{"effort":"none"},"description":"Controls analysis effort. Omit it or set `effort` to `none` to use the standard analyzer; `xhigh` uses the reasoning-based analyzer. Both modes return the same FoodAnalysisResult shape and use the same rate-limit bucket and credit cost.","allOf":[{"$ref":"#/components/schemas/Reasoning"}]}},"required":["image"]},"DetectedFood":{"type":"object","properties":{"name":{"type":"string","nullable":true,"example":"Oatmeal","description":"Null only when the producer sent a food with no name.","minLength":1,"maxLength":256},"brand_name":{"type":"string","nullable":true,"example":null,"description":"Null for generic (non-branded) foods."},"id":{"type":"string","pattern":"^\\d{1,16}$","example":"70379835","description":"Matched catalog food id. Pass it back as food_id when logging this food."},"quantity":{"type":"number","minimum":0,"exclusiveMinimum":true,"maximum":10000,"description":"Positive number of selected catalog servings consumed. Use it unchanged as food-log quantity. Display the consumed amount as food.quantity × food.serving.quantity, followed by food.serving.unit: 4 × 0.5 cup = 2 cups; 0.4 × 100 g = 40 g. Nutrients already describe this consumed portion; do not multiply them again.","example":0.4},"serving":{"description":"Selected catalog serving definition; its quantity is the size of one serving, not the amount eaten.","allOf":[{"$ref":"#/components/schemas/ServingSummary"}]},"nutrients":{"description":"Nutrition for the consumed portion, already scaled by quantity. Calculation precision is retained; round only for display and send the original values back for corrections.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]}},"required":["name","brand_name","id","quantity","serving","nutrients"]},"Detection":{"type":"object","properties":{"confidence":{"type":"string","enum":["high","medium","low",null],"nullable":true,"example":"high","description":"How sure the vision model is of this detection. Null on text analyses and on corrected results."},"food":{"$ref":"#/components/schemas/DetectedFood"}},"required":["confidence","food"]},"FoodAnalysisResult":{"type":"object","properties":{"meal_name":{"type":"string","nullable":true,"example":"Breakfast Bowl","maxLength":256,"pattern":"\\S","description":"A name for the meal as a whole. Null on text analyses — the caller already has the words. Corrections preserve a null meal name."},"total_nutrients":{"description":"Aggregated nutrition across all detections. Always present; individual keys are omitted when no producer had a value.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"detections":{"maxItems":100,"description":"Detected foods. Always present — an empty array means nothing was recognized.","type":"array","items":{"$ref":"#/components/schemas/Detection"}}},"required":["meal_name","total_nutrients","detections"]},"TextFoodAnalysisRequest":{"type":"object","properties":{"text":{"type":"string","description":"Natural-language description of what was eaten; parsed into detected foods with quantities.","example":"a bowl of oatmeal with honey and a banana","maxLength":512}},"required":["text"]},"CorrectionServing":{"type":"object","properties":{"id":{"type":"string","pattern":"^\\d{1,16}$","example":"34237662","description":"Catalog serving id. Pass it back as serving_id when logging this food."},"quantity":{"type":"number","example":1,"minimum":0,"exclusiveMinimum":true,"maximum":10000,"description":"Positive amount of unit represented by this serving definition. In food analysis and food logs this is one catalog serving: consumed amount = food.quantity × food.serving.quantity (4 × 0.5 cup = 2 cups). Food alternatives instead report their recommended portion amount here."},"unit":{"type":"string","nullable":true,"example":"cup","minLength":1,"maxLength":64,"description":"Null only when the producer sent a serving with no unit."},"weight_grams":{"type":"number","nullable":true,"minimum":0,"example":60,"description":"Weight in grams of this serving definition. For food analysis and food logs this is one catalog serving, not the consumed portion: consumed grams = food.quantity × food.serving.weight_grams. Null when unknown."}},"required":["id","quantity","unit"]},"CorrectionFood":{"type":"object","properties":{"name":{"type":"string","nullable":true,"example":"Oatmeal","description":"Null only when the producer sent a food with no name.","minLength":1,"maxLength":256},"brand_name":{"type":"string","nullable":true,"example":null,"description":"Null for generic (non-branded) foods."},"id":{"type":"string","pattern":"^\\d{1,16}$","example":"70379835","description":"Matched catalog food id. Pass it back as food_id when logging this food."},"quantity":{"type":"number","minimum":0,"exclusiveMinimum":true,"maximum":10000,"description":"Positive number of selected catalog servings consumed. Use it unchanged as food-log quantity. Display the consumed amount as food.quantity × food.serving.quantity, followed by food.serving.unit: 4 × 0.5 cup = 2 cups; 0.4 × 100 g = 40 g. Nutrients already describe this consumed portion; do not multiply them again.","example":0.4},"nutrients":{"description":"Nutrition for the consumed portion, already scaled by quantity. Calculation precision is retained; round only for display and send the original values back for corrections.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"serving":{"$ref":"#/components/schemas/CorrectionServing"}},"required":["name","brand_name","id","quantity","nutrients","serving"]},"CorrectionDetection":{"type":"object","properties":{"confidence":{"type":"string","enum":["high","medium","low",null],"nullable":true,"example":"high","description":"How sure the vision model is of this detection. Null on text analyses and on corrected results."},"food":{"$ref":"#/components/schemas/CorrectionFood"}},"required":["confidence","food"]},"CorrectionAnalysis":{"type":"object","properties":{"meal_name":{"type":"string","nullable":true,"example":"Breakfast Bowl","maxLength":256,"pattern":"\\S","description":"A name for the meal as a whole. Null on text analyses — the caller already has the words. Corrections preserve a null meal name."},"total_nutrients":{"description":"Optional original totals. Ignored on input; corrections recalculate totals from the corrected foods.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"detections":{"maxItems":100,"type":"array","items":{"$ref":"#/components/schemas/CorrectionDetection"}}},"required":["detections"]},"CorrectFoodAnalysisRequest":{"type":"object","properties":{"analysis":{"description":"The result from `POST /v1.2/food-analysis/image` or `/text`, sent back exactly as it was returned. Omitted zero-value nutrient keys are filled in automatically. `meal_name` may be omitted (treated as null); `total_nutrients` may be omitted because it is recalculated rather than trusted. Older results may omit serving `weight_grams`; it is treated as unknown. The forwarded nutrients (calories, protein, carbohydrates, net_carbohydrates, total_fat, saturated_fat, fiber, total_sugars, added_sugars, sodium) must each have a `value` from 0 to 1000000 and a `unit` of at most 16 characters; every analysis result already satisfies this. A detection the analysis returned incomplete — with a null `name` or serving `unit` — is left out of the corrected result, because the correction model needs what it lacks; describe that food in `instruction` if it belongs in the meal.","allOf":[{"$ref":"#/components/schemas/CorrectionAnalysis"}]},"instruction":{"type":"string","description":"Plain-English description of what to correct.","example":"The oatmeal was steel-cut, about 2 cups, and there was no honey.","maxLength":1000}},"required":["analysis","instruction"]},"FoodSelection":{"type":"object","properties":{"food_id":{"type":"string","example":"101963552","pattern":"^\\d{1,16}$","description":"Food id from a search or food-analysis result."},"serving_id":{"type":"string","example":"68051535","pattern":"^\\d{1,16}$","description":"One of the food's serving ids."},"quantity":{"type":"number","example":1.4,"description":"How many of that serving were consumed.","minimum":0,"exclusiveMinimum":true,"maximum":10000}},"required":["food_id","serving_id","quantity"]},"CreateFoodLogRequest":{"type":"object","properties":{"foods":{"minItems":1,"maxItems":100,"type":"array","items":{"$ref":"#/components/schemas/FoodSelection"}},"eaten_at":{"type":"string","example":"2024-09-13T11:34:56Z","format":"date-time","description":"When the meal was eaten — any ISO-8601 offset; stored and returned in UTC with milliseconds. Omitted = now."},"name":{"type":"string","example":"Breakfast","maxLength":256}},"required":["foods"]},"LoggedFood":{"type":"object","properties":{"food_id":{"type":"string","nullable":true,"example":"101963552","description":"Food id from a search or food-analysis result. Null only when the upstream sent a food with no id."},"name":{"type":"string","nullable":true,"example":"Greek Yogurt, Plain, Whole Milk","description":"Null only when the upstream sent none."},"brand_name":{"type":"string","nullable":true,"example":null,"description":"Null for generic (non-branded) foods."},"image_url":{"type":"string","nullable":true,"example":null},"glycemic_index":{"type":"number","nullable":true,"example":11.3},"glycemic_load":{"type":"number","nullable":true,"example":1.4},"nutrients":{"description":"Scaled to the consumed quantity.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]},"quantity":{"type":"number","nullable":true,"example":1.4,"description":"Number of selected servings consumed. Consumed amount = food.quantity × food.serving.quantity, in food.serving.unit (4 × 0.5 cup = 2 cups). Nutrients are already scaled to this portion. Null when unavailable."},"serving":{"description":"The serving definition the quantity refers to.","allOf":[{"$ref":"#/components/schemas/ServingSummary"}]}},"required":["food_id","name","brand_name","image_url","glycemic_index","glycemic_load","nutrients","quantity","serving"]},"FoodLog":{"type":"object","properties":{"id":{"type":"string","nullable":true,"example":"78129823-8ba2-4183-b13b-71f0e963c606","description":"Save this id to update or delete the log. Null only when the upstream sent a log with no id — such a log cannot be addressed."},"foods":{"type":"array","items":{"$ref":"#/components/schemas/LoggedFood"}},"eaten_at":{"type":"string","example":"2024-09-13T11:34:56.000Z","format":"date-time","description":"When the meal was eaten. UTC, with milliseconds."},"name":{"type":"string","nullable":true,"example":"Breakfast","description":"Null when no name was given."}},"required":["id","foods","eaten_at","name"]},"FoodLogList":{"type":"object","properties":{"items":{"description":"Logs in the range, ordered by timestamp. An empty list is a valid result.","type":"array","items":{"$ref":"#/components/schemas/FoodLog"}}},"required":["items"]},"FoodLogSummaryBucket":{"type":"object","properties":{"start_date":{"type":"string","example":"2024-09-01","format":"date","description":"First local calendar date this bucket covers. Clipped to the requested range, so the first week bucket may be partial."},"end_date":{"type":"string","example":"2024-09-07","format":"date","description":"Last local calendar date this bucket covers, inclusive. Equal to start_date when grouping by day."},"logs_count":{"type":"integer","example":5,"description":"How many logs fall in this bucket."},"days_with_logs":{"type":"integer","example":3,"description":"How many distinct local calendar dates in this bucket carry at least one log."},"nutrients":{"description":"Nutrients summed over this bucket. A key is absent when no value was available; `{}` means nothing could be totalled — read `logs_count` to tell an empty bucket from one whose logs were unresolvable.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]}},"required":["start_date","end_date","logs_count","days_with_logs","nutrients"]},"FoodLogSummaryTotals":{"type":"object","properties":{"logs_count":{"type":"integer","example":41,"description":"Logs in the whole range."},"days_with_logs":{"type":"integer","example":28,"description":"Distinct local calendar dates in the range that carry at least one log."},"nutrients":{"description":"Nutrients summed over the whole range, with the same sparseness as a bucket’s.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]}},"required":["logs_count","days_with_logs","nutrients"]},"FoodLogSummaryAverage":{"type":"object","properties":{"nutrients":{"description":"Totals divided by `totals.days_with_logs` — an average over days that were logged, not over days in the range. `{}` when none were.","allOf":[{"$ref":"#/components/schemas/Nutrients"}]}},"required":["nutrients"]},"FoodLogSummary":{"type":"object","properties":{"group_by":{"type":"string","enum":["day","week"],"example":"week","description":"The bucket size used, echoing the request."},"week_start":{"type":"string","nullable":true,"enum":["monday","sunday",null],"example":"monday","description":"The weekday week buckets begin on. Always present; `null` when `group_by=day`, where it does not apply."},"timezone":{"type":"string","example":"America/Los_Angeles","description":"The IANA timezone the buckets were cut in — the canonical spelling of what was requested."},"start_date":{"type":"string","example":"2024-09-01","format":"date","description":"First local calendar date of the summarized range, echoing the request."},"end_date":{"type":"string","example":"2024-09-30","format":"date","description":"Last local calendar date of the summarized range, inclusive."},"buckets":{"description":"The buckets tiling the range, in chronological order and covering it end to end — a day or week with no logs is returned with zero counts rather than skipped.","type":"array","items":{"$ref":"#/components/schemas/FoodLogSummaryBucket"}},"totals":{"$ref":"#/components/schemas/FoodLogSummaryTotals"},"average_per_logged_day":{"$ref":"#/components/schemas/FoodLogSummaryAverage"}},"required":["group_by","week_start","timezone","start_date","end_date","buckets","totals","average_per_logged_day"]},"UpdateFoodLogRequest":{"type":"object","properties":{"foods":{"minItems":1,"maxItems":100,"type":"array","items":{"$ref":"#/components/schemas/FoodSelection"}},"eaten_at":{"type":"string","example":"2024-09-13T11:34:56Z","format":"date-time","description":"When the meal was eaten — any ISO-8601 offset; stored and returned in UTC with milliseconds. Omit to leave it unchanged."},"name":{"type":"string","example":"Breakfast","maxLength":256}},"minProperties":1,"additionalProperties":false},"WaterAmount":{"type":"object","properties":{"value":{"type":"number","example":8,"minimum":1,"maximum":24000,"description":"Accepted range depends on unit: 1–811.5 fl_oz, 30–24000 ml."},"unit":{"type":"string","enum":["fl_oz","ml"],"example":"fl_oz"}},"required":["value","unit"]},"CreateWaterLogRequest":{"type":"object","properties":{"amount":{"description":"How much water. An end user's total is capped at 24 L (about 811 fl oz) per day.","allOf":[{"$ref":"#/components/schemas/WaterAmount"}]},"consumed_at":{"type":"string","example":"2026-09-10T07:30:00-07:00","format":"date-time","description":"When the water was consumed — any ISO-8601 offset; stored and returned in UTC with milliseconds. Omitted = now. Its day is the one the daily cap counts it against."}},"required":["amount"]},"WaterLog":{"type":"object","properties":{"id":{"type":"string","example":"78129823-8ba2-4183-b13b-71f0e963c606","description":"Save this id to delete the log."},"amount":{"description":"The amount as logged, in the unit it was sent in.","allOf":[{"$ref":"#/components/schemas/WaterAmount"}]},"consumed_at":{"type":"string","example":"2026-09-10T14:30:00.000Z","format":"date-time","description":"When the water was consumed. UTC, with milliseconds."}},"required":["id","amount","consumed_at"]},"Volume":{"type":"object","properties":{"value":{"type":"number","example":64,"minimum":0,"description":"Rounded to one decimal place."},"unit":{"type":"string","enum":["fl_oz","ml"],"example":"fl_oz"}},"required":["value","unit"]},"DailyWaterTotal":{"type":"object","properties":{"date":{"type":"string","example":"2026-09-10","format":"date","description":"Local calendar date in the request’s `timezone`."},"total":{"description":"Everything logged on this local day, in the unit the request asked for.","allOf":[{"$ref":"#/components/schemas/Volume"}]}},"required":["date","total"]},"WaterLogList":{"type":"object","properties":{"items":{"description":"One entry per local day with water logged, oldest first. Days with nothing logged are absent. An empty list is a valid result.","type":"array","items":{"$ref":"#/components/schemas/DailyWaterTotal"}}},"required":["items"]},"Weight":{"type":"object","properties":{"value":{"type":"number","example":150,"minimum":1,"maximum":1500,"description":"Accepted range depends on unit: 2–1500 lb, 1–700 kg."},"unit":{"type":"string","enum":["lb","kg"],"example":"lb"}},"required":["value","unit"]},"CreateWeightLogRequest":{"type":"object","properties":{"weight":{"description":"The measured weight. `value` must be 10–1000 for `lb`, or 4.5–453.6 for `kg`; it is stored and returned in the unit sent.","allOf":[{"$ref":"#/components/schemas/Weight"}]},"measured_at":{"type":"string","example":"2026-09-10T07:30:00-07:00","format":"date-time","description":"When the weight was measured — any ISO-8601 offset; stored and returned in UTC with milliseconds. Omitted = now."}},"required":["weight"]},"WeightLog":{"type":"object","properties":{"weight":{"description":"The weight as logged, in the unit it was sent in.","allOf":[{"$ref":"#/components/schemas/Weight"}]},"measured_at":{"type":"string","example":"2026-09-10T14:30:00.000Z","format":"date-time","description":"When the weight was measured. UTC, with milliseconds."}},"required":["weight","measured_at"]},"DailyWeight":{"type":"object","properties":{"date":{"type":"string","example":"2026-09-10","format":"date","description":"Local calendar date in the request’s `timezone`."},"weight":{"description":"The weight with the latest `measured_at` on this day — later measurements replace earlier ones — in the unit it was logged in.","allOf":[{"$ref":"#/components/schemas/Weight"}]}},"required":["date","weight"]},"WeightLogList":{"type":"object","properties":{"items":{"description":"One entry per day that has a weight, oldest first. Days with no weight are absent. An empty list is a valid result.","type":"array","items":{"$ref":"#/components/schemas/DailyWeight"}}},"required":["items"]},"Height":{"type":"object","properties":{"value":{"type":"number","example":66,"minimum":20,"maximum":275,"description":"Accepted range depends on unit: 20–108 in, 50–275 cm."},"unit":{"type":"string","enum":["in","cm"],"example":"in"}},"required":["value","unit"]},"GlucoseUserProfile":{"type":"object","properties":{"age":{"type":"integer","example":42,"minimum":1,"maximum":120},"sex":{"type":"string","enum":["male","female"],"example":"female","description":"Biological sex, as consumed by the prediction model."},"height":{"$ref":"#/components/schemas/Height"},"weight":{"$ref":"#/components/schemas/Weight"},"activity_level":{"type":"string","enum":["sedentary","lightly_active","moderately_active","very_active"],"example":"moderately_active"},"health_conditions":{"type":"array","maxItems":2,"example":["prediabetes"],"description":"Omit it (or send []) if none apply. Type 1 diabetes is not supported by the prediction model.","items":{"type":"string","enum":["type_2_diabetes","prediabetes"]}}},"required":["age","sex","height","weight"]},"CgmReading":{"type":"object","properties":{"timestamp":{"type":"string","example":"2024-09-10T08:15:00Z","format":"date-time","description":"When the reading was taken. Must carry a timezone designator."},"value":{"type":"number","example":104,"description":"mg/dL. At most one reading per 15-minute window.","minimum":10,"maximum":600}},"required":["timestamp","value"]},"ConsumedFood":{"type":"object","properties":{"timestamp":{"type":"string","example":"2024-09-10T08:00:00Z","format":"date-time","description":"When the food was eaten. Must carry a timezone designator."},"food_id":{"type":"string","example":"101963552","pattern":"^\\d{1,16}$","description":"Food id from a search or food-analysis result."},"serving_id":{"type":"string","example":"68051535","pattern":"^\\d{1,16}$","description":"One of the food's serving ids."},"quantity":{"type":"number","example":1.4,"description":"How many of that serving were consumed.","minimum":0,"exclusiveMinimum":true,"maximum":10000}},"required":["timestamp","food_id","serving_id","quantity"]},"GlucosePredictionRequest":{"type":"object","properties":{"user_profile":{"$ref":"#/components/schemas/GlucoseUserProfile"},"timezone":{"type":"string","example":"America/New_York","description":"The IANA timezone the end user is in. The prediction depends on the meal's local time of day."},"foods":{"description":"The meal to predict the glucose response for.","minItems":1,"maxItems":100,"type":"array","items":{"$ref":"#/components/schemas/FoodSelection"}},"start_time":{"type":"string","example":"2024-09-13T11:34:56Z","format":"date-time","description":"When the meal is (or will be) eaten. Must carry a timezone designator."},"cgm_data":{"description":"Optional CGM history, to personalize the prediction to this end user. Send it together with `consumed_foods` covering the same period — **at least 5 complete days of paired history**, which is what the model needs to train on them. Fewer is refused with `invalid_request`. Omit both fields for a standard prediction, which needs no sensor and no history.","minItems":1,"maxItems":5000,"type":"array","items":{"$ref":"#/components/schemas/CgmReading"}},"consumed_foods":{"description":"The meals eaten during the CGM history; requires cgm_data.","minItems":1,"maxItems":5000,"type":"array","items":{"$ref":"#/components/schemas/ConsumedFood"}}},"required":["user_profile","timezone","foods","start_time"]},"GlucosePredictionPoint":{"type":"object","properties":{"minutes":{"type":"integer","example":30,"description":"Minutes after start_time."},"value":{"type":"number","example":140,"description":"Predicted glucose, mg/dL."}},"required":["minutes","value"]},"GlucoseChart":{"type":"object","properties":{"min":{"type":"number","nullable":true,"example":70,"description":"Suggested Y-axis lower bound (mg/dL). A fixed target-range bound, not the minimum of the curve. Null when the model sent none."},"max":{"type":"number","nullable":true,"example":140,"description":"Suggested Y-axis upper bound (mg/dL): 180 with Type 2 diabetes in health_conditions, otherwise 140. Not the maximum of the curve. Null when the model sent none."}},"required":["min","max"]},"GlucosePrediction":{"type":"object","properties":{"points":{"description":"The points of the predicted glucose curve, at 15-minute intervals starting at start_time.","type":"array","items":{"$ref":"#/components/schemas/GlucosePredictionPoint"}},"impact_score":{"type":"string","enum":["low","medium","high",null],"nullable":true,"example":"low","description":"The meal's overall glucose impact. Null when the model returned no grade, or one outside this list."},"chart":{"$ref":"#/components/schemas/GlucoseChart"}},"required":["points","impact_score","chart"]}},"parameters":{"JanuaryEndUserId":{"name":"January-End-User-ID","in":"header","description":"Your stable ID for the end user whose logs this request reads or writes. Opaque to January — use the same ID your system already uses for them.\n\n| Credential | Header | Result |\n| --- | --- | --- |\n| API key (`sk-…`) | absent | `400 end_user_id_required` |\n| API key (`sk-…`) | present | the request acts on that end user |\n| Client token (`ct-…`) | absent | filled in from the token |\n| Client token (`ct-…`) | the end user the token is bound to | accepted |\n| Client token (`ct-…`) | any other end user | `403 end_user_id_mismatch` |","required":false,"schema":{"type":"string"}}}}}