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

# Split traffic and billing across subaccounts: staging vs production

> Migrate one org's traffic onto two subaccounts — staging and production — with scoped API keys, per-subaccount rate limits and spend caps, a zero-downtime blue/green cut, and a per-environment billback rollup at month-end.

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

```
Parent org (your company — owns billing, keys, operators)
 ├─ subaccount: staging    plan: growth   monthly cap: $500
 └─ subaccount: production plan: growth   monthly cap: unbounded
```

By the end, each environment runs under its own subaccount id and its own scoped API key. Any spend is attributed at the subaccount row, so a staging regression run can never leak into the production line on your statement.

## 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](/guides/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.

A split is the right call once any one of these is a real operational load. If you are only experimenting, [tag sends with `metadata.cost_center`](/guides/cost-center-chargeback) 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:

| Subaccount | Channels in scope | Sender / number assignment | Key purpose |
| - | - | - | - |
| staging | SMS outbound only | Test sender (not for live sends) | CI smoke test; integration regression |
| production | SMS + WhatsApp + voice + email + AI | Every live sender and number | Live sends; per-env workers |

Two exercise-the-plan rules:

1. **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.
2. **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.

See [Concepts — subaccount–organization model](/concepts/subaccount-organization-model) for the underlying tenancy shape.

## 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](/guides/subaccount-create-wizard).

**API** — one call per subaccount:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/subaccounts \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "payments-staging",
    "plan": "growth",
    "monthly_spend_cap_cents": 50000,
    "rate_limit_per_second": 50
  }'
```

Run it once per environment (`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](/guides/subaccount-create-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:

```bash theme={null}
# staging worker
curl -X POST https://api.orbit.devotel.io/api/v1/subaccounts/sub_stage9x/api-keys \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "staging-worker", "scopes": ["messages:read", "messages:write"] }'

# production worker
curl -X POST https://api.orbit.devotel.io/api/v1/subaccounts/sub_prod7k2/api-keys \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "production-worker", "scopes": ["messages:read", "messages:write"] }'
```

Two kinds of scoping stack on each other:

* **Which organization the key can see.** A key minted on `sub_stage9x` calls `/sms`, `/messages`, analytics, and AI endpoints *as that child* — it cannot read the parent's tree or the sibling's data. A `403` on `POST /api/v1/subaccounts` from a child key and a `404` on 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](/guides/api-keys-messaging-ccaas-cdp) for the scope-by-surface matrix.

**Rate limits and billing attribute up the tree by design.** A key minted on a child draws against *that child's* rate limit and *that child's* spend cap, and its usage rolls up into the parent's per-subaccount line items on the consolidated statement. See [Per-API-key usage limits](/guides/api-key-usage-limits) for the per-key rate-limit and monthly-quota knobs you set inside a child.

## 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](/guides/subaccount-connection-modes).

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.

Pair [connection modes](/guides/subaccount-connection-modes) with the [white-label subaccounts](/guides/subaccounts-reseller) page when you also operate children for customers — the reseller flow is one level of indirection on top of what you build here.

## 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 on `GET/PUT /api/v1/subaccounts/{id}/ai-config` per child:

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/subaccounts/sub_stage9x/ai-config \
  -H "X-API-Key: dv_live_sk_your_parent_key" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false, "provider": null, "comped": true }'
```

The full field contract (the `stored` vs resolved view, reset-by-null semantics, the comped marker) is in [Per-subaccount AI configuration](/guides/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:

1. **Verify the children are healthy.** Confirm each child shows `active` under **Settings → Subaccounts**, that its scoped key mints, and that a smaller-than-usual test send succeeds on the staging child.
2. **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-rollup` and confirm the staging line is growing and is the only line that grows.
3. **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.
4. **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.

If you run canary deploys, route 5% of live sends to the production child's key first, verify the rollup line grows proportionally, then widen. The usage rollup (see next section) is the monitor you watch throughout; you do not need a separate observability wiring for the cut itself.

## 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}/usage` returns one child's metered usage, and `GET /api/v1/subaccounts/usage-rollup` folds 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/ai` scoped per subaccount — the surface is the same shape as the org-wide [AI usage split](/guides/billing-ai-usage-split), just on the child.
* **Intra-environment chargeback.** When one child splits its own spend further (say, `stage_regression` vs `stage_ux`), the [`metadata.cost_center` tag](/guides/cost-center-chargeback) 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.

The statement exporter serves the same period as CSV or JSON:

* `GET /api/v1/billing/statements` — monthly summaries
* `GET /api/v1/billing/statements/:period` — a month's detail rows
* `GET /api/v1/billing/statements/:period/download` — CSV or JSON export per period

Together, the usage rollup + AI split + chargeback tagger + the statement export give you a per-environment invoiceable artifact without contacting support.

## 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](/api-reference/subaccounts) covers the per-child template surface.
* **Billing surprises.** Two places staging invoices itself into production: a stale `pending` child 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 status `pending`; 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_cents` and 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](/guides/subaccounts-reseller) — the reseller variant when the children are your customers, not your own environments
* [Multi-environment subaccounts](/guides/multi-environment-subaccounts) — the per-stage model this recipe migrates you into
* [New subaccount wizard](/guides/subaccount-create-wizard) — dashboard walkthrough of the create flow
* [Per-channel connection mode](/guides/subaccount-connection-modes) — BYO vs platform default inheritance per channel
* [Per-subaccount AI configuration](/guides/subaccount-ai-configuration) — per-child AI enable/provider/comped
* [Cost-center chargeback](/guides/cost-center-chargeback) — the `metadata.cost_center` tag inside a child
* [AI usage split](/guides/billing-ai-usage-split) — per-agent/model/channel AI attribution read
* [Subaccounts API](/api-reference/subaccounts) — endpoint catalog


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.