White-label subaccounts
Resellers — agencies, MSPs, vertical SaaS platforms — run Orbit under their own brand by creating subaccounts: child organizations fully operated by your parent organization. Each subaccount gets its own API keys, isolated data, optional branding and custom domain, and a per-subaccount reseller margin. You buy at Orbit wholesale prices and set your own retail rates over them. Full endpoint catalog: Subaccounts API.Prerequisites
Every call in this guide runs under a parent-org session JWT or API key with the admin role. A subaccount’s own admin can manage only that subaccount when the{id} in the path is their own organization.
The model
- Plan and limits — each subaccount carries a plan (
starter,growth,business,enterprise) plus optional seat and rate-limit caps. - Reseller margin — a flat markup percent (
0–100) applied over Orbit wholesale. The reseller statement for a subaccount shows wholesale cost, marked-up price, and earned margin per day × meter so you can invoice your end-customer. - Key and data isolation — issue keys for the subaccount, not the parent. A subaccount key can reach only its own org’s data.
- Branding — override colors, dashboard name, logo, and notification sender, or inherit the parent’s branding.
Provision in one call
POST /api/v1/subaccounts/provision runs the whole setup — organization, plan and limits, reseller pricing, branding, and initial credit funding — as a single atomic request. Reach for it when you onboard customers from your own portal or back-office automation instead of the dashboard wizard:
name is required; every other field is optional. On success (201) the response carries the new subaccount, the applied pricing, the branding fields that were set, the initial credit transfer, and any non-blocking warnings — for example, a margin below the recommended floor.
Pick a plan and limits up front in the same call:
The multi-step alternative
The dashboard’s five-step new-subaccount wizard issues the same API calls in sequence. Mirror it over the API when you want a human approving each step, or when your onboarding flow collects information gradually:- Create —
POST /api/v1/subaccounts/withpending_setup: true. The subaccount sits inpendingso an aborted setup never leaves a billing-active tenant behind. - Pricing —
PUT /api/v1/subaccounts/{id}/pricingwithreseller_margin_pctandmonthly_spend_cap_cents. - Branding —
PUT /api/v1/subaccounts/{id}/brandingwith the override fields. - Initial credit —
POST /api/v1/subaccounts/{id}/transfer-credits(see Fund the subaccount). - Finalize —
POST /api/v1/subaccounts/{id}/finalizeflipspending→active. The endpoint is idempotent: calling it on an already-active subaccount returns200and changes nothing, so a retried “Launch” click is safe.
Fund the subaccount
Credits live at the parent wallet; you push them down to a subaccount either at creation (initial_credits_cents on create or provision) or later with a transfer:
monthly_spend_cap_cents ceiling so one subaccount can’t drain the parent wallet past its budget. GET /api/v1/subaccounts/{id}/pricing returns the margin, the cap, and the current-period spend against it.
Brand the subaccount
Set colors, logo, brand name, and notification sender:inherit_branding_from_parent: true clones your parent branding instead — useful when every customer portal should look like your product rather than each customer’s.
For notification email, PUT /api/v1/subaccounts/{id}/notification-email sets from_name / from_address. The override becomes the effective sender once that sending domain passes DKIM/SPF verification; until then resolution falls back to the parent’s sender, then the platform default.
Custom domains
A subaccount’s dashboard can be served from its own hostname. Add the domain, point its DNS CNAME at the target shown in the dashboard, and verification runs automatically:POST /api/v1/subaccounts/{id}/domains/{domainId}/verify. List (GET) and remove (DELETE) domains through the same group.
Self-serve rate cards
Flat margins and inline decks price one subaccount at a time — you re-enter the same markup sheet per child. Rate cards lift that pricing into a named, persistent library on the parent org: name a card, add its markup cells once, then assign it to any subaccount. The full API contract (cell families, 50-card library limit, resolution order) lives in Rate cards; this section is the walk-through from the dashboard side.The dashboard surface
Open Settings → Subaccounts and a subaccount’s Pricing tab now carries a Rate cards section alongside the flat reseller margin:- Draft a card — give it a name and add markup cells (
channel, optionaldestination,margin_pct). Saving a channel+destination pair the card already has replaces that cell in place. - Persist it to the library — the card lands in your parent organization’s library, shared by every subaccount, and survives deployments and re-provisioning.
- Assign it — the same tab’s Assigned rate card dropdown picks any saved card for that subaccount.
Compose vs. assign
Assignment and composition are separate acts:- Assign —
PUT /api/v1/subaccounts/{id}/assigned-rate-cardwith{ "rate_card_id": "rc_..." }, or the Pricing tab dropdown. The library is the single source of truth: assigning an id that no longer exists there returns400 BAD_REQUEST. A child left assigned to a deleted card shows a Stale link badge on its Pricing tab — re-assign before the next statement runs. - Un-assign — send
rate_card_id: null, or pick “none” in the dropdown. The child falls straight back to its own pricing. - Fallback chain — assigned rate card → the child’s inline rate deck → the flat
reseller_margin_pct. The precedence applies at send time per usage line; see Rate cards: how resolution picks the card.
API and dashboard parity
The Pricing tab mirrors the API one-to-one.GET /api/v1/subaccounts/{id}/rate-deck returns both the child’s inline deck and its assigned_rate_card_id, and the dashboard renders exactly that — the Rate deck section shows the inline cells, the Assigned rate card dropdown shows the saved card the child prices against. Nothing on the tab can be set that the API cannot express, so a mixed workflow (draft in the dashboard, assign from automation) never drifts.
Pointer, not a copy
Assigning a card does not copy its cells onto the subaccount. When you update the card in the parent’s library, every assigned child resolves against the new cells on its next send. Version by creating a new card (rc_whatsapp_west_africa_q4) and re-assigning — an assignment never pins a child to the cells it saw at assignment time.
Operate the subaccount
- API keys —
GET/POST /api/v1/subaccounts/{id}/api-keys. Have the end-customer integrate with the subaccount’s key, never the parent’s. - Suspend / reactivate —
POST .../suspendblocks API and dashboard access (for non-payment or offboarding);POST .../reactivatelifts it. - Usage —
GET .../{id}/usagefor one subaccount;GET .../usage-rollupfor the whole tree; each with a CSV export for invoicing. - Webhooks — webhooks register per organization. Each of your end-customers registers their own endpoint and signing secret under their subaccount, so one customer’s deliveries never surface on another’s dashboard. See Webhooks.
Retire a subaccount
DELETE /api/v1/subaccounts/{id} releases the subaccount’s phone numbers and removes its data. The account remains visible in your list under deleted subaccounts for audit. Number release is the destructive part — confirm the end-customer has ported out or no longer needs the numbers first.
Anti-patterns
- Pending forever — a subaccount stuck in
pendingfrom an aborted wizard still shows up in your list but can’t serve traffic. Finalize it (idempotent, safe to retry) or delete it. - Parent keys for end-customers — never hand your parent key to a customer. Issuing a subaccount key takes one call and scopes them to their own org.
- Margin 0 by accident — omitting
reseller_margin_pctsells at wholesale. Check thewarningsarray in the provision response for pricing issues. - No spend cap — without
monthly_spend_cap_centsa runaway subaccount draws from your parent wallet unbounded. - Treating an assignment as a copy — assigning a rate card points the subaccount at the parent’s library; editing the card reprices every assigned child on their next send. Version by writing a new card and re-assigning, never by assuming an assignment froze the old cells.
How provision rollback works
POST /api/v1/subaccounts/provision is atomic: if any step fails after the organization is created — pricing, branding, funding — the half-provisioned subaccount is rolled back and the slug stays free for a clean retry. A failed provision never leaves a billable orphan behind.
See also
- Subaccounts API — full endpoint catalog
- Rate cards — named, versioned price books you assign to subaccounts
- Billing & wallet concepts — how credits and reseller markup roll up
- Tenant isolation — what subaccount isolation does and does not guarantee
- Webhooks overview — per-org endpoint registration