Atribu
API Reference

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 response
{
  "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 exact code.
  • required_scopes — present on insufficient_scope when 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.

On this page