Menu
shell python javascript

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:

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.

Quickstart

Scan a photo of a meal. Replace YOUR_API_KEY with 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 — trimmed here; real responses include every detection and all ten nutrients per food:

{
  "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:

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:

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'
};

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

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 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:

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:

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)

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
limit Optional Maximum number of results to return. Default is 10, maximum 400. Name searches (general/branded) never return more than 40 items
offset Optional Number of results to skip. 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

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
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 — trimmed to one detection; totalNutrients sums every detected food, including ones not shown:

{
  "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. 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
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.

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. Must be sent together with lon
lon Optional Longitude for proximity ranking. Must be sent together with lat
distance Optional Search radius in meters. Default is 16093 (~10 miles), maximum 17000
limit Optional Maximum number of results to return. Default is 50, maximum 100

Response Structure

The response includes:

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), maximum 17000
limit Optional Maximum number of results to return. Default is 50, maximum 100

Response Structure

The response includes:

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 — trimmed here; real responses include every detection and all ten nutrients per food:

{
  "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). Raw base64 is treated as JPEG; for other formats, send a data URL (data:image/png;base64,...)

Photo Requirements

Response Structure

The response includes:

Edit Photo Scan Results

import requests
import json

url = 'https://partners.january.ai/v1.1/vision/foods/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": "",
        "nutrients": {
          "calories": {
            "value": 300,
            "unit": "calories"
          },
          "protein": {
            "value": 10.5,
            "unit": "grams"
          },
          "carbohydrates": {
            "value": 54,
            "unit": "grams"
          },
          "netCarbohydrates": {
            "value": 46,
            "unit": "grams"
          },
          "totalFat": {
            "value": 5.5,
            "unit": "grams"
          },
          "saturatedFat": {
            "value": 1,
            "unit": "grams"
          },
          "fiber": {
            "value": 8,
            "unit": "grams"
          },
          "totalSugars": {
            "value": 1.5,
            "unit": "grams"
          },
          "addedSugars": {
            "value": 0,
            "unit": "grams"
          },
          "sodium": {
            "value": 9,
            "unit": "milligrams"
          }
        },
        "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/foods/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": "",
        "nutrients": {
          "calories": {
            "value": 300,
            "unit": "calories"
          },
          "protein": {
            "value": 10.5,
            "unit": "grams"
          },
          "carbohydrates": {
            "value": 54,
            "unit": "grams"
          },
          "netCarbohydrates": {
            "value": 46,
            "unit": "grams"
          },
          "totalFat": {
            "value": 5.5,
            "unit": "grams"
          },
          "saturatedFat": {
            "value": 1,
            "unit": "grams"
          },
          "fiber": {
            "value": 8,
            "unit": "grams"
          },
          "totalSugars": {
            "value": 1.5,
            "unit": "grams"
          },
          "addedSugars": {
            "value": 0,
            "unit": "grams"
          },
          "sodium": {
            "value": 9,
            "unit": "milligrams"
          }
        },
        "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/foods/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": "",
          "nutrients": {
            "calories": {
              "value": 300,
              "unit": "calories"
            },
            "protein": {
              "value": 10.5,
              "unit": "grams"
            },
            "carbohydrates": {
              "value": 54,
              "unit": "grams"
            },
            "netCarbohydrates": {
              "value": 46,
              "unit": "grams"
            },
            "totalFat": {
              "value": 5.5,
              "unit": "grams"
            },
            "saturatedFat": {
              "value": 1,
              "unit": "grams"
            },
            "fiber": {
              "value": 8,
              "unit": "grams"
            },
            "totalSugars": {
              "value": 1.5,
              "unit": "grams"
            },
            "addedSugars": {
              "value": 0,
              "unit": "grams"
            },
            "sodium": {
              "value": 9,
              "unit": "milligrams"
            }
          },
          "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 the corrected scan — the detection is now steel-cut oats, and the totals match it:

{
  "mealName": "Breakfast Bowl",
  "totalNutrients": {
    "calories": {
      "value": 310,
      "unit": "calories"
    },
    "protein": {
      "value": 11,
      "unit": "grams"
    },
    "carbohydrates": {
      "value": 56,
      "unit": "grams"
    },
    "netCarbohydrates": {
      "value": 48,
      "unit": "grams"
    },
    "totalFat": {
      "value": 5,
      "unit": "grams"
    },
    "saturatedFat": {
      "value": 1,
      "unit": "grams"
    },
    "fiber": {
      "value": 8,
      "unit": "grams"
    },
    "totalSugars": {
      "value": 1,
      "unit": "grams"
    },
    "addedSugars": {
      "value": 0,
      "unit": "grams"
    },
    "sodium": {
      "value": 5,
      "unit": "milligrams"
    }
  },
  "detections": [
    {
      "confidenceScore": "high",
      "food": {
        "name": "Steel-Cut Oats",
        "id": 789013,
        "brandName": "",
        "nutrients": {
          "calories": {
            "value": 310,
            "unit": "calories"
          },
          "protein": {
            "value": 11,
            "unit": "grams"
          },
          "carbohydrates": {
            "value": 56,
            "unit": "grams"
          },
          "netCarbohydrates": {
            "value": 48,
            "unit": "grams"
          },
          "totalFat": {
            "value": 5,
            "unit": "grams"
          },
          "saturatedFat": {
            "value": 1,
            "unit": "grams"
          },
          "fiber": {
            "value": 8,
            "unit": "grams"
          },
          "totalSugars": {
            "value": 1,
            "unit": "grams"
          },
          "addedSugars": {
            "value": 0,
            "unit": "grams"
          },
          "sodium": {
            "value": 5,
            "unit": "milligrams"
          }
        },
        "servings": [
          {
            "id": 45679,
            "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/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
userInput Yes Text representation of changes to make

Response Structure

The response includes:

Food Alternatives

import requests
import json

url = 'https://partners.january.ai/v1.1/food-alternatives/70373460'
headers = {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
}
data = {
    "dietRestrictions": ["Gluten"],
    "dietPreferences": ["Vegetarian"]
}

response = requests.post(url, headers=headers, json=data)
print(response.json())
curl --location 'https://partners.january.ai/v1.1/food-alternatives/70373460' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
  "dietRestrictions": ["Gluten"],
  "dietPreferences": ["Vegetarian"]
}'
const axios = require('axios');

const config = {
  method: 'post',
  url: 'https://partners.january.ai/v1.1/food-alternatives/70373460',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  data: {
    dietRestrictions: ["Gluten"],
    dietPreferences: ["Vegetarian"]
  }
};

axios(config)
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.log(error);
  });

The above command returns JSON structured like this:

{
  "alternatives": [
    {
      "food": {
        "name": "brown rice",
        "id": 70372230,
        "brandName": "",
        "nutrients": {
          "calories": {
            "value": 109.2,
            "unit": "calories"
          },
          "protein": {
            "value": 2.26,
            "unit": "grams"
          },
          "carbohydrates": {
            "value": 22.92,
            "unit": "grams"
          },
          "totalFat": {
            "value": 0.81,
            "unit": "grams"
          },
          "fiber": {
            "value": 1.75,
            "unit": "grams"
          }
        },
        "servings": [
          {
            "id": 34113801,
            "quantity": 0.5,
            "unit": "cup"
          }
        ]
      }
    },
    {
      "food": {
        "name": "wild rice",
        "id": 70383579,
        "brandName": "",
        "nutrients": {
          "calories": {
            "value": 165.64,
            "unit": "calories"
          },
          "protein": {
            "value": 6.54,
            "unit": "grams"
          },
          "carbohydrates": {
            "value": 35,
            "unit": "grams"
          },
          "totalFat": {
            "value": 0.56,
            "unit": "grams"
          },
          "fiber": {
            "value": 2.95,
            "unit": "grams"
          }
        },
        "servings": [
          {
            "id": 34201413,
            "quantity": 1,
            "unit": "cup"
          }
        ]
      }
    }
  ]
}

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

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

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 Optional UTC timestamp when food was consumed (YYYY-MM-DDTHH:MM:SSZ). When omitted, the log is stamped with the current server time
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. You pass the user's profile in the request body on every call — nothing is stored, and no setup is required.

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
            }
        }
    ]
}

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
      }
    }
  ]
}'
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
        }
      }
    ]
  }
};

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": 180
}

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. When you do, the history 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:

Send at most one CGM reading per 15-minute window — downsample denser feeds (e.g. 5-minute CGM data) first.

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 — 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 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:

Response Structure

The response includes:

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.

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

Request Errors

Server Errors

Missing x-partner-user-id (400)

A user-scoped endpoint called without the x-partner-user-id header 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.