---
title: "Errors"
description: "Handle Lookio API failures with one error format, stable error codes, request IDs and retry guidance, with an explanation for every code."
canonical: "https://lookio.app/docs/api/errors"
updated: "2026-11-03"
---

# Errors

Every failure of the API uses one envelope. Branch on `error.code`, never on the message.

```json title="Example error"
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This credential does not have the 'resources:write' scope. Grant it at https://dashboard.lookio.app/developer on the workspace that owns the credential, or use a credential that already has it.",
    "docs_url": "https://lookio.app/docs/api/errors#insufficient_scope"
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}
```

| Field              | Meaning                                                                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `error.type`       | The family of the failure.                                                                                          |
| `error.code`       | The stable code. Adding a code is not a breaking change, so handle unknown codes with their `type` and HTTP status. |
| `error.message`    | A sentence that names the offending value and, where possible, the fix. Do not parse it.                            |
| `error.param`      | The field at fault, when there is one.                                                                              |
| `error.docs_url`   | The explanation of the code, on this page.                                                                          |
| `error.action_url` | A link to fix it, when there is one, such as the Permissions page.                                                  |
| `request_id`       | Also in the `X-Request-Id` header. Keep it in your logs.                                                            |

The error types are `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `conflict_error`, `rate_limit_error` and `api_error`.

A `401` also carries a `WWW-Authenticate` header, a `405` carries `Allow` and a `429` carries `Retry-After`.

> **Note**
>
> A streamed answer reports a failure that happens after the first event as an `error` event carrying this same envelope, not as an HTTP status. See [Streaming](/docs/api/streaming).

## Status codes

| Status   | Meaning                                           | What to do                                 |
| -------- | ------------------------------------------------- | ------------------------------------------ |
| 400      | Malformed request, unknown field, bad value       | Fix the request.                           |
| 401      | Credential missing, invalid, expired or revoked   | Replace or renew it.                       |
| 402      | Not enough credits                                | Add credits, then ask again.               |
| 403      | Authenticated but not allowed                     | Grant the scope or permission.             |
| 404      | Absent, or in another workspace                   | Check the ID and the workspace.            |
| 409      | The current state conflicts with the request      | Read the resource, then decide.            |
| 405      | The endpoint exists, but not for this HTTP method | Use a method listed in the `Allow` header. |
| 413      | Body or file too large                            | Send less.                                 |
| 422      | Valid request that the plan cannot take           | Change the plan or the content.            |
| 429      | Rate limited                                      | Wait for `Retry-After`.                    |
| 500, 503 | Lookio failed, or is briefly unavailable          | Retry with backoff.                        |

**Fix before retrying**

A 400, 401, 402, 403, 404, 405, 409, 413 or 422 needs a different credential, ID, state or request. Retrying it unchanged fails again.

**Retry safely**

Retry a 429 after `Retry-After`. Retry a 500 or 503 with exponential backoff and random jitter, and send an `Idempotency-Key` on a POST.

A resource of another workspace answers the same `resource_not_found` as one that does not exist, so the API never reveals what other workspaces hold.

## Authentication codes

### authentication\_required

No bearer credential was sent. Add the `Authorization: Bearer` header.

### invalid\_api\_key

The credential is malformed or unknown, or an OAuth token was used on a resource it was not issued for. Check that the whole `sk_live_...` value was copied.

### invalid\_access\_key

An access (`/a/{access_id}`) refused the key: it is malformed, revoked, belongs to another access, or is a workspace API key (`sk_live_...`) where an access key (`ak_live_...`) is needed. Ask the access's owner for a key.

### api\_key\_expired

The credential passed its expiry date. Create a replacement.

### api\_key\_revoked

An admin revoked or rotated the key, or the person who authorised a connection is no longer a member of the workspace. Use an active credential.

## Permission codes

### insufficient\_scope

The credential is valid but lacks the scope this endpoint needs. The message names the scope. See [Authentication](/docs/api/authentication#scopes).

### feature\_not\_available

The plan or the kind of credential does not include this. Granular member permissions need a paid plan, and thumbs on an answer ([Send feedback on an answer](/docs/api/conversations/submit-answer-feedback)) work for an API key's conversations, not yet for a person's own chats.

### admin\_required

Only a workspace admin may do this, for example renaming a tag or listing members. Use a personal connection signed in by an admin.

### permission\_denied

The person behind a connection lacks a Lookio permission, such as creating resources, or using the API or MCP on a paid plan. The error carries an `action_url` to the Permissions page. Ask a workspace admin.

## Request codes

### invalid\_request

The request cannot be understood: the JSON is malformed, a required value is missing, or two options exclude each other, such as `conversation_id` with `history`.

### unknown\_field

A property of the JSON body is not recognised. The message suggests the closest valid field. Unknown _query_ parameters never cause this error: they are ignored.

### invalid\_parameter

A value is invalid. `error.param` names the field. A bad `Idempotency-Key` also lands here.

### invalid\_cursor

The `cursor` matches no resource. Start again from the first page.

### request\_too\_large

The JSON body is over 1 MB, or a file is over the limit of its format. See [File formats](/docs/platform/file-formats).

### method\_not\_allowed

The path is an endpoint, but not for this HTTP method. The status is `405` and the `Allow` header lists the methods it does answer, for example `GET, POST` on `/v1/resources`.

## Not found and conflict codes

### resource\_not\_found

The ID does not exist in this workspace, or the caller may not see it. This covers resources, assistants, widgets, conversations, members and tags, and a path that is not an endpoint.

### conflict

The request is valid but the current state forbids it: a resource that an assistant still uses cannot be deleted, an assistant that a widget still uses cannot be deleted, a site action was sent to a resource that is not a site, or an `Idempotency-Key` was reused for a different request or is still running. Read the state, then decide.

### assistant\_has\_no\_resources

The assistant reads no resource, so it cannot answer: none is connected (a **Selected** pick that is empty, a **Tags** mode with a tag nobody carries, or **All** in a workspace with no resource), or none is `ready` yet. The question is refused before any credit is held and before any model runs, so it costs nothing. The status is `409`. Connect resources to the assistant (or wait for them to be ready), then ask again. Every question that does reach the model is charged, even when it finds nothing.

## Credit and plan codes

### insufficient\_credits

The balance cannot cover the price of the answer's mode. Add credits, then ask again. The status is `402`, and `error.action_url` links to the pricing page.

### request\_too\_large\_for\_plan

Adding this content would pass your plan's words-stored limit. Delete resources or change plan. See [Word allowance](/docs/platform/resources#word-allowance).

## Rate limit code

### rate\_limited

Too many requests. Wait for the number of seconds in `Retry-After`. See [Rate limits](/docs/api/rate-limits).

## Server codes

### internal\_error

Something failed on Lookio's side. Retry with backoff, and contact support with the `request_id` if it persists.

### service\_unavailable

A dependency is unreachable: the engine, file storage, or a maintenance window. Retry with backoff.
