Split staging and production across subaccounts
The migration moment most multi-environment questions reduce to is: I’ve been running everything on one organization, and I now want a prescriptive recipe that puts test sends on one subaccount, live sends on another, with separate API keys, separate rate limits, and a clean chargeback line per environment at month-end. This guide is that recipe, end to end. It is tenant-owned throughout — nothing below changes how outbound traffic terminates; it changes who bills against whose wallet. The shape you are aiming for:1. When to split
Pick this shape when one or more of these is true:- Separate test, staging, and production traffic. One subaccount per environment keeps regression sends, soak tests, and live sends from competing on the same rate limit or wallet. The sibling multi-environment subaccounts page walks through the flat per-stage model; this guide is the migration recipe into it.
- Department-level billing. Each business unit gets its own child so spend lines can be invoiced or transferred per unit — the parent statement stays the single source of truth.
- Brand-level channels. A multi-brand org isolates one brand per subaccount so that sender IDs, WhatsApp templates, and AI configuration stay per brand and a leaked key on one brand can’t pivot across the rest.
metadata.cost_center first — the cost-center tag gives you per-department attribution on one org without a structural change.
2. Plan the tree
Draw the mapping before you touch the API. The three axes you need to settle are subaccounts, channels, and keys, and the plan is a table, not a diagram:
Two exercise-the-plan rules:
- Senders and numbers scope to a subaccount, not the parent. When you assign a sender to a child, that sender is reachable only by the child’s keys. Plan explicitly which senders belong to every child — and expect to keep a “test sender” distinct from the production set.
- Keys are per subaccount. A parent-scoped key mints, funds, and retires children; a child-scoped key operates only that child’s messaging. The tree is unambiguous about which key goes where.
3. Create the subaccounts
You have two surfaces. The dashboard is the right choice when a human approves each step; the API is the right choice when this is part of an automated onboarding run. Dashboard — open Settings → Subaccounts → New subaccount (/settings/subaccounts/new) and walk the wizard through once per environment. The step-by-step contract (Basics → Brand → Domain → Connections → Credits → Review) is documented in New subaccount wizard.
API — one call per subaccount:
payments-staging, payments-production). The response carries the child id (sub_…) you carry into every later step. pending_setup: true keeps the child out of a billing-active state until you finalize it — see 5-step wizard for the finalize call.
Assign senders and numbers next, then fund each child from the parent wallet with POST /api/v1/subaccounts/{id}/transfer-credits (owner role only). The parent’s wallet is the feeder — traffic on each child draws only from that child’s balance, and an empty staging child stops sending without touching production.
4. Per-subaccount API keys
Mint a scoped key per child, never a single shared key across environments:- Which organization the key can see. A key minted on
sub_stage9xcalls/sms,/messages, analytics, and AI endpoints as that child — it cannot read the parent’s tree or the sibling’s data. A403onPOST /api/v1/subaccountsfrom a child key and a404on a child-scoped read from a parent key are the expected isolation failure modes and a feature, not a bug. - What that key can do. Scope it down to
messages:read+messages:write(or whichever family the surface needs) so a leaked key is bounded. See Scope API keys per integration for the scope-by-surface matrix.
5. Connection modes per child
Per channel, decide whether each subaccount sends on the platform default connection or on a connection you bring (your own SIP trunk, SMPP account, sending domain, WhatsApp Business account, and so on). The rule is: parent picks it per channel, and every child inherits. There is no per-subaccount override. The full model — the 11 provider types, the master reseller toggle, credential rotation, and fallback to platform default — is in Per-channel connection mode. For a staging vs production split, the two common patterns are:- Both children inherit the platform default. Simplest; everything meters at standard rates against the parent wallet.
- Production on a BYO connection, staging on platform default. Set the BYO row on the parent once (it covers every child), then constrain staging’s spending by its monthly cap and rate limit so the BYO carrier never sees test traffic ramp.
6. Apply AI configuration per subaccount
If the environments behave differently on AI — a staging child that never runs paid models, a production child pinned to a premium provider — set it onGET/PUT /api/v1/subaccounts/{id}/ai-config per child:
stored vs resolved view, reset-by-null semantics, the comped marker) is in Per-subaccount AI configuration. Do not try to control spend with enabled: false; that gates the feature. Use the monthly spend cap and per-key quota for spend control.
7. Move traffic with a blue/green cut
Re-point an existing integration without a freeze window:- Verify the children are healthy. Confirm each child shows
activeunder Settings → Subaccounts, that its scoped key mints, and that a smaller-than-usual test send succeeds on the staging child. - Dual-send for a full business day. Point your integration’s staging traffic at the staging child’s key. Keep live traffic on the existing key. Read
GET /api/v1/subaccounts/usage-rollupand confirm the staging line is growing and is the only line that grows. - Cut production to the new key. Deploy the integration with the production child’s key. Watch the parent’s usage rollup: the production line ticks up as the old line ticks down. A falling line where you expect a rising one means a key is pointing at the wrong child.
- Quiesce the old keys. Leave the parent-scope integration key in place for one more day for emergency rollback, then rotate it out. Stale keys are the most common production leak after a cut — a worker that still has the old credential keeps billing against the old wallet, invisible from the new subaccount’s dashboard.
8. Bill each subaccount separately
At month-end the parent reads one rollup line per child and reconciles against the existing chargeback surfaces:- Per subaccount usage.
GET /api/v1/subaccounts/{id}/usagereturns one child’s metered usage, andGET /api/v1/subaccounts/usage-rollupfolds the whole tree. That is your staging-vs-production statement. - AI spend per child. Open Billing → Usage → AI in the dashboard or call
GET /api/v1/usage/aiscoped per subaccount — the surface is the same shape as the org-wide AI usage split, just on the child. - Intra-environment chargeback. When one child splits its own spend further (say,
stage_regressionvsstage_ux), themetadata.cost_centertag still applies inside the child — tag at send time, read the rollup, and the parent’s per-child line stays as the outer wallet boundary.
GET /api/v1/billing/statements— monthly summariesGET /api/v1/billing/statements/:period— a month’s detail rowsGET /api/v1/billing/statements/:period/download— CSV or JSON export per period
9. Common pitfalls
- Channel leakage. Senders not scoped to a child are unreachable from it (the “404 on a child-scoped read” isolation failure) — but senders left on the parent keep leaking traffic there. Audit every sender after the split and move the test senders explicitly; the staging-versus-production sender split is what makes the billback real.
- Template sharing does NOT cross subaccounts. A WhatsApp or SMS template approved on one child is not visible to the sibling. Recreate the template on each child, or plan a shared template review pass per child as part of onboarding. Subaccounts API covers the per-child template surface.
- Billing surprises. Two places staging invoices itself into production: a stale
pendingchild never finalized (it cannot serve traffic but also shows in the list — either finalize it or delete it), and a stale parent-scoped integration key still bound in a worker. The first shows in Settings → Subaccounts with statuspending; the second shows up as a usage line on the parent rollup when every other line is where you expect it. - Budget ceilings. The child’s
monthly_spend_cap_centsand its rate limit are what they say they are — staging does not borrow from production’s headroom. A staging regression run that needs headroom you did not plan needs a cap raise, not an emergency credit transfer.
See also
- White-label subaccounts — the reseller variant when the children are your customers, not your own environments
- Multi-environment subaccounts — the per-stage model this recipe migrates you into
- New subaccount wizard — dashboard walkthrough of the create flow
- Per-channel connection mode — BYO vs platform default inheritance per channel
- Per-subaccount AI configuration — per-child AI enable/provider/comped
- Cost-center chargeback — the
metadata.cost_centertag inside a child - AI usage split — per-agent/model/channel AI attribution read
- Subaccounts API — endpoint catalog