Skip to main content

Worked organization samples

The endpoint list below documents each operation’s parameters; this overlay walks the org profile the way a dashboard shell or account-integration actually uses it: read the org → branch on plan and wallet balance → update name and settings → handle the auth failures. Every request uses your API key (X-API-Key) against https://api.orbit.devotel.io. The base plan field, budget, and settings keys you see here are the public contract — examples below tab cURL and Node.js (TypeScript). Every response follows the same envelope — data plus a meta block carrying request_id and timestamp. Quote meta.request_id when you report a failure; support can trace the request end-to-end from it.

1. Read the org profile

GET /api/v1/organization returns the authenticated organization’s record: name, slug, plan, wallet balance, team-member and per-second rate limits, logo, branding, general settings, and whether it is a subaccount. This is the call the dashboard shell makes on every page load, so it is also the right “ping” for a server-side client — a 200 here confirms the key is valid, scoped to a live org, and outside its rate limit.
200
Field notes a reader will hit on first integration:
  • balance is the wallet balance in USD cents — 250000 is $2,500.00. It is null on the brief degraded shell (see below), so branch on that before rendering a dollar figure.
  • billing_currency is resolved from settings.wallet_currency and falls back to USD; format every cost figure with it so displayed amounts match the currency billing charges against.
  • monthlyBudgetCents is the org’s monthly budget ceiling — the denominator spend-percent billing alerts divide by. null means no budget is set and spend-percent alerts never fire.
  • isSubaccount is derived server-side from the org’s parent link. A true value gates reseller-only affordances — a subaccount cannot create nested subaccounts.
  • A transient database blip returns a degraded shell — same 200, id + plan + tenantId filled from the auth context, the remaining DB-sourced fields null. Handle the nulls rather than erroring; the next poll self-corrects.

2. Branch on plan and balance

A typical shell reads the org once, then decides what to render: a low-balance banner, a plan-gated feature, a budget progress bar. Both edge cases matter — the balance can be null (degraded shell) and the budget can be null (never configured).
Node.js
Wallet semantics per endpoint family (what fires a running balance down, what tops it up) live in the billing reference; this page documents the org record itself.

3. Update name and general settings

PUT /api/v1/organization updates the org’s name, general settings, and monthly budget. It shares its handler with PUT /api/v1/settings/general, so the same write-boundary guards apply (unsubscribe-redirect URL checks, escalation-recipient allowlist, IP-allowlist lockout protection) and the settings cache invalidates immediately. Requires the owner or admin role — a member or developer key gets 403 (see Errors). The settings bag is free-form JSONB; a few keys are load-bearing and are listed below. Regional display prefs (timezone, locale, dateFormat) can be sent either as top-level fields or inside settings — the controller folds top-level values into the bag before writing, and lifts them back to the top level on read.
200
Semantics to code against:
  • monthly_budget_cents — integer budget ceiling in cents; 0 is rejected (a zero divisor would make every spend-percent alert fire). Send null to clear the budget; omit the field to leave it unchanged.
  • name — 1–200 characters; the rename echoes to the org record provider-side, so the dashboard header updates without a separate call.
  • settings.dnc_sync_enabled — must be a literal boolean. A string "true" or numeric 1 is rejected at validation — and even if it slipped through, the DNC pre-flight endpoint gates on the literal boolean and would keep returning 403.
  • Idempotency-Key — pass a stable client-generated value to dedupe retries on transient timeouts; the same key replays the original response for 24 h on 2xx. 409 means the same key is still in flight.
Sandbox. The update is a control-plane write — it changes only your org’s own record, triggers no provider delivery, and deducts no credits, so it is safe to exercise from any environment. The X-Test-Mode header matters for session-authenticated calls (browser clients): set X-Test-Mode: true to route the call through the test-mode pipeline and see meta.test_mode: true on the response. For server-to-server keys it is ignored — use a dv_test_sk_* test-prefixed key, which unconditionally enables sandbox behaviour, instead of toggling the header on a dv_live_sk_* key. The read and the update both honour the header for session clients, so a full read→write round trip in test mode touches no live state.

4. Errors

Errors follow the { error, meta } envelope. The two failures every integration must handle: 401 — the API key is invalid or has been revoked. This fires for a malformed key, a key that was rotated or deleted under Developer → API keys, and a key whose owning org was removed. Do not retry a 401 — rotate from a known-good credential before re-sending anything.
401
403 — role too low for the write. PUT /api/v1/organization requires the owner or admin role. A member or developer-role key (and a session for a user without the role) cannot update the org — reads still work with any role:
403
A validation failure (a 0 budget, a non-boolean dnc_sync_enabled, an unsafe unsubscribe or sandbox webhook URL) returns 422 VALIDATION_ERROR with a details object pointing at the rejected field, the same shape the rest of this page’s writes follow.

5. Provision a scoped key + invite the first member

Two provisioning calls close the loop for a new organization: mint a least-privilege API key for the server-side client, and get the first teammate in. Both responses landed as "data": {} on this page; the envelopes below are the real shapes. Mint the key. POST /api/v1/organization/api-keys (the SDK alias of POST /api/v1/settings/api-keys — body name, type secret|public, scopes, mode live|test, expiresIn, allowed_ips; the same body the settings route takes). The plaintext key appears exactly once — in this 201. Store it now; the API keeps only its hash, and the _warning string says so. keyPrefix and lastFourChars are the only pieces of the key that ever appear again — they identify it in GET /api/v1/organization/api-keys and in the dashboard (section 10 shows the list after a revoke). Omit scopes and the key stores an empty array.
201
Invite the member. POST /api/v1/organization/invites (the SDK alias of POST /api/v1/settings/team/invite — body email, role) records the pending invitation and returns the stored record: the invited email, the role granted on acceptance (owner can never be assigned via invite), invitedAt, and an email_sent flag. false means the invite is stored and can be resent — it is not an error. The pending-invites list (GET /api/v1/organization/invites) reads back every pending row with invitedBy added; an invite that was given an expiry answers 410 Gone when accepted past it.
201

6. Rotate a key without breaking a running integration

POST /api/v1/organization/api-keys/{id}/rotate mints a replacement for the key {id} and reports what happened to the old key in the same response. The new plaintext appears exactly once, in key — store it before you acknowledge the response. Two modes, picked by the request body:
  • Grace (default). Omit mode and the old key keeps authenticating until old_key_expires_at — 24 hours out by default, clamped to 1–168 hours when you send grace_period_hours. Deploy the new key everywhere first and let the old one lapse; nothing breaks mid-flight.
  • Immediate. Sending "mode": "immediate" revokes the old key inside the same transaction that mints the new one — the leaked-key panic path. The old key stops authenticating from the moment of the call, so expect 401s from anything still presenting it.
200
Read the old-key semantics off the response, not the key list:
  • old_key_expires_at — the absolute UTC instant the OLD key stops authenticating. With "mode": "immediate" it equals the rotation instant and _warning switches to “the old key has been revoked immediately and no longer authenticates”.
  • rotation_mode — echoes which path ran, so a retry-aware client can tell a grace rotation from a panic revoke in its own logs.
  • keyPrefix / lastFourChars — the public prefix and last four of the NEW key, the only pieces of it ever shown again. They identify the key in GET /api/v1/organization/api-keys and in the dashboard.
A cron that mints a weekly key should call rotate, store the new plaintext, and keep deploying against the old key until the grace window closes — rotation is the automation-friendly alternative to revoke-then-create.

7. Read verification state — three enums, three questions

The four verification reads on this page answer three different questions and use three different status enums; the per-operation examples above render a generic placeholder status that belongs to none of them. The real values:
  • GET /api/v1/organization/kyc/status (and its legacy alias GET /api/v1/organization/kyc) — is my business approved to send? KYC states: not_started (nothing submitted), pending / pending_review (a submission is with the compliance team; every submission lands here, including the one auto-stamped when your signup email was verified), approved, and rejected. With any state past not_started the response also echoes company_name, country, industry, submitted_at, and reviewed_at.
  • GET /api/v1/organization/kyc/idv/status — is the signing person’s identity verified? eKYC identity-verification states: pending (a hosted capture session is still open), verified, declined, and expired. provider, session_id, and hosted_url are populated while a session is open; configured: false means no identity-verification provider is provisioned for the platform, in which case the step does not apply to your org.
  • GET /api/v1/organization/kyb/status — did the business screening flag anything? KYB / AML screening verdicts: clear (no sanctioned country, no watchlist hit) or review (at least one hit in matches; each match names the label it hit on — country, legal name, or a declared beneficial owner — plus a detail). A review verdict never blocks sending by itself; it is a signal a super-admin reviews.
All three degrade politely: if the org record cannot be read in that instant, each endpoint answers 200 with status: "not_started" rather than an error. Treat a sudden drop back to not_started as “poll again”, not as a state change. A worked KYB read for a clean submission:
cURL
200
…and a worked eKYC read once the hosted session has completed:
200
For the submission and session flows behind these statuses, follow Organization KYC onboarding.

8. Account-trust: why is this account restricted?

GET /api/v1/organization/account-status restates facts the platform already records — paused or blocked outbound, a past-due subscription, a rejected KYC, a closed account — as one overall state plus a machine-readable restrictions list. state is active, restricted, or closed. Every restriction carries a stable code to branch on (subscription_past_due, subscription_canceled, kyc_rejected, outbound_paused, outbound_blocked, account_closed), a severity (block, pause, or review), a plain-language reason, concrete remediation.steps, and an appeal channel. The endpoint adds no new policy — it tells you what is restricted, why, and how to fix it.
200
A healthy account answers "state": "active" with an empty restrictions array — which also makes this the right liveness read for a status page: a restricted account still authenticates and answers 200.

9. Business hours and custom-domain branding

GET /api/v1/organization/business-hours reads back the org-wide default schedule exactly as stored — both fields null until you set one (the generated example on this page shows that empty state):
200
Queues, inbound routes, and auto-attendants without a local schedule inherit this default. The per-day shape ("mon": { "open": "08:00", "close": "20:00" }, day absent = closed) is accepted too; inheritance rules live in Business hours. GET /api/v1/public/branding is the unauthenticated read your login page makes when served from a custom domain: the host resolves to the org that owns it, and the response carries only presentational branding — no ids, no contact details:
200
A host that is not a registered, active custom domain answers 404.

10. Day-2 admin: resend invites, change roles, revoke keys, remove members

The four follow-up calls a provisioning script reaches for after day one are SDK aliases under /api/v1/organization: each delegates to the same handler, role gate, and audit trail as the Settings surface, so the envelopes below are exactly what the dashboard’s own calls return. Resend an invite. POST /api/v1/organization/invites/{id}/resend, where {id} is the invited email address, URL-encoded (teammate%40acme.example). Requires owner or admin. The resend forces a fresh email delivery and reuses the invite’s existing accept token, so invitedAt keeps the original invite date:
200
No pending invite for that address answers 404. A resend does not extend the invite’s expiry: an invite already past expiresAt still answers 410 Gone when the recipient accepts. Change a member’s role. PUT /api/v1/organization/members/{id} (the SDK’s organization.members.update), body { "role": ... }, requires owner or admin. The assignable roles are admin, supervisor, developer, viewer, and billing; owner is never assignable through this call — ownership transfer runs through support — and sending it returns 400. The response echoes the member and the new role:
200
Transition rules to code against:
  • A member currently holding owner can be demoted only while another owner remains; demoting the organization’s last owner returns 400.
  • {id} not a member of this organization answers 404; a caller below owner/admin answers 403.
  • The member’s next session refresh picks up the new role, so a demoted admin loses access without a manual sign-out.
Revoke a key. DELETE /api/v1/organization/api-keys/{id} requires owner, admin, or developer and answers 204 with an empty body. Revocation takes effect immediately and is irreversible: the key string stops authenticating on the next request, and a later POST /api/v1/organization/api-keys mints a brand-new secret — the revoked string is never issued again. The revoked row stays visible in GET /api/v1/organization/api-keys, newest-first, with active flipped to false. The list carries only keyPrefix and lastFourChars, never the plaintext:
200
Remove a member. DELETE /api/v1/organization/members/{id} is owner-only and answers 204 with an empty body. The removed member’s sessions end and their API keys stop working as part of the same operation, and the organization’s last owner cannot be removed.

11. Where the flows live

The samples on this page stay response-body focused; the end-to-end sequences each link into a guide: