Scopes and errors
Grant least privilege and handle every stable public API error code.
Grant least privilege and handle every stable public API error code.
Every public API operation requires one explicit scope. A valid credential without that scope receives 403 insufficient_scope; it is never silently upgraded to broader access.
Scope reference#
| Scope | Allows |
|---|---|
account:read | Read the authenticated account, plan, entitlements, and wallet summary |
tickets:read | Read support tickets and their messages |
tickets:write | Update tickets, assign teammates, and add internal notes or customer replies |
team:read | Read team members, pending invitations, and permitted audit events |
team:write | Create or revoke invitations and manage non-owner team roles |
integrations:read | Inspect authorized GitHub App installations and repositories |
integrations:write | Prepare integration changes and create draft pull requests |
sites:read | List and retrieve sites |
sites:write | Create, update, publish, unpublish, and delete sites |
agents:read | Read agent settings |
agents:write | Update agent behavior, language, voice, and embed settings |
knowledge:read | List knowledge sources and FAQs |
knowledge:write | Create, update, and delete knowledge sources and FAQs |
conversations:read | List conversations and retrieve their messages |
conversations:write | Take a conversation over: reply to a visitor as the business, pause the agent on it, and hand it back |
leads:read | List captured leads |
leads:write | Create, update, qualify, and accept leads into the opportunity pipeline |
pipeline:read | Read stages, accounts, opportunities, activities, attribution, and CRM sync state |
pipeline:write | Configure stages; create or update opportunities and accounts; import or retry CRM outcomes |
analytics:read | Read site analytics events |
connectors:read | List connector state without returning secrets |
connectors:write | Create, update, enable, disable, and remove connectors |
domains:read | Removed. Kept only for compatibility; every /domains route returns 409 because custom domains are not offered |
domains:write | Removed. Kept only for compatibility; every /domains route returns 409 |
broadcasts:read | List broadcasts and delivery state |
broadcasts:write | Create drafts and queue a broadcast send |
notifications:read | List owner notifications |
notifications:write | Mark notifications read or unread |
monetization:read | List monetization products |
monetization:write | Create, update, activate, deactivate, and delete products |
usage:read | Read metered usage events |
webhooks:read | List webhook endpoints and delivery state |
webhooks:write | Create and delete webhook endpoints |
Use read scopes for reporting jobs. Add a write scope only when the integration performs that mutation. For example, a lead export normally needs sites:read and leads:read, not sites:write or agents:write.
Scope failures#
{
"error": {
"code": "insufficient_scope",
"message": "The API key does not grant the required scope.",
"details": { "required": ["sites:write"] },
"request_id": "6c0b2f2e-..."
}
}Add the missing scope to the key under Settings → Developer → Edit, or create a replacement key and update the server secret. Editing changes the key's name and scopes in place; its expiry and token stay the same. Keys issued through OAuth cannot be edited; reconnect the application to change what it was granted.
Error envelope#
All API errors use the same top-level shape:
type ApiError = {
error: {
code: string;
message: string;
details?: unknown;
request_id: string;
};
};Branch on error.code, not the English message. Messages can improve without a version change. Log error.request_id and the x-request-id response header with the operation name, status, and retry attempt—but never log the authorization header or request secrets.
Status and code reference#
| HTTP | Stable code | Meaning | Normal response |
|---|---|---|---|
400 | invalid_request | Validation failed or a field is unsupported | Correct the fields in details; do not retry unchanged input |
400 | invalid_json | Body is not a JSON object or contains invalid JSON | Serialize one valid JSON object |
401 | authentication_required | Bearer header is missing | Attach a server-side credential |
401 | invalid_api_key | Key is invalid, expired, revoked, or its owner no longer exists | Replace or rotate the key |
403 | subscription_required | The owner does not have an active paid plan | Restore the subscription before retrying |
403 | insufficient_scope | The credential lacks the operation's scope | Create a least-privilege replacement credential |
403 | account_paused | API access is paused for the account | Resume API access or contact the account owner |
404 | not_found | Endpoint or owned resource was not found | Verify the path and tenant-owned ID |
409 | conflict | Current resource state prevents the action | Read current state before retrying |
413 | payload_too_large | The JSON body exceeds the 256 KB request cap | Send a smaller body; split large content into several requests |
415 | invalid_request | Mutation body is not application/json | Send Content-Type: application/json |
429 | rate_limit_exceeded | The key's one-minute plan bucket is exhausted | Wait for Retry-After and apply jitter |
500 | internal_error | The request could not be completed | Retry a safe or idempotent operation and report the request ID |
503 | outcome_unknown | A mutation may have completed, but its result could not be recorded; details.retryable is false | Do not retry. Read the resource to see whether the change happened; the same idempotency key keeps returning this result |
Resources outside the credential owner's account can return 404 instead of revealing whether another tenant's ID exists.
Validation details#
Field validation returns paths that can be shown next to your own form controls:
{
"error": {
"code": "invalid_request",
"message": "Request validation failed.",
"details": [
{ "path": "content.headline", "message": "String must contain at least 1 character(s)" }
],
"request_id": "6c0b2f2e-..."
}
}Mutation bodies must be JSON objects. Arrays, empty bodies, form data, and JSON sent with a text content type are rejected.
Retry decisions#
- Retry
429after the number of seconds inRetry-After. - Retry
500only for safe reads or writes protected by the sameIdempotency-Key. - Retry the in-progress idempotency
409after itsRetry-After: 2header. - Never retry
503 outcome_unknownblindly. Check the resource first; use a new idempotency key only after confirming the change did not happen. - Do not retry
400,401,403,404,413, or a state conflict until the cause changes.
See Rate limits for backoff behavior and API reference for the required scope on each operation.