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

# Sandbox model: the boundary, the synthetic providers, and promotion

> Where the sandbox boundary sits on the request path, how each channel's synthetic provider answers, how sandbox data is isolated, and what carries over — and what does not — when you promote to production.

# The sandbox model

Orbit sandbox works because it is a **model**, not a demo flag in each
handler. At one point on the request path the platform stamps the request
as test-mode; everything downstream of that stamp — routing, providers,
events, billing — reads the stamp and behaves sandbox-safe. This page
explains that model: where the boundary sits, how the synthetic provider
family answers per channel, how data is isolated, and what carries over
when you promote to production. The how-to walkthrough lives in the
[sandbox and test mode guide](/guides/sandbox-test-mode); this page is
the concept behind it.

## 1. What sandbox is

Sandbox is a **synthetic tenant context activated per request**, resolved
before any routing or provider decision. Three inputs activate it, any
one sufficient: a `dv_test_sk_*` API key, an organization flagged as a
sandbox org, or `X-Test-Mode: true` on a dashboard session. Live
credentials resolve to live; test credentials resolve to sandbox; never
both.

The sandbox context runs the **identical API surface** with the same
endpoints, request shapes, and webhook payload format. Two things
distinguish it: requests route to a **synthetic provider family** instead
of carrier dispatch, and event traffic carries **distinct markers** —
`metadata.test_mode: true` on messages and events, `provider: "sandbox"`
on numbers — so a reader or handler can always separate the two worlds.

## 2. Where the boundary sits

Sandbox mode is resolved on every request by middleware, before any
route handler, routing table, or provider adapter runs. The stamp is an
input decision evaluated once; downstream layers read it, they do not
re-derive it.

That ordering buys three properties:

* **Routing reads the stamp.** A sandbox send short-circuits before the
  routing table picks a carrier. Provider adapters never see it.
* **Every response carries the marker.** Envelopes say
  `meta.test_mode: true`, so asserting the sandbox boundary in code is a
  one-field check.
* **Outbound events carry the marker on every event type.** Every event
  payload for sandbox traffic carries its `test_mode` flag —
  `message.sent`, simulated delivery receipts, inbox events — so a
  handler can gate even if it ignores the separate sandbox webhook URL.
  A dedicated **sandbox webhook URL** (under **Settings → API Keys →
  Sandbox**) delivers sandbox events to a separate receiver, so a
  production handler never sees synthetic traffic.

A synthetic fragment for a sandbox send:

```json theme={null}
{
  "data": {
    "id": "msg_3a9f0c2e8b1d4f6a5c7e9b2d0a1f4c6e",
    "status": "test_sent",
    "channel": "sms",
    "direction": "outbound",
    "to": "+15005550002"
  },
  "meta": {
    "request_id": "req_4e2b8c1a7f9d3e5b6a8c0d2f4b6a8c1e",
    "timestamp": "2026-09-12T03:41:18.442Z",
    "test_mode": true
  }
}
```

The dedicated `test_sent` status and the `meta.test_mode: true` envelope
field are the two markers that prove the boundary engaged; assert both in
integration tests.

## 3. The synthetic provider family

Each channel has a sandbox provider that answers without touching a
carrier. The response is canned and deterministic so tests stay
reproducible:

| Channel   | Sandbox provider behavior                                                                                          | Simulated terminal outcome                                                                          |
| --------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| SMS / MMS | Immediate terminal DLR; [magic numbers](/sandbox/magic-numbers) key the scenario on the recipient's trailing digit | `test_sent` plus a simulated `delivered` / `undelivered` / `rejected` receipt in the webhook stream |
| WhatsApp  | Templated echo reply; simulated template acceptance, no Meta handoff                                               | Accepted, with the test marker on the stored row                                                    |
| Voice     | Session completes without real media; voice paths emit call-lifecycle events on a synthetic session                | A synthetic end-of-session event, no PSTN leg                                                       |
| RCS / fax | Analogous short-circuit: persisted, provider echo, simulated receipt                                               | Test-stamped rows with a simulated terminal status                                                  |
| Email     | Accepted at the send boundary; no SMTP session opens                                                               | Test-stamped accepted state                                                                         |

Every row in the table is persisted with the test marker, and every
emitted event carries it — your webhook handlers and retrieval code run
against real rows, while no carrier ever participates.

## 4. What you can and cannot do

**You can**, with no carrier traffic involved: exercise sends on all
channels, drive every webhook path with simulated receipts, purchase
fictional `+1(555)01XX` test numbers with `provider: "sandbox"`,
inject simulated inbound messages into a sandbox inbox, and pin
deterministic verification codes (`custom_code` on the Verify API) for
QA and CI.

**You cannot**: reach a real carrier, ring a real phone, or hit Meta,
SMPP, or SMTP. Live credentials **do not** join the sandbox context
`403 CUSTOM_CODE_FORBIDDEN` on `custom_code`, and the `X-Test-Mode`
header is honored only for session auth — a live API key that sends it
stays live.

Sole exception: **trial numbers are still real.** Because they sit on the
real inventory, a trial number can receive and place real traffic while
the rest of the org is sandbox-only; treat the trial-number boundary as
its own gate.

## 5. Data isolation

Sandbox rows live **in your tenant schema**, marked with the test stamp
on the message or `provider: "sandbox"` on the number. They are never
metered or billed — `cost_usd_cents: 0`, no wallet debit, no Stripe
traffic — and they are excluded from metrics by filtering on
`metadata.test_mode`. The tenant-isolation model itself lives in
[Tenant isolation](/concepts/tenant-isolation).

Purge semantics: `POST /sandbox/reset` wipes sandbox contacts,
conversations, and messages and releases sandbox numbers, so a test run
can start from a clean slate. The route rejects any non-sandbox request
with `403 SANDBOX_ONLY` before a single row changes, so a live context
can never wipe production data through it.

## 6. Transition to production

At promotion, **configuration carries over; traffic does not.** Mint a
live key, point the same integration code at it, and the following
survive: webhook subscriptions and URLs, routing configurations, flows,
and reusable templates or lists. The following do **not**: sandbox
message rows, status histories, and purchased sandbox numbers — those
stay test-scoped and are wiped by reset. Re-purchase live numbers and
re-register a live sender against the live credential; the pre-launch
checklist reports readiness while any of those steps is pending.

Carries over vs. does not:

| Carries over                                         | Does not                                          |
| ---------------------------------------------------- | ------------------------------------------------- |
| Webhook subscriptions and URLs                       | Sandbox message rows                              |
| Routing configs and flow definitions                 | Status histories of test sends                    |
| Templates, lists, and sandbox-ready integration code | Purchased sandbox (`provider: "sandbox"`) numbers |

## 7. Pointers

* [Sandbox and test mode guide](/guides/sandbox-test-mode) — the
  end-to-end walkthrough (magic numbers, test numbers, simulated inbound,
  deterministic verify codes, go-live gate). Pin markers as in
  [provision and test mode](/concepts/provisioning-and-test-mode).
* [Sandbox overview](/sandbox/overview) — the orientation page: the full
  endpoint surface and billing posture.
* [Sandbox magic numbers](/sandbox/magic-numbers) — the digit-to-scenario
  table for simulated receipts.
* [Pre-launch checklist](/sandbox/pre-launch-checklist) — the five-step
  go-live gate.
* [Provisioning and test mode concept](/concepts/provisioning-and-test-mode) —
  the three test-mode switches and what a test send does and does not touch.
* [Tenant isolation](/concepts/tenant-isolation) — the per-tenant schema
  sandbox rows live in.
