Error Codes
All Synapse API errors follow a consistent format with an HTTP status code, a human-readable detail message, and a machine-readable code.
Error Response Format
| Field | Type | Description |
|---|---|---|
detail | string | A sentence describing what went wrong and how to fix it |
code | string | A stable identifier for programmatic error handling |
HTTP Status Codes
| Status | Meaning | When It Occurs |
|---|---|---|
200 | OK | Successful read, update, or direct send |
201 | Created | Resource created successfully |
202 | Accepted | Event accepted and queued for processing |
204 | No Content | Resource deleted successfully |
400 | Bad Request | Invalid request body, missing required fields |
401 | Unauthorized | Missing, invalid, or expired authentication credentials |
403 | Forbidden | Valid auth but insufficient permissions or plan limits |
404 | Not Found | Resource does not exist or is not accessible |
409 | Conflict | Duplicate idempotency key or unique constraint violation |
422 | Unprocessable Entity | Request body is valid JSON but fails business validation |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Unexpected server error (reported to Sentry) |
502 | Bad Gateway | Upstream service (pyrx.auth, pyrx.payment) unavailable |
503 | Service Unavailable | Authentication service warming up or temporarily unreachable — retryable (see Retry-After) |
Error Codes Reference
Authentication Errors (401)
| Code | Detail | Cause |
|---|---|---|
invalid_api_key | "API key is invalid or has been revoked" | The X-API-KEY header does not match any active key |
missing_api_key | "X-API-KEY header is required" | No API key provided |
missing_workspace_id | "X-WORKSPACE-ID header is required" | No workspace ID provided |
missing_token | "Missing bearer token" | No Authorization: Bearer <token> header was supplied |
token_expired | "Token has expired" | JWT has passed its exp timestamp |
invalid_token | "Token signature is invalid" | JWT was tampered with or signed by an unknown key |
Authorization Errors (403)
A 403 means your credentials are valid but not permitted for this operation. Which code you receive depends on why the request was denied — API-key scope, RBAC permission, JWT role, or plan limits. See the note below the table for the two response shapes these codes use.
| Code | Detail | Cause |
|---|---|---|
missing_permission | "Missing permission: <permission>" | The caller's role (or SDK data-plane API key) lacks the specific permission this endpoint requires — for example events:write or flows:write. The most common 403. Carries required_permission. |
insufficient_scope | "API key requires scopes […] but only has […]" | The API key's scope (data, reporting, management, full) does not cover this endpoint. Carries required_scopes and your_scopes. |
scope_forbidden | "API key scope does not include <scope>" | A data-plane write endpoint (contacts, devices, identify, alias) was called by an API key whose scope does not include the required write scope. Carries required_scope. |
insufficient_role | "This endpoint requires one of roles […] but your role is <role>" | A dashboard (JWT) request was made by a user whose role is not permitted for this endpoint. Carries required_roles and your_role. |
control_plane_only | "This operation is only available from the dashboard" | An API key was used against a dashboard-only (control-plane) endpoint. Carries a dashboard_url deep link. |
workspace_mismatch | "API key does not belong to the specified workspace" | Key belongs to a different workspace than X-WORKSPACE-ID |
plan_limit_reached | "Email limit reached for your plan. Upgrade to send more emails." | Monthly email, contact, flow, member, or API key limit exceeded |
workspace_suspended | "Workspace is suspended due to payment failure" | Workspace is in read-only mode |
403 responses use one of two body shapes. Permission-style codes
(missing_permission, scope_forbidden) return the standard envelope with the
identifier under code. The scope/role/plane family (insufficient_scope,
insufficient_role, control_plane_only) returns an envelope with error: "forbidden"
and the identifier under reason, plus the relevant required_* / your_*
fields. When handling 403s programmatically, read code first and fall back to
reason.
Validation Errors (400)
| Code | Detail | Cause |
|---|---|---|
validation_error | Missing required field | A required field is missing from the request body |
invalid_email | Value is not a valid email address | Email field fails format validation |
invalid_event_name | "Event name must be lowercase snake_case" | Event name contains invalid characters |
invalid_filter_operator | Unknown filter operator | Segment filter uses an unsupported operator |
invalid_date_param | "from_date/to_date must be ISO-8601…" | 422 — from_date/to_date on GET /v1/flows/{id}/goal-conversions is not valid ISO-8601 (URL-encode a + offset as %2B) |
Not Found Errors (404)
| Code | Detail | Cause |
|---|---|---|
contact_not_found | "No contact found with the given identifier" | Contact ID or external_id does not exist |
flow_not_found | "Flow not found" | Flow ID does not exist or is archived |
template_not_found | Template not found or is inactive | Template slug does not exist or was soft-deleted |
segment_not_found | "Segment not found" | Segment ID does not exist |
Conflict Errors (409)
| Code | Detail | Cause |
|---|---|---|
duplicate_event | "Event with this idempotency key has already been processed" | Same idempotency_key submitted twice |
duplicate_external_id | "A contact with this external_id already exists" | POST /v1/contacts with an existing external_id |
duplicate_slug | "A template with this slug already exists" | POST /v1/templates with an existing slug |
goal_event_name_conflict | "A goal for event '…' already exists on this flow" | Creating or renaming a conversion goal to an event_name already used by another goal on the same flow |
Rate Limit Errors (429)
| Code | Detail | Cause |
|---|---|---|
rate_limit_exceeded | Rate limit exceeded. Retry after N seconds. | Too many requests in the current window |
Server Errors (5xx)
| Code | Detail | Cause |
|---|---|---|
internal_error | "An unexpected error occurred" | Unhandled server exception (logged to Sentry) |
auth_unavailable | "Authentication service is warming up. Please retry." | Returned as a retryable 503 when the pyrx.auth JWKS fetch fails or the auth circuit breaker is open (e.g. a cold cache during a fresh deploy, or pyrx.auth temporarily unreachable). Carries a Retry-After header — retry after the advertised delay. |
payment_service_unavailable | "Payment service temporarily unavailable" | pyrx.payment API unreachable |
Handling Errors
Python
JavaScript
Always check the code field for programmatic error handling. The detail message may change between API versions, but code values are stable.