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

# Errors, pagination, idempotency and limits

> The error envelope and codes, pagination, idempotency, rate limits.

## Error envelope

Every refusal has one shape:

```json theme={null}
{"error": {"code": "insufficient_scope", "message": "this credential cannot agents publish", "request_id": "req_...", "scope": "agents:publish"}}
```

| Code | Status | Meaning |
| - | - | - |
| `authentication_required` | 401 | No credential |
| `invalid_credential` | 401 | A key that is unknown, expired or revoked |
| `insufficient_scope` | 403 | The key lacks the route's scope |
| `forbidden` | 403 | The role cannot do this |
| `workspace_suspended` | 403 | The workspace is paused, so its agents take no new actions. Reads and exports still work |
| `csrf_failed` | 403 | An App-session write without its CSRF proof |
| `validation_failed` | 400 | A field is missing or wrong (`field` names it) |
| `AGENT_ENV_UNPINNED` | 400 | The call asked for the production version of an agent that has no version pinned to production |
| `credits_exhausted` | 402 | Not enough credits for this building action, so nothing started. Carries `needed`, `balance`, `debt` and, where buying can work, `buy_path`. Live agents are not affected ([Billing boundaries](https://docs.sempera.org/billing-boundaries.md)) |
| `not_found` | 404 | No such resource in your scope |
| `method_not_allowed` | 405 | |
| `conflict` | 409 | A rule refused it (a stale draft, a live version, a taken number); `reason` may carry a finer code |
| `idempotency_key_reused` | 409 | Same key, different request |
| `gone` | 410 | It existed and is no longer served (a deleted recording, or one whose access ended); `reason` says which |
| `payload_too_large` | 413 | |
| `rate_limited` | 429 | Wait `Retry-After` seconds |
| `CALL_RL_PLAN_CONCURRENCY` | 429 | A personal phone plan allows one call at a time ([phone plan](https://docs.sempera.org/phone-plan.md)) |
| `CALL_RL_PLAN_HOURLY` | 429 | The plan's calls per hour are used |
| `CALL_RL_PLAN_DAILY` | 429 | The plan's calls for today are used |
| `CALL_RL_PLAN_DAILY_MINUTES` | 429 | The plan's call minutes for today are used |
| `CALL_RL_PLAN_ALLOWANCE` | 429 | The plan's minutes are used (limited mode) |
| `SMS_RL_PLAN_HOURLY` | 429 | The plan's texts per hour are used |
| `SMS_RL_PLAN_DAILY` | 429 | The plan's texts for today are used |
| `SMS_RL_PLAN_ALLOWANCE` | 429 | The plan's text segments are used (limited mode) |
| `PHONE_PLAN_INACTIVE` | 403 | No active phone plan |
| `PHONE_DESTINATION_BLOCKED` | 403 | The plan cannot reach that number |
| `PHONE_COST_UNKNOWN` | 503 | That destination or route has no approved cost, so nothing started |
| `PHONE_APPROVAL_REQUIRED` | 403 | The account owner has not approved this yet |
| `PHONE_APPROVAL_INVALID` | 403 | The request does not match what was previewed |
| `PHONE_APPROVAL_EXPIRED` | 410 | The approval expired. Preview again |
| `PHONE_FUNDING_NOT_LIVE` | 403 | A test payment cannot fund a real call or text |
| `PHONE_AGENT_UNPINNED` | 409 | The owner has not pinned an agent to the plan |
| `PHONE_NUMBER_MISSING` | 409 | The plan has no number ready |
| `PHONE_RECIPIENT_OPTED_OUT` | 403 | The recipient texted STOP |
| `PHONE_OUTSIDE_HOURS` | 409 | Outside calling hours |
| `capability_unavailable` | 501 | Not offered on this platform. `POST /v1/calls` also answers it with `reason: do_not_contact` when the number is on the workspace's do-not-contact list ([Sessions and calls](https://docs.sempera.org/sessions-and-calls.md)) |
| `provider_failed` | 502 | A provider behind the API failed |
| `provider_timeout` | 504 | |
| `owner_setup_required` | 503 | A feature the workspace owner has not configured yet |
| `credits_unavailable` | 503 | The credit balance could not be read, so nothing started. Live agents are not affected |
| `service_unavailable` | 503 | |
| `internal_error` | 500 | Quote the `request_id` to support |

Errors never carry stack traces, secrets or internal hostnames.

### Reasons

A refusal can carry a finer `reason` beside its `code`. A reason is stable and safe to branch on; the `message` is for people and may change.

| Reason | Code and status | Where | Meaning |
| - | - | - | - |
| `invalid_voice_id` | `validation_failed`, 400 | A draft, a session, a voice's detail and preview, a favorite | The value looks like a Sempera voice id (`voice_` then lowercase letters and digits) and is not one Sempera issued. Choose an id from `GET /v1/voices`. The App's own routes answer it as `code: invalid_voice_id` |
| `voice_unknown`, `engine_unavailable`, `engine_not_allowed`, `catalog_unavailable`, `preview_unavailable` | `validation_failed` or `conflict` | Voice reads, previews and drafts | Why a voice or its engine cannot be used right now, in Sempera's words. None names a speech supplier or its id for the voice |
| `consent_missing`, `consent_no_workspace`, `consent_store_unavailable` | `forbidden`, with `code: voice_consent_required` on the App's routes | A custom voice | The consent record a custom voice needs is absent or unreadable |
| `stale_draft` | `conflict`, 409 | `PATCH /v1/agents/{id}/draft`, MCP `update_agent_draft` | Your `expect_revision` is older than the draft. Read it again and merge |
| `stale_webhook` | `conflict`, 409 | `PATCH /v1/webhooks/{id}` | Your `expected_updated_at` is older than the endpoint's. The answer carries the `updated_at` now on file |
| `do_not_contact` | `capability_unavailable`, 501 | `POST /v1/calls` | The number is on the workspace's do-not-contact list |
| `judge_unavailable`, `judge_not_independent` | `service_unavailable`, 503 | `POST /v1/evals/run`, `POST /v1/evals/quick-run` | No judging model is reachable, or the judge is not independent of the builder. No calls were scored |

An engine in a draft, a session or a runtime reading is named by a sealed Sempera name (`engine_` then letters and digits) and a voice by its Sempera voice id; a refusal never repeats the engine or the engine's own id.

### A paused workspace

While a workspace is paused, a signed-in member gets `403 workspace_suspended` on every request that would take a new action, and still reads and exports. Its API keys behave differently: a key of a paused workspace is refused as an unknown key would be, with `401 invalid_credential`, on reads as well as writes, and works again unchanged when the workspace is restored. Do not rotate or reissue keys in response to that 401 without first checking the workspace's status in the App.

A workspace that only ever used API keys still has a road to its data while paused. The workspace owner signs in to the Sempera App, where the agent export, the call export and the call list keep working, and where the data deletion and workspace closure paths stay open. Nothing is deleted by a pause.

## Pagination

Most Sempera-native list routes take `limit` (1 to 200, default 50) and `cursor`. Responses are `{"object": "list", "data": [...], "has_more": true, "next_cursor": "..."}`. Pass `next_cursor` back unchanged; cursors are opaque. Compatibility facade routes keep their observed external envelopes, including `tools`/`hasMore`/`nextCursor`, `data.count`/`data.integrations`, `data` plus `errors`, and `voices`-only shapes. Check `openapi.json` for the exact response shape on each route.

## Idempotency

Send `Idempotency-Key` (up to 255 characters) on `POST /v1/agents`, `/publish`, `/rollback`, `/v1/sessions`, `/v1/calls`, `/v1/phone-numbers`, `/v1/tools`, `/v1/tools/{id}/execute`, `/v1/snippets`, `/v1/snippets/{id}/publish`, `/v1/integrations`, `/v1/tests`, `/v1/evals`, `/v1/evals/user-templates`, `/v1/evals/run` and `/v1/webhooks`. A retry with the same key and the same request replays the stored answer with `Idempotent-Replayed: true`; the same key with a different request is refused with `409 idempotency_key_reused`. Keys live 24 hours and are scoped to your workspace.

## Rate limits

| Subject | Limit |
| - | - |
| Per API key | 600 requests per minute |
| Anonymous (public routes), per IP | 60 requests per minute |
| Carrier media-stream ingress, per IP (its own budget, never the anonymous one) | 600 requests per minute |

Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix seconds). A `429` carries `Retry-After` in seconds. Back off exponentially.

Separately from these request limits, a workspace owner may cap the outbound calls the workspace places per UTC hour and per UTC day. `POST /v1/calls` over a cap answers `429 rate_limited` with `retry_after_s` in the error, and places nothing; if the cap cannot be read it answers `503 service_unavailable` and places nothing ([Owner features in the App](https://docs.sempera.org/app-features.md)).

## Body size

JSON bodies are limited to 1 MiB; knowledge uploads to 25 MiB.


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