> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# White-label subaccounts: provision, brand, fund, and operate

> Reseller flow: provision a branded child organization in one call, transfer credits, set reseller margins, and operate or retire subaccounts through the API.

# 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](/api-reference/subaccounts).

## 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

```
Parent org (you)
 ├── subaccount: Acme Retail      plan: growth    margin: 30%
 ├── subaccount: Delta Logistics  plan: business  margin: 25%
 └── subaccount: West Clinic      plan: starter   margin: 40%
```

* **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](/api-reference/subaccounts) 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:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/subaccounts/provision \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Retail",
    "plan": "growth",
    "reseller_margin_pct": 30,
    "monthly_spend_cap_cents": 500000,
    "initial_credits_cents": 100000,
    "branding": {
      "dashboard_name": "Acme Messaging",
      "primary_color": "#0B67D0",
      "support_email": "support@acme.example"
    }
  }'
```

Only `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:

| Field                          | Notes                                               |
| ------------------------------ | --------------------------------------------------- |
| `plan`                         | `starter`, `growth`, `business`, or `enterprise`    |
| `max_team_members`             | Seat cap for the subaccount                         |
| `rate_limit_per_second`        | Per-second API rate limit                           |
| `inherit_branding_from_parent` | Skip per-child branding overrides and inherit yours |

## 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:

1. **Create** — `POST /api/v1/subaccounts/` with `pending_setup: true`. The subaccount sits in `pending` so an aborted setup never leaves a billing-active tenant behind.
2. **Pricing** — `PUT /api/v1/subaccounts/{id}/pricing` with `reseller_margin_pct` and `monthly_spend_cap_cents`.
3. **Branding** — `PUT /api/v1/subaccounts/{id}/branding` with the override fields.
4. **Initial credit** — `POST /api/v1/subaccounts/{id}/transfer-credits` (see [Fund the subaccount](#fund-the-subaccount)).
5. **Finalize** — `POST /api/v1/subaccounts/{id}/finalize` flips `pending` → `active`. The endpoint is idempotent: calling it on an already-active subaccount returns `200` and 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:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/subaccounts/sub_abc123/transfer-credits \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{ "amount_cents": 250000 }'
```

Pair funding with a `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:

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/subaccounts/sub_abc123/branding \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{
    "dashboard_name": "Acme Messaging",
    "primary_color": "#0B67D0",
    "accent_color": "#1FC8A9",
    "support_email": "support@acme.example"
  }'
```

`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:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/subaccounts/sub_abc123/domains \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "portal.acme.example" }'
```

If verification stalls, re-trigger it with `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](/billing/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:

1. **Draft a card** — give it a name and add markup cells (`channel`, optional `destination`, `margin_pct`). Saving a channel+destination pair the card already has replaces that cell in place.
2. **Persist it to the library** — the card lands in your parent organization's library, shared by every subaccount, and survives deployments and re-provisioning.
3. **Assign it** — the same tab's **Assigned rate card** dropdown picks any saved card for that subaccount.

Because the PUT is full-set, editing a card's cells replaces the stored card the next time the library is saved — you never version cards by hand.

### Compose vs. assign

Assignment and composition are separate acts:

* **Assign** — `PUT /api/v1/subaccounts/{id}/assigned-rate-card` with `{ "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 returns `400 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](/billing/rate-cards).

### 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 .../suspend` blocks API and dashboard access (for non-payment or offboarding); `POST .../reactivate` lifts it.
* **Usage** — `GET .../{id}/usage` for one subaccount; `GET .../usage-rollup` for 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](/webhooks/overview).

## 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 `pending` from 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_pct` sells at wholesale. Check the `warnings` array in the provision response for pricing issues.
* **No spend cap** — without `monthly_spend_cap_cents` a 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](/api-reference/subaccounts) — full endpoint catalog
* [Rate cards](/billing/rate-cards) — named, versioned price books you assign to subaccounts
* [Billing & wallet concepts](/concepts/billing-and-wallet) — how credits and reseller markup roll up
* [Tenant isolation](/concepts/tenant-isolation) — what subaccount isolation does and does not guarantee
* [Webhooks overview](/webhooks/overview) — per-org endpoint registration
