For the complete documentation index, see llms.txt. This page is also available as Markdown.

API v1.1 (legacy)

Introduction

Welcome to January AI's Nutritional Intelligence APIs documentation. These APIs put the food and metabolic-health intelligence behind January's own products into yours: a verified database of 54+ million foods, an AI vision model that reads meals from photos, and glucose prediction that works without a sensor — all through one REST API.

With a single integration, your product can:

  • Search foods by name, barcode, or natural language ("milkshake with banana") across branded foods, groceries, and restaurant menus.

  • Scan meals from a photo to detect the foods on the plate and their nutrients.

  • Find restaurant dishes on local and national menus, by name and location.

  • Log food — create, retrieve, and manage each user's food diary.

  • Predict glucose response before the first bite, from a user's demographics, health profile, and activity — no CGM or sensors required.

Every endpoint speaks JSON over HTTPS. Code samples in shell, Python, and JavaScript run down the dark panel on the right — pick your language at the top of that panel.

New here? The Quickstart below takes you from API key to your first photo scan in about a minute, and Authentication explains the two identifying headers.

Endpoints at a glance

Endpoint

Method and path

x-partner-user-id

GET /v1.1/search/foods

Optional

GET /v1.1/search/foods/nlp

Optional

GET /v1.1/search/restaurants

Not used

GET /v1.1/search/restaurants/menu

Not used

POST /v1.1/vision/foods

Required

POST /v1.1/vision/foods/fix-ai

Required

POST /v1.1/food-alternatives/{foodId}

Not used

POST /v1.1/logs/foods

Required

GET /v1.1/logs/foods

Required

DELETE /v1.1/logs/foods/{logId}

Required

POST /v1.1/cgm/glucose-predict

Not used — requires x-partner-timezone instead

Every request also carries the Authorization: Bearer YOUR_API_KEY header — see Authentication.

Quickstart

Scan a photo of a meal. Replace YOUR_API_KEY with your key and run it:

A successful response looks like this — trimmed here; real responses include every detection and all ten nutrients per food:

The photo-scan request on the right is the fastest way to confirm your credentials work end to end: it sends one meal photo to Photo Scan and returns the foods January detects in it. Don't have an API key yet? Email us to request one.

The Python sample needs the requests package (pip install requests); the JavaScript sample needs axios (npm install axios) and runs on Node 18 or later. The error branch in each sample prints the HTTP status and body on failure — keep it, since that's where January's error messages (wrong key, malformed request) show up.

Two of this request's headers deserve a close look now:

  • Authorization: Bearer YOUR_API_KEY — identifies your organization.

  • x-partner-user-id: test-7f3c1a — identifies the end user the scan is performed on behalf of.

Requests carry other headers as well — Content-Type: application/json here — but these two do the identifying. Every call needs the API key; each endpoint's Headers table says whether it needs the user ID too. See Authentication for what belongs in each.

Test values

While you're experimenting, use test- followed by a few random characters you make up — for example x-partner-user-id: test-7f3c1a. Pick one and keep reusing it, so your trial data stays in one place and stays separate from your colleagues'.

Note what this value is not: it isn't your name, your email, or anything that identifies a person. That's the same rule your production IDs follow, so the habit you build here is the one you want to ship.

There is no separate registration step for a test value: x-partner-user-id is the user's identity in January, and any stable ID you choose becomes that identity.

Authentication

Every user-scoped request carries both headers:

A request to the January AI API can identify two things:

Identity
Header
Changes per request?

Your organization

Authorization: Bearer YOUR_API_KEY

No — the same key on every call

The end user you are acting on behalf of

x-partner-user-id: YOUR_PARTNER_USER_ID

Yes — one value per end user

Your API key says who is calling. x-partner-user-id says whose data this call is about. Endpoints that read or write a person's data need both.

Your API key

To get an API key, email us with your request.

Include the key on every request, in an Authorization header:

Authorization: Bearer YOUR_API_KEY

Replace YOUR_API_KEY in every code sample with your actual key.

A missing or invalid API key returns 401 Unauthorized. 401 means the key and nothing else. If a request that should work still gets 401, check the header's exact form: the scheme is Bearer followed by a single space, and the key must be sent exactly as issued — the comparison is case-sensitive.

The end user: x-partner-user-id

Endpoints that act on a person — photo scans and food logs — are performed on behalf of one of your end users. The x-partner-user-id header names that user.

x-partner-user-id: YOUR_PARTNER_USER_ID

Send an identifier that is:

  • Stable — the same end user must get the same value on every call, forever. This is the key January stores their data under.

  • Unique per user within your organization.

  • Opaque — never PII. Do not send email addresses, phone numbers, names, dates of birth, or medical record numbers. Our partners are health companies; use your internal user ID, or an opaque random ID you map to the user on your side.

Any stable ID you choose works — it becomes that user's identity in January. There is no separate registration call to make first.

x-partner-user-id is your identifier for the user, carried through to January — not an ID you have to look up from us.

Why the header exists

The value scopes everything January stores about that person:

Send the wrong value and you read and write the wrong person's data. Omit it on an endpoint that requires it and the request fails with 400 Bad Request.

Each endpoint's Headers table below states whether x-partner-user-id is required for that endpoint.

Lifestyle Intelligence APIs

Look up any food a user might eat — by name, barcode, plain-English description, or photo — and search restaurant menus by dish and location, and suggest alternatives to any food.

Food Search (By Name or Barcode)

The above command returns JSON structured like this:

Search for food items by name or barcode from January’s verified database of 54+ million foods, including branded, grocery, and restaurant items.

HTTP Request

GET https://partners.january.ai/v1.1/search/foods

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

x-partner-user-id

No

Identifies the end user this request acts on behalf of. See Authentication.

Query Parameters

Parameter
Required
Description

query

Optional

The search term for food items

category

Optional

The category of food items to filter results (branded, general, or recipe — case-sensitive)

upc

Optional

The UPC representing the barcode of a food item

limit

Optional

Integer 1–400. Default is 10. Name searches (general/branded) never return more than 40 items

offset

Optional

Integer, 0 or greater. Default is 0. For name searches (general/branded) only the first 40 matches are reachable, so an offset of 40 or more returns no items

Send at most one of query and upc — sending both returns a 400. category defaults to general. Out-of-range or non-integer limit/offset values return a 400 — nothing is clamped.

Response Structure

Field
Type
Description

totalCount

number

Number of matching food items. Capped at 40 for name searches (general/branded)

items

array

Array of food item objects

Food Item Fields

Field
Type
Description

id

number

Food item ID

name

string

Food name

brand_name

string or null

Brand name, null or an empty string for generic foods

protein

number or null

Protein (g)

energy

number or null

Calories (kcal)

carbs

number or null

Total carbohydrates (g)

fat

number or null

Total fat (g)

fat_total_saturated

number or null

Saturated fat (g)

net_carbs

number or null

Net carbs: carbohydrate - fiber (g)

sodium

number or null

Sodium (mg)

sugars

number or null

Total sugars (g)

added_sugars

number or null

Added sugars (g)

gi

number or null

Glycemic index

gl

number or null

Glycemic load

fiber

number or null

Fiber (g)

potassium

number or null

Potassium (mg). Not returned for category=recipe

cholesterol

number or null

Cholesterol (mg). Not returned for category=recipe

photo_url

string or null

Food photo URL. Null or absent when no image is available

servings

array

Array of serving options

Serving Fields

Field
Type
Description

id

number

Serving ID

quantity

number

Serving quantity

unit

string

Serving unit description

scaling_factor

number

Scaling factor

weight_grams

number or null

Weight of the serving in grams

is_primary

boolean

Whether the returned serving is the food's primary serving

The above command returns JSON structured like this — trimmed to one detection; totalNutrients sums every detected food, including ones not shown:

Search for multiple foods at once by describing them in plain English — via voice or text.

HTTP Request

GET https://partners.january.ai/v1.1/search/foods/nlp

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

x-partner-user-id

No

Identifies the end user this request acts on behalf of. See Authentication.

Query Parameters

Parameter
Required
Description

query

Yes

The search term for food items in natural language. Required for meaningful results, but not enforced by the service — omitting it fails silently (no validation error) instead of returning a 400

Response Structure

Field
Type
Description

totalNutrients

object

Sum of scaled nutrients across all detected foods

detections

array

One entry per detected food item

Detection Object

Field
Type
Description

food.name

string

Food name

food.id

number

Food item ID

food.brandName

string

Brand name, or empty string for generic foods

food.nutrients

object

Scaled nutrients for this detection (see nutrient keys below)

food.servings

array

Matched servings for the detection

Nutrient Keys

All nutrient values are objects with value (number) and unit (string). Fields are omitted when the source data is not available, and also when the value is exactly zero.

Key
Unit
Description

calories

calories

Energy (kcal)

protein

g

Protein

carbohydrates

g

Total carbohydrates

netCarbohydrates

g

Net carbs (carbohydrate - fiber)

totalFat

g

Total fat

saturatedFat

g

Saturated fat

fiber

g

Fiber

totalSugars

g

Total sugars

addedSugars

g

Added sugars

sodium

mg

Sodium

Serving Fields

Field
Type
Description

id

number

Serving ID

quantity

number

Serving quantity

unit

string

Serving unit description

selected_quantity

number

Detected quantity for the serving. Optional — omitted when the serving has no gram weight

The above command returns JSON structured like this:

Search for restaurants — local spots and chains — by name, ranked by distance when you provide the user's coordinates.

When you send coordinates, a generic dish name like pizza — or any query that matches no restaurant — returns menu items instead. Those items have "type": "menu_item" and the shape described under Restaurant Menu Search, so check type on each item before reading it. Without coordinates, results are always restaurants.

HTTP Request

GET https://partners.january.ai/v1.1/search/restaurants

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Query Parameters

Parameter
Required
Description

query

Yes

Restaurant name to search for. Pass an empty value to list the nearest restaurants.

lat

Optional

Latitude for proximity ranking, −90 to 90. Must be sent together with lon

lon

Optional

Longitude for proximity ranking, −180 to 180. Must be sent together with lat

distance

Optional

Search radius in meters. Default is 16093 (~10 miles), maximum 17000 — larger values return a 400 rather than being clamped

limit

Optional

Maximum number of results to return. Default is 50, maximum 100 — larger values return a 400

Response Structure

The response includes:

  • items: Array of restaurants — or of menu items, when coordinates are sent and the query matches no restaurant. Restaurant entries always carry type, id, name, and is_chain; distance, city, address1, and address2 are added only when nearby locations are found for the coordinates you sent

The above command returns JSON structured like this:

Search for dishes across the menus of restaurants near a location — every "burger" within ten miles, with nutrition data for each match.

HTTP Request

GET https://partners.january.ai/v1.1/search/restaurants/menu

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Query Parameters

Parameter
Required
Description

query

Yes

Dish or restaurant name to search for

lat

Yes

Latitude to search around, −90 to 90

lon

Yes

Longitude to search around, −180 to 180

distance

Optional

Search radius in meters. Default is 16093 (~10 miles), maximum 17000 — larger values return a 400 rather than being clamped

limit

Optional

Maximum number of results to return. Default is 50, maximum 100 — larger values return a 400

Response Structure

The response includes:

  • items: Array of menu items with id, name, restaurant name, photo URL, nutrients and primary serving information

Photo Scan

The above command returns JSON structured like this — trimmed here; real responses include every detection and all ten nutrients per food:

Scan a photo of a meal to identify and retrieve nutritional data for the foods detected.

HTTP Request

POST https://partners.january.ai/v1.1/vision/foods

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Content-Type

Yes

Must be application/json

x-partner-user-id

Yes

Identifies the end user this request acts on behalf of. See Authentication.

Request Body

Parameter
Required
Description

photoUrl

No

The URL of the image to be analyzed

photoBase64

No

Base64 encoded image data (if not using photoUrl). Raw base64 is treated as JPEG; for other formats, send a data URL (data:image/png;base64,...)

Either photoUrl or photoBase64 must be provided. If both are sent, photoUrl is used and photoBase64 is ignored.

Photo Requirements

  • JPG, JPEG, PNG, WEBP, or non-animated GIF

  • No location or personal metadata

  • Well lit and in focus

Response Structure

The response includes:

  • mealName: Name of the meal identified in the image

  • totalNutrients: Summary of total nutritional content

  • detections: Array of detected food items with confidence scores

Edit Photo Scan Results

The above command returns the corrected scan — the detection is now steel-cut oats, and the totals match it:

Lets a user correct a photo scan in natural language, by voice or text. For example, after scanning a chicken and rice bowl and spotting a couple of inaccuracies, a user might say:

"It was actually 1 cup of chicken. It was also white rice, not brown rice."

The API parses this input and updates the scan results accordingly.

HTTP Request

POST https://partners.january.ai/v1.1/vision/foods/fix-ai

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Content-Type

Yes

Must be application/json

x-partner-user-id

Yes

Identifies the end user this request acts on behalf of. See Authentication.

Request Body

Parameter
Required
Description

mealName

Yes

Name of the meal to edit

detections

Yes

Array of detected food items to edit

detections[].food.nutrients

Yes

Must contain all ten macros: calories, protein, carbohydrates, netCarbohydrates, totalFat, saturatedFat, fiber, totalSugars, addedSugars, and sodium

detections[].food.servings

Yes

Must be present and non-empty

userInput

Yes

Text representation of changes to make

Send back each food object exactly as Photo Scan returned it and describe the changes in userInput — a partial nutrients object fails the request. That failure is currently a generic 500 that doesn't name the missing key, so if you assemble the object yourself, check all ten keys client-side first.

Response Structure

The response includes:

  • mealName: Modified name of the meal

  • totalNutrients: Summary of total nutritional content

  • detections: Array of modified food items with confidence scores

Food Alternatives

The above command returns JSON structured like this:

Suggest alternatives for a food the user is about to eat or log. Given a food ID and the user's dietary restrictions and preferences, returns similar foods, best match first, each with a recommended serving size calibrated using serving weight and available glycemic-load data — for example, brown rice and wild rice as swaps for white rice.

The food ID comes from any discovery endpoint: Food Search, NLP Food Search, and Photo Scan all return the same food IDs.

HTTP Request

POST https://partners.january.ai/v1.1/food-alternatives/{foodId}

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Content-Type

Yes

Must be application/json

URL Parameters

Parameter
Description

foodId

The ID of the food to find alternatives for. An unknown ID returns 404 Not Found

Request Body

Parameter
Required
Description

dietRestrictions

Yes

Array of the user's allergies and intolerances, from Dietary Restrictions. Send ["None"] if the user has no restrictions.

dietPreferences

Yes

Array of the user's diet styles, from Dietary Preferences. Send ["None"] if the user has no preferences.

Dietary Restrictions

dietRestrictions accepts: 'None', 'Gluten', 'Lactose', 'Yeast', 'Tree nuts', 'Peanuts', 'Dairy', 'Eggs', 'Sulfites', 'Soy', 'Wheat', 'Shellfish', 'Fish', 'Mushrooms', 'Sesame', 'Monosodium glutamate (MSG)', 'Caffeine', 'FODMAPs'.

Dietary Preferences

dietPreferences accepts: 'None', 'Vegetarian', 'Vegan', 'Keto', 'Paleo', 'Pescatarian', 'Low carbohydrate', 'High protein', 'Kosher'.

These values are case-sensitive strings and must match exactly as written — 'vegan' wouldn't work; send 'Vegan'.

Response Structure

Field
Type
Description

alternatives

array

Suggested alternatives for the requested food, best match first. Each entry wraps a food object.

An empty alternatives array is a valid response, not an error: it means January found no suitable alternative for that food under the given restrictions and preferences.

Alternative Food Fields

Field
Type
Description

name

string

Food name

id

number

Food item ID, accepted anywhere a food ID is — for example, Glucose Prediction

brandName

string

Brand name, empty string for generic foods

nutrients

object

calories, protein, carbohydrates, totalFat, and fiber, each as { "value", "unit" }

servings

array

A single recommended serving with id, quantity, and unit — the quantity is calibrated by January, not one of the food's standard serving options

Food Logging APIs

Write to and read from each user's food diary.

Create Food Logs

The above command returns 200 OK with the created log — each food hydrated with its name, scaled nutrients, and serving details:

Log one or more foods to a user's food diary at a given time. Food and serving IDs come from the search and photo scan endpoints.

HTTP Request

POST https://partners.january.ai/v1.1/logs/foods

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Content-Type

Yes

Must be application/json

x-partner-user-id

Yes

Identifies the end user this request acts on behalf of. See Authentication.

Request Body

Parameter
Required
Description

foods

Yes

Array of food items to be logged — at least one entry

foods[].id

Yes

Unique identifier of the food item, as a JSON number (a quoted "123" returns a 400)

foods[].serving

Yes

Serving details of the food item

foods[].serving.id

Yes

Serving size identifier, as a JSON number

foods[].serving.quantity

Yes

Amount of the serving consumed

timestampUtc

Optional

UTC timestamp when food was consumed, ending in a literal Z — e.g. 2024-09-13T11:34:56Z (seconds and fractional seconds optional). A timestamp with a UTC offset (+02:00), without the Z, or as a date only returns a 400. When omitted, the log is stamped with the current server time

name

Optional

Name of the meal. Omit optional fields entirely rather than sending null — explicit nulls return a 400

Omitting timestampUtc stamps the log with the current server time — always send it when logging past meals (backfills, imports, offline queues).

Response Structure

Success is 200 OK with the created log:

Field
Type
Description

id

string

Unique ID of the created log. Save it — it's the {logId} for Delete Food Log

foods

array

The logged foods, hydrated with names, nutrients, and serving details (see below)

timestampUtc

string

The timestamp the log was stored with

name

string or null

Meal name; null when name was not sent

Logged Food Fields

Field
Type
Description

id

number

Food item ID

name

string

Food name

brandName

string or null

Brand name; null for generic foods

imageUrl

string or null

Food photo URL; null when no image is available

glycemicIndex

number or null

Glycemic index

glycemicLoad

number or null

Glycemic load

nutrients

object

Nutrients scaled to the consumed serving and quantity (see below)

consumedServing

object

Echo of what you logged: id and quantity

servingDetails

object

The consumed serving's definition: id, unit, quantity, and weightGrams (null when unknown)

nutrients values are objects with value (number) and unit (string), scaled to the consumed quantity. Possible keys: calories, protein, carbohydrates, netCarbohydrates, totalFat, transFat, saturatedFat, fiber, totalSugars, addedSugars, cholesterol, calcium, iron, potassium, sodium, and vitaminD. Units are calories, g, or mg, except iron (mcg) and vitaminD (IU). As in NLP Food Search, keys are omitted when the source data is unavailable and when the value is exactly zero.

Retrieve Food Logs

The above command returns 200 OK with a JSON array of log objects — the same shape Create Food Logs returns. Trimmed here to one log with one food:

Retrieve a user's food logs over a date range.

HTTP Request

GET https://partners.january.ai/v1.1/logs/foods

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

x-partner-user-id

Yes

Identifies the end user this request acts on behalf of. See Authentication.

Query Parameters

Parameter
Required
Description

start

Yes

Start date, strictly YYYY-MM-DD (zero-padded) — other formats return a 400

end

Yes

End date, strictly YYYY-MM-DD (zero-padded), and strictly after start — start equal to end returns a 400

start must be earlier than end. Future end dates are allowed.

Both dates are read as UTC calendar days, and both ends are inclusive: the range runs from start at 00:00:00 UTC through end at 23:59:59 UTC. Because end must be after start and both days are included, the smallest possible window covers two days — to read a single day's logs, set end to the following day and filter by timestampUtc on your side.

Response Structure

Success is 200 OK with a JSON array of log objects, each shaped exactly like the Create Food Logs response: id, foods, timestampUtc, and name. An empty array ([]) means no logs in the range — it is not an error.

Delete Food Log

Delete a single food log entry by its ID.

HTTP Request

DELETE https://partners.january.ai/v1.1/logs/foods/{logId}

URL Parameters

Parameter
Description

logId

The unique identifier of the food log to delete

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

x-partner-user-id

Yes

Identifies the end user this request acts on behalf of. See Authentication.

Response Structure

Success is 200 OK with an empty body. Deletion is idempotent: deleting an ID that doesn't exist — or was already deleted — also returns 200 OK, so retrying a delete after a timeout is safe.

Glucose Insights APIs

Predict how a meal will move a user's glucose — before they take the first bite. You pass the user's profile in the request body on every call — nothing is stored, and no setup is required.

Glucose Prediction

The above command returns JSON structured like this:

Returns a glucose response curve for a meal, covering the two hours and 15 minutes after startTime as 10 points at 15-minute intervals. The curve is computed from the user's profile and the foods in the request. Nothing is stored — the profile travels with every call.

You can optionally provide cgmData and consumedFoods to personalize the prediction with the user's own history. Like the profile, the history is used only to compute this one response — it is not stored, and there is no separate setup or training step to run first. When you provide history, it must include at least five complete training days — otherwise the request fails with 400 Bad Request and a message starting with "Not enough good days for AI Training".

A training day is complete when it meets both of the following criteria:

  • At least 12 hours of CGM data (48 15-minute windows — they don't need to be contiguous)

  • At least 2 logged meals in distinct 15-minute windows

Days are the user's local calendar days in x-partner-timezone and don't need to be consecutive, but only full calendar days count — the partial first and last days of the history you send never qualify. Send a margin: seven calendar days of history is a safe minimum for five complete days.

Send at most one CGM reading per 15-minute window — downsample denser feeds (e.g. 5-minute CGM data) first. This is a hard requirement, not advice: denser data fails the request instead of being downsampled server-side.

HTTP Request

POST https://partners.january.ai/v1.1/cgm/glucose-predict

Headers

Header
Required
Description

Authorization

Yes

Bearer token for authentication

Content-Type

Yes

Must be application/json

x-partner-timezone

Yes

User's local IANA timezone (e.g. America/New_York). Defines the user's local calendar days when evaluating optional history; an invalid name fails the request

Request Body

Parameter
Required
Description

userProfile

Yes

User's profile data

userProfile.age

Yes

User's age

userProfile.gender

Yes

Exactly male or female, lowercase. Other values and casings (e.g. Male) are not rejected — the prediction silently ignores gender instead, so match the exact values

userProfile.height

Yes

User's height in inches

userProfile.weight

Yes

User's weight in pounds

userProfile.activity_level

No

Activity level ('sedentary', 'lightly_active', 'moderately_active', 'very_active'). Case-sensitive — Sedentary returns a 400

userProfile.medical_conditions

No

Array of medical conditions (excluding 'Type 1 diabetes'). Values are case-sensitive and must match the list below exactly

foods

Yes

Array of foods and servings for prediction. An empty array is not rejected but produces a meal-less baseline curve — always send at least one food

foods[].id

Yes

Unique identifier of the food item

foods[].serving

Yes

Serving details

foods[].serving.id

Yes

Serving size identifier

foods[].serving.quantity

Yes

Amount of serving consumed

startTime

Yes

ISO 8601 timestamp for when foods are consumed. Always include a timezone designator (Z or an offset) — a timestamp without one is read as UTC, not in x-partner-timezone

cgmData

No

CGM history used to personalize this prediction (in-request only; not stored) — at most one reading per 15-minute window. If cgmData is provided, consumedFoods must be provided also.

cgmData[].timestamp

Yes

ISO 8601 timestamp for the CGM data point

cgmData[].value

Yes

Blood sugar level (in mg/dL)

consumedFoods

No

Array of historical user foods and servings, paired with cgmData to personalize this prediction (in-request only; not stored).

consumedFoods[].timestamp

Yes

ISO 8601 timestamp for when the food is consumed

consumedFoods[].id

Yes

Unique identifier of the food item

consumedFoods[].serving

Yes

Serving details

consumedFoods[].serving.id

Yes

Serving size identifier

consumedFoods[].serving.quantity

Yes

Amount of serving consumed

Medical Conditions

Valid medical conditions include:

  • Type 2 diabetes

  • Prediabetes

  • None of the above

Response Structure

The response includes:

  • cgp: Array of data points representing glucose response curve [minute, glucose_value]

  • scoring: Qualitative impact scoring ('low_impact', 'medium_impact', 'high_impact')

  • cgp_min: Lower bound of the target glucose range. Always 70 mg/dL.

  • cgp_max: Upper bound of the target glucose range: 180 mg/dL when medical_conditions includes 'Type 2 diabetes', otherwise 140.

Reliability

What to do when a request fails, times out, or returns 429 — and which requests are safe to send again.

Timeouts

Most endpoints respond in well under a second. Photo Scan and Edit Photo Scan Results run an AI model and can take tens of seconds — set a client timeout of 60 seconds, and expect an occasional 504 when the vision model itself times out (treat it like any other 5xx).

When your own timeout fires, treat the outcome as unknown: the server may or may not have completed the work. What that means per endpoint is in the table below.

Retrying safely

Endpoint
After a timeout or 5xx

All four search endpoints, Food Alternatives, Retrieve Food Logs

Retry freely — read-only

Retry freely — stateless, and identical input returns an identical curve

Retry freely — idempotent by design; deleting an already-deleted log also succeeds

Retry freely — stores nothing

Retry with care: every attempt, including failed ones, counts against the per-user daily scan allowance. Use one or two bounded retries with backoff

Not safe to retry blindly — a retry after an ambiguous timeout can write a duplicate log entry. On timeout, call Retrieve Food Logs for the date and check whether the log landed before re-creating it; remove accidental duplicates with Delete Food Log

The API has no idempotency-key mechanism — an Idempotency-Key header is ignored, so it cannot make Create Food Logs retry-safe.

Rate limits

Rate limits are configured per partner, per API family, as part of your agreement — there is no universal published number. Exceeding a limit returns 429 with the body {"message": "Too Many Requests"}.

Three things about the buckets are worth knowing:

  • Some endpoints share one budget: Photo Scan and Edit Photo Scan Results draw from the same allowance; so do the two restaurant search endpoints; so do all three Food Logging operations.

  • Two per-end-user daily allowances apply on top of your partner limits: photo scans (150 per user per day) and glucose predictions (500 per user per day).

  • Windows are fixed, anchored at the first request in the window — and requests sent while over the limit still count, so hammering a 429 never shortens the wait.

Handling a 429: honor the Retry-After header when one is present; most partner-limit 429s don't carry one, so back off exponentially starting around a minute. Never auto-retry a per-day allowance — that 429 cannot succeed until the day's window resets.

Error format

Every error is JSON carrying the HTTP status and a single message field: {"message": "..."}. (A crash inside the API — rare — returns {"statusCode": 500, "message": "Internal Server Error"} instead.) Status meanings are listed under Errors. Treat 5xx responses and network-level failures as transient and retry them per the table above.

Errors

The January AI API uses conventional HTTP status codes:

Error Code
Meaning

400

Bad Request -- Your request is invalid or malformed. Most often this means the x-partner-user-id header is missing on an endpoint that requires it — see Missing x-partner-user-id (400).

401

Unauthorized -- Your API key is invalid or missing.

403

Forbidden -- You don't have permission to access this resource.

404

Not Found -- The specified resource could not be found.

405

Method Not Allowed -- You tried to access a resource with an invalid HTTP method.

406

Not Acceptable -- You requested a format that isn't supported.

422

Unprocessable Entity -- The request was well-formed but contains semantic errors.

429

Too Many Requests -- You're sending requests too quickly. Slow down and retry.

500

Internal Server Error -- We had a problem with our server. Try again later.

503

Service Unavailable -- We're temporarily offline for maintenance. Please try again later.

Error Response Format

Error responses are formatted like this:

All error responses follow the same format: an object with a single top-level message field.

  • message: Human-readable description of what went wrong

Unhandled 500 Internal Server Error responses are the exception: they also include a statusCode field, and the message is always the generic Internal Server Error.

Common Error Scenarios

Authentication Errors

  • 401 Unauthorized: Missing or invalid API key

  • 403 Forbidden: Valid API key but insufficient permissions

Request Errors

  • 400 Bad Request: Missing required parameters or invalid parameter values — most commonly a missing x-partner-user-id header

  • 422 Unprocessable Entity: Valid request format but business logic errors

  • 429 Too Many Requests: Rate limiting exceeded

Server Errors

  • 500 Internal Server Error: Unexpected server-side error

  • 503 Service Unavailable: Temporary service outage

Missing x-partner-user-id (400)

A user-scoped endpoint called without the x-partner-user-id header returns:

A valid API key alone is not enough on endpoints that act on a person. If the x-partner-user-id header is missing or empty on one of those endpoints, the request fails with 400 Bad Request and the message string above.

This is not an API key problem. Your key was accepted — the request just didn't say which end user it was for. Add the header and retry:

x-partner-user-id: YOUR_PARTNER_USER_ID

See Authentication for what value to send, and each endpoint's Headers table for whether it requires the header.

An empty or whitespace-only x-partner-user-id is treated as missing, and returns the same 400.

Questions?

If anything in these docs is unclear — or the API returns something they don't explain — email umer@january.ai. Don't hesitate to reach out; we're happy to help.

Last updated

Was this helpful?