Skip to main content
Every non-2xx response has the same body:
Branch on type for control flow and on code for specific handling. Both are stable; message is written for humans and may be reworded. Always log request_id. It is echoed in the X-Request-ID response header, and quoting it lets us find your exact request.

Types

A 404 does not distinguish “does not exist” from “belongs to another workspace”. That is deliberate: telling them apart would let anyone probe for record ids across workspaces.

Missing scopes

A 403 from a scope check names exactly what is missing, so you do not have to guess:
A 403 that is not insufficient_scope usually means workspace write policy — for example, an entity mirrored from an external CRM. Call GET /v1/capabilities to see which entities you may write.

Retrying

Retry rate_limit_error and api_error. Do not retry 4xx errors unchanged; they will fail identically.
Use exponential backoff with jitter. Retrying immediately during a rate limit extends it.

Error codes

Every error.code, grouped by HTTP status. error.documentation_url links straight to the entry for the code you received. Codes are permanent: branch on them.

400 Bad Request

bad_request

The request could not be understood.

invalid_cursor

The cursor is corrupt, or was issued for a different query. Pass back links.next unchanged, with the same filters and sort.

unknown_expand

A relationship in expand does not exist on this resource.

expand_limit_exceeded

Too many relationships in expand; the limit is 4.

unknown_field

A name in fields is not on this resource.

unknown_sort_field

This resource cannot be sorted by that key.

unknown_entity

No resource has that name.

too_many_filter_values

A filter parameter was repeated more times than allowed.

unsupported_sort

This collection cannot be sorted that way.

invalid_window

The time window is empty or reversed.

401 Unauthorized

unauthorized

The token is missing, unknown, revoked or expired.

token_expired

The OAuth access token expired. Refresh it and retry.

403 Forbidden

insufficient_scope

The token lacks a scope this endpoint needs; param names the first missing one. GET /v1/me lists what the token has.

forbidden

The request is not allowed for this token.

local_write_denied

The workspace has switched off this kind of write, usually because an external CRM is its system of record. GET /v1/capabilities shows the policy.

user_grant_denied

The person who authorized this OAuth connection is not allowed to make this change in Sailer.

note_author_only

Only the teammate who wrote this note can change or delete it.

conversation_access_denied

The person who authorized this OAuth connection cannot act on this conversation in Sailer.

channel_access_denied

The person who authorized this OAuth connection cannot use a channel this campaign sends through.

campaign_dispatch_denied

The person who authorized this OAuth connection is not allowed to send campaigns.

404 Not Found

not_found

No such record in this workspace.

contact_not_found

A contact id in the request does not exist in this workspace.

pipeline_not_found

The pipeline id does not exist in this workspace.

stage_not_found

No stage with that id or name in the pipeline.

record_not_found

A contact, organization or deal id in the request body does not exist in this workspace, or is not visible to this token; param names the field, such as record_id, contact_id or organization_id.

405 Method Not Allowed

api_error

The method is not supported on this path.

409 Conflict

conflict

The request conflicts with the current state.

already_exists

The record already exists.

external_id_taken

Another record of the same resource already has this external_id; the message names it.

external_id_conflict

The external_id and the phone of an upsert name different contacts, or the matched contact already has another external id.

phone_mismatch

The contact with this external_id has a different phone. Upsert never changes a contact’s phone.

contact_already_exists

A contact with this phone already exists; the message names it. POST /v1/contacts/upsert creates or updates in one call.

campaign_not_accepting_participants

The campaign has finished and takes no new participants.

idempotency_key_reused

This Idempotency-Key was already used with a different request body. Use a new key for a new request.

idempotency_key_in_flight

A request with this Idempotency-Key is still being processed. Retry after it completes.

tag_already_exists

The resource’s tag catalog already has a tag with this name.

conversation_closed

The conversation is closed; it cannot be handed off, assigned or resumed.

conversation_not_with_ai

A teammate or the queue already has this conversation, so there is nothing to hand off. Use assign to change the teammate.

conversation_with_ai

The AI agent is handling this conversation. Use handoff to give it to a person first.

conversation_already_with_ai

The AI agent is already handling this conversation.

conversation_has_no_contact

The conversation has no contact, or its contact no longer exists, so it cannot be closed here.

no_agent_available

The conversation’s queue has no active AI agent with a share of its work to hand it back to.

conversation_queue_unavailable

The queue the conversation is in no longer exists, so it cannot be handed off. Move it to another queue in Sailer.

campaign_not_running

Only a running campaign can be paused.

campaign_not_paused

Only a paused campaign can be resumed.

campaign_not_ready

The campaign is missing setup it needs to send; the message names it.

campaign_has_no_message

The campaign has no active message to send.

campaign_template_variables_unresolved

A template variable in the campaign message cannot be filled.

campaign_template_not_approved

A template the campaign sends is not approved by WhatsApp yet.

campaign_channel_unavailable

A channel the campaign sends through is missing or not active.

already_reversed

This event was already reversed; an event can be reversed once.

not_a_record

A reversal cannot itself be reversed.

422 Unprocessable Content

validation_failed

Some fields are invalid; detail lists each one. Query, path and header problems answer 400 with the same code.

invalid_request

The request is well-formed but cannot be applied as given.

stage_not_in_pipeline

The stage belongs to a different pipeline than the deal.

agent_activity_read_only

This activity is the AI agent’s plan to message the contact again; it can only be completed or canceled.

assignee_not_found

The assignee_id is not a teammate who can take this conversation.

won_reason_without_won

won_reason is only accepted with outcome: won.

unknown_event_code

The workspace has no success event type with this code. GET /v1/success-event-types lists them.

value_basis_mismatch

The event type requires a value and none was sent, or forbids one and one was sent.

429 Too Many Requests

rate_limit_exceeded

Too many requests on this token. Wait Retry-After seconds before retrying.

500 Internal Server Error

internal_error

Something went wrong on our side. Retry with backoff, and quote error.request_id to support if it persists.

Field-level codes

A validation_failed error lists each offending field in detail, with one of these codes or a standard validation type such as missing.