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 headers every call is built on.
Quickstart
Scan a photo of a meal. Replace
YOUR_API_KEYwith your key and run it:
curl --location 'https://partners.january.ai/v1.1/vision/foods' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: test-7f3c1a' \
--data '{
"photoUrl": "https://i.imgur.com/bTQIGxf.png"
}'
import requests
url = 'https://partners.january.ai/v1.1/vision/foods'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'test-7f3c1a'
}
data = {
"photoUrl": "https://i.imgur.com/bTQIGxf.png"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/vision/foods',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'test-7f3c1a'
},
data: {
photoUrl: 'https://i.imgur.com/bTQIGxf.png'
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
A successful response looks like this:
{
"mealName": "Breakfast Bowl",
"totalNutrients": {
"calories": {
"value": 520,
"unit": "calories"
},
"protein": {
"value": 15,
"unit": "grams"
},
"carbohydrates": {
"value": 68,
"unit": "grams"
}
},
"detections": [
{
"confidenceScore": "high",
"food": {
"name": "Oatmeal",
"id": 789012,
"brandName": "",
"nutrients": {
"calories": {
"value": 300,
"unit": "calories"
}
},
"servings": [
{
"id": 45678,
"quantity": 1,
"unit": "cup"
}
]
}
}
]
}
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.
Two of this request's headers deserve a close look now, because nearly every January endpoint uses the same pair:
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, and most endpoints require both. 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:
curl "https://partners.january.ai/v1.1/endpoint" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-partner-user-id: YOUR_PARTNER_USER_ID"
import requests
# Replace YOUR_API_KEY with your actual API key, and
# YOUR_PARTNER_USER_ID with the end user's stable ID from your system
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID',
'Content-Type': 'application/json'
}
const axios = require('axios');
// Replace YOUR_API_KEY with your actual API key, and
// YOUR_PARTNER_USER_ID with the end user's stable ID from your system
const headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID',
'Content-Type': 'application/json'
};
Every request to the January AI API identifies 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
A missing or invalid API key returns 401 Unauthorized. 401 means the key and nothing else.
The end user: x-partner-user-id
Endpoints that act on a person β photo scans, food logs, AI training, personalized glucose predictions, and the user profile endpoints β 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
- AI training data submitted to Mark User as AI-Trained, and the personalized model built from it
- Personalized results from AI-Trained Glucose Prediction
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.
Food Search (By Name or Barcode)
import requests
url = 'https://partners.january.ai/v1.1/search/foods'
params = {
'query': 'banana',
'category': 'branded'
}
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
response = requests.get(url, params=params, headers=headers)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/search/foods?query=banana&category=branded' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID'
const axios = require('axios');
const config = {
method: 'get',
url: 'https://partners.january.ai/v1.1/search/foods',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
params: {
query: 'banana',
category: 'branded'
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"totalCount": 40,
"items": [
{
"id": 84222716,
"name": "Banana",
"brand_name": "One",
"protein": 2,
"energy": 160,
"carbs": 15,
"fat": 10,
"fat_total_saturated": 5,
"net_carbs": 13,
"sodium": 20,
"sugars": 13,
"added_sugars": 11,
"gi": 34.9,
"gl": 5.2,
"fiber": 2,
"potassium": 170,
"cholesterol": 2.5,
"photo_url": null,
"servings": [
{
"id": 67943292,
"quantity": 1,
"unit": "bar",
"scaling_factor": 1,
"weight_grams": 60,
"is_primary": true
}
]
}
]
}
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') |
| upc | Optional | The UPC representing the barcode of a food item |
Response Structure
| Field | Type | Description |
|---|---|---|
| totalCount | number | Total number of matching food items |
| 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 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) |
| cholesterol | number or null | Cholesterol (mg) |
| photo_url | string or null | Food photo URL, null if no image 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 | Weight of the serving in grams |
| is_primary | boolean | Whether this is the food's primary serving (always true for this API) |
NLP Food Search
import requests
url = 'https://partners.january.ai/v1.1/search/foods/nlp'
params = {
'query': '1 banana, 1 bowl of oatmeal, glass of orange juice'
}
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
response = requests.get(url, params=params, headers=headers)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/search/foods/nlp?query=1%20banana%2C%201%20bowl%20of%20oatmeal%2C%20glass%20of%20orange%20juice' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID'
const axios = require('axios');
const config = {
method: 'get',
url: 'https://partners.january.ai/v1.1/search/foods/nlp',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
params: {
query: '1 banana, 1 bowl of oatmeal, glass of orange juice'
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"totalNutrients": {
"calories": {
"value": 435,
"unit": "calories"
},
"protein": {
"value": 8.5,
"unit": "g"
},
"carbohydrates": {
"value": 92,
"unit": "g"
},
"totalFat": {
"value": 18.3,
"unit": "g"
},
"fiber": {
"value": 6.1,
"unit": "g"
},
"sodium": {
"value": 320,
"unit": "mg"
}
},
"detections": [
{
"food": {
"name": "Banana",
"id": 123456,
"brandName": "",
"nutrients": {
"calories": {
"value": 105,
"unit": "calories"
},
"protein": {
"value": 1.3,
"unit": "g"
},
"carbohydrates": {
"value": 27,
"unit": "g"
},
"totalFat": {
"value": 0.4,
"unit": "g"
},
"fiber": {
"value": 3.1,
"unit": "g"
},
"sodium": {
"value": 1,
"unit": "mg"
}
},
"servings": [
{
"id": 78901,
"quantity": 1,
"unit": "medium (7\" to 7-7/8\" long)",
"selected_quantity": 1
}
]
}
}
]
}
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 |
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.
| 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 |
Restaurant Search
import requests
url = 'https://partners.january.ai/v1.1/search/restaurants'
params = {
'query': 'mcdonalds',
'lat': 37.549,
'lon': -121.989
}
headers = {
'Authorization': 'Bearer YOUR_API_KEY'
}
response = requests.get(url, params=params, headers=headers)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/search/restaurants?query=mcdonalds&lat=37.549&lon=-121.989' \
--header 'Authorization: Bearer YOUR_API_KEY'
const axios = require('axios');
const config = {
method: 'get',
url: 'https://partners.january.ai/v1.1/search/restaurants',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
},
params: {
query: 'mcdonalds',
lat: 37.549,
lon: -121.989
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"items": [
{
"type": "restaurant",
"id": "53fc3b8a-e6bf-404d-83c8-9f42124d1bee",
"name": "McDonald's",
"is_chain": false,
"distance": 8,
"city": "San Francisco",
"address1": "123 Main Street",
"address2": "Suite 100"
}
]
}
Search for restaurants β local spots and chains β by name, ranked by distance when you provide the user's coordinates.
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 |
| lon | Optional | Longitude for proximity ranking |
| distance | Optional | Search radius in meters. Default is 16093 (~10 miles) |
| limit | Optional | Maximum number of results to return. Default is 50 |
Response Structure
The response includes:
- items: Array of restaurant locations with id, name, chain status, distance, and address information
Restaurant Menu Search
import requests
url = 'https://partners.january.ai/v1.1/search/restaurants/menu'
params = {
'query': 'burger',
'lat': 37.549,
'lon': -121.989
}
headers = {
'Authorization': 'Bearer YOUR_API_KEY'
}
response = requests.get(url, params=params, headers=headers)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/search/restaurants/menu?query=burger&lat=37.549&lon=-121.989' \
--header 'Authorization: Bearer YOUR_API_KEY'
const axios = require('axios');
const config = {
method: 'get',
url: 'https://partners.january.ai/v1.1/search/restaurants/menu',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
},
params: {
query: 'burger',
lat: 37.549,
lon: -121.989
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"items": [
{
"type": "menu_item",
"id": "228990954",
"name": "burger",
"restaurant_name": "morning due cafe",
"is_chain": false,
"data_source_id": 12,
"source_original_id": "ngxZD7tEA1cQGCuOYBF05Ckft2ZP3z3Ja88wVaXYqqr7P7F3QiGFN1BSCl6H0Bt_5iN7y",
"protein": 48,
"energy": 800,
"carbs": 40,
"net_carbs": 38,
"sugars": 6,
"added_sugars": null,
"fat": 45,
"gi": 48.507698,
"gl": 36.67089,
"fiber": 2,
"photo_url": "https://cdn-img.ai/23361362e6a706567",
"servings": [
{
"id": 189343592,
"scaling_factor": 1,
"quantity": 1,
"unit": "serving",
"weight_grams": null,
"is_primary": true
}
],
"distance": 124
}
]
}
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 |
| lon | Yes | Longitude to search around |
| distance | Optional | Search radius in meters. Default is 16093 (~10 miles) |
| limit | Optional | Maximum number of results to return. Default is 50 |
Response Structure
The response includes:
- items: Array of menu items with id, name, restaurant name, photo URL, nutrients and primary serving information
Photo Scan
import requests
import json
url = 'https://partners.january.ai/v1.1/vision/foods'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
data = {
"photoUrl": "https://i.imgur.com/bTQIGxf.png"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/vision/foods' \
--header 'Content-Type: application/json' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"photoUrl": "https://i.imgur.com/bTQIGxf.png"
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/vision/foods',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
data: {
photoUrl: 'https://i.imgur.com/bTQIGxf.png'
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"mealName": "Breakfast Bowl",
"totalNutrients": {
"calories": {
"value": 520,
"unit": "calories"
},
"protein": {
"value": 15,
"unit": "grams"
},
"carbohydrates": {
"value": 68,
"unit": "grams"
}
},
"detections": [
{
"confidenceScore": "high",
"food": {
"name": "Oatmeal",
"id": 789012,
"brandName": "",
"nutrients": {
"calories": {
"value": 300,
"unit": "calories"
}
},
"servings": [
{
"id": 45678,
"quantity": 1,
"unit": "cup"
}
]
}
}
]
}
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) |
Photo Requirements
- JPG, JPEG, or PNG, between 512x512 and 1024x1024 pixels
- Maximum file size: 4 MB
- 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
import requests
import json
url = 'https://partners.january.ai/v1.1/vision/fix-ai'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
data = {
"mealName": "Breakfast Bowl",
"detections": [
{
"confidenceScore": "high",
"food": {
"name": "Oatmeal",
"id": 789012,
"brandName": null,
"nutrients": {
"calories": {
"value": 300,
"unit": "kcal"
}
},
"servings": [
{
"id": 45678,
"quantity": 1,
"unit": "cup"
}
]
}
}
],
"userInput": "change oatmeal to steel-cut oats"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/vision/fix-ai' \
--header 'Content-Type: application/json' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"mealName": "Breakfast Bowl",
"detections": [
{
"confidenceScore": "high",
"food": {
"name": "Oatmeal",
"id": 789012,
"brandName": null,
"nutrients": {
"calories": {
"value": 300,
"unit": "kcal"
}
},
"servings": [
{
"id": 45678,
"quantity": 1,
"unit": "cup"
}
]
}
}
],
"userInput": "change oatmeal to steel-cut oats"
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/vision/fix-ai',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
data: {
"mealName": "Breakfast Bowl",
"detections": [
{
"confidenceScore": "high",
"food": {
"name": "Oatmeal",
"id": 789012,
"brandName": null,
"nutrients": {
"calories": {
"value": 300,
"unit": "kcal"
}
},
"servings": [
{
"id": 45678,
"quantity": 1,
"unit": "cup"
}
]
}
}
],
"userInput": "change oatmeal to steel-cut oats"
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"mealName": "Breakfast Bowl",
"totalNutrients": {
"calories": {
"value": 520,
"unit": "kcal"
},
"carbs": {
"value": 68,
"unit": "g"
},
"protein": {
"value": 15,
"unit": "g"
}
},
"detections": [
{
"confidenceScore": "high",
"food": {
"name": "Oatmeal",
"id": 789012,
"brandName": null,
"nutrients": {
"calories": {
"value": 300,
"unit": "kcal"
}
},
"servings": [
{
"id": 45678,
"quantity": 1,
"unit": "cup"
}
]
}
}
]
}
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/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 |
| 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 Logging APIs
Write to and read from each user's food diary β the history that grounds January's personalized predictions.
Create Food Logs
import requests
import json
url = 'https://partners.january.ai/v1.1/logs/foods'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
data = {
"foods": [
{
"id": 101963552,
"serving": {
"id": 68051535,
"quantity": 1.4
}
}
],
"timestampUtc": "2024-09-13T11:34:56Z"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/logs/foods' \
--header 'Content-Type: application/json' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"foods": [
{
"id": 101963552,
"serving": {
"id": 68051535,
"quantity": 1.4
}
}
],
"timestampUtc": "2024-09-13T11:34:56Z"
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/logs/foods',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
data: {
foods: [
{
id: 101963552,
serving: {
id: 68051535,
quantity: 1.4
}
}
],
timestampUtc: "2024-09-13T11:34:56Z"
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
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 |
| foods[].id | Yes | Unique identifier of the food item |
| foods[].serving | Yes | Serving details of the food item |
| foods[].serving.id | Yes | Serving size identifier |
| foods[].serving.quantity | Yes | Amount of the serving consumed |
| timestampUtc | Yes | UTC timestamp when food was consumed (YYYY-MM-DDTHH:MM:SSZ) |
| name | Optional | Name of the meal |
Retrieve Food Logs
import requests
url = 'https://partners.january.ai/v1.1/logs/foods'
params = {
'start': '2023-09-12',
'end': '2024-09-15'
}
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
response = requests.get(url, params=params, headers=headers)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/logs/foods?start=2023-09-12&end=2024-09-15' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--header 'Authorization: Bearer YOUR_API_KEY'
const axios = require('axios');
const config = {
method: 'get',
url: 'https://partners.january.ai/v1.1/logs/foods',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
params: {
start: '2023-09-12',
end: '2024-09-15'
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
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 (YYYY-MM-DD format) |
| end | Yes | End date (YYYY-MM-DD format) |
Delete Food Log
import requests
url = 'https://partners.january.ai/v1.1/logs/foods/78129823-8ba2-4183-b13b-71f0e963c606'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
response = requests.delete(url, headers=headers)
print(response.status_code)
curl --location --request DELETE 'https://partners.january.ai/v1.1/logs/foods/78129823-8ba2-4183-b13b-71f0e963c606' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--header 'Authorization: Bearer YOUR_API_KEY'
const axios = require('axios');
const config = {
method: 'delete',
url: 'https://partners.january.ai/v1.1/logs/foods/78129823-8ba2-4183-b13b-71f0e963c606',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
};
axios(config)
.then(response => {
console.log(response.status);
})
.catch(error => {
console.log(error);
});
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. |
Glucose Insights APIs
Predict how a meal will move a user's glucose β before they take the first bite.
There are two ways to get a prediction, and they differ in what you have to set up first:
- Glucose Prediction works immediately. You pass the user's profile in the request body on every call β nothing is stored, and no setup is required.
- AI-Trained Glucose Prediction is personalized to one user's own CGM and meal history. It is more accurate, and it requires setting that user up first: create their profile, then submit their training data.
The endpoints between the two below β Create User, Get and Update User Profile, and Mark User as AI-Trained β exist to support the personalized path. They store the profile and training history that AI-Trained Glucose Prediction reads from. If you are only using the stateless prediction, food search, photo scan, or food logging, you do not need any of them.
Glucose Prediction
import requests
import json
url = 'https://partners.january.ai/v1.1/cgm/glucose-predict'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-timezone': 'America/Los_Angeles'
}
data = {
"userProfile": {
"age": 25,
"gender": "male",
"height": 65,
"weight": 165,
"activity_level": "very_active",
"medical_conditions": ["Type 2 diabetes"]
},
"startTime": "2025-04-09T19:06:12Z",
"foods": [
{
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
],
"cgmData": [
# Optional CGM data array
{
"timestamp": "2025-04-03T14:56:03Z",
"value": 92
},
{
"timestamp": "2025-04-03T14:59:40Z",
"value": 95
}
],
"consumedFoods": [
# Optional historical user foods
{
"timestamp": "2025-04-03T14:56:03Z",
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/cgm/glucose-predict' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-timezone: America/Los_Angeles' \
--data '{
"userProfile": {
"age": 25,
"gender": "male",
"height": 65,
"weight": 165,
"activity_level": "very_active",
"medical_conditions": ["Type 2 diabetes"]
},
"startTime": "2025-04-09T19:06:12Z",
"foods": [
{
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
],
"cgmData": [
{
"timestamp": "2025-04-03T14:56:03Z",
"value": 92
},
{
"timestamp": "2025-04-03T14:59:40Z",
"value": 95
}
],
"consumedFoods": [
{
"timestamp": "2025-04-03T14:56:03Z",
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/cgm/glucose-predict',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-timezone': 'America/Los_Angeles'
},
data: {
userProfile: {
age: 25,
gender: "male",
height: 65,
weight: 165,
activity_level: "very_active",
medical_conditions: ["Type 2 diabetes"]
},
startTime: "2025-04-09T19:06:12Z",
foods: [
{
id: 70380652,
serving: {
id: 34176931,
quantity: 1
}
}
],
cgmData: [
// Optional CGM data array
{
"timestamp": "2025-04-03T14:56:03Z",
"value": 92
}
],
consumedFoods: [
// Optional historical user foods
{
timestamp: "2025-04-03T14:56:03Z",
id: 70380652,
serving: {
id: 34176931,
quantity: 1
}
}
]
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"cgp": [
[0, 95],
[15, 110],
[30, 140],
[45, 155],
[60, 145],
[75, 125],
[90, 110],
[105, 100],
[120, 95],
[135, 92]
],
"scoring": "medium_impact",
"cgp_min": 70,
"cgp_max": 140
}
Returns a two-hour glucose response curve for a meal, 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. To count as training data, the input must include at least five complete training days.
A training day is complete when it meets both of the following criteria:
- At least 12 hours of CGM data
- At least 2 logged meals at different times of the day
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 local timezone (e.g. America/New_York or US/Pacific) |
Request Body
| Parameter | Required | Description |
|---|---|---|
| userProfile | Yes | User's profile data |
| userProfile.age | Yes | User's age |
| userProfile.gender | Yes | User's gender ('male' or 'female') |
| 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') |
| userProfile.medical_conditions | No | Array of medical conditions (excluding 'Type 1 diabetes') |
| foods | Yes | Array of foods and servings for prediction |
| 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 |
| cgmData | No | CGM data array for AI training. Please provide at least one data point per 15 minutes. 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 for AI training. |
| 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 1 diabetes
- 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: User's minimum glucose level (in mg/dL), based on the input user profile.
- cgp_max: User's maximum glucose level (in mg/dL), based on the input user profile.
Set Up a User for Personalized Predictions
The three endpoints that follow β Create User, Mark User as AI-Trained, and the profile read/update pair β prepare a user for AI-Trained Glucose Prediction.
The sequence is:
- Create User with the user's profile. Store the returned
user_id. - Mark User as AI-Trained with at least five valid training days of CGM and meal data.
- AI-Trained Glucose Prediction using that
user_idas thex-partner-user-id.
Create User
import requests
import json
url = 'https://partners.january.ai/v1.1/user'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/user' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/user',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
data: {
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"user_id": "a88888ef-cc9c-5882-ba2c-9cbee8a4b73c",
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
Creates a January user with the profile the glucose prediction endpoints rely on β age, gender, height, and weight β and returns the generated user_id.
HTTP Request
POST https://partners.january.ai/v1.1/user
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer token for authentication |
| Content-Type | Yes | Must be application/json |
Request Body
| Parameter | Required | Description |
|---|---|---|
| profile | Yes | User profile object |
| profile.age | Yes | User's age |
| profile.gender | Yes | User's gender ('male' or 'female') |
| profile.height | Yes | User's height in inches |
| profile.weight | Yes | User's weight in pounds |
| profile.activity_level | No | Activity level ('sedentary', 'lightly_active', 'moderately_active', 'very_active') |
| profile.medical_conditions | No | Array of medical conditions |
Medical Conditions
Valid medical conditions include:
- Type 1 diabetes
- Type 2 diabetes
- Prediabetes
- None of the above
Response Structure
The response includes:
- user_id: Created user ID
- profile: User profile object
Get User Profile
import requests
import json
url = 'https://partners.january.ai/v1.1/user'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
response = requests.get(url, headers=headers)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/user' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID'
const axios = require('axios');
const config = {
method: 'get',
url: 'https://partners.january.ai/v1.1/user',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"user_id": "a88888ef-cc9c-5882-ba2c-9cbee8a4b73c",
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
Returns the user's ID and stored profile. If January has no user under the x-partner-user-id you sent, the request returns 404 Not Found.
HTTP Request
GET https://partners.january.ai/v1.1/user
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. |
Response Structure
The response includes:
- user_id: Partner user ID
- profile: User profile object
Update User Profile
import requests
import json
url = 'https://partners.january.ai/v1.1/user'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
data = {
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
response = requests.patch(url, headers=headers, json=data)
print(response.json())
curl --location --request PATCH 'https://partners.january.ai/v1.1/user' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--data '{
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}'
const axios = require('axios');
const config = {
method: 'patch',
url: 'https://partners.january.ai/v1.1/user',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
data: {
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"user_id": "a88888ef-cc9c-5882-ba2c-9cbee8a4b73c",
"profile": {
"age": 20,
"gender": "male",
"weight": 80,
"height": 36,
"activity_level": "sedentary",
"medical_conditions": []
}
}
Updates the user's stored profile and returns the updated record.
HTTP Request
PATCH https://partners.january.ai/v1.1/user
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 |
|---|---|---|
| profile | Yes | User profile object |
| profile.age | Yes | User's age |
| profile.gender | Yes | User's gender ('male' or 'female') |
| profile.height | Yes | User's height in inches |
| profile.weight | Yes | User's weight in pounds |
| profile.activity_level | No | Activity level ('sedentary', 'lightly_active', 'moderately_active', 'very_active') |
| profile.medical_conditions | No | Array of medical conditions |
Medical Conditions
Valid medical conditions include:
- Type 1 diabetes
- Type 2 diabetes
- Prediabetes
- None of the above
Response Structure
The response includes:
- user_id: Partner user ID
- profile: User profile object
Mark User as AI-Trained
import requests
import json
url = 'https://partners.january.ai/v1.1/user/training'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
}
data = {
"cgm_data": [
# CGM data array
{
"timestamp": "2025-04-03T14:56:03Z",
"value": 92
},
{
"timestamp": "2025-04-03T14:59:40Z",
"value": 95
}
],
"consumed_foods": [
# Historical user foods
{
"timestamp": "2025-04-03T14:56:03Z",
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/user/training' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--data '{
"cgm_data": [
{
"timestamp": "2025-04-03T14:56:03Z",
"value": 92
},
{
"timestamp": "2025-04-03T14:59:40Z",
"value": 95
}
],
"consumed_foods": [
{
"timestamp": "2025-04-03T14:56:03Z",
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/user/training',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID'
},
data: {
"cgm_data": [
// CGM data array
{
"timestamp": "2025-04-03T14:56:03Z",
"value": 92
},
{
"timestamp": "2025-04-03T14:59:40Z",
"value": 95
}
],
"consumed_foods": [
// Historical user foods
{
"timestamp": "2025-04-03T14:56:03Z",
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"good_days": [
"2025-09-01",
"2025-09-02",
"2025-09-03",
"2025-09-04",
"2025-09-05"
]
}
Submits a user's historical CGM and meal data to train their personalized glucose model.
Both cgm_data and consumed_foods are required, with enough data to complete training: five or more valid training days.
A valid training day must include:
- At least 12 hours of CGM data
- At least 2 logged meals at different times of the day
If the data does not meet these requirements, the endpoint returns a 400 Bad Request error.
HTTP Request
POST https://partners.january.ai/v1.1/user/training
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. |
| x-partner-timezone | Yes | User local timezone (e.g. America/New_York or US/Pacific) |
Request Body
| Parameter | Required | Description |
|---|---|---|
| cgm_data | Yes | CGM data array for AI training. Please provide at least one data point per 15 minutes. |
| cgm_data[].timestamp | Yes | ISO 8601 timestamp for the CGM data point |
| cgm_data[].value | Yes | Blood sugar level (in mg/dL) |
| consumed_foods | Yes | Array of historical user foods and servings for AI training. |
| consumed_foods[].timestamp | Yes | ISO 8601 timestamp for when the food is consumed |
| consumed_foods[].id | Yes | Unique identifier of the food item |
| consumed_foods[].serving | Yes | Serving details |
| consumed_foods[].serving.id | Yes | Serving size identifier |
| consumed_foods[].serving.quantity | Yes | Amount of serving consumed |
Response Structure
The response includes:
- good_days: Array of good days (ISO 8601 date format)
AI-Trained Glucose Prediction
import requests
import json
url = 'https://partners.january.ai/v1.1/cgm/user-glucose-predict'
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID',
'x-partner-timezone': 'America/Los_Angeles'
}
data = {
"startTime": "2025-04-09T19:06:12Z",
"foods": [
{
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/cgm/user-glucose-predict' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'x-partner-user-id: YOUR_PARTNER_USER_ID' \
--header 'x-partner-timezone: America/Los_Angeles' \
--data '{
"startTime": "2025-04-09T19:06:12Z",
"foods": [
{
"id": 70380652,
"serving": {
"id": 34176931,
"quantity": 1
}
}
]
}'
const axios = require('axios');
const config = {
method: 'post',
url: 'https://partners.january.ai/v1.1/cgm/user-glucose-predict',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'x-partner-user-id': 'YOUR_PARTNER_USER_ID',
'x-partner-timezone': 'America/Los_Angeles'
},
data: {
startTime: "2025-04-09T19:06:12Z",
foods: [
{
id: 70380652,
serving: {
id: 34176931,
quantity: 1
}
}
]
}
};
axios(config)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
The above command returns JSON structured like this:
{
"cgp": [
[0, 95],
[15, 110],
[30, 140],
[45, 155],
[60, 145],
[75, 125],
[90, 110],
[105, 100],
[120, 95],
[135, 92]
],
"scoring": "medium_impact",
"cgp_min": 70,
"cgp_max": 140
}
Returns a two-hour glucose prediction using a personalized AI model trained on a specific user's CGM and meal history.
Pass the end user in the x-partner-user-id header, as with every user-scoped endpoint. Unlike the others, this one requires that the user has already completed AI training β see Mark User as AI-Trained.
Accepts either the foods or nutrients parameter as input. Unlike the standard glucose prediction endpoint, this version does not require passing CGM data in the request β improving performance by using previously stored training data.
HTTP Request
POST https://partners.january.ai/v1.1/cgm/user-glucose-predict
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. |
| x-partner-timezone | Yes | User local timezone (e.g. America/New_York or US/Pacific) |
Request Body
| Parameter | Required | Description |
|---|---|---|
| startTime | No | ISO 8601 timestamp for when foods are consumed (default is current time) |
| foods | No | Array of foods and servings for prediction. If not specified, then nutrients parameter is applied. |
| 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 |
| nutrients | No | Object with nutrients quantities (applied only when foods parameter is not specified) |
| nutrients.water | No | Quantity of water (g) |
| nutrients.energy | No | Quantity of energy (calories) |
| nutrients.protein | No | Quantity of protein (g) |
| nutrients.fat_total_lipid | No | Quantity of total fat (g) |
| nutrients.carbohydrate | No | Quantity of carbohydrates (g) |
| nutrients.fiber | No | Quantity of fiber (g) |
| nutrients.sugars | No | Quantity of sugars (g) |
| nutrients.calcium | No | Quantity of calcium (g) |
| nutrients.iron | No | Quantity of iron (g) |
| nutrients.potassium | No | Quantity of potassium (g) |
| nutrients.sodium | No | Quantity of sodium (mg) |
| nutrients.vit_c | No | Quantity of vit C (g) |
| nutrients.vit_a_iu | No | Quantity of vit A iu (g) |
| nutrients.fat_total_saturated | No | Quantity of saturated fat (g) |
| nutrients.fat_total_monounsaturated | No | Quantity of monounsaturated fat (g) |
| nutrients.fat_total_polyunsaturated | No | Quantity of polyunsaturated fat (g) |
| nutrients.fat_total_trans | No | Quantity of trans fat (g) |
| nutrients.cholesterol | No | Quantity of cholesterol (g) |
| nutrients.caffeine | No | Quantity of caffeine (g) |
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: User's minimum glucose level (in mg/dL), based on the input user profile.
- cgp_max: User's maximum glucose level (in mg/dL), based on the input user profile.
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:
{
"message": "The 'query' parameter is required for this endpoint."
}
All error responses follow the same format: an object with a single top-level message field.
- message: Human-readable description of what went wrong
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-idheader - 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-idheader returns:
{
"message": "The x-partner-user-id header is required for this endpoint. It identifies the end user this request is performed on behalf of. Use a stable ID from your system. See https://docs.january.ai/#authentication."
}
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.