Skip to main content

Troubleshooting: authentication, key mode, and IP allowlist failures

Every request to the Orbit API passes through the same chain of gates: the credential resolves, the key’s expiry and revocation state is checked, the sandbox/live key mode is compared against the workspace, the source IP is matched against the allowlist, and the key’s scopes are checked against the route. A rejection tells you which gate said no — but only if you read the code, since several gates share the same HTTP status. This page maps every authentication rejection code to its gate and the fix, then covers the SAML/SCIM gates enterprise tenants hit at login and provisioning time. Find the code on your response in the table, then jump to the section.
Several gates deliberately return generic codes. A per-key IP allowlist block returns INVALID_API_KEY (not IP_NOT_ALLOWED) so a leaked key cannot enumerate your allowlist. A revoked key is reported as EXPIRED_TOKEN with the message “The API key has been revoked.” Read the message and code together, and treat the response envelope’s status field as authoritative.

The 401 family: UNAUTHORIZED, INVALID_API_KEY, EXPIRED_TOKEN

UNAUTHORIZED is the generic refusal: no X-API-Key header (or no dashboard session) reached the route, the value was truncated, or it carried a character set the key format never uses. Confirm the header name is X-API-Key and the full key value is sent — prefixes like dv_live_sk_ alone are not a key. INVALID_API_KEY means the key resolved to nothing usable. Work the causes in order:
  1. Wrong key value. Re-copy the key from Settings → API Keys. The full secret is shown once at creation; if you lost it, create a replacement and revoke the old one.
  2. Revoked key. Keys revoked in the dashboard stop authenticating immediately. Check the key’s state under Settings → API Keys.
  3. Per-key IP allowlist. If the key has an allowed_ips list and your request originates outside it, the rejection is INVALID_API_KEY by design. See the IP allowlist section below.
  4. Workspace mismatch. A key copied from a different organization never authenticates here — create one in this workspace instead.
EXPIRED_TOKEN on an API-key request has exactly two causes:
  • The key’s configured expiry time passed. Create a replacement key and update your integration; expiry cannot be extended in place.
  • The key had a scheduled revocation cut-off (revoke_at) that has arrived. The key is treated as revoked from that moment regardless of any scheduler timing — rotate to a new key.
The same code also appears on dashboard-session flows (for example a SAML relay with an out-of-date token). There it means “sign in again,” not “rotate a key.”

The key-mode split: sandbox vs live

Orbit separates sandbox and live state hard: a sandbox key must never authenticate into a live workspace, and a live key must never authenticate into a sandbox workspace. The mode comes from the key prefix — a dv_test_sk_ key is sandbox-mode, a dv_live_sk_ key is live-mode — and the API compares it against the workspace the key belongs to. On a mismatch the request fails with 403 WRONG_KEY_MODE before any handler runs, and nothing is read or written. The two directions and their fixes: The day-one pattern is the first row: integrate and test with a dv_test_sk_ key, then switch to dv_live_sk_ at go-live. Test sends in sandbox deliver to magic numbers and simulate provider outcomes; live keys send real traffic and debit the wallet. The go-live checklist walks the full switch.

The IP allowlist gates

Orbit has two independent IP allowlists, and they fail differently:
  • Per-API-key allowlist — a list of source IPs or CIDR ranges on one key, managed through the key’s allowed_ips field. A request from a non-allowed source is rejected with 401 INVALID_API_KEY before any other gate runs, and the rejection is deliberately indistinguishable from an unknown or revoked key. Setup, validation rules, and the audit trail are in Per-API-key IP allowlist.
  • Org-level allowlist — a workspace-wide list gating the dashboard and SAML login surfaces and API requests that carry a session or key. A request from a non-allowed source IP is rejected with 403 IP_NOT_ALLOWED and the message “Your IP address is not in the allowlist.”
To diagnose a suspected allowlist block:
  1. Determine which gate. A 403 IP_NOT_ALLOWED is the org-level list; a 401 INVALID_API_KEY that only appears from certain networks is the per-key list.
  2. Confirm your egress IP as the API edge sees it (your NAT / proxy’s public address — curl https://api.orbit.devotel.io/api/v1/me from the same host and check which call fails where).
  3. Compare against the list: Settings → Security for the org-level list, or GET /api/v1/settings/api-keys/{keyId}/allowed-ips for a per-key list.
  4. Add the egress IP or its CIDR range, or clear the list (null or []) to make a per-key list unrestricted again.
Changes take effect on the next request — no propagation lag. If you locked yourself out of the dashboard by tightening the org-level list, any owner on a still-allowed network can widen it back; if nobody is on an allowed network, escalate with the details listed below.

Public-key write attempts: 422

Public keys (dv_..._pk_), which exist to be embedded in browser or mobile client code, are restricted to read-only scopes at creation. Requesting a write or administrative scope — messages:write, contacts:write, admin, the * wildcard, or the sensitive reads billing:read / settings:read — on a public key is rejected at create time with a 422. This is a key-creation gate, not a per-request one: you hit it when creating or updating the key, not when calling an endpoint. The fix is never to find a way around it — put a secret key (dv_..._sk_) on your backend for anything that sends or modifies data, and keep the public key’s scopes to the minimum reads the client needs. The FAQ: Are public keys read-only? covers the scope split; Authentication covers key formats end to end.

Webhook rejections: STALE_WEBHOOK (401)

STALE_WEBHOOK fires on inbound webhook and DLR receiver surfaces when the delivery’s timestamp is missing or falls outside the allowed replay window — it is the clock-skew and replay-attack guard for provider callbacks, not for requests you authenticate toward the API. If your own webhook endpoint rejects Orbit deliveries with a 401, that is your signature verifier, and the failure classes (raw-body parsing, rotated secrets, mutated bodies, clock skew) are covered in Signature verification failures and Webhook security.

SAML and SCIM gates (enterprise tenants)

These gates surface on the login and provisioning path, not on API-key traffic.

SAML_USER_NOT_IN_ORG

The identity provider assertion succeeded cryptographically — the user proved who they are — but the SAML callback could not attach them to the workspace: the asserted email belongs to a different organization, or there is no invitation or SSO enforcement covering it on the callback organization.
  • The user must exist in the organization they sign into — invite them (or have SCIM provision them) before the first login.
  • If the same email is a member of two organizations, SSO enforcement decides which one the assertion attaches to; signing in with the other organization configured as the SSO target fails with this code.
  • Re-check the IdP’s org slug in the SAML login URL — pointing the IdP at the wrong workspace produces exactly this rejection.

SCIM_ROLE_CEILING_EXCEEDED

A SCIM group/role push asked Orbit to assign a role above the organization’s configured max_assignable_role ceiling (the default ceiling is admin). The provisioning request is refused and nothing is assigned.
  • Map IdP groups only to roles at or below the ceiling; keep owner-level assignment to the dashboard.
  • If a group genuinely needs the higher role, an owner raises the ceiling in the workspace’s SSO/SCIM settings — the ceiling is a tenant-owned control.
The full SCIM setup — base URL, Bearer token, role mapping, health checks — is in SCIM 2.0 provisioning.

What to send support

If the rejection survives the fixes above, open a support ticket with:
  • Your tenant ID (dashboard → Settings → Organization; also organizationId from GET /api/v1/me).
  • The error code and message exactly as returned, and the request_id from the response’s meta block — these let support find the exact gate decision.
  • For key issues: the key prefix (dv_live_sk_… / dv_test_sk_…) and whether the key has an IP allowlist configured. Never send the full secret value.
  • For an IP allowlist lockout: the source IP you are calling from as your egress sees it, and the list you believe is blocking it.
  • For SAML/SCIM: the IdP (Okta, Entra ID, …), the org slug in the SAML URL, the asserted email’s domain, and for SCIM the role the IdP tried to push.
Never include the full API key or a signing secret in a ticket — the prefix and request IDs are all support needs.

See also