Skip to main content

Multi-environment subaccounts

Subaccounts are not only a reseller feature. A single organization can declare its own child organizations — one per environment (dev, staging, prod) or one per product line — and get per-subaccount wallets, spend caps, connection-scoped keys, and a spend-attribution structure that flat membership cannot express. Traffic and billing stay separated by design, without you maintaining three login environments. Every control in this guide is tenant-owned: nothing below changes how outbound traffic terminates. This guide covers the whole lifecycle — election, provisioning, funding, scope assignment, billing, and daily operations. Readers: platform engineers splitting one org into stages, or an org head splitting product lines.

1. The account after election

Provisioning follows the account operators you chose during signup. The creator of the organization becomes its owner; the people you invite into Settings → Team become the operators who administer subaccounts. Subaccount writes accept only owner or admin role tokens, so the election step of choosing operators lands here: subaccount management follows whoever you made an admin. See Team members, invitations, and roles if the operator list needs adjusting first — an environment subaccount inherits nothing about your member roster, and the parent’s admins are the people who can wire it.

2. Declare one subaccount per environment

One call per stage against the flat create endpoint. Reach for the plain POST /api/v1/subaccounts (or the one-shot POST /api/v1/subaccounts/provision) — both the reseller import flow and the channel connection modes pages build on the same pair:
The 201 body is the one every later step hangs off — the child id, the wallet state after the seed, and the cap echoed back:
201
Only name is required; plan, cap, and initial funding are optional. Repeat once per environment so naming stays unambiguous: payments-dev, payments-staging, or product-a / product-b for a product-line split. Carry data.id (the sub_... child id) into every later path; initial_credits.transferred_cents confirms the seed landed in the child wallet.

3. Deploy stages

A subaccount per stage gives each environment its own wallet, budget, and isolation boundary:
  • Per-subaccount wallet — credits transfer between the parent’s wallet and the child’s wallet with POST /api/v1/subaccounts/{id}/transfer-credits. Traffic on the child draws only from the child’s balance.
  • Budget — monthly_spend_cap_cents on the child stops a stage from draining the parent wallet. Stage budgets are usually tighter than the production cap.
  • Plan and limits — seats and the rate limit follow the child, so a dev subaccount’s traffic ceiling does not compete with production callers.
The parent’s wallet is the feeder for every stage. When staging empties, another transfer tops it up; the parent’s remaining balance is unchanged until you move credits. A top-up is one call, and the response reports both balances after the move, so you reconcile without a second read:
200
The child now holds its seed plus the top-up (10,000 + 15,000 = 25,000) and the parent shows the debit. Retrying with the same client_reference_id returns this result again instead of moving the credits twice.

4. Reuse stages through scoped keys

Inheritance means the scoping rules your integration already knows apply per subaccount too. A key minted on the parent administers the tree; a key minted on the child reaches only that child — one leak can’t pivot across environments. The scope-per-integration guide walks through which scope family each surface needs; on a child you mint the same narrow pair (messages:write + messages:read, e.g.) and your staging worker uses its own scoped key just as a production one does.
201
data.key is the one-time plaintext — store it before you discard the response; later key lists show only keyPrefix and lastFourChars. scopes is the exact pair the worker holds, nothing more. Hand the returned key to the staging worker. A production worker gets a key minted on the production subaccount the same way — the pair never share credentials. Because the child-scope boundary sits at the key layer, you can rotate the stage key on a schedule without touching production.

5. Billing and attribution

A multi-environment tree gives you two attribution patterns at once; pick either or combine:
  • Payer/payee attribution — the parent’s org is the payer; each child is a payee. GET /api/v1/subaccounts/{id}/usage reads one child’s metered usage, and GET /api/v1/subaccounts/usage-rollup folds the whole tree, so the parent’s statement line-items per environment.
  • Chargeback per stage — finer tags still work: a metadata.cost_center on the send (see Cost-center chargeback) lets one environment split its own spend internally, e.g. stage_regression vs stage_ux, while the parent still sees one rollup line per child.
Read one child’s metered usage (trimmed body; the full field list lives in the Subaccounts API):
200
Fold the whole tree for a billing period — one line item per environment:
200
The payments-staging line matches the single-child read above, and totals is the sum of the lines — that reconciliation is what makes the rollup a statement, not a snapshot. Omit period to default to the current UTC month. Membership structure stays relevant here: the parent’s admins are the members who read both surfaces — see Team management for who can reach the billing endpoints when the operator list changes.

6. Operate the lifecycle

Day-to-day operation is the parts you exercise once the tree exists:
  1. Subaccount-scoped vs tenant-scoped keys — a parent-scope key mints, funds, and retires children; a child-scope key does only the child’s messaging. Guard the integration by which scope the caller actually holds — a child key against POST /api/v1/subaccounts returns 403, and a key outside a child’s tree — a sibling environment’s key, or a parent key from another organization — returns 404 on that child’s reads, never an empty 200.
  2. Wallet isolation — a child cannot see the parent’s balance; the parent’s remaining-credit figure only moves when you transfer. Stage top-ups are explicit calls, never automatic drain.
  3. Organization-profile hygiene — when the parent’s profile changes (billing contact, organization name), the change lives on GET/PUT /api/v1/organization/profile; children inherit nothing by design, so environment display names and branding remain per-child even if you update the parent.
The two boundary outcomes, as error bodies. A child-scoped key cannot administer the tree:
403
And a key outside a child’s tree cannot read it:
404
Worked example: a dev org runs a staged campaign against a stage child while production runs against the primary. POST /api/v1/campaigns on stage’s key sends the regression traffic; the same call with the production child’s key — which is the production workspace — handles live traffic. The parent’s rollup shows one usage line per stage, and the spend cap on stage means a runaway test campaign cannot drain production’s wallet.

7. Create, fund, and mint in one script

When a pipeline provisions stages rather than a person, the first three writes collapse into a single run through the SDK’s escape hatch (the same pattern as the CDR export guide):
Hand workerKey.key to the staging worker’s secret store in the same run; from then on the worker authenticates as the child, never as the parent.

See also