Error reference

Every code the API can return. The docs field in an error envelope links straight to the entry for that code, so an error message is one click from what it means.

The envelope

Every error on /v1 has the same shape. On the Graph passthrough you get Meta's envelope instead, unchanged, because an existing integration's error handling has to keep working — the one exception is the routing gate, which is documented separately and is written in Meta's shape for the same reason.

an error on /v1
{
  "error": {
    "type": "conflict",
    "code": "connection_not_active",
    "message": "This connection is not currently active.",
    "param": "connection_id",
    "request_id": "req_01K3F8QY7ZC4M2N9V6XJ0RTBHE",
    "docs": "https://cosend.app/docs/errors#connection_not_active"
  }
}

Branch on type, log code. The nine types are a closed set and they are what your retry and error-handling logic should switch on. The codes are the specific reason, and new ones are added over time — treating an unfamiliar code as fatal is how a client breaks on a release that broke nothing.

request_id is in the body, in the response header, and in our logs. Quoting it to support starts the conversation at the right request rather than the right hour.

A resource in another organization returns 404, not 403. A 403 would confirm that the ID exists somewhere in the system, which turns a guess into an oracle. This is deliberate, and it is the one behaviour on this page that reads like a bug and is not.

The nine types

The nine error types, the HTTP status each maps to, and what it means.
Type HTTP Meaning
invalid_request 400 Malformed or semantically wrong input
authentication 401 Missing, bad, revoked or expired credential
permission 403 Authenticated, but not allowed — scope, role or plan
not_found 404 Does not exist, or exists in another organization
conflict 409 An idempotency conflict, or a state transition that is not legal
quota 402 A plan limit or cap was reached. The body names the meter and the upgrade URL
rate_limit 429 Ours or Meta’s. Retry-After is set
upstream 502 Meta or a connector failed. upstream_error carries their payload verbatim
internal 500 Ours. request_id is the only useful thing, and nothing internal leaks

Authentication and permission

missing_credential 401
No Authorization header.
invalid_credential 401
Unrecognised, malformed or revoked.
expired_credential 401
Past its expiry.
insufficient_scope 403
The credential is valid but lacks the scope. The body names which one.
insufficient_role 403
The member’s role does not permit this.
test_key_on_live_connection 403
A test credential against a live connection. The credential is the mode, so there is no header to forget.
plan_does_not_include 403
The feature is not on this plan — the MCP server on Free, for instance. The body carries an upgrade URL.
keys_write_not_available_to_oauth 403
keys:write is never granted to an OAuth token. A client that could mint API keys could turn a scoped, revocable, audited consent into a permanent unaudited credential.

Request

invalid_request 400
Schema validation failed. details[] carries up to ten JSON pointers and param is the first.
unknown_field 400
A field the schema does not define. Bodies are strict, so a mistyped template_nmae fails at 10am rather than silently sending the wrong thing at 3am.
missing_required_field 400
A field the schema requires was absent.
invalid_cursor 400
A malformed or non-existent after / before.
idempotency_key_required 400
Send endpoints require one. A retried send with no key is a duplicate message to a real person.
idempotency_key_reuse 409
The same key with a different body. Silently returning the first response would swallow a second, different intent.
idempotency_key_in_flight 409
The first request with this key is still running. Retry-After is 1.
not_found 404
Absent, or another organization’s.
unsupported_api_version 400
A passthrough version segment outside the supported range.

Connection and messaging

connection_not_found 404
No connection with that ID — or it belongs to another organization.
connection_not_active 409
The connection needs reauthorisation, is disconnected, or is payment-blocked.
connection_not_routable 403
The passthrough gate. reason names which of the seven applies — the Runtime API reference lists them all.
not_supported_on_coexistence 403
Groups, calls, catalogs, register and deregister. These are what coexistence costs on the API side; the catalog and the profile keep working in the app.
direct_mode_conflicts 409
Switching this connection to direct delivery would break the objects listed.
direct_mode_no_content 409
An endpoint that needs message content, on a connection that stores none. Direct-delivery mode writes metadata and never a body.
service_window_closed 409
A free-form message outside the 24-hour window with no template fallback. Maps Meta 131047.
template_not_found 404
No template with that name or ID.
template_not_approved 409
Not approved, or paused. Carries paused_until when Meta gave one.
recipient_opted_out 409
Consent was withdrawn for this category.
throughput_exceeded 429
The connection’s messages-per-second ceiling — 20 on coexistence, fixed and not raisable. Maps Meta 130429.
pair_rate_limited 429
The same recipient, too often. Maps Meta 131056.
identity_changed 409
The contact’s identity key changed. Maps Meta 137000.
conversation_locked 409
Another agent holds the lease and is active. The body carries who holds it, when it expires, and the URL to request it.
lock_force_not_permitted 403
A forced take-over from a member. Only an owner or an admin may seize an active lock.
lock_request_expired 409
Declining a take-over request after its sixty seconds — it has already transferred.
media_too_large 413
Over the 100 MB ceiling, which is Meta’s own limit rather than ours.
media_unavailable 410
Meta’s asset expired and no copy of it exists in our storage.

Automation, quota and upstream

automation_invalid 400
Validation failed. The body is the full validation report, not a single message.
automation_not_deployed 409
The automation has no active version to trigger.
connector_not_connected 409
An action needs a connector account that does not exist.
connector_needs_reauth 409
The credential is no longer valid at the provider.
approval_required 202
Held for a human decision. The body carries the approval’s ID and its URL. Not a failure — the run is waiting.
approval_expired 409
The held approval was not answered in time.
quota_exceeded 402
Included units are used and the cap is reached. The body names the meter, the usage, the cap and the upgrade URL.
ai_conversations_capped 402
The AI overage cap. AI replies stop; the inbox, the API and transactional sends keep working.
concurrency_limit 429
The organization’s concurrent-run limit.
upstream_error 502
Meta or a connector failed. upstream_error carries their payload verbatim, so you can handle our vocabulary and still read theirs.
upstream_timeout 504
Meta or a connector did not answer in time. On the Runtime API that is thirty seconds, connect plus read.
internal_error 500
Ours. request_id is the only content, and it is the one to quote to support.