Errors
API error codes, HTTP status, and what to do about each one
Every error response shares one envelope, on both the Hono and Next-served /api/v1/** surfaces.
{
"error": {
"code": "insufficient_scope",
"message": "This API key does not have the 'campaigns:read' scope",
"status": 403,
"request_id": "req_a1b2c3d4",
"docs_url": "https://www.atribu.app/docs/api/errors#insufficient_scope",
"required_scopes": ["campaigns:read"]
}
}Envelope fields
code— the machine-readable code — see the table below for the full catalogue.docs_url— always present. Deep link to the section on this page for this exactcode.required_scopes— present oninsufficient_scopewhen the missing scope(s) are known by name. Mint a new key with one of these, or see Authentication.reconnect_required / reconnect_url— present when a connection's provider token was revoked and the caller must re-authorize.
Error codes
unauthorized — 401
The request carries no valid credential — a missing, malformed, expired, or revoked API key or session. Include a valid Bearer token in the Authorization header.
forbidden — 403
The credential is valid but not allowed to perform this action (for example, a workspace role that lacks the required permission).
insufficient_scope — 403
The API key or session does not carry a scope this route requires. When the missing scope is known, required_scopes names it — mint a new key with that scope, or have a workspace admin grant a role that includes it.
not_found — 404
The requested resource does not exist, or exists but is outside what this credential can read.
invalid_parameter — 400
A query parameter or path segment failed validation (wrong type, a malformed UUID, or a value out of range).
invalid_request — 400
The request itself is malformed — invalid JSON, the wrong content type, or a shape the endpoint cannot parse.
validation_error — 422
The request body parsed but failed schema validation. message names the offending field.
invalid_content — 400
The request body's content — not just its shape — is invalid (for example, an empty attachment or the wrong multipart part).
invalid_date_range — 400
date_from/date_to failed the shared date-range contract: both required, YYYY-MM-DD, real calendar dates, date_from on or before date_to, and within the maximum span.
invalid_state — 409
The resource is not in a state that allows this operation (for example, a duplicate name, or an invitation already accepted or revoked).
rate_limit_exceeded — 429
The caller exceeded its rate limit. Respect the Retry-After header before retrying.
connection_not_ready — 409
The named data connection exists but is not yet usable (for example, it is missing an access token).
provider_error — 502
An upstream provider (Meta, GoHighLevel, …) returned an error while fulfilling this request.
service_unavailable — 503
A dependency this endpoint needs is not configured, or is temporarily down.
not_implemented — 501
This capability is not available on this deployment, or not yet for this provider.
internal_error — 500
An unexpected server error. request_id identifies it for support.
calendar_scope_required — 403
The connection's Google grant is missing the calendar scope this operation needs (calendar.app.created to manage calendars, calendar.acls to share).
calendar_unsupported — 422
The request targeted a calendar Atribu doesn't manage (for example primary, or an unknown id). Booking operations only run on Atribu-created calendars.
provider_not_configured — 501
A read-only external-calendar provider (Microsoft) has no app registration on this deployment, so the endpoint cannot serve it yet.
invalid_window — 400
The requested read-events time window exceeds the endpoint's 90-day cap.
media_expired — 410
The referenced media id has expired or the provider no longer serves it. WhatsApp webhook media ids expire after 7 days.
whatsapp_register_limit — 429
WhatsApp phone-number registration is capped at 10 attempts per number per 72 hours. This is propagated verbatim from Meta and is never retried server-side — wait out the window.
whatsapp_payment_required — 402
The WhatsApp Business Account has no attached payment method, or its funding source was declined.
whatsapp_otp_relay_unavailable — 503
The OTP-capture relay's store is unreachable, so a captured code cannot be held for the connect page.
audit_timeout — 504
The on-demand UTM-health scan did not finish inside the request budget (Postgres cancelled the statement, or the gateway gave up first). The previously cached summary is still readable; retry the scan later.
weak_password — 422
The new password is shorter than the minimum required length.
compromised_password — 422
The new password appears in a public breach corpus (HaveIBeenPwned k-anonymity check).
current_password_required — 400
The caller has an email/password identity, so proving the current password is required, and the request omitted it.
current_password_invalid — 400
The supplied current password did not verify against the caller's identity.
plan_upgrade_required — 402
This workspace-admin write needs a plan this workspace is not on. The remedy is upgrading the plan, not a different role.
whatsapp_service_window_expired — 422
The WhatsApp customer-service window (24 hours since the customer's last message) has closed. Use a template or interactive message instead.
payload_too_large — 413
The uploaded media exceeded the provider's per-type size cap (5MB image / 16MB video or audio / 100MB document).
profile_limit_reached — 403
The workspace's active_profiles plan limit is already reached. Carries upgrade_required: true and upgrade: { plan } — the fix is adding capacity (a plan upgrade, or on Agency/Enterprise a support-granted override), not a different credential.
idempotency_key_conflict — 409
The Idempotency-Key header was already used for a different operation than the one it first claimed. Generate a fresh key per logical write attempt.
demo_purge_blocked — 409
A database trigger refused part of the delete because the rows are audit evidence. The demo profile is archived and its seeded data removed; the conversion-export and signal-audit rows stay attached to it. Retrying will not change this — nothing further is needed.