API v1.1 (legacy)
Deprecated. This is the archived reference for the v1.1 API, kept for existing integrations. It is not linked from the documentation navigation and receives no updates. New integrations should use the v1.2 REST API.
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
Every request also carries the Authorization: Bearer YOUR_API_KEY header — see Authentication.
Quickstart
Scan a photo of a meal. Replace
YOUR_API_KEYwith 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.
Never ship a hardcoded x-partner-user-id to production. A single fixed value collapses every one of your end users into one January user — their food logs and photo scans merge into one timeline. Send each end user's own stable ID.
Authentication
Every user-scoped request carries both headers:
A request to the January AI API can identify two things:
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
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.
Your API key is a server-side secret. Call the API from your backend — never from browser or mobile code, where the key is visible to anyone who looks — and load it from an environment variable or secrets manager rather than committing it to source control.
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.
Why the header exists
The value scopes everything January stores about that person:
Food logs written by Create Food Logs and read back by Retrieve Food Logs
Photo scan history and the corrections made through Edit Photo Scan Results
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
Authorization
Yes
Bearer token for authentication
Query Parameters
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
Response Structure
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
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
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
NLP Food Search
The above command returns JSON structured like this — trimmed to one detection;
totalNutrientssums 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
Authorization
Yes
Bearer token for authentication
Query Parameters
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
totalNutrients
object
Sum of scaled nutrients across all detected foods
detections
array
One entry per detected food item
Detection Object
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.
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
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
Restaurant Search
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
Authorization
Yes
Bearer token for authentication
Query Parameters
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, andis_chain;distance,city,address1, andaddress2are added only when nearby locations are found for the coordinates you sent
Restaurant Menu Search
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
Authorization
Yes
Bearer token for authentication
Query Parameters
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
Authorization
Yes
Bearer token for authentication
Content-Type
Yes
Must be application/json
Request Body
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,...)
Request bodies over 5 MiB are rejected with 413 Payload Too Large before the request reaches the endpoint. Base64 encoding adds roughly a third of overhead, so when sending photoBase64 keep the raw image under about 3.5 MiB — resize or compress larger photos first.
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
Authorization
Yes
Bearer token for authentication
Content-Type
Yes
Must be application/json
Request Body
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
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
Authorization
Yes
Bearer token for authentication
Content-Type
Yes
Must be application/json
URL Parameters
foodId
The ID of the food to find alternatives for. An unknown ID returns 404 Not Found
Request Body
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.
Both arrays are required and must be non-empty — to opt out, send ["None"], not []. A single unrecognized value fails the whole request with 400 Bad Request and a message naming the invalid value.
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'.
Alternatives are AI-generated. dietRestrictions guides how alternatives are selected, but January cannot guarantee that every returned food is 100% free of a restricted ingredient or allergen. If you surface alternatives to users with food allergies, show an appropriate disclaimer and screen suggestions on your side before presenting them as safe to eat.
Response Structure
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
name
string
Food name
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 OKwith 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
Authorization
Yes
Bearer token for authentication
Content-Type
Yes
Must be application/json
Request Body
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
Response Structure
Success is 200 OK with the created 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
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 OKwith 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
Authorization
Yes
Bearer token for authentication
Query Parameters
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
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
logId
The unique identifier of the food log to delete
Headers
Authorization
Yes
Bearer token for 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
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
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
Glucose Predictions are not compatible with users who report having Type 1 Diabetes or who report taking Insulin. Requests with 'Type 1 diabetes' in medical_conditions are rejected with a 400 Bad Request.
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_conditionsincludes '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
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:
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-idheader422 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-idheader 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.
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?