> ## Documentation Index
> Fetch the complete documentation index at: https://docs-agents.fpt.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Technical appendix

> Error codes, system limits and the pre-production checklist for Live Chat and the API channel

This page collects the lookup tables shared by [Live Chat channel](/en/live-chat-channel), [Mobile SDK](/en/mobile-sdk) and [API integration](/en/api-integration).

## Error codes

Every error from the Visitor API and the history API comes back in the same shape. `code` is a string, not a number - branch on it, never on `message`.

The HTTP status is on the status line and is not repeated in the body. For 5xx errors, `message` is always a fixed sentence; the real cause goes into the platform log. For 4xx errors, `message` is written for you to read and is worth displaying.

### API channel - inbound message webhook

| Status | Meaning | Retry? |
| - | - | - |
| 200 | Accepted | No |
| 400 | Invalid body or over a limit | No - fix the request |
| 401 | Invalid signature or timestamp | No - check the key and the clock |
| 405 | Wrong HTTP method | No |
| 503 | The platform could not accept it | Yes, with backoff |

### Live Chat - Visitor API

| Code | HTTP | Meaning |
| - | - | - |
| `INVALID_ARGUMENT` | 400 | A required parameter is missing, or a value is over its length limit |
| `INVALID_REQUEST_BODY` | 400 | The body could not be parsed, or is larger than allowed |
| `WORKSPACE_ID_NOT_ACCEPTED` | 400 | The body carries an organisation, session or connection identifier. These always come from the token |
| `ORIGIN_NOT_ALLOWED` | 403 | The origin is not on the allow list, or the session request carried no `Origin` header |
| `CONVERSATION_UNAVAILABLE` | 403 | This customer has been blocked |
| `SESSION_FORBIDDEN` | 403 | The token does not belong to the requested session |
| `AGENT_FORBIDDEN` | 403 | The token does not belong to the requested agent |
| `HISTORY_FORBIDDEN` | 403 | The caller may not read history |
| `USAGE_FORBIDDEN` | 403 | Quota inspection is for API keys only |
| `UPLOAD_FORBIDDEN` | 403 | File upload is for `visitorToken` only |
| `FEEDBACK_FORBIDDEN` | 403 | Submitting feedback is for `visitorToken` only |
| `CONNECTION_NOT_FOUND` | 404 | The `connectionKey` does not exist, the channel is not connected, has no agent attached, or has been deleted |
| `CONVERSATION_NOT_FOUND` | 404 | The conversation does not exist, belongs to another organisation, or is internal |
| `AGENT_NOT_FOUND` | 404 | The agent does not exist or is outside the token's scope |
| `ARTIFACT_NOT_FOUND` | 404 | The file does not exist or does not belong to the caller |
| `FILE_TOO_LARGE` | 413 | The file is over 30 MiB |
| `UNSUPPORTED_TYPE` | 415 | The extension is not on the list, or the file contents do not match the extension |
| `RATE_LIMITED` | 429 | Over quota. Read the `Retry-After` header for when to try again |
| `STORAGE_DISABLED` | 503 | File storage is not enabled. Chat still works normally |
| `HISTORY_UNAVAILABLE` | 503 | The history service is temporarily down |

<Note>
  Several 404 codes deliberately cover more than one cause. `CONNECTION_NOT_FOUND` covers four cases, and `ARTIFACT_NOT_FOUND` covers both a missing file and one the caller has no rights to. That is by design, so nobody can probe which resources exist.
</Note>

### Console - channel configuration

| Code | HTTP | Meaning |
| - | - | - |
| `CHANNEL_CONFIG_INVALID` | 400 | Bad configuration: `callback_url` is not https, `idle_window_hours` is outside 1 to 720, or there is an unknown field |
| `CHANNEL_NOT_FOUND` | 404 | The channel does not exist or belongs to another organisation |
| `CHANNEL_NO_AGENT` | 409 | The channel has no agent attached, so it cannot connect |
| `CHANNEL_SECRET_NOT_MINTABLE` | 409 | This channel type does not use a platform-generated secret |
| `API_KEY_ALREADY_REVOKED` | 409 | The API key was already revoked |
| `CALLBACK_TEST_RATE_LIMITED` | 429 | Over 10 callback tests per minute |

## System limits

### API channel - inbound

| Item | Limit |
| - | - |
| `integrationKey`, `messageId`, `user.id` | 256 characters |
| `user.name`, `user.locale` | 256 characters |
| `user.avatar`, `attachments[].url` | 2048 characters |
| `text` | 16,384 characters (not bytes) |
| `postback` | 1024 characters |
| `metadata` | 32 keys, 8192 bytes once JSON-encoded |
| `attachments` | 5 items |
| Whole body | 1 MiB |
| Allowed timestamp drift | ±5 minutes |
| Deduplication window on `messageId` | 5 minutes |

### API channel - callbacks

| Item | Limit |
| - | - |
| Timeout per call | 10 seconds |
| Attempts | 3 |
| Interval between attempts | About 1s, 3s, 9s |
| Maximum `Retry-After` honoured | 30 seconds |
| Follows redirects | Never |
| `callback_url` | 2048 characters, https required, publicly reachable |
| Callback tests from Console | 10 per minute per organisation |

### Live Chat

| Item | Limit |
| - | - |
| `visitorToken` lifetime | 1 hour |
| `POST /v1/runs` body | 256 KiB; at most 5 attachments per turn |
| `POST /v1/visitor/session` body | 16 KiB |
| Attachment size | 30 MiB, one file at a time |
| Allowed extensions | pdf, doc, docx, ppt, pptx, jpg, jpeg, png, gif, svg, webp, heic, jfif, xlsx, xls, csv |
| `visitorName` | 256 bytes on submission, truncated to 64 characters when stored |
| `metadata.postback` | 1024 bytes |
| Feedback `comment` | Truncated to 2000 characters |
| Suggested questions | 6 questions, 120 characters each |
| Allowed domain list | 20 entries, 253 characters each |
| Download link lifetime | 300 seconds |

### Organisation-wide quotas

| Item | Default limit | Notes |
| - | - | - |
| Runs per day | 2000 | Counted across the whole organisation, reset at 00:00 UTC |
| Calls per minute | 20 | Counted per caller |
| History reads per minute | 120 | A separate quota per API key, not counted against the daily limit |
| Delay when revoking an API key | 30 seconds | Because keys are cached |

## Pre-production checklist

### Live Chat channel

* The channel configuration has been saved at least once and you have the embed snippet.
* The snippet sits before the closing `</body>` tag, `data-connection-key` is correct, and `data-app-origin` still carries the `/chat-widget/` path.
* Tested on a phone, not only on a desktop.
* If you use the mobile app: the device `locale` is passed, since the SDK does not read the default language from Console.
* If you build your own interface: your domain is on the allow list, and you do not call with `credentials: "include"`.

### API channel

* Messages go to the webhook host taken from Console (`https://console-agents.fpt.ai/webhooks/api`), not the application host.
* The secret is in a secret store, not in source code or a config file committed to git.
* Servers are clock-synced with NTP. More than 5 minutes out and every packet is rejected.
* The signing function uses the raw bytes of the body, not a re-serialised version.
* Signatures are compared with a constant-time function.
* `messageId` is your own stable identifier and stays the same across retries.
* The webhook returns 200 immediately and processes asynchronously. Each call has only 10 seconds.
* The webhook handles the `type == "verification"` branch and echoes `challenge`.
* **Test callback** has been run from Console and passed.
* Deduplication on `eventId` is in place.
* `conversationId` is stored from every callback, including the delivery acknowledgement.
* A reconciliation loop over the history API compensates for failed callbacks.
* A dedicated API key exists for reading history and is stored safely.
* 429 is handled by reading the `Retry-After` header.
* 503 is handled by retrying with backoff, and is not treated as a rejected message.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.