Docs

Errors

Every error response of the REST API is an RFC 9457 problem details document with content type application/problem+json:

{
  "type": "https://api.3x.example/v1/docs/api/errors#insufficient_scope",
  "title": "Insufficient scope",
  "status": 403,
  "detail": "this key lacks conversions:write",
  "code": "insufficient_scope",
  "required": ["conversions:write"],
  "requestId": "0f6c2d0e-…"
}
  • Branch on code – it is stable. title and detail are for humans and may change.
  • type links to the matching section of this page.
  • requestId is also sent as the x-request-id header; quote it when you report a problem. You may send your own x-request-id (8–64 characters [A-Za-z0-9_-]) to correlate logs.
  • Validation errors carry errors: [{path, message}].

Two endpoints are deliberately different: the tracking endpoint POST /v1/e and the conversion beacon POST /v1/cb answer with bodyless status codes (204, 400, 403, 413, 429), because browsers send them with sendBeacon and never read a body. See Ingest protocol.

Codes

bad_request

400. The request is syntactically wrong in a way validation cannot describe – malformed JSON, an invalid cursor, contradictory parameters. Fix the request; retrying the same request will fail again.

validation_error

422. Parameters, query string or body do not match the schema of the endpoint. errors lists every problem with its path (body.scopes.0, query.preset). Unknown query parameters are validation errors too, so a typo never silently widens a report.

unauthorized

401. No credentials were sent, or they were sent in the wrong place. Send Authorization: Bearer <key>; the response carries WWW-Authenticate: Bearer. On /mcp the header also names the OAuth resource metadata, which is how Claude starts its sign-in.

invalid_token

401. The credential is malformed, has a wrong checksum, is unknown, expired or revoked. Create a new key; if the key was rolled, switch to the new value.

forbidden

403. The credential is valid but the action is not allowed for this kind of principal – for example a secret key calling a user-only endpoint (POST /v1/orgs), a derived key creating keys, or a narrowed key requesting org:admin.

insufficient_scope

403. The credential has access to the organization or project but lacks a scope. required lists the missing scopes. Create a key with these scopes (Scopes and roles) or, for OAuth, reconnect and approve the additional permission.

sudo_required

403. A dashboard session tried a sensitive action (keys, destinations, members, deleting a project) without re-authenticating in the last 15 minutes. Call POST /v1/auth/sudo with the password (or a fresh magic link) and repeat the request. Secret keys are never asked for sudo.

not_found

404. The resource does not exist or you have no access to it – 3x does not reveal the existence of organizations and projects you cannot see. Also returned for unknown routes.

conflict

409. The request collides with the current state: a slug that is already taken, a resource that was changed concurrently, an Idempotency-Key reused with a different body.

last_owner

409. The change would leave the organization without an owner (demoting or removing the last owner). Make someone else owner first.

gone

410. The resource existed but cannot be used any more – an accepted, revoked or expired invitation, a used magic link, an ad-platform connect link older than ten minutes.

payload_too_large

413. The body exceeds the endpoint's limit: 16 KB for tracking batches, 4 KB for conversion beacons and 64 KB for MCP requests.

unsupported_media_type

415. Wrong content-type. JSON endpoints need application/json; the tracking endpoint accepts text/plain (to avoid CORS preflights).

rate_limited

429. Too many requests. Wait for the number of seconds in Retry-After. JSON endpoints that apply a limit also send RateLimit and RateLimit-Policy headers. Limits: Rate limits.

not_configured

503. The deployment lacks something the endpoint needs – no database URL, no Composio API key for ad connections, no SMTP for e-mails. The operator has to set the environment variable.

db_unavailable

503. The database is unreachable or waking up (Neon cold start). Retry with backoff. 3x never answers 401 in this case, so OAuth clients such as Claude keep their connection.

upstream_error

502. An external service failed: Composio, an ad platform, or the site fetched by setup_verify_installation. detail names the service; provider error texts are sanitized.

not_implemented

501. The endpoint exists in the API contract but is not available on this deployment (for example a feature disabled by the operator).

internal

500. An unexpected error. Nothing about it is leaked in the response; the operator finds it in the logs by requestId. Retrying is safe for GET requests and for requests with an Idempotency-Key.