> 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/client-sdks/web-sdk/reference/error-handling.md).

# Error handling

API and network failures reject with `JanuaryError`. Branch on `category`, and use `code` for detail:

```ts
import { JanuaryError } from '@januaryai/web-sdk';

const controller = new AbortController();
try {
  await user.foods.search({ query: 'banana', signal: controller.signal });
} catch (error) {
  if (controller.signal.aborted) {
    // Your code canceled the request; not a failure.
  } else if (error instanceof JanuaryError) {
    switch (error.category) {
      case 'validation': /* show error.message next to the input */ break;
      case 'notFound': /* fall back */ break;
      case 'rateLimit': /* back off */ break;
      case 'authentication':
      case 'authorization': /* check the session and the token's scopes */ break;
      default: /* show a generic error; log error.requestId */
    }
  } else {
    throw error;
  }
}
```

## Categories

| Category         | HTTP status                      | Common `code` values                                                                                                                                              | What to do                                                                                                                                                        |
| ---------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation`     | 400, 422                         | `invalid_request`, `date_range_too_large`, `daily_water_limit_exceeded`, `image_unreachable`, `image_corrupt`, `image_format_unsupported`, `image_invalid_base64` | Fix the input. Sending the same request again fails the same way.                                                                                                 |
| `authentication` | 401, or none                     | `unauthorized`, `token_invalid`, `token_revoked`. With no status: the token provider ran out of retries or returned an unusable token.                            | Check your token endpoint and the app session. The SDK already handles `token_expired`.                                                                           |
| `authorization`  | 403                              | `scope_insufficient`, `end_user_id_mismatch`, `forbidden`                                                                                                         | Mint the token with the scopes the call needs ([scopes](/client-sdks/web-sdk/getting-started/backend-token-endpoint.md#what-the-web-sdk-needs)).                  |
| `notFound`       | 404                              | `not_found`                                                                                                                                                       | Fall back, for example to text search after a barcode miss.                                                                                                       |
| `rateLimit`      | 429                              | `rate_limited`, `request_limit_exceeded`, `credit_limit_exceeded`                                                                                                 | Back off on `rate_limited`. The other two last until your billing period resets ([Credits](/rest-api/credits.md)).                                                |
| `server`         | 500–599                          | `internal_error`, `upstream_error`, `service_unavailable`, `upstream_timeout`                                                                                     | Retry reads with backoff. Before retrying a create, list the day to check it didn't go through.                                                                   |
| `transport`      | none                             | none                                                                                                                                                              | The request didn't complete or never started: a network failure, an origin January hasn't enabled, a failing token provider, or invalid log input. Check `cause`. |
| `unknown`        | Any other, including 409 and 413 | `conflict`, `payload_too_large`                                                                                                                                   | Show a generic error and log `requestId`.                                                                                                                         |

`status`, `code`, and `requestId` are set when January answered. All codes are listed in [REST errors](/rest-api/api-overview.md#errors). Log `category`, `status`, `code`, and `requestId`; never log tokens, meal images, nutrition data, or health profiles.

## Invalid input

Most invalid requests throw a `TypeError` before anything is sent: food, restaurant, and food-analysis requests, `foodLogs.update` with no fields or a bad `timestampUtc`, and `forUser` with a blank end-user ID. `FoodPortion.from` throws `FoodPortionError`, and `preparePhotoScanImage` throws `TypeError` or `RangeError`.

In 0.3.1, invalid input to the other food-, water-, and weight-log calls and to glucose timestamps is also caught before anything is sent, but it rejects as a `JanuaryError` with category `transport` whose `cause` is the `TypeError`. Examples are an impossible date such as `2026-02-31`, an unknown unit, and a timestamp that isn't a date.

## Cancellation

A request canceled through its `signal` rejects with an `AbortError` (`error.name === 'AbortError'`), not with a `JanuaryError`. Check your signal's `aborted` flag first, as the sample does, so cancellation isn't handled as a failure. An `AbortError` thrown by your token provider also reaches the caller unchanged.
