# Errors

Page: https://docs.sonecentral.com/en/hatalar
Markdown: https://docs.sonecentral.com/en/hatalar.md

> Error response formats, every error code, retry rules and recovery steps.

Errors use OpenAI's error format. OpenAI SDKs convert them into their own error classes.

This format applies to the documented JSON endpoints. Unknown paths or incorrect HTTP methods return PocketBase's `404` format with `status`, `message` and `data`, without an `error` field.

```json
{
  "error": {
    "message": "Bu tarayıcı anahtarı bu sitede kullanılamaz. Siteyi anahtarın izinli siteleri arasına ekleyin.",
    "type": "permission_error",
    "param": null,
    "code": "origin_not_allowed"
  }
}
```

- `message` is a simple, visitor-facing **Turkish** explanation. The example above preserves the actual server language.
- `code` is stable and English. Always branch on `code`, not `message`.
- `param` identifies the invalid field, such as `model`, `messages`, `input` or `after`, or is `null` when no field applies.

`POST /chat` is the exception: its body is plain Turkish text containing the message. Vercel AI SDK exposes it as `useChat`'s `error.message`. The code appears in `X-Sone-Error-Code`; this header is available to server requests, but is not exposed to browser code.

## Codes

Do not repeat the same request when `x-should-retry` is `false`. `Retry-After` is the wait specified by that header.

| Status | `code` | `x-should-retry` | `Retry-After` | Meaning and recovery |
| --- | --- | --- | --- | --- |
| `400` | `invalid_body` | none | none | Body is empty or unreadable. Send a JSON body. |
| `400` | `invalid_json` | none | none | Invalid JSON or an incorrect field type. See message for details. |
| `400` | `missing_user_message` | none | none | The final message must be a user message containing text. |
| `400` | `invalid_role` | none | none | Unknown message role, or a Responses message item without a role. |
| `400` | `invalid_content` | none | none | Message content is not a string or an array of parts. |
| `400` | `unsupported_content` | none | none | A new message contains an image, audio or file. Send text only. |
| `400` | `message_too_long` | none | none | A new message exceeds 4,000 characters. Shorten it. |
| `400` | `too_many_new_messages` | none | none | More than 12 new visitor messages in one request. Include the assistant reply in history before continuing, or start a new conversation. |
| `400` | `tools_not_supported` | none | none | Remove tools, functions or function-call items. The assistant uses the business tools configured in Sonecentral. |
| `400` | `unsupported_parameter` | none | none | n is not 1, response_format or text.format is not text, or background is true. param identifies the field. |
| `400` | `missing_input` | none | none | Responses request has no input. |
| `400` | `invalid_input` | none | none | Responses input is not text or an array of items. |
| `400` | `unsupported_item` | none | none | Unknown Responses input item type. |
| `400` | `conflicting_parameters` | none | none | previous_response_id and conversation were supplied together. Use one. |
| `400` | `instructions_too_long` | none | none | Responses instructions exceeds 2,000 characters or 4 KB JSON. |
| `400` | `invalid_metadata` | none | none | Responses metadata exceeds 16 keys, 64 characters per key, 512 characters per string value, or 4 KB JSON. Values must be strings. |
| `400` | `safety_identifier_too_long` | none | none | Responses safety_identifier exceeds 64 characters. |
| `400` | `user_too_long` | none | none | Responses user exceeds 64 characters. |
| `400` | `previous_response_id_too_long` | none | none | Trimmed previous_response_id is longer than a response ID. Send the original response id. |
| `400` | `item_id_too_long` | none | none | An item_reference ID exceeds 64 characters. |
| `400` | `conversation_model_mismatch` | `false` | none | The conversation belongs to another assistant. Use the same model or start a new conversation. |
| `400` | `conversation_full` | `false` | none | The conversation has reached 200 visitor messages. Start a new one. |
| `400` | `invalid_cursor` | none | none | after is not a message ID in this conversation. Clear the cursor and read from the start. |
| `400` | `not_cancellable` | none | none | Responses complete within the request; there is no background task to cancel. |
| `401` | `missing_api_key` | none | none | No key supplied. |
| `401` | `invalid_api_key` | none | none | Unknown or malformed key, or prefix does not match its type. |
| `401` | `revoked_api_key` | none | none | The key was revoked. Create a new one. |
| `402` | `plan_required` | `false` | none | The workspace plan does not permit assistant replies. The assistant did not reply. |
| `403` | `origin_not_allowed` | none | none | Publishable key used without Origin or outside its allowed origins. Add the site to the key. |
| `403` | `secret_key_in_browser` | none | none | A server key was used from a browser. Use a publishable key. |
| `404` | `model_not_found` | none | none | No published assistant with this ID is available to the key. GET /models lists available assistants. |
| `404` | `conversation_not_found` | none | none | Not an API conversation in this workspace, or its assistant is unavailable to the key. Clear the saved ID and start a new conversation. |
| `404` | `previous_response_not_found` | none | none | The referenced response is missing, deleted, was created with store: false, or belongs to an assistant unavailable to the key. |
| `404` | `response_not_found` | none | none | Stored response is missing, deleted, older than 30 days or unavailable to the key. On DELETE, also returned when another key stored it. |
| `404` | `file_not_found` | none | none | Invalid or expired signed URL. Read conversation messages again to obtain a fresh URL. |
| `409` | `conversation_busy` | none | 2 | The previous turn is still running. This message was not stored. Wait and retry with the same Idempotency-Key. |
| `413` | `request_too_large` | none | none | Body exceeds 1 MB. Send the conversation ID instead of the full history. |
| `422` | `idempotency_key_reused` | `false` | none | The same Idempotency-Key was used with different text. Use a new key for each new message. |
| `429` | `rate_limit_exceeded` | none | Window-dependent | Request or read limit exceeded. Wait for Retry-After. |
| `429` | `ai_turn_ceiling` | `false` | 300 | The workspace hourly/daily API reply limit, or its publishable-key share, was reached. The assistant did not reply. |
| `429` | `assistant_quota_exhausted` | `false` | none | Sonecentral AI capacity is temporarily unavailable. Your workspace has not exhausted a plan allowance; API replies do not consume it. The assistant did not reply. |
| `429` | `file_download_limit` | none | Window-dependent | This file ID was downloaded 10 times in the last hour. Wait for Retry-After. |
| `429` | `file_egress_limit` | none | Until midnight UTC | Workspace daily file download limit reached. |
| `503` | `assistant_paused` | If delivered to team `false` | 60 | Automatic replies are disabled. The assistant did not reply. |
| `503` | `assistant_unavailable` | none | 30 | The assistant is temporarily unavailable. Wait and retry with the same Idempotency-Key. |
| `503` | `conversation_not_created` | none | none | The conversation could not be created. Retry later. |
| `503` | `message_not_stored` | none | none | The message could not be stored. Retry with the same Idempotency-Key. |
| `503` | `messages_unavailable` | none | none | Conversation messages could not be read. Retry on the next poll. |
| `503` | `file_unavailable` | none | none | The file is temporarily unavailable. Retry later. |
| `503` | `response_not_deleted` | none | none | The stored response could not be deleted. Retry later. |

`Retry-After` is measured in seconds and rounded up.

Two other cases may use a different body: more than 120 file-download requests per minute from one address returns a non-OpenAI `429`; unexpected server errors may also have another format. Retry unexpected `5xx` responses with the same `Idempotency-Key`.

### Where messages go when the assistant cannot reply

`plan_required`, `assistant_quota_exhausted`, `ai_turn_ceiling` and `assistant_paused` mean the assistant could not reply:

- **Existing conversation:** the visitor message is stored and delivered to the team. The Turkish `message` says it was forwarded, and for `assistant_quota_exhausted` also says a team member will reply. Read replies from [conversation messages](https://docs.sonecentral.com/en/insan-devri.md#asistan-yanit-vermediginde).
- **First message:** nothing is stored and no conversation is created. The Turkish `message` asks the visitor to try later.

`assistant_unavailable` also has two cases. If the turn never started, nothing happened and the message asks the visitor to retry later. If the turn started and then failed, the visitor message was stored and the explanation says it was forwarded to the team. Retrying with the same key does not store it twice and lets the assistant answer it.

## `type` values

| `type` | Errors |
| --- | --- |
| `invalid_request_error` | `400`, `404`, `409`, `413`, `422` |
| `authentication_error` | `401` |
| `permission_error` | `403` |
| `insufficient_quota` | `402 plan_required`, `429 assistant_quota_exhausted` |
| `rate_limit_error` | `429 rate_limit_exceeded`, `ai_turn_ceiling`, `file_download_limit`, `file_egress_limit` |
| `server_error` | `503` |

## Response headers

| Header | Purpose |
| --- | --- |
| `x-request-id` | Request ID, present on every response. Include it when contacting support. |
| `retry-after` | Seconds to wait before retrying. |
| `x-should-retry` | `false` means do not repeat the same request. OpenAI SDKs respect it automatically. |
| `x-sone-conversation-id` | Conversation ID, included in turn responses and at the start of streams. |
| `x-sone-error-code` | Error code for `POST /chat` only. Not exposed to browser code. |

Browser code can read `x-request-id`, `retry-after`, `x-should-retry` and `x-sone-conversation-id`.

## Retrying

- Do not retry when `x-should-retry: false`. This applies to `plan_required`, `assistant_quota_exhausted`, `ai_turn_ceiling`, `idempotency_key_reused`, `conversation_model_mismatch`, `conversation_full`, and `assistant_paused` when the message reached the team.
- If `Retry-After` is present without `x-should-retry: false`, wait that long. It is provided for `rate_limit_exceeded`, `conversation_busy`, `assistant_paused`, `assistant_unavailable`, `file_download_limit` and `file_egress_limit`. `ai_turn_ceiling` also includes it, but must not be retried automatically.
- For `503` without `Retry-After`, or a network failure, resend the same request with the same `Idempotency-Key`. Back off for 1, 2, 4 and 8 seconds. The message is not stored twice. See [Retries](https://docs.sonecentral.com/en/sohbet-surekliligi.md#tekrar-denemek), including the publishable-key network-address exception.
- `conversation_busy` is a race: two turns were sent concurrently. Disable sending until the current turn finishes.
- Other `4xx` errors require correcting the request.

OpenAI SDKs retry `408`, `409`, `429` and `5xx` twice by default. They respect `x-should-retry` and wait for `retry-after` values up to 60 seconds. They do not add `Idempotency-Key`; pass it in request options: [Chat Completions](https://docs.sonecentral.com/en/chat-completions.md#tekrar-denemek).

## Errors during streaming

Key, limit, assistant, conversation and plan checks run before streaming begins, even with `"stream": true`. Rejections return normal HTTP errors. Once streaming starts, the HTTP status cannot change, so the server writes an error and closes the stream:

- **Chat Completions:** `data: {"error": {…}}`, using the error object above, followed by `data: [DONE]`. OpenAI SDKs throw this as an error.
- **Responses:** an `error` event with `type`, `code`, `message`, `param` and `sequence_number`, then `response.failed`. The failed response has `status: "failed"` and the same `code` and `message` in `error`. No `[DONE]` follows. OpenAI's Node SDK throws `APIError` on `error`, with the code at `error.code`; Python delivers an event with `type: "error"`.
- **Vercel AI SDK:** `{"type":"error","errorText":"…"}`, followed by `data: [DONE]`. `useChat` changes `status` to `error` and exposes the text through `error.message`.

Possible post-start errors are `ai_turn_ceiling` if the limit is reached mid-turn, and `assistant_unavailable` if the turn fails. Messages sent before the error were stored; recover them through `GET /conversations/{id}/messages`.

## What to show visitors

You can display `message`, but a clear next step is often better. For `429` and `503`, say you cannot reply now and ask the visitor to try later. In an existing conversation, `ai_turn_ceiling`, `assistant_quota_exhausted`, `plan_required` or `assistant_paused` means the message reached your team; explain that the team will reply in this chat. Do not display raw error codes to visitors.
