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
balanceis the wallet balance in USD cents —250000is $2,500.00. It isnullon the brief degraded shell (see below), so branch on that before rendering a dollar figure.billing_currencyis resolved fromsettings.wallet_currencyand falls back toUSD; format every cost figure with it so displayed amounts match the currency billing charges against.monthlyBudgetCentsis the org’s monthly budget ceiling — the denominator spend-percent billing alerts divide by.nullmeans no budget is set and spend-percent alerts never fire.isSubaccountis derived server-side from the org’s parent link. Atruevalue gates reseller-only affordances — a subaccount cannot create nested subaccounts.- A transient database blip returns a degraded shell — same 200,
id+plan+tenantIdfilled from the auth context, the remaining DB-sourced fieldsnull. 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 benull (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
monthly_budget_cents— integer budget ceiling in cents;0is rejected (a zero divisor would make every spend-percent alert fire). Sendnullto 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 numeric1is 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.409means the same key is still in flight.
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
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
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
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
modeand the old key keeps authenticating untilold_key_expires_at— 24 hours out by default, clamped to 1–168 hours when you sendgrace_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
old_key_expires_at— the absolute UTC instant the OLD key stops authenticating. With"mode": "immediate"it equals the rotation instant and_warningswitches 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 inGET /api/v1/organization/api-keysand in the dashboard.
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 aliasGET /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, andrejected. With any state pastnot_startedthe response also echoescompany_name,country,industry,submitted_at, andreviewed_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, andexpired.provider,session_id, andhosted_urlare populated while a session is open;configured: falsemeans 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) orreview(at least one hit inmatches; each match names thelabelit hit on — country, legal name, or a declared beneficial owner — plus adetail). Areviewverdict never blocks sending by itself; it is a signal a super-admin reviews.
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
200
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
"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
"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
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
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
- A member currently holding
ownercan be demoted only while another owner remains; demoting the organization’s last owner returns 400. {id}not a member of this organization answers404; a caller below owner/admin answers403.- The member’s next session refresh picks up the new role, so a demoted admin loses access without a manual sign-out.
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
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:- API keys end to end — minting, scoping, rotating, and revoking keys for server-side clients.
- Organization KYC onboarding — the submission, review, and approval path behind the verification statuses in section 7.