Skip to main content

Real envelopes for the generated { data: {} } shells

The numbered ops in this section fill in the response envelope the page’s autogen blocks render as {}. Each opener names its route so the generator keeps this body across rebuilds (see comment block above the lifecycle).

1. List subaccounts

GET /api/v1/subaccounts

2. Get one subaccount

GET /api/v1/subaccounts/{id}

3. List the child’s API keys

GET /api/v1/subaccounts/{id}/api-keys

4. Read branding

GET /api/v1/subaccounts/{id}/branding

5. List channel budgets

GET /api/v1/subaccounts/{id}/channel-budgets
The same route family reads the current set and replaces it full-set. Both shapes appear here because GET lists what the POST/PUT writes: one budget per channel.

6. List custom domains

GET /api/v1/subaccounts/{id}/domains

7. Read the canonical CNAME target

GET /api/v1/subaccounts/{id}/domains/cname-target

8. Download a branded invoice run

GET /api/v1/subaccounts/{id}/invoice.html
A 2xx request streams a standalone HTML document with Content-Type: text/html and Content-Disposition: attachment. Build the invoice shell from the same usage statement the JSON reads return — the wires below mirror the statement rows the HTML embeds.

9. Read notification sender email

GET /api/v1/subaccounts/{id}/notification-email

10. Export usage as CSV

GET /api/v1/subaccounts/{id}/usage/export.csv
One CSV attachment per call: day × channel × direction rows with quantity, base cost, marked-up price, margin earned, and a TOTAL footer. Headers: date,channel,direction,quantity,base_cost,marked_up_price,margin_earned,currency. ?period=YYYY-MM defaults to the current UTC month. The mirrored JSON below is the same figures as rows on the statement the CSV is derived from.

11. Read live usage counters

GET /api/v1/subaccounts/{id}/usage/live

12. Read the usage statement JSON

GET /api/v1/subaccounts/{id}/usage/statement

13. Meter the calling org’s BYO fee

GET /api/v1/subaccounts/byo-fee-meter

14. List the rate-card library

GET /api/v1/subaccounts/rate-cards

15. Consolidated usage rollup across the tree

GET /api/v1/subaccounts/usage-rollup

16. Export the rollup as CSV

GET /api/v1/subaccounts/usage-rollup/export.csv
Same rollup as #15 streamed as one CSV attachment, one row per subaccount plus a TOTAL footer. The mirrored JSON below is the aggregated figure the CSV’s footer reads.

17. Create subaccount (full envelope)

POST /api/v1/subaccounts

18. Provision a subaccount in one call

POST /api/v1/subaccounts/provision

19. Update the subaccount row

PUT /api/v1/subaccounts/{id}

20. Replace the child’s assigned rate card

PUT /api/v1/subaccounts/{id}/assigned-rate-card

21. Update branding

PUT /api/v1/subaccounts/{id}/branding

22. Update channel budgets

PUT /api/v1/subaccounts/{id}/channel-budgets

23. Update notification sender email

PUT /api/v1/subaccounts/{id}/notification-email

24. Create an API key for the child

POST /api/v1/subaccounts/{id}/api-keys
The response returns the plaintext key exactly once — store it before discarding the body. type: "secret" is a server-to-server key (dv_live_sk_*); type: "public" is a browser-safe key.

25. Create the channel-budget policy

POST /api/v1/subaccounts/{id}/channel-budgets
One call seeds the child’s spend-rail policy — each cell states the channel, the spend threshold, the evaluation period, and the breach action (block the next send, or alert only).

26. Attach a custom domain

POST /api/v1/subaccounts/{id}/domains
Returns the pending row with its CNAME target. Point the host’s DNS at cname_target, then re-check with POST /{id}/domains/{domainId}/verify until status flips to verified.

The lifecycle at a glance

A subaccount is a child organization under your parent org. It starts pending the moment you create it, and it only becomes billing-active when you finalize. Create it, optionally attach branding and a custom domain, fund it, verify ownership of the domain, then flip it to active — and read back usage, pricing, and status. Every role gate on this page is owner or admin on the parent organization, except suspend/reactivate (owner only). Worked responses below use empty-path-safe ids, not shell echoes.

Worked sequence: create → fund → finalize

1. Create the subaccount

POST /api/v1/subaccounts takes a minimal body — a name is enough; plan, limits, and reseller pricing can follow as separate calls. The response carries the new org id and the one-time plaintext default API key.
Store api_key before you discard the response — it is shown once. Until finalization the subaccount is pending: reads work, but billing metering does not start.

2. Fund the wallet

Push initial credits from the parent wallet; pass a client_reference_id so a retried top-up replays instead of double-charging.

3. Attach a custom domain (optional)

White-label hosts claim a domain and re-check its CNAME before finalizing.
Point the domain’s DNS at cname_target, then re-check. status flips to verified when the CNAME resolves; the SSL certificate provisions asynchronously after that.

4. Finalize

Flip the subaccount to active. Owner or admin on the parent. The call is idempotent — a rerun on an already-active row is a 200 no-op, so a retried “Launch” click is safe.
Shortcut: POST /api/v1/subaccounts/provision runs create → pricing → branding → funding → finalize as one atomic call. Reach for it when onboarding is driven by your own portal instead of a human wizard.

Read back: usage, pricing, and status

Once active, the reads your reseller portal makes most:
GET /{id} returns the org row (subscription_status, plan, parent hierarchy) — the status check the finalize step above flips. GET /{id}/usage/statement?period=YYYY-MM returns the billing-grade per-day × meter rows the rollup endpoint folds; GET /{id}/usage/export.csv streams the same figures as a CSV attachment to invoice your end-customer.

Rating: rate deck, rate cards, and MCCMNC overrides

Subaccount pricing is a flat reseller_margin_pct over wholesale, plus optional per-channel/destination markup cells (the rate deck) or a named rate card from the parent org’s price-book library. For specific mobile operators, layer per-MCCMNC overrides — the send-path resolver prices an SMS segment against them before falling through to the wholesale deck. Read the assigned deck — an empty rate_deck means every usage line prices at the flat margin:
List the parent org’s persisted cards — each carries at least one cell family (lane_cells and/or deck_cells); cards without cells never persist:
Upsert a per-operator override — POST returns the written row; GET /{id}/mccmnc-overrides lists them cursor-paginated (meta.cursor, meta.has_more, meta.total):

Subaccount ops: read back, update, and inheritance semantics

GET /api/v1/subaccounts/{id} returns the child org row with its parent hierarchy attached; PUT /api/v1/subaccounts/{id} is the PATCH-style read-modify write for the display name, plan, and white-label policy. Both are owner or admin on the parent organization. The parent block names the organization the child inherited from — inherits_from_parent: true whenever any white-label field was pulled rather than set directly. When a parent supplies a value, the org id it was pulled from appears on inherited_from_organization_id and inherited_fields maps each inherited field to that source org, exactly the resolution chain the Branding resolver documents. A child that sets its own overrides returns inherits_from_parent: false, inherited_from_organization_id: null, and inherited_fields: null.
Update in place — the response echoes the merged row. An explicit null on a policy field clears the override and re-opens it to inheritance, so the parent’s value flows down on the next read:

The hierarchy from an empty list

A brand-new parent org starts with zero children. The scaffold reads that state, creates the first subaccount, then reads it back to confirm the hierarchy is one level deep:
Create the first child (see step 1 above), then the same list call returns one row and GET /{id} resolves the parent link — the loop your portal runs when a fresh reseller account onboards its first end-customer:

Error shapes worth branching on

Every failure on this page returns the standard { "error": { "code", "message", "status" }, "meta": { "request_id", "timestamp" } } envelope. Three classes are specific to the reseller hierarchy:
Treat 409 CONFLICT on a hierarchy write as terminal — retrying the same nesting replays the cycle. 402 INSUFFICIENT_BALANCE is recoverable: fund the parent, replay the transfer with the SAME client_reference_id, and the idempotent guard returns one debit, not two. 403 FORBIDDEN means your portal’s key needs elevating to the parent’s owner/admin role; 422 VALIDATION_ERROR means the request never reached the write and is safe to correct and resend.

Worked reseller provisioning chain

Use the parent organization’s API key for provisioning. The child key created in step 3 is for requests made by that subaccount; it cannot create or administer sibling subaccounts. Add X-Test-Mode: true when using Clerk session authentication against the sandbox. For API-key authentication, use a dv_test_sk_* key to enter sandbox mode; X-Test-Mode is ignored for dv_live_sk_* keys. The examples use org_child_01J8S7Q2K3M4N5P6Q7R8S9T0UV as the child id. Replace it with the id returned by step 1. Provisioning is intentionally explicit: create the pending child, finalize it, then apply credentials and controls.

1. Create the pending subaccount

Set pending_setup to true so the child stays out of the active billing state until the provisioning chain is complete. The parent key must have owner or admin access. Request
Response
The default key is shown only when it is created. Store it securely; if it is not available, use step 3 to mint a replacement.

2. Finalize the subaccount

Finalize after the child’s required setup is ready. This idempotent call changes subscription_status from pending to active. Request
Response

3. Create a child API key

Use the parent key to mint a child key. The plaintext key is returned once and is not recoverable from the list endpoint. Request
Response

4. Apply channel budgets

Use the parent key to manage the child’s controls. budgets is a full-set replacement: send the complete desired array, including period: "monthly" and a breach action. Request
Response

5. Read lifetime usage

Read counters from the parent key to monitor the child independently of a billing period.

6. Read the consolidated usage rollup

Use the parent key to retrieve the whole reseller tree for a billing period. period is YYYY-MM and defaults to the current UTC month.

7. Suspend and reactivate the child

Both lifecycle operations use the parent key and return the resulting state. Suspending stops send activity without deleting the child; reactivation restores active status. Both calls are idempotent.
Suspend response
Reactivate response
The same parent-key distinction applies to the reseller controls around this chain. Use POST /api/v1/subaccounts/{id}/mccmnc-overrides for an operator-specific sell rate, PUT /api/v1/subaccounts/{id}/rate-deck for differentiated channel pricing, POST /api/v1/subaccounts/{id}/domains followed by POST /api/v1/subaccounts/{id}/domains/{domainId}/verify for custom domains, and GET /api/v1/subaccounts/{id}/invoice.html for an invoice export. These endpoints return the configured tenant-owned controls; they do not change provider routing.

Worked recipes

The recipes below survive regeneration: they are mirrored here in api-reference/endpoints/_overlay/subaccounts.mdx and re-spliced by the doc generator. Do not hand-edit the generated operation sections; correct samples in this overlay and regenerate.
Three end-to-end sequences that cover a reseller’s first week on the API: onboard a subaccount and mint its keys, white-label its dashboard, and run the billing loop. Responses below are full envelopes, identical in shape to what the operations above return.

Recipe 1: Create and provision a subaccount

Create the child org, read it back once it is active, then mint the key its integration will use.
The subaccount starts pending. Finalize it (or let your provisioning flow call POST /api/v1/subaccounts/provision) and the same read now reports active:
Mint a scoped key for the child’s integration. The plaintext key is returned exactly once; the list endpoint only ever shows the prefix and last four characters.

Recipe 2: White-label a subaccount

Set the child’s branding, read it back normalized, then verify the custom domain the branded dashboard is served from.
GET branding returns the same row with inherited values folded in: anything you did not set resolves from the parent org and flips inherits_from_parent to true for those fields. Colors come back uppercase-normalized. Attach the custom domain and read the CNAME target it must point at:
Add a CNAME record for hub.rahal.example pointing at domains.orbit.devotel.io, then re-check with POST /api/v1/subaccounts/{id}/domains/{domainId}/verify. When status flips to verified, the SSL certificate provisions within a few minutes.

Recipe 3: The reseller billing loop

Cap what the child can spend, read back the tier pricing you set, and pull the branded invoice.
threshold_cents is the monthly spend cap for the channel; action: "block" hard-stops sends at the cap, "alert" keeps sending and notifies you. GET reseller pricing returns the margin tiers applied to the child’s usage:
GET invoice.html does not return JSON: it streams a standalone HTML document, so save it to a file. The response carries Content-Type: text/html; charset=utf-8 and Content-Disposition: attachment; filename="invoice-<period>.html".
Open invoice.html in a browser: it is the branded statement for the period, with per-day line items and totals, ready to forward to your end-customer.

Suspended subaccounts

Writes against a suspended subaccount return 403 with a dedicated code so your portal can distinguish “paused by the reseller” from “missing permission”:

Reseller orientation and the worked chain

27. Reseller orientation: subaccount vs tenant

Your organization is one tenant; a subaccount is a child organization provisioned under it, with its own id (org_sub_*), its own API keys, branding, and spend caps. Billing rolls up to you, the reseller parent: the child’s usage is marked up at your margin and folded into your statement, while the child sees only its own traffic. You administer children with your parent key; the child’s keys act only on the child, so an end-customer integration can never touch your organization or its other subaccounts. A subaccount inherits your defaults (rate card, notification sender, compliance settings) until you override them per child. Treat the child id the create call returns as the handle for everything that follows: key minting, usage, budgets, and suspension all take {id}.

28. Worked chain: create, mint a scoped key, send, read back usage

Run these four round trips in order to take a new end-customer from provisioning to live traffic. Substitute your own ids and keep the child key from round trip 2; it is shown once. Round trip 1 — Create the subaccount
POST /api/v1/subaccounts
The child id in data.id is the {id} every later step takes. billing_email receives the child’s invoices; monthly_spend_cap_cents hard-stops the child’s send traffic at the cap.
subscription_status starts pending and flips to active once the child is finalized (step 4 of the lifecycle above). The samples below assume a finalized child. Round trip 2 — Mint a scoped API key for the child
POST /api/v1/subaccounts/{id}/api-keys
Hand the child’s integration plaintextKey and nothing else: it is scoped to this subaccount and to the scopes you listed, and it is returned exactly once.
Round trip 3 — Send on the child’s behalf The child’s integration now calls the regular send endpoints with its own key in the X-API-Key header; no parent credential and no extra parameter are needed. The response meta names the subaccount the send was billed to.
POST /api/v1/messages/sms
meta.subaccount_id echoes the child the key belongs to; the same id appears on the usage rows in round trip 4, so reconciliation ties sends to the child without extra bookkeeping. Round trip 4 — Read back per-subaccount usage Call with your parent key to audit one child’s spend for a period; the child’s own key sees the same rows for itself.
GET /api/v1/subaccounts/{id}/usage
Page with meta.cursor until has_more is false; total counts the rows for the period. For the margin math on the same rows, use GET /api/v1/subaccounts/{id}/usage/statement (sample 12 above).

29. Suspend and reactivate

Suspend halts a child’s send traffic without deleting its data; billing ownership stays with you. Reactivate is the inverse and is a no-op on an already-active child, so retries are safe.
POST /api/v1/subaccounts/{id}/suspend
While suspended, the child’s integrations keep authenticating but every send and write is rejected with 403 subaccount_suspended, so a portal can tell “paused by the reseller” apart from a bad key:
Restore the child with POST /api/v1/subaccounts/org_sub_rahal_00x9/reactivate; the response mirrors the suspend shape with "status": "active".

30. SDKs without typed subaccount helpers

No SDK ships typed methods for the subaccount routes yet, so the typed tabs on this page use plain fetch against the same paths. In Python, Go, Ruby, and PHP use the client’s generic request escape hatch instead; it signs the request exactly like the typed helpers and returns the same { data, meta } envelope.