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
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.
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 active — POST /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
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; passclient_reference_id for safe retries on network failure:
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:
List a subaccount’s API keys
The parent can enumerate every key the subaccount has issued — secrets are masked and atotal line tells you how many exist across pages:
Branding & custom domains
Update branding
Every branding field is optional in the patch; explicitnull 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 thecname_target to place in DNS:
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.
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, 0–100) 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:
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.