---
title: Limits and errors
description: Every limit, HTTP error and tool error code of the Queue CallBack MCP server, with what it means, how to fix it and when to retry.
status: Stable
last_updated: 2026-11-05
canonical: https://queuecallback.com/docs/mcp/errors
---

# Limits and errors

The Queue CallBack MCP server reports a problem in one of two ways: an HTTP error before any tool runs, with a stable code in `error.data.code`, or a tool result with `isError: true` and a stable code in `structuredContent.error.code`. This page lists the limits, every code with its meaning and its fix, and the rules for when an agent may retry.

> For AI agents: this page is also available as Markdown at `https://queuecallback.com/docs/mcp/errors.md`. The whole MCP documentation in one file: https://queuecallback.com/docs/llms-full.txt

Every code on this page has its own anchor, `https://queuecallback.com/docs/mcp/errors#<code>`, for example `#mcp_disabled`. Branch on the code, not on the message: messages are written for people and can change.

## Limits

These are the limits of the Queue CallBack MCP server and of the Queue CallBack API behind it. The values are the current defaults, and Queue CallBack can change them. A `Retry-After` header or a `retry_after_seconds` field in an answer always wins over this table.

| Limit | Value | Answer when exceeded |
|---|---|---|
| Request body | 1 MiB per HTTP request | HTTP 413, not a JSON-RPC body |
| Requests in flight per credential | 8 at once for one API key, or for one person signed in from Claude or ChatGPT; the server also caps its total | HTTP 429 [`concurrency_limited`](#concurrency_limited), `Retry-After: 1` |
| API requests in flight per organization | 4 at once, counting every API key and connected client of the organization; a total cap also applies across the service | 429 [`concurrency_limited`](#concurrency_limited), retry after 1 second |
| API request rate per key | 120 requests per minute, bursts of 30, for one API key or one signed-in person. One tool call makes one or more API requests | 429 [`rate_limited`](#rate_limited) with `Retry-After` |
| API request rate per organization | 300 requests per minute for all of its keys and signed-in people together, bursts of 60 | 429 [`rate_limited`](#rate_limited) with `Retry-After` |
| Requests per client address | a per-address request limit at the edge | HTTP 429 [`rate_limited`](#rate_limited) as a JSON-RPC error, `Retry-After: 1` |
| Tool time limit | 30 seconds; the reporting tools 40 seconds; `edit_flow_draft`, `publish_flow` and `restore_flow_version` 60 seconds | [`backend_timeout`](#backend_timeout) ([`edit_interrupted`](#edit_interrupted) for `edit_flow_draft`). The change may still complete |
| Result size | 100,000 bytes of JSON; `validate_flow` 200,000; `get_flow`, `diff_flow` and `edit_flow_draft` 300,000 | A long list is halved until it fits and marked `truncated`; otherwise [`result_too_large`](#result_too_large) |
| Items per list | each list tool has a default and a maximum `limit` ([tool reference](/docs/mcp/tools)) | The result carries `truncated: true` and a `truncation` object with `pointer`, `returned`, `total` and `note` |
| Report date range | 400 days from `from` to `to` per request | [`date_range_too_long`](#date_range_too_long) |
| One `edit_flow_draft` call | 100 operations and 1 MiB together; 64 KiB per node `config_json`; node id 100 characters, name 255, condition 200, label 255 | [`tool_refused`](#tool_refused), before anything is changed |
| Queue changes | per organization, through API keys and connected clients: 3 at once, then 1 per minute, counted separately for callback settings changes and for voice and language changes. A settings call that changes nothing is not counted | 429 [`queue_write_limited`](#queue_write_limited) with `Retry-After` |
| Callbacks added with `add_callback` | 30 per rolling hour per organization, including callbacks added in the portal. Callbacks that callers request do not count | 429 [`manual_callback_limit_reached`](#manual_callback_limit_reached), with `retry_after_seconds` when the server can tell when a callback can be added again |
| Requeues with `requeue_callback` | 5 per callback in 24 hours | 409 [`requeue_limit_reached`](#requeue_limit_reached) |
| Renames with `set_organization_name` | about one every 6 seconds | 429 without a code ([429 without a code](#status-429)) |
| Confirm token | single use, valid 10 minutes | [`confirm_refused`](#confirm_refused) |
| Handle for a long operation | 24 hours, only for the credential that received it | [`invalid_handle`](#invalid_handle) |

## HTTP errors

An HTTP error from the Queue CallBack MCP server means the request was refused before any tool ran, so nothing changed. The exceptions are a 500, and a 502 or 504 from the edge, which can come while a tool runs. The server checks a request in this order: the `Host` name, the browser origin, the credential, the request limits, then the MCP transport rules. A request without a valid credential that passes the first two checks answers 401, even a GET or one with the wrong `Content-Type`.

Most HTTP errors carry a JSON-RPC error body with `"id": null`. Its `error.code` is `-32001` for a credential problem (401 and 403) and `-32000` for 429 and 5xx. `error.data.code` is the stable reason. `error.data.retry_after_seconds` is set, together with a `Retry-After` header, when waiting and retrying makes sense. Every answer from the server itself carries an `X-Request-ID` header; quote it when you contact Queue CallBack support. Answers made by the edge (the per-address 429, a 413, a 404 for an unknown path, a 502 or 504) and `host_not_allowed` have none.

This is the answer to a request without a valid API key or sign-in:

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Cache-Control: no-store
WWW-Authenticate: Bearer resource_metadata="https://mcp.queuecallback.com/.well-known/oauth-protected-resource/mcp", error="invalid_token", scope="ivr:read reports:read callbacks:read queues:read org:read"
X-Content-Type-Options: nosniff
X-Request-ID: <request id>

{"error":{"code":-32001,"data":{"code":"invalid_token"},"message":"The credential is missing, malformed or unknown, or the API key has expired, been revoked or been suspended (a key is suspended while the admin who created it is deactivated or no longer in the organization), or the sign-in has ended. Send a valid Queue CallBack API key as Authorization: Bearer \u003ckey\u003e, or sign in again; an organization admin sees each key's status on the portal's API keys page."},"id":null,"jsonrpc":"2.0"}
```

`\u003c` and `\u003e` are JSON escapes for `<` and `>`. The message lists every state that gets this answer, because the server cannot tell them apart. The `WWW-Authenticate` header tells a client where to sign in: `resource_metadata` points to the discovery document, and `scope` names the five read scopes a sign-in asks for. Claude and ChatGPT use it to start the sign-in ([OAuth sign-in](/docs/mcp/access#oauth-sign-in)). A client that sends an API key does not need it.

| HTTP status | JSON-RPC `error.code` | `error.data.code` | Meaning | Fix |
|---|---|---|---|---|
| 400 | usually none (plain text) | none | `Accept` does not list both `application/json` and `text/event-stream`, the body is not valid JSON-RPC, or the `MCP-Protocol-Version` header names a revision the server does not support. | Send `Accept: application/json, text/event-stream` and a supported protocol revision. |
| 401 | `-32001` | <a id="invalid_token"></a>`invalid_token` | For an API key: the key is missing, malformed, expired, revoked or suspended; Queue CallBack has switched API keys off for the whole service; or more than one `Authorization` header was sent. For a sign-in from Claude or ChatGPT: the sign-in expired or was not accepted, or the signed-in account is not an active user of a Queue CallBack organization. A bearer that is neither a Queue CallBack API key nor an accepted sign-in token gets the same answer. Apart from the case of two `Authorization` headers ("Send exactly one Authorization header."), the message is the same for every cause, so it does not say which check failed. A client that signs in starts a new sign-in on this answer. | API key: check the key's status in the portal (**Settings**, then **Manage API keys**), check that `QCB_API_KEY` is set where the client runs, and send exactly one `Authorization: Bearer <key>` header. Sign-in: sign in again with your Queue CallBack portal account; if the client keeps asking, see [sign-in fails or repeats](/docs/mcp/troubleshooting#sign-in-from-claude-or-chatgpt-fails-or-repeats). |
| 403 | `-32001` | <a id="mcp_disabled"></a>`mcp_disabled` | The organization's API access is off: "API and MCP access is not enabled for this organization". Every request is refused, with a valid key and after a successful sign-in alike. A tool result carries the same code when access goes off during a session; within about 30 seconds every request gets this HTTP 403. | Nothing in the key or the client changes this answer. If you see it, contact Queue CallBack support (support@queuecallback.com) with the `X-Request-ID`. |
| 403 | `-32001` | [`insufficient_scope`](#insufficient_scope) | Sign-in only: the sign-in token grants none of the five read scopes the server asks for. `WWW-Authenticate` carries `error="insufficient_scope"` and `scope` with those scopes, so the client can sign in again for them. An API key never gets this answer before a tool runs. | Remove the connection in the client, add it again, sign in and approve the access ([sign-in fails or repeats](/docs/mcp/troubleshooting#sign-in-from-claude-or-chatgpt-fails-or-repeats)). |
| 403 | `-32001` | <a id="no_organization"></a>`no_organization` | Sign-in only: the signed-in account belongs to no Queue CallBack organization. | Sign in with the account of your organization, or ask an organization admin to add you. |
| 403 | `-32001` | <a id="forbidden"></a>`forbidden` | The Queue CallBack API refused the credential for another reason; the message says why. | Follow the message, or contact Queue CallBack support with the `X-Request-ID`. |
| 403 | none (plain JSON) | <a id="cross_origin_refused"></a>`cross_origin_refused`, in the body's `error` field | A web page on another site sent the request from a browser. The server sends no CORS headers on `/mcp`. | Call the server from an MCP client or from server-side code, not from a web page. |
| 403 | none (plain JSON) | <a id="host_not_allowed"></a>`host_not_allowed`, in the body's `error` field | The request's `Host` header is not the server's name. | Use `https://mcp.queuecallback.com/mcp`. |
| 404 | none | none | The path is not `/mcp`, `/healthz` or the discovery document. | Send MCP requests to `/mcp`. |
| 405 | none (plain text) | none | A GET or DELETE with a valid key. The server is stateless and offers no event stream. The answer carries `Allow: POST`. | Send every request as a POST. |
| 413 | none | none | The request body is larger than 1 MiB. | Send less. Split a large `edit_flow_draft` into several calls. |
| 415 | none (plain text) | none | `Content-Type` is not `application/json`. | Send `Content-Type: application/json`. |
| 429 | `-32000` | [`concurrency_limited`](#concurrency_limited) | More than 8 requests in flight for this credential, the server at its total, or the organization at its API cap while the credential was checked. `Retry-After: 1`. | Wait, then send fewer requests in parallel. |
| 429 | `-32000` | [`rate_limited`](#rate_limited) | The key or the organization exceeded its request rate while the credential was checked. | Wait for `Retry-After`. |
| 429 | `-32000` | [`rate_limited`](#rate_limited) | The per-address request limit at the edge in front of the server. The message reads "Too many requests from this address. Retry in a second." `Retry-After: 1` and `error.data.retry_after_seconds` 1; no `X-Request-ID`. | Wait at least 1 second, then send requests more slowly ([Retries](#retries)). |
| 500 | none (plain JSON) | <a id="internal_error"></a>`internal_error`, in the body's `error` field | The server failed while handling the request. | Read the current state before you repeat a change. Contact Queue CallBack support with the `X-Request-ID` if it repeats. |
| 502 | `-32000` | [`backend_unreachable`](#backend_unreachable) | The credential could not be checked because the Queue CallBack API could not be reached. `Retry-After: 5`. | Retry after `Retry-After`. |
| 502 | `-32000` | [`backend_error`](#backend_error) | The credential could not be checked because the Queue CallBack API answered in an unexpected way. | Retry after `Retry-After`, or after a few seconds when it is missing. If it repeats, contact Queue CallBack support with the `X-Request-ID`. |
| 502, 504 | none | none | The edge in front of the server answered itself, because the server is restarting or did not answer in time. The body is not JSON-RPC and there is no `X-Request-ID`. If the request was a tool call, the change may have run. | Repeat a read after a few seconds. For a change, read the current state first ([Retries](#retries)). |
| 503 | `-32000` | <a id="auth_busy"></a>`auth_busy` | Too many credential checks are running. A tool result can carry the same code, with `status` 503. `Retry-After: 1`. | Retry after 1 second. |
| 503 | `-32000` | <a id="backend_unavailable"></a>`backend_unavailable` | The Queue CallBack API is busy. | Retry after `Retry-After`. |
| 503 | `-32000` | <a id="feature_unavailable"></a>`feature_unavailable` | The Queue CallBack API cannot check API keys right now because of a fault on the service. The message reads "API keys and delegated credentials are not enabled on this Queue CallBack server." `Retry-After` is usually 5 seconds. | Retry after `Retry-After`. If it repeats, contact Queue CallBack support with the `X-Request-ID`. |
| 503 | `-32000` | <a id="authorization_server_unavailable"></a>`authorization_server_unavailable` | Sign-in only: the sign-in service's signing keys could not be read, so the sign-in token could not be checked. The token may be valid. `Retry-After: 5`. | Keep the token and retry after the wait. Do not sign in again. |
| 503 | `-32000` | <a id="canceled"></a>`canceled` | The request was canceled while it waited, usually because the client gave up. | Send it again if you still need the answer. |
| 200 (400 with protocol revision 2026-07-28) | `-32602` | none | The message reads `unknown tool "<name>"`: the name is not a tool of the Queue CallBack MCP server at all, for example a typo or a tool of another server. A tool of the server that this credential does not see (a missing scope, the Custom IVR add-on, the IVR call reports, a server switch) is not a protocol error: the call is refused as a tool result with a code that says why ([Access, scopes and switches](#access-scopes-and-switches)). | Fetch the tool list again and use a name from it. Check the connection with `get_me` ([check the connection](/docs/mcp/connect#check-the-connection)). |

## Tool errors

A tool error is a normal JSON-RPC result from the Queue CallBack MCP server, not a protocol error. `isError` is `true`, the text content says what happened and how to fix it, and `structuredContent.error` carries the details. The tool did not do what was asked. Whether anything changed depends on the code: see [Retries](#retries).

### The error object

This is a tool error the Queue CallBack API raised (a rate limit):

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "isError": true,
    "content": [
      {
        "type": "text",
        "text": "HTTP 429 (rate_limited): Too many requests for this credential or organization; retry after the time in Retry-After. Too many requests; retry after 3 seconds."
      }
    ],
    "structuredContent": {
      "error": {
        "status": 429,
        "code": "rate_limited",
        "message": "Too many requests for this credential or organization; retry after the time in Retry-After",
        "retry_after_seconds": 3,
        "details": {
          "error": "Too many requests",
          "code": "rate_limited",
          "message": "Too many requests for this credential or organization; retry after the time in Retry-After",
          "retry_after_seconds": 3
        }
      }
    }
  }
}
```

| Field of `structuredContent.error` | Present | Holds |
|---|---|---|
| `code` | almost always; missing only when the API refused without a code ([answers without a code](#answers-without-a-code)) | The stable reason. Branch on it |
| `message` | always | The reason in plain words |
| `status` | most API answers | The API's HTTP status, or a 502 or 504 that the server sets itself for `edit_interrupted` and some `backend_error` answers. Without `status`, the MCP server answered on its own: either it refused the call before any change (`refused`, `request_refused`, `tool_refused`, `confirm_required`, `confirm_refused`, `invalid_handle`, and the codes for a tool this credential does not see, such as `insufficient_scope` or `writes_disabled`), or the API's answer was missing, late or unusable (`backend_timeout`, `backend_unreachable`, `backend_error`, `result_too_large`, `no_answer`), and then a change may have happened |
| `required_scope` | with `insufficient_scope` | The scope the call needs |
| `retry_after_seconds` | when the API sent `Retry-After` | Seconds to wait before a retry |
| `details` | API refusals with a 4xx status | The API's whole answer, for example a current `etag`, `failed_ops`, a `validation` report, a `callback_id` or a `retry_after` time |
| `issued_by` | with `confirm_required` | The tool that issues the missing token |
| `confirm_token_spent` | `true` when the call's confirm token is used up: on any failed call of a tool that took a confirm token, and on `confirm_refused` for a token an earlier call used | The token cannot be sent again. Get a new one before you retry ([Confirm tokens are spent on use](#confirm-tokens-are-spent-on-use)) |
| `next_step` | with `confirm_token_spent` on a failed call | How to retry: where the new token comes from and, for `requeue_callback`, which `idempotency_key` to send |

For an API refusal the text reads `HTTP <status> (<code>): <message>. <hint>`. For a refusal by the MCP server the text is the message. Read `structuredContent.error` when it is present; otherwise read the code from the text.

### Access, scopes and switches

| Code | `status` | Meaning | Fix |
|---|---|---|---|
| <a id="insufficient_scope"></a>`insufficient_scope` | 403, or none | The credential lacks the scope in `required_scope`. With `status` 403 the Queue CallBack API refused the call. Without `status`, the MCP server refused a call to a tool that this credential does not see, before anything was sent, with a message such as "This credential lacks the callbacks:dial scope that requeue_callback needs. The API key was created without it, the organization or the role the credential acts with does not allow it, or this is a sign-in, which can only read. get_me lists the scopes this credential has and its role. For more, an organization admin can create an API key with that scope; signing in again does not add scopes." `required_scope` can then be a scope the key holds that does not take effect in the organization, for example `ivr:write` where IVR flows are not edited through drafts, or `pii:read` for `add_callback` where caller data stays masked. Without an effective `pii:read`, it is also the answer to a search made only of digits and phone punctuation, and to the `caller` filter or `sort=caller`; a sign-in from Claude or ChatGPT never has `pii:read`, so it always gets this answer for those. A signed-in connection lists no tool that changes something, and a call to one by name gets this code. A User's sign-in has no `ivr:read`, so the IVR flow tools answer this code too. Before any tool runs, HTTP 403 with this code means a sign-in token grants none of the five read scopes ([HTTP errors](#http-errors)). | Use an API key that has the scope ([scopes](/docs/mcp/access#scopes)); a sign-in cannot gain more scopes. If the key holds the scope but `get_me` does not list it, an organization setting keeps it from taking effect ([When a scope does not take effect](/docs/mcp/access#when-a-scope-does-not-take-effect)). For masked numbers, see [caller data](/docs/mcp/safety#caller-data). |
| [`mcp_disabled`](#mcp_disabled) | 403 | The organization's API access went off during the session. | See [`mcp_disabled`](#mcp_disabled) under HTTP errors. |
| <a id="invalid_credentials"></a>`invalid_credentials` | 401 | The API refused the credential during a session: the key was revoked, expired or suspended moments ago, API keys were switched off for the whole service, or the signed-in person was deactivated or left the organization. Within about 30 seconds, requests get HTTP 401 `invalid_token`. | Replace the key ([rotate, revoke and suspension](/docs/mcp/access#rotate-revoke-and-suspension)) and restart the client. For a sign-in, sign in again with an active account. |
| <a id="route_not_allowed"></a>`route_not_allowed` | 403 | The operation is not available to API keys. | Use the Queue CallBack portal. |
| <a id="custom_ivr_not_enabled"></a>`custom_ivr_not_enabled` | 403, or none | The organization does not have the Custom IVR add-on (the portal's **Billing** page sells it as the Custom IVA plan), so the IVR flow tools are unavailable. Without `status`, the MCP server refused a call to an IVR flow tool that is not listed for the organization. The queue, callback and callback report tools still work. | If the message names the **Billing** page, an organization admin can buy or restore the add-on there. Otherwise ask Queue CallBack support. |
| <a id="ivr_reporting_not_enabled"></a>`ivr_reporting_not_enabled` | 403, or none | The IVR call reports are for organization admins of organizations with the Custom IVR add-on (or of organizations that keep IVR reporting after the add-on lapsed), and this credential is not one: `get_me` shows `ivr_reporting_enabled: false`. The five IVR report tools (`get_ivr_summary`, `list_ivr_calls`, `get_ivr_call`, `get_flow_decisions`, `list_ivr_flow_stats`) are then not listed. Without `status`, the MCP server refused the call before anything was sent; with 403, the Queue CallBack API refused it. The message ends: "The callback reports (get_callback_summary, list_callbacks, get_callback_journey, get_live_stats) still work." | Use the callback reports, or ask an organization admin. An organization admin's API key or sign-in reads the IVR call reports where the organization has the add-on. |
| <a id="custom_ivr_queue_required"></a>`custom_ivr_queue_required` | 403 | The flow belongs to a queue that is not a Custom IVR queue, and the add-on covers only the flows of Custom IVR queues. Reading the flow still works. | An organization admin marks the queue as a Custom IVR queue in the portal (each Custom IVR queue uses one license of the Custom IVR add-on), then call again. |
| <a id="writes_disabled"></a>`writes_disabled` | none | Queue CallBack has switched off changes through the MCP server. While changes are off, the tools that change something are not listed, and a call to one by name, for example from an older tool list, gets this code and sends nothing. The message reads "Queue CallBack has switched off changes through this server for now. Make the change in the Queue CallBack portal; read-only tools still work." | Make the change in the portal, or try again later. |
| <a id="queue_writes_disabled"></a>`queue_writes_disabled` | none | Queue CallBack has switched off changes to a queue's voice and language through the MCP server, apart from the other changes. While they are off, `set_queue_voice` and `set_queue_language` are not listed, and a call to either by name gets this code and sends nothing. | Change them in the portal, or try again later. |
| <a id="queue_settings_disabled"></a>`queue_settings_disabled` | none | Queue CallBack has switched off changes to a queue's callback settings through the MCP server, apart from the other changes. While they are off, `update_queue_settings` is not listed, and a call to it by name gets this code and sends nothing. | Change them in the portal, or try again later. |

### Arguments, confirm tokens and handles

| Code | `status` | Meaning | Fix |
|---|---|---|---|
| <a id="refused"></a>`refused` | none | The arguments do not match the tool's input schema (an unknown argument, a wrong type, a missing required argument, a value outside its pattern), or are not one JSON object. The text names the argument, sometimes in raw schema-validator wording. Nothing was sent. | Fix the argument and call again. The [tool reference](/docs/mcp/tools) lists every argument. |
| <a id="request_refused"></a>`request_refused` | none | The request could not be built or was stopped before sending, for example because an idempotency key is longer than 255 characters. Nothing was sent. If the message says the request "did not pass this server's own checks", the arguments are not the cause. | Fix the arguments and call again. When the arguments are not the cause, try again later, and contact Queue CallBack support if it repeats. |
| <a id="tool_refused"></a>`tool_refused` | none | The tool's own check refused the call, and nothing was changed (the tool may have read the current state first). Examples: an `edit_flow_draft` call over its limits; an organization that edits its IVR flows live instead of through drafts; an unknown node type in `get_node_type`; `get_live_stats` with `tz` and `timezone` naming different zones. | Follow the message. |
| <a id="confirm_required"></a>`confirm_required` | none | `publish_flow` was called with an empty `confirm_token`. A call that leaves the argument out is refused earlier with [`refused`](#refused), because `confirm_token` is required. `issued_by` is `validate_flow`. | Call `validate_flow`, show the user `diff_flow`, then call `publish_flow` with the digest, base version and token ([confirm tokens](/docs/mcp/safety#confirm-tokens)). |
| <a id="confirm_refused"></a>`confirm_refused` | none | The confirm token is not valid, has expired, was already used, or was issued for another tool, credential, organization or other argument values. Rarely, the server holds too many open tokens. This call changed nothing. A token refused as invalid, expired or issued for other values is not spent. A token that was already used comes back with `confirm_token_spent: true`, and the message says that the earlier call that used it may have made the change: a call that timed out or got no answer can still have run. | With `confirm_token_spent: true`, read the current state first (`get_queue`, `get_requeue_status` or `list_callback_queue`, `list_flow_versions`). Then get a new token from the issuing call (`validate_flow` for `publish_flow`; the same tool without `confirm_token` for the others), show the user what will change, and call again; for `requeue_callback`, send the same `idempotency_key` as the earlier call. Never invent or reuse a token. |
| <a id="invalid_handle"></a>`invalid_handle` | none | The handle is unknown, expired (handles last 24 hours) or belongs to another credential. The message says how to check whether the operation already ran, by the kind of handle. | For `get_requeue_status`: do not requeue again yet. Check the callback in `list_callback_queue`, and requeue it only if it is still expired, failed or cancelled, with the same `idempotency_key` as before. For `get_publish_status`: the publish or restore already happened, so do not publish again; `list_flow_versions` shows the current version. Only the warm-up report is lost. |
| <a id="idempotency_key_reused"></a>`idempotency_key_reused` | 422 | The idempotency key was already used in this organization for a different request (another queue, number or callback). Nothing was done. | Send a new key. Reuse a key only to repeat the identical request. |
| <a id="invalid_idempotency_key"></a>`invalid_idempotency_key` | 400 | The API refused the key's format. Through the MCP server this is rare: a key with characters other than letters, digits, `.`, `_`, `:` and `-` is refused first with [`refused`](#refused), and a key longer than 255 characters with [`request_refused`](#request_refused). Nothing was done. | Use letters, digits, `.`, `_`, `:` and `-`, at most 255 characters. |
| <a id="idempotency_key_in_progress"></a>`idempotency_key_in_progress` | 409 | A requeue with this idempotency key is still running. | Wait, then check `get_requeue_status` or `list_callback_queue`. Keep the same key. |

### Limits and availability

| Code | `status` | Meaning | Fix |
|---|---|---|---|
| <a id="rate_limited"></a>`rate_limited` | 429 | The API key or the organization sent requests faster than its rate ([Limits](#limits)). Can also arrive as HTTP 429 before any tool runs, from the credential check or from the edge's per-address limit ([HTTP errors](#http-errors)). | Wait `retry_after_seconds`, then call again. |
| <a id="concurrency_limited"></a>`concurrency_limited` | 429 | More than 4 API requests were in flight at once for the organization, or the service was at its total for all organizations. Before any tool runs, HTTP 429 with this code can also mean more than 8 requests in flight for the credential at the MCP server. | Wait `retry_after_seconds` (1), and run fewer tool calls in parallel. |
| <a id="queue_write_limited"></a>`queue_write_limited` | 429 | The organization changed queue callback settings (or voices and languages) through API keys and connected clients too often. | Wait `retry_after_seconds`. |
| [`auth_busy`](#auth_busy) | 503 | Too many credential checks were running at the API. | Retry after `retry_after_seconds` (1). |
| <a id="result_too_large"></a>`result_too_large` | none | The answer is larger than the tool's result limit, even after a long list was shortened. The API did answer, so for a tool that changes something the change may have happened. | Narrow the request: a shorter date range, filters or a smaller `limit`. After a change, read the current state before you repeat it. |
| <a id="backend_timeout"></a>`backend_timeout` | none | The Queue CallBack API did not answer within the tool's time limit. The operation may still complete. | A read: call again. A change: read the current state first ([Retries](#retries)). |
| <a id="backend_unreachable"></a>`backend_unreachable` | none | The Queue CallBack API could not be reached. Can also arrive as HTTP 502 before any tool runs. | A read: retry shortly. A change: read the current state first. |
| <a id="backend_error"></a>`backend_error` | none, or 502 | The API's answer was unreadable or not in the documented shape. One case: `edit_flow_draft` was accepted but the answer lacked the draft's state, so the operations were most likely applied. | Read the current state, then decide whether to repeat. |
| <a id="no_answer"></a>`no_answer` | none | The tool produced no answer. | Read the current state, then decide whether to repeat. |

### Callbacks and reporting

| Code | `status` | Meaning | Fix |
|---|---|---|---|
| <a id="requeue_limit_reached"></a>`requeue_limit_reached` | 409 | The callback was requeued 5 times in the last 24 hours, the most allowed. `details.retry_after` is the time it can be requeued again. | Tell the user, and wait until that time. |
| <a id="requeue_busy"></a>`requeue_busy` | 503 | Too many requeues are running on the service, so this one was not started. | Retry after `retry_after_seconds`, with a new confirm token ([Retries](#retries)). |
| <a id="date_range_too_long"></a>`date_range_too_long` | 400 | The report's range from `from` to `to` is longer than 400 days. | Choose a shorter range. |
| <a id="query_timeout"></a>`query_timeout` | 504 | A report query took too long and was stopped. | Narrow the date range or the filters, then call again. |
| <a id="reporting_busy"></a>`reporting_busy` | 503 | Too many report requests are running right now. | Retry after `retry_after_seconds`. |
| <a id="interactive_busy"></a>`interactive_busy` | 503 | Too many quick reads are running right now. This can also answer `list_flows`, `get_callback_journey` and `get_ivr_call`. | Retry after `retry_after_seconds`. |
| <a id="invalid_timezone"></a>`invalid_timezone` | 400 | `get_live_stats` got a time zone that is not an IANA time zone name. Nothing was read. The other reporting tools refuse an unknown `tz` with 400 and no code. | Send an IANA name such as `America/Chicago`. |

`add_callback` refuses a callback with one of these codes. None of them adds a callback or places a call. For a 4xx answer, `details` carries the API's answer, whose texts are written for people.

| Code | `status` | Meaning | Fix |
|---|---|---|---|
| <a id="invalid_request"></a>`invalid_request` | 400 | The number or the queue is missing. Through the MCP server this is rare: the tool's schema requires both, so a missing one is refused first with [`refused`](#refused). | Send `callback_number` and `queue_id`. |
| <a id="invalid_callback_number"></a>`invalid_callback_number` | 400 | The number does not have 8 to 15 digits. | Ask the user for the whole number. Never use a masked number. |
| <a id="callback_number_outside_country"></a>`callback_number_outside_country` | 400 | The number is outside the organization's country; only numbers in that country can be called back. | Tell the user. A number with `+` must be in the organization's country. |
| <a id="queue_not_found"></a>`queue_not_found` | 404 | No queue with this `queue_id` in the organization. Another organization's queue, and an id that is not a UUID, get the same answer. | Take the id from `list_queues`. |
| <a id="queue_in_other_organization"></a>`queue_in_other_organization` | 403 | Only Queue CallBack's own administrators, who can name another organization, get this code. A key or sign-in of your organization gets `queue_not_found` for another organization's queue. | Choose a queue of that organization. |
| <a id="queue_not_running"></a>`queue_not_running` | 409 | The queue is stopped. | Tell the user to start the queue in the portal, then add the callback. |
| <a id="queue_without_extension"></a>`queue_without_extension` | 409 | The queue has no extension to ring. | Tell the user to set the queue's extension in the portal. |
| <a id="callback_already_waiting"></a>`callback_already_waiting` | 409 | The number already has a callback waiting in that queue. `details.callback_id` names it. | Do not add another. Use the waiting callback. |
| <a id="manual_callback_limit_reached"></a>`manual_callback_limit_reached` | 429 | The organization added 30 callbacks by hand in the last hour, in the portal or with `add_callback`. `retry_after_seconds` says when a callback can be added again; it is missing when the server cannot tell. | Tell the user when they can add one again. Without a wait time, try again later: the limit counts a rolling hour. |
| <a id="callback_queue_unavailable"></a>`callback_queue_unavailable` | 503 | The callback queue of the service is not available right now. | Retry later. Contact Queue CallBack support if it repeats. |

`add_callback` can also answer [`idempotency_key_reused`](#idempotency_key_reused), [`invalid_idempotency_key`](#invalid_idempotency_key) and the general codes on this page.

### IVR flows (Preview)

These codes belong to the IVR flow tools, which are in Preview while the IVR builder is redesigned. Codes may be added or changed; the [changelog](/docs/mcp/changelog) records changes. `edit_flow_draft` can refuse an operation with a code that is not listed here; every such refusal names the operation in `details.failed_ops`, and nothing of the call is applied. The validation issues that `validate_flow` reports carry codes of their own, which are not listed here: branch on them and show the user the issue's text ([Validate, review and publish](/docs/mcp/ivr#validate-review-and-publish)). The editing loop is on [Edit a flow safely](/docs/mcp/ivr#edit-a-flow-safely).

| Code | `status` | Meaning | Fix |
|---|---|---|---|
| <a id="revision_mismatch"></a>`revision_mismatch` | 409 | The draft changed since you read it, so your `etag` is stale. Nothing changed. `details.etag` is the current etag. | Read the flow with `get_flow`, apply your change to the current draft, and send it with the current etag. |
| <a id="node_exists"></a>`node_exists` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. The node id is taken. | Fix that operation and send the whole edit again with the same etag. |
| <a id="node_not_found"></a>`node_not_found` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. No node has that id. | Fix that operation and send the whole edit again with the same etag. |
| <a id="invalid_body"></a>`invalid_body` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. The operation's fields or `config_json` are malformed. | Fix that operation and send the whole edit again with the same etag. |
| <a id="no_fields"></a>`no_fields` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. An `update_node` changes nothing. | Fix that operation and send the whole edit again with the same etag. |
| <a id="entry_node"></a>`entry_node` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. The entry node cannot be deleted. | Fix that operation and send the whole edit again with the same etag. |
| <a id="node_referenced"></a>`node_referenced` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. Other nodes still connect to the node. | Fix that operation and send the whole edit again with the same etag. |
| <a id="connection_target_missing"></a>`connection_target_missing` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. A connection points to a node that does not exist. | Fix that operation and send the whole edit again with the same etag. |
| <a id="invalid_node_type"></a>`invalid_node_type` | 422 | `edit_flow_draft` was refused at the operation named in `details.failed_ops`; no operation of the call was applied. The node type is unknown. | Get node types from `list_node_types` ([node types](/docs/mcp/ivr#node-types)), fix that operation and send the whole edit again with the same etag. |
| <a id="edit_interrupted"></a>`edit_interrupted` | 502 or 504 | `edit_flow_draft` got no answer, or none in time. An edit applies all of its operations or none. | Read the flow with `get_flow` to see which happened before you send the edit again. |
| <a id="no_draft"></a>`no_draft` | 409, or 404 from `diff_flow` | The flow has no draft: there is nothing to publish, or nothing to compare. | To change the flow, start a draft with `create_flow_draft`, edit it, validate it, then publish. To compare versions only, call `diff_flow` with `from` and `to` set to `published` or version numbers. |
| <a id="draft_exists"></a>`draft_exists` | 409 | `restore_flow_version` with `target` `live` was refused because the flow has a draft and `discard_draft` is `false`. Nothing changed. | Publish or discard the draft first, or, if the user agrees to lose the draft, start again with a first call that has `discard_draft: true` and get a new confirm token. |
| <a id="draft_stale"></a>`draft_stale` | 409 | The draft was started from an older version than the one now published. | Discard the draft (or restore into it) and make the change again. |
| <a id="base_version_mismatch"></a>`base_version_mismatch` | 409 | `base_version` is not the flow's current version: someone published in the meantime. `details.current_version` is the current one. | Read the flow, check the change with `diff_flow`, then run `validate_flow` again. |
| <a id="digest_mismatch"></a>`digest_mismatch` | 409 | The draft changed after `validate_flow` checked it. | Run `validate_flow` again and publish with the new digest and token. |
| <a id="validation_failed"></a>`validation_failed` | 422 | The draft has errors; `details.validation` lists them. | Fix them, or acknowledge the acknowledgeable ones, then validate again. |
| <a id="version_not_found"></a>`version_not_found` | 404 | No version of the flow has that number. | List the versions with `list_flow_versions`. |
| <a id="draft_publish_disabled"></a>`draft_publish_disabled` | 409 | The organization edits its IVR flows live, not through drafts, so the MCP server cannot change them. | Use the portal's flow builder, or ask Queue CallBack support to switch the organization to drafts. |

### Answers without a code

Some refusals from the Queue CallBack API carry a `status` and a message but no `code`. Branch on `status`.

| `status` | Meaning | Fix |
|---|---|---|
| <a id="status-400"></a>400 | The API refused an argument value; the message names it. | Fix the value and call again. |
| <a id="status-403"></a>403 | "This credential may not do this." | Check the key's role and scopes with `get_me`, or use the portal. |
| <a id="status-404"></a>404 | Nothing with that id exists in this organization. `complete_callback` also answers 404 for a callback that is already completed or cancelled. | Get a valid id from the matching list tool, such as `list_queues`, `list_callback_queue` or `list_flows`. |
| <a id="status-409"></a>409 | The current state does not allow the change. | Read the current state, then decide with the user. |
| <a id="status-422"></a>422 | The API refused the request as a whole; the message says why. | Fix the arguments. |
| <a id="status-429"></a>429 | `set_organization_name` more than about once every 6 seconds. | Wait (`retry_after_seconds` when present), then call again. |
| <a id="status-5xx"></a>500, 502, 503, 504 | The API failed or is busy. | A read: retry with back-off. A change: read the current state first. |

## Retries

These rules tell an AI agent, or any client of the Queue CallBack MCP server, when to repeat a request, how long to wait, and what to read before repeating a change.

### Wait as long as the server says

1. When an answer has a `Retry-After` header or a `retry_after_seconds` field (in `error.data` or `structuredContent.error`), wait at least that long.
2. When a 429 or 503 comes without a wait time, back off exponentially with jitter: about 1, 2, 4, 8 and 16 seconds, never more than 60 seconds between attempts.
3. Stop after about 5 attempts. Tell the user what failed and give the `code` (and the `X-Request-ID` header if the client shows it).
4. After `concurrency_limited`, run fewer calls at once: at most 4 tool calls in parallel per organization, counting every key.

### Repeat reads freely, changes only after reading the state

A read-only tool can be repeated after any error that is about load or availability: `rate_limited`, `concurrency_limited`, `auth_busy`, `reporting_busy`, `interactive_busy`, `query_timeout` (narrow the query first), `backend_timeout`, `backend_unreachable`, `backend_error`, HTTP 5xx.

A tool that changes something is different. After `backend_timeout`, `backend_unreachable`, `backend_error`, `result_too_large`, `no_answer`, `edit_interrupted`, a 5xx status, or a dropped connection, the change may have happened. Never repeat it blindly. Read the state first:

| Tool | Read this first | Then |
|---|---|---|
| `add_callback` | `list_callback_queue`: is a callback for that number waiting in that queue? | Repeat with the same `idempotency_key`. The API replays the first answer instead of adding a second callback. |
| `requeue_callback` | `get_requeue_status` with the handle, if the first answer had one; otherwise `list_callback_queue`: is the callback back in the queue? | Repeat only if it was not requeued (see below). |
| `complete_callback` | `list_callback_queue` | A repeat is harmless: a callback already completed answers 404. |
| `update_queue_settings` | `get_queue`: compare the settings | If they did not change, start again with the first call and the user's agreement. |
| `set_queue_voice`, `set_queue_language` | `get_queue`: compare the voice, speed or language | If they did not change, start again with the first call and the user's agreement. |
| `set_organization_name` | `get_organization` | Repeat if the name did not change. |
| `create_flow_draft`, `edit_flow_draft`, `discard_draft` | `get_flow`: the draft and its `etag` | An edit applies all of its operations or none. Resend with the current etag only if they are missing. |
| `publish_flow`, `restore_flow_version` | `list_flow_versions`: is there a new version? With a handle, also `get_publish_status`. For `restore_flow_version` with `target` `draft`, which creates no version, read `get_flow` instead: does the draft now match the restored version (`diff_flow` from that version number to `draft`)? | If nothing was published, run `validate_flow` again (for `restore_flow_version`, the first call again), then repeat with the user's agreement. |

An answer that refused the call before it ran (`rate_limited`, `concurrency_limited`, `auth_busy`, `queue_write_limited`, `requeue_busy`, any HTTP error other than a 500 or a 502 or 504 without a JSON-RPC body) changed nothing, and the change can be repeated after the wait.

### Confirm tokens are spent on use

A confirm token is spent the moment the server accepts it, before the Queue CallBack API answers, even if the change then fails or is refused by a limit. Every repeat of a confirmed change therefore needs a new token: from `validate_flow` for `publish_flow`, and from a first call without `confirm_token` for `requeue_callback`, `restore_flow_version`, `update_queue_settings`, `set_queue_voice` and `set_queue_language`. Show the user what will change and get their agreement again.

A call that fails after its token was accepted says so. Its `structuredContent.error` carries `confirm_token_spent: true` and a `next_step` with the retry steps, and its text ends with the same steps:

| The failed call | `next_step` says |
|---|---|
| `publish_flow` | Call `validate_flow` again for a new token, then call `publish_flow` with it |
| `requeue_callback` with an `idempotency_key` | Call `requeue_callback` again without `confirm_token` for a new token, then call it again with that token and the same `idempotency_key`, so a requeue that already ran is not run twice |
| `requeue_callback` without an `idempotency_key` | Check the current state first, because the requeue may have run. Then get a new token the same way and send an `idempotency_key` with the retry |
| Any other tool with a confirm token | Call the tool again without `confirm_token` for a new token, then call it again with that token |

A `confirm_refused` answer changed nothing. With `confirm_token_spent: true`, the token was used by an earlier call, which may have made the change: read the current state before you get a new token. See [confirm tokens](/docs/mcp/safety#confirm-tokens).

### Requeues place real calls

Every `requeue_callback` makes the system ring an agent and then call the customer. Never repeat a requeue without checking `get_requeue_status` first.

| `get_requeue_status` status | What it means | Next step |
|---|---|---|
| `pending`, `running` | The requeue is still in progress. | Check again in a few seconds. Do not requeue again. |
| `succeeded` | The callback is back in the queue. | Nothing to repeat. |
| `failed`, `conflict` | The callback was not requeued. | Read the answer and tell the user before trying again. A new attempt needs a new first call and a new `idempotency_key`. |
| `interrupted` | The server stopped before it recorded the outcome. | Check the callback with `list_callback_queue` before any new requeue. |

If `get_requeue_status` answers [`invalid_handle`](#invalid_handle), check the callback with `list_callback_queue` instead of starting the requeue again. To learn what happened to the first attempt, call `requeue_callback` again with the same `idempotency_key` and a new confirm token: when the API recorded the first attempt, it replays the recorded request instead of dialing again. A first attempt that never reached the API runs now, so get the user's agreement for this call too. If the replayed status is `failed` or `conflict` and the user agrees to try again, use a new confirm token and a new `idempotency_key`: the old key only replays the failed attempt.

### Idempotency keys

`add_callback` and `requeue_callback` take an `idempotency_key` argument: letters, digits, `.`, `_`, `:` and `-`, at most 255 characters. A client can send `_meta.idempotencyKey` on the request instead; the argument wins.

1. Make one new random key, such as a UUID, for each action you intend.
2. Send the same key with every repeat of that action. A repeat returns the recorded answer instead of acting twice, even when that answer was a failure: a requeue recorded as `failed` or `conflict` needs a new key to be tried again.
3. Never use the key for a different action. Keys are unique per organization, across all of its keys, and a reused key gets [`idempotency_key_reused`](#idempotency_key_reused).
4. Send your own key with `add_callback`. Without one, the server makes a key for that single call only, so a repeat call gets a different key.

For symptoms rather than codes (cannot connect, a tool is missing, numbers are masked), see [troubleshooting](/docs/mcp/troubleshooting).
