Skip to main content

Subaccounts API

For platforms reselling Orbit to their own customers — agencies, MSPs, vertical SaaS. A parent org creates and operates subaccount orgs, each with its own API keys, isolated data, optional custom branding and domain, and per-subaccount pricing. Base path: /api/v1/subaccounts Authentication: Parent-org session JWT or API key with admin role. Response envelope: Every endpoint on this page returns {"data": ..., "meta": {...}}. meta carries request_id (the server’s unique reference for the call — pass it to support when reporting a problem) and timestamp. List endpoints add pagination fields under meta: cursor (opaque next page), has_more, and on counts total.

Using the SDKs

Python (same call via the SDK’s escape hatch):
The Python SDK is core-scope — it wraps the 8 core resources (messaging, voice, contacts, campaigns, verify, numbers) and reaches everything else through the generic client.request() escape hatch above. See the Python SDK. Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.

Lifecycle

A subaccount created through the multi-step wizard starts as pending so an aborted setup never leaves a billing-active tenant behind. POST /api/v1/subaccounts/{id}/finalize promotes it to active on the final Launch step. It is idempotent — calling it on an already-active subaccount returns 200 and changes nothing. POST /api/v1/subaccounts/provision finalizes for you as its last step.

Single-call provisioning

POST /api/v1/subaccounts/provision stands up a complete, branded, funded subaccount in a single request — the API equivalent of the five-step new-subaccount wizard (organization → plan & limits → reseller pricing → funding → branding). Reach for it when you provision tenants from your own portal or back-office automation and don’t want to orchestrate the separate create → pricing → branding → transfer-credits → finalize calls yourself.

Wizard vs single call

Pick the single call when anything programmatic triggers onboarding; pick the wizard when a person drives it. Both paths land on the same role gate — owner or admin on the parent org. The body flattens every wizard step. Only name is required; everything else is optional: The call is atomic: if any step fails after the org is created, the half-provisioned subaccount is rolled back so the slug stays free to retry. On success (201) the response carries the new subaccount plus its applied pricing, the branding_applied fields, the initial_credits transfer, and any non-blocking warnings — for example, a reseller margin below the recommended floor.
To create a bare subaccount without the flattened wizard body, use the plain create path:
The api_key in the response is the subaccount’s default key — shown once in plaintext; store it before you discard the response. subscription_status starts as pending here; POST /api/v1/subaccounts/{id}/finalize (owner or admin) flips it to activePOST /provision finalizes for you.

List subaccounts

Page through the subaccount tree with ?limit= (max 200) and ?cursor=:
meta.cursor is the opaque continuation token for the next page; has_more: false means the tree ends here. A fresh ?cursor=<value> call returns the next batch.

Get a single subaccount

The parent block carries the current tenant hierarchy so the detail view can link back to the parent without a second call.

Update a subaccount

PATCH-style read-modify writes name, plan, seat limit, rate limit, and settings:

Lifecycle transitions

Suspend stops all send activity while the parent keeps billing ownership — reactivate flips it back. These are owner-only (owner on the parent org, per the router’s guard hierarchy); create/finalize/read paths accept owner or admin:
POST /{id}/reactivate returns the same shape with "active". POST /{id}/finalize (owner or admin) returns { "id", "subscription_status": "active" } and is idempotent — a rerun on an already-active subaccount is a 200 no-op. DELETE /{id} is a soft delete — it releases phone numbers, marks the row deleted, and (on success) returns 204 No Content with no body.

Usage & credits

GET /api/v1/subaccounts/usage-rollup?period=YYYY-MM returns one consolidated report across your whole subaccount tree for a billing period: grand totals plus a per-subaccount breakdown that reconciles line-for-line with each subaccount’s own usage. Omit period to default to the current UTC month.

Get usage

Read lifetime counters — credits used, message counts, calls made — without paging:

Transfer credits

Transfer credits into a subaccount from the parent wallet; pass client_reference_id for safe retries on network failure:
The response returns the balance deltas — the parent’s debited balance and the child’s credited balance after the transfer — so your portal can render the top-up ledger without a second call. Retrying with the same client_reference_id returns the original result instead of double-charging. Read a subaccount’s billing-grade statement — a line per day × channel/meter carrying quantity, wholesale cost, marked-up price under the subaccount’s reseller margin, and the margin earned:
GET /api/v1/subaccounts/{id}/usage/statement?period=YYYY-MM returns the reseller statement for one subaccount: a line per day × channel/meter carrying quantity, base (wholesale) cost, the marked-up price under that subaccount’s reseller margin, and the margin you earned, plus a totals footer. GET /api/v1/subaccounts/{id}/usage/export.csv streams the identical figures as a CSV attachment so you can invoice your end-customer or hand it to an auditor. Both default to the current UTC month when period is omitted; a malformed period returns 422 VALIDATION_ERROR.

API keys

Issue a key for a subaccount from the parent — the response carries the plaintext key exactly once:
Own-org key management (rotate with a grace period, revoke) lives on the Organization API; scopes are validated against the same closed catalog there.

List a subaccount’s API keys

The parent can enumerate every key the subaccount has issued — secrets are masked and a total line tells you how many exist across pages:
To rotate or revoke a subaccount’s key, use the subaccount’s own bearer (or a parent’s role-equivalent) against the Organization API keys endpoints — the same revoke path applies to any key of that org.

Branding & custom domains

Update branding

Every branding field is optional in the patch; explicit null clears a field:
GET /api/v1/subaccounts/{id}/branding returns the same merged shape — the effective branding after parent/child inheritance.

Custom domains & DNS verification

Claim a custom domain; the response carries the cname_target to place in DNS:
Point the domain’s DNS at cname_target, then re-check:
GET /api/v1/subaccounts/{id}/domains returns the same row shape as an array; DELETE /.../domains/{domainId} is a 204 No Content purge.

White-label notification email

Notification emails a subaccount sends can go out under its own sender identity. PUT /api/v1/subaccounts/{id}/notification-email accepts from_name and from_address; the override is saved immediately but only becomes the effective sender once its sending domain passes DKIM/SPF. Until then — or when you clear the override by sending an empty from_address — resolution falls back to the parent’s sender, then the platform default. The GET response returns the stored override, its verified flag, and the effective sender actually in use. This endpoint shares branding’s auth model: a parent admin manages any child’s config, and a subaccount’s own admin/owner manages its own when {id} is the caller’s organization.
Check the stored override and which sender is actually in effect:
The override is stored but verified: false until the sending domain passes DKIM/SPF on the Email → Senders surface — while it is unverified the effective sender keeps resolving to the parent or the platform default, so no mail goes out under an unproven identity.

Pricing overrides

Subaccount pricing is a flat reseller margin: a single markup percent (reseller_margin_pct, 0100) applied over Orbit wholesale rates, plus an optional monthly spend cap (monthly_spend_cap_cents). It is not priced per channel. When you need finer control for specific mobile destinations, layer per-MCCMNC rate overrides on top. PUT /api/v1/subaccounts/{id}/pricing replaces the margin and/or cap — a null monthly_spend_cap_cents clears the ceiling back to uncapped:
Read the same configuration back with GET /api/v1/subaccounts/{id}/pricing — the response also carries the current-period spend the cap is evaluated against, so the spend-cap progress bar and the margin deck are one call.

Per-MCCMNC overrides (PATCH-style upsert)

Layer a per-operator rate on top when the flat margin isn’t fine enough for a given destination. POST upserts the override and returns the written row:
GET /api/v1/subaccounts/{id}/mccmnc-overrides returns the same rows cursor-paginated; DELETE .../mccmnc-overrides/{overrideId} removes one and returns 204 No Content.