> For the complete documentation index, see [llms.txt](https://docs.january.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.january.ai/api-v1.1-legacy.md).

# API v1.1 (Legacy)

{% hint style="warning" %}
**v1.1 is deprecated.** It receives no new features and this page exists only for integrations built before v1.2. Everything below keeps working unchanged for existing partners. For new work, switch to API v1.2 — these docs cover it end-to-end, and the interactive reference lives at <https://partners.january.ai/v1.2/docs>. Migration questions: <support@january.ai>.
{% endhint %}

## 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 use `curl`, and example responses are shown as JSON.

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

### Endpoints at a glance

| Endpoint                                            | Method and path                         | `x-partner-user-id`                              |
| --------------------------------------------------- | --------------------------------------- | ------------------------------------------------ |
| [Food Search](#food-search-by-name-or-barcode)      | `GET /v1.1/search/foods`                | Optional                                         |
| [NLP Food Search](#nlp-food-search)                 | `GET /v1.1/search/foods/nlp`            | Optional                                         |
| [Restaurant Search](#restaurant-search)             | `GET /v1.1/search/restaurants`          | Not used                                         |
| [Restaurant Menu Search](#restaurant-menu-search)   | `GET /v1.1/search/restaurants/menu`     | Not used                                         |
| [Photo Scan](#photo-scan)                           | `POST /v1.1/vision/foods`               | Required                                         |
| [Edit Photo Scan Results](#edit-photo-scan-results) | `POST /v1.1/vision/foods/fix-ai`        | Required                                         |
| [Food Alternatives](#food-alternatives)             | `POST /v1.1/food-alternatives/{foodId}` | Not used                                         |
| [Create Food Logs](#create-food-logs)               | `POST /v1.1/logs/foods`                 | Required                                         |
| [Retrieve Food Logs](#retrieve-food-logs)           | `GET /v1.1/logs/foods`                  | Required                                         |
| [Delete Food Log](#delete-food-log)                 | `DELETE /v1.1/logs/foods/{logId}`       | Required                                         |
| [Glucose Prediction](#glucose-prediction)           | `POST /v1.1/cgm/glucose-predict`        | Not used — requires `x-partner-timezone` instead |

Every request also carries the `Authorization: Bearer YOUR_API_KEY` header — see [Authentication](#authentication).

## Quickstart

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

```bash
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"
}'
```

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

```json
{
  "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 above is the fastest way to confirm your credentials work end to end: it sends one meal photo to [Photo Scan](#photo-scan) and returns the foods January detects in it. Don't have an API key yet? [Email us](mailto:support@january.ai) to request one.

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

* `Authorization: Bearer YOUR_API_KEY` — identifies **your organization**.
* `x-partner-user-id: test-7f3c1a` — identifies **the end user** the scan is performed on behalf of.

Requests carry other headers as well — `Content-Type: application/json` here — but these two do the identifying. Every call needs the API key; each endpoint's Headers table says whether it needs the user ID too. See [Authentication](#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.

{% hint style="warning" %}
Never ship a hardcoded `x-partner-user-id` to production. A single fixed value collapses every one of your end users into one January user — their food logs and photo scans merge into one timeline. Send each end user's own stable ID.
{% endhint %}

## Authentication

*Every user-scoped request carries both headers:*

```bash
curl "https://partners.january.ai/v1.1/endpoint" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "x-partner-user-id: YOUR_PARTNER_USER_ID"
```

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](mailto:support@january.ai) with your request.

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

`Authorization: Bearer YOUR_API_KEY`

{% hint style="info" %}
Replace `YOUR_API_KEY` in every code sample with your actual key.
{% endhint %}

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

{% hint style="warning" %}
Your API key is a server-side secret. Call the API from your backend — never from browser or mobile code, where the key is visible to anyone who looks — and load it from an environment variable or secrets manager rather than committing it to source control.
{% endhint %}

### The end user: `x-partner-user-id`

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

`x-partner-user-id: YOUR_PARTNER_USER_ID`

Send an identifier that is:

* **Stable** — the same end user must get the same value on every call, forever. This is the key January stores their data under.
* **Unique per user** within your organization.
* **Opaque — never PII.** Do not send email addresses, phone numbers, names, dates of birth, or medical record numbers. Our partners are health companies; use your internal user ID, or an opaque random ID you map to the user on your side.

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

{% hint style="info" %}
`x-partner-user-id` is **your** identifier for the user, carried through to January — not an ID you have to look up from us.
{% endhint %}

#### Why the header exists

The value scopes everything January stores about that person:

* Food logs written by [Create Food Logs](#create-food-logs) and read back by [Retrieve Food Logs](#retrieve-food-logs)
* Photo scan history and the corrections made through [Edit Photo Scan Results](#edit-photo-scan-results)

Send the wrong value and you read and write the wrong person's data. Omit it on an endpoint that requires it and the request fails with [400 Bad Request](#missing-x-partner-user-id-400).

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)

```bash
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'
```

*The above command returns JSON structured like this:*

```json
{
  "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](#the-end-user-x-partner-user-id). |

#### Query Parameters

| Parameter | Required | Description                                                                                                                                                       |
| --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| query     | Optional | The search term for food items                                                                                                                                    |
| category  | Optional | The category of food items to filter results (`branded`, `general`, or `recipe` — case-sensitive)                                                                 |
| upc       | Optional | The UPC representing the barcode of a food item                                                                                                                   |
| limit     | Optional | Integer 1–400. Default is 10. Name searches (`general`/`branded`) never return more than 40 items                                                                 |
| offset    | Optional | Integer, 0 or greater. Default is 0. For name searches (`general`/`branded`) only the first 40 matches are reachable, so an offset of 40 or more returns no items |

{% hint style="info" %}
Send at most one of `query` and `upc` — sending both returns a 400. `category` defaults to `general`. Out-of-range or non-integer `limit`/`offset` values return a 400 — nothing is clamped.
{% endhint %}

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

### NLP Food Search

```bash
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'
```

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

```json
{
  "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](#the-end-user-x-partner-user-id). |

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

### Restaurant Search

```bash
curl --location 'https://partners.january.ai/v1.1/search/restaurants?query=mcdonalds&lat=37.549&lon=-121.989' \
--header 'Authorization: Bearer YOUR_API_KEY'
```

*The above command returns JSON structured like this:*

```json
{
  "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](#restaurant-menu-search), so check `type` on each item before reading it. Without coordinates, results are always restaurants.

#### HTTP Request

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

#### Headers

| Header        | Required | Description                     |
| ------------- | -------- | ------------------------------- |
| Authorization | Yes      | Bearer token for authentication |

#### Query Parameters

| Parameter | Required | Description                                                                                                                  |
| --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| query     | Yes      | Restaurant name to search for. Pass an empty value to list the nearest restaurants.                                          |
| lat       | Optional | Latitude for proximity ranking, −90 to 90. Must be sent together with `lon`                                                  |
| lon       | Optional | Longitude for proximity ranking, −180 to 180. Must be sent together with `lat`                                               |
| distance  | Optional | Search radius in meters. Default is 16093 (\~10 miles), maximum 17000 — larger values return a 400 rather than being clamped |
| limit     | Optional | Maximum number of results to return. Default is 50, maximum 100 — larger values return a 400                                 |

#### Response Structure

The response includes:

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

### Restaurant Menu Search

```bash
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'
```

*The above command returns JSON structured like this:*

```json
{
  "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, −90 to 90                                                                                         |
| lon       | Yes      | Longitude to search around, −180 to 180                                                                                      |
| distance  | Optional | Search radius in meters. Default is 16093 (\~10 miles), maximum 17000 — larger values return a 400 rather than being clamped |
| limit     | Optional | Maximum number of results to return. Default is 50, maximum 100 — larger values return a 400                                 |

#### Response Structure

The response includes:

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

### Photo Scan

```bash
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"
}'
```

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

```json
{
  "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](#the-end-user-x-partner-user-id). |

#### 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,...`) |

{% hint style="info" %}
Either `photoUrl` or `photoBase64` must be provided. If both are sent, `photoUrl` is used and `photoBase64` is ignored.
{% endhint %}

{% hint style="warning" %}
Request bodies over 5 MiB are rejected with `413 Payload Too Large` before the request reaches the endpoint. Base64 encoding adds roughly a third of overhead, so when sending `photoBase64` keep the raw image under about 3.5 MiB — resize or compress larger photos first.
{% endhint %}

#### Photo Requirements

* JPG, JPEG, PNG, WEBP, or non-animated GIF
* No location or personal metadata
* Well lit and in focus

#### Response Structure

The response includes:

* **mealName**: Name of the meal identified in the image
* **totalNutrients**: Summary of total nutritional content
* **detections**: Array of detected food items with confidence scores

### Edit Photo Scan Results

```bash
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"
}'
```

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

```json
{
  "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](#the-end-user-x-partner-user-id). |

#### Request Body

| Parameter                    | Required | Description                                                                                                                                                              |
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| mealName                     | Yes      | Name of the meal to edit                                                                                                                                                 |
| detections                   | Yes      | Array of detected food items to edit                                                                                                                                     |
| detections\[].food.nutrients | Yes      | Must contain all ten macros: `calories`, `protein`, `carbohydrates`, `netCarbohydrates`, `totalFat`, `saturatedFat`, `fiber`, `totalSugars`, `addedSugars`, and `sodium` |
| detections\[].food.servings  | Yes      | Must be present and non-empty                                                                                                                                            |
| userInput                    | Yes      | Text representation of changes to make                                                                                                                                   |

{% hint style="info" %}
Send back each `food` object exactly as [Photo Scan](#photo-scan) returned it and describe the changes in `userInput` — a partial `nutrients` object fails the request. That failure is currently a generic `500` that doesn't name the missing key, so if you assemble the object yourself, check all ten keys client-side first.
{% endhint %}

#### Response Structure

The response includes:

* **mealName**: Modified name of the meal
* **totalNutrients**: Summary of total nutritional content
* **detections**: Array of modified food items with confidence scores

### Food Alternatives

```bash
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"]
}'
```

*The above command returns JSON structured like this:*

```json
{
  "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](#food-search-by-name-or-barcode), [NLP Food Search](#nlp-food-search), and [Photo Scan](#photo-scan) all return the same food IDs.

#### HTTP Request

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

#### Headers

| Header        | Required | Description                     |
| ------------- | -------- | ------------------------------- |
| Authorization | Yes      | Bearer token for authentication |
| Content-Type  | Yes      | Must be `application/json`      |

#### URL Parameters

| Parameter | Description                                                                        |
| --------- | ---------------------------------------------------------------------------------- |
| foodId    | The ID of the food to find alternatives for. An unknown ID returns `404 Not Found` |

#### Request Body

| Parameter        | Required | Description                                                                                                                                          |
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| dietRestrictions | Yes      | Array of the user's allergies and intolerances, from [Dietary Restrictions](#dietary-restrictions). Send `["None"]` if the user has no restrictions. |
| dietPreferences  | Yes      | Array of the user's diet styles, from [Dietary Preferences](#dietary-preferences). Send `["None"]` if the user has no preferences.                   |

{% hint style="warning" %}
Both arrays are required and must be non-empty — to opt out, send `["None"]`, not `[]`. A single unrecognized value fails the whole request with `400 Bad Request` and a `message` naming the invalid value.
{% endhint %}

#### 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'`.

{% hint style="warning" %}
Alternatives are AI-generated. `dietRestrictions` guides how alternatives are selected, but January cannot guarantee that every returned food is 100% free of a restricted ingredient or allergen. If you surface alternatives to users with food allergies, show an appropriate disclaimer and screen suggestions on your side before presenting them as safe to eat.
{% endhint %}

#### 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](#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

```bash
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"
}'
```

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

```json
{
  "id": "78129823-8ba2-4183-b13b-71f0e963c606",
  "foods": [
    {
      "id": 101963552,
      "name": "Greek Yogurt, Plain, Whole Milk",
      "brandName": null,
      "imageUrl": null,
      "glycemicIndex": 11.3,
      "glycemicLoad": 1.4,
      "nutrients": {
        "calories": {
          "value": 308,
          "unit": "calories"
        },
        "protein": {
          "value": 28,
          "unit": "g"
        },
        "carbohydrates": {
          "value": 12.6,
          "unit": "g"
        },
        "netCarbohydrates": {
          "value": 12.6,
          "unit": "g"
        },
        "totalFat": {
          "value": 15.4,
          "unit": "g"
        },
        "saturatedFat": {
          "value": 9.8,
          "unit": "g"
        },
        "totalSugars": {
          "value": 12.6,
          "unit": "g"
        },
        "sodium": {
          "value": 119,
          "unit": "mg"
        },
        "calcium": {
          "value": 322,
          "unit": "mg"
        },
        "potassium": {
          "value": 420,
          "unit": "mg"
        }
      },
      "consumedServing": {
        "id": 68051535,
        "quantity": 1.4
      },
      "servingDetails": {
        "id": 68051535,
        "quantity": 1,
        "unit": "cup",
        "weightGrams": 245
      }
    }
  ],
  "timestampUtc": "2024-09-13T11:34:56Z",
  "name": null
}
```

Log one or more foods to a user's food diary at a given time. Food and serving IDs come from the [search](#food-search-by-name-or-barcode) and [photo scan](#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](#the-end-user-x-partner-user-id). |

#### Request Body

| Parameter                 | Required | Description                                                                                                                                                                                                                                                                                      |
| ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| foods                     | Yes      | Array of food items to be logged — at least one entry                                                                                                                                                                                                                                            |
| foods\[].id               | Yes      | Unique identifier of the food item, as a JSON number (a quoted `"123"` returns a 400)                                                                                                                                                                                                            |
| foods\[].serving          | Yes      | Serving details of the food item                                                                                                                                                                                                                                                                 |
| foods\[].serving.id       | Yes      | Serving size identifier, as a JSON number                                                                                                                                                                                                                                                        |
| foods\[].serving.quantity | Yes      | Amount of the serving consumed                                                                                                                                                                                                                                                                   |
| timestampUtc              | Optional | UTC timestamp when food was consumed, ending in a literal `Z` — e.g. `2024-09-13T11:34:56Z` (seconds and fractional seconds optional). A timestamp with a UTC offset (`+02:00`), without the `Z`, or as a date only returns a 400. When omitted, the log is stamped with the current server time |
| name                      | Optional | Name of the meal. Omit optional fields entirely rather than sending `null` — explicit nulls return a 400                                                                                                                                                                                         |

{% hint style="info" %}
Omitting `timestampUtc` stamps the log with the current server time — always send it when logging past meals (backfills, imports, offline queues).
{% endhint %}

#### Response Structure

Success is `200 OK` with the created log:

| Field        | Type           | Description                                                                                        |
| ------------ | -------------- | -------------------------------------------------------------------------------------------------- |
| id           | string         | Unique ID of the created log. Save it — it's the `{logId}` for [Delete Food Log](#delete-food-log) |
| foods        | array          | The logged foods, hydrated with names, nutrients, and serving details (see below)                  |
| timestampUtc | string         | The timestamp the log was stored with                                                              |
| name         | string or null | Meal name; `null` when `name` was not sent                                                         |

#### Logged Food Fields

| Field           | Type           | Description                                                                                        |
| --------------- | -------------- | -------------------------------------------------------------------------------------------------- |
| id              | number         | Food item ID                                                                                       |
| name            | string         | Food name                                                                                          |
| brandName       | string or null | Brand name; null for generic foods                                                                 |
| imageUrl        | string or null | Food photo URL; null when no image is available                                                    |
| glycemicIndex   | number or null | Glycemic index                                                                                     |
| glycemicLoad    | number or null | Glycemic load                                                                                      |
| nutrients       | object         | Nutrients scaled to the consumed serving and quantity (see below)                                  |
| consumedServing | object         | Echo of what you logged: `id` and `quantity`                                                       |
| servingDetails  | object         | The consumed serving's definition: `id`, `unit`, `quantity`, and `weightGrams` (null when unknown) |

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

### Retrieve Food Logs

```bash
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'
```

*The above command returns `200 OK` with a JSON array of log objects — the same shape* [*Create Food Logs*](#create-food-logs) *returns. Trimmed here to one log with one food:*

```json
[
  {
    "id": "78129823-8ba2-4183-b13b-71f0e963c606",
    "foods": [
      {
        "id": 101963552,
        "name": "Greek Yogurt, Plain, Whole Milk",
        "brandName": null,
        "imageUrl": null,
        "glycemicIndex": 11.3,
        "glycemicLoad": 1.4,
        "nutrients": {
          "calories": {
            "value": 308,
            "unit": "calories"
          },
          "protein": {
            "value": 28,
            "unit": "g"
          },
          "carbohydrates": {
            "value": 12.6,
            "unit": "g"
          }
        },
        "consumedServing": {
          "id": 68051535,
          "quantity": 1.4
        },
        "servingDetails": {
          "id": 68051535,
          "quantity": 1,
          "unit": "cup",
          "weightGrams": 245
        }
      }
    ],
    "timestampUtc": "2024-09-13T11:34:56Z",
    "name": null
  }
]
```

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](#the-end-user-x-partner-user-id). |

#### Query Parameters

| Parameter | Required | Description                                                                                                      |
| --------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| start     | Yes      | Start date, strictly `YYYY-MM-DD` (zero-padded) — other formats return a 400                                     |
| end       | Yes      | End date, strictly `YYYY-MM-DD` (zero-padded), and strictly after `start` — `start` equal to `end` returns a 400 |

{% hint style="info" %}
`start` must be earlier than `end`. Future end dates are allowed.
{% endhint %}

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

#### Response Structure

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

### Delete Food Log

```bash
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'
```

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](#the-end-user-x-partner-user-id). |

#### Response Structure

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

## Glucose Insights APIs

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

### Glucose Prediction

```bash
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
      }
    }
  ]
}'
```

*The above command returns JSON structured like this:*

```json
{
  "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. Like the profile, the history is used only to compute this one response — it is not stored, and there is no separate setup or training step to run first. When you provide history, it must include at least five complete training days — otherwise the request fails with `400 Bad Request` and a message starting with "Not enough good days for AI Training".

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

* At least 12 hours of CGM data (48 15-minute windows — they don't need to be contiguous)
* At least 2 logged meals in distinct 15-minute windows

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

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

#### HTTP Request

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

#### Headers

| Header             | Required | Description                                                                                                                                                      |
| ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authorization      | Yes      | Bearer token for authentication                                                                                                                                  |
| Content-Type       | Yes      | Must be `application/json`                                                                                                                                       |
| x-partner-timezone | Yes      | User's local IANA timezone (e.g. `America/New_York`). Defines the user's local calendar days when evaluating optional history; an invalid name fails the request |

#### Request Body

| Parameter                         | Required | Description                                                                                                                                                                                |
| --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| userProfile                       | Yes      | User's profile data                                                                                                                                                                        |
| userProfile.age                   | Yes      | User's age                                                                                                                                                                                 |
| userProfile.gender                | Yes      | Exactly `male` or `female`, lowercase. Other values and casings (e.g. `Male`) are not rejected — the prediction silently ignores gender instead, so match the exact values                 |
| userProfile.height                | Yes      | User's height in inches                                                                                                                                                                    |
| userProfile.weight                | Yes      | User's weight in pounds                                                                                                                                                                    |
| userProfile.activity\_level       | No       | Activity level ('sedentary', 'lightly\_active', 'moderately\_active', 'very\_active'). Case-sensitive — `Sedentary` returns a 400                                                          |
| userProfile.medical\_conditions   | No       | Array of medical conditions (excluding 'Type 1 diabetes'). Values are case-sensitive and must match the list below exactly                                                                 |
| foods                             | Yes      | Array of foods and servings for prediction. An empty array is not rejected but produces a meal-less baseline curve — always send at least one food                                         |
| foods\[].id                       | Yes      | Unique identifier of the food item                                                                                                                                                         |
| foods\[].serving                  | Yes      | Serving details                                                                                                                                                                            |
| foods\[].serving.id               | Yes      | Serving size identifier                                                                                                                                                                    |
| foods\[].serving.quantity         | Yes      | Amount of serving consumed                                                                                                                                                                 |
| startTime                         | Yes      | ISO 8601 timestamp for when foods are consumed. Always include a timezone designator (`Z` or an offset) — a timestamp without one is read as UTC, not in `x-partner-timezone`              |
| cgmData                           | No       | CGM history used to personalize this prediction (in-request only; not stored) — at most one reading per 15-minute window. If `cgmData` is provided, `consumedFoods` must be provided also. |
| cgmData\[].timestamp              | Yes      | ISO 8601 timestamp for the CGM data point                                                                                                                                                  |
| cgmData\[].value                  | Yes      | Blood sugar level (in mg/dL)                                                                                                                                                               |
| consumedFoods                     | No       | Array of historical user foods and servings, paired with `cgmData` to personalize this prediction (in-request only; not stored).                                                           |
| consumedFoods\[].timestamp        | Yes      | ISO 8601 timestamp for when the food is consumed                                                                                                                                           |
| consumedFoods\[].id               | Yes      | Unique identifier of the food item                                                                                                                                                         |
| consumedFoods\[].serving          | Yes      | Serving details                                                                                                                                                                            |
| consumedFoods\[].serving.id       | Yes      | Serving size identifier                                                                                                                                                                    |
| consumedFoods\[].serving.quantity | Yes      | Amount of serving consumed                                                                                                                                                                 |

#### Medical Conditions

Valid medical conditions include:

* Type 2 diabetes
* Prediabetes
* None of the above

{% hint style="warning" %}
Glucose Predictions are not compatible with users who report having Type 1 Diabetes or who report taking Insulin. Requests with 'Type 1 diabetes' in `medical_conditions` are rejected with a `400 Bad Request`.
{% endhint %}

#### Response Structure

The response includes:

* **cgp**: Array of data points representing glucose response curve \[minute, glucose\_value]
* **scoring**: Qualitative impact scoring ('low\_impact', 'medium\_impact', 'high\_impact')
* **cgp\_min**: Lower bound of the target glucose range. Always 70 mg/dL.
* **cgp\_max**: Upper bound of the target glucose range: 180 mg/dL when `medical_conditions` includes 'Type 2 diabetes', otherwise 140.

## Reliability

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

### Timeouts

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

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

### Retrying safely

| Endpoint                                                                                                      | After a timeout or 5xx                                                                                                                                                                                                                                                                                    |
| ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| All four search endpoints, [Food Alternatives](#food-alternatives), [Retrieve Food Logs](#retrieve-food-logs) | Retry freely — read-only                                                                                                                                                                                                                                                                                  |
| [Glucose Prediction](#glucose-prediction)                                                                     | Retry freely — stateless, and identical input returns an identical curve                                                                                                                                                                                                                                  |
| [Delete Food Log](#delete-food-log)                                                                           | Retry freely — idempotent by design; deleting an already-deleted log also succeeds                                                                                                                                                                                                                        |
| [Edit Photo Scan Results](#edit-photo-scan-results)                                                           | Retry freely — stores nothing                                                                                                                                                                                                                                                                             |
| [Photo Scan](#photo-scan)                                                                                     | Retry with care: every attempt, including failed ones, counts against the per-user daily scan allowance. Use one or two bounded retries with backoff                                                                                                                                                      |
| [Create Food Logs](#create-food-logs)                                                                         | **Not safe to retry blindly** — a retry after an ambiguous timeout can write a duplicate log entry. On timeout, call [Retrieve Food Logs](#retrieve-food-logs) for the date and check whether the log landed before re-creating it; remove accidental duplicates with [Delete Food Log](#delete-food-log) |

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

### Rate limits

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

Three things about the buckets are worth knowing:

* **Some endpoints share one budget:** [Photo Scan](#photo-scan) and [Edit Photo Scan Results](#edit-photo-scan-results) draw from the same allowance; so do the two restaurant search endpoints; so do all three Food Logging operations.
* **Two per-end-user daily allowances apply on top of your partner limits:** photo scans (150 per user per day) and glucose predictions (500 per user per day).
* **Windows are fixed**, anchored at the first request in the window — and requests sent while over the limit still count, so hammering a 429 never shortens the wait.

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

### Error format

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

## Errors

The January AI API uses conventional HTTP status codes:

| Error Code | Meaning                                                                                                                                                                                                                       |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400        | Bad Request -- Your request is invalid or malformed. Most often this means the `x-partner-user-id` header is missing on an endpoint that requires it — see [Missing x-partner-user-id (400)](#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:*

```json
{
  "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

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

### Common Error Scenarios

#### Authentication Errors

* **401 Unauthorized**: Missing or invalid API key
* **403 Forbidden**: Valid API key but insufficient permissions

#### Request Errors

* **400 Bad Request**: Missing required parameters or invalid parameter values — most commonly a missing [`x-partner-user-id` header](#missing-x-partner-user-id-400)
* **422 Unprocessable Entity**: Valid request format but business logic errors
* **429 Too Many Requests**: Rate limiting exceeded

#### Server Errors

* **500 Internal Server Error**: Unexpected server-side error
* **503 Service Unavailable**: Temporary service outage

#### Missing `x-partner-user-id` (400)

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

```json
{
  "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](#the-end-user-x-partner-user-id) for what value to send, and each endpoint's **Headers** table for whether it requires the header.

{% hint style="info" %}
An empty or whitespace-only `x-partner-user-id` is treated as missing, and returns the same 400.
{% endhint %}

### Questions?

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