Skip to main content

Sandbox Overview

Orbit sandbox is a complete simulation environment for building and testing your integration before a single carrier sees traffic. Everything in this section works against your sandbox workspace and never bills, never rings a real phone, and never touches a live carrier. This page is your orientation: it lists what activates sandbox mode, the full sandbox surface, a worked session from first key to the Virtual Phone, what a reset preserves and what it wipes, and the exact swap matrix when you go live.

What sandbox is

Sandbox mode activates when your request uses any of these three signals — the same gate every /sandbox/* route verifies before it mutates anything:
  1. A sandbox API key (prefix dv_test_sk_*), or
  2. An organisation flagged is_sandbox = true in the Orbit data plane, or
  3. The X-Test-Mode: true header on a dashboard session.
Any one is sufficient. The same three signals drive magic-number sends, sandbox number provisioning, fixture spawning, and inbound injection. Every mutation endpoint refuses a non-sandbox context with 403 SANDBOX_ONLY before it touches data. Public-key-equivalent sandbox keys use the dv_test_pk_* prefix (paired with the secret dv_test_sk_*). Live keys use dv_live_sk_*. New accounts come with a paired sandbox org — use Developer → Sandbox in the dashboard to flip between the two.

The sandbox surfaces

The dashboard’s Developer → Sandbox page maps one-to-one onto these endpoints — every button on that page calls the route in the table: Anything you exercise in the console has an API counterpart you can script into CI, and anything you script shows up in the console — both fronts operate on the same sandbox workspace. The sandbox gate on each route rejects a live context with 403 SANDBOX_ONLY.

A worked sandbox session

This is the loop a new integration runs the first time: connect a test key, provision numbers, seed fixtures, push inbound traffic at the inbox, and reset to run it again. Set your sandbox key once:
Mint test keys under Settings → API Keys — they arrive with the dv_test_sk_ prefix and route every request to the sandbox gate.

1. Provision three numbers

Provisioned numbers are deterministic per tenant: the same tenant always receives the same +1555 01XX fictional numbers in order, so a reset-then-provision cycle returns the same address book and your fixture scripts stay stable. A call asking for more than 25 numbers in one shot is rejected by the 25-per-call cap.

2. Spawn two fixture contacts

Each fixture phone is pinned to the trailing digit of one magic number, so each contact is a ready-made recipient for a known delivery outcome — here, Ada replies on the delivered-2 scenario and Grace on undelivered-3. With with_conversations: true each fixture arrives with one seeded inbound conversation in the inbox. A single call accepts at most 50 contacts.

3. Send to a magic recipient

The send terminates locally at test_sent and reports cost_usd_cents: 0; your sandbox webhook URL receives the sent → delivered receipt pair. The full state table is on magic numbers.

4. Run the Virtual Phone — inject an inbound reply

Now simulate the handset answering. The reply threads into the same conversation because the from matches the fixture’s phone:
The dashboard inbox live-refreshes like a real inbound webhook did the work: created_contact / created_conversation tell you whether the injection opened a new thread or appended to the existing one, the unassigned-conversation notification fires on a genuinely new thread, and STOP-keyword or auto-responder handling runs against the injected body. Channels accepted are sms, mms, and whatsapp. This is the endpoint the dashboard’s Virtual Phone drives, so console-injected and API-injected messages land in the same thread.

5. Reset and re-run

Provision and spawn again and the deterministic numbering hands you the same +1555 01XX numbers — one script runs the whole loop on every CI job.

The pre-launch checklist — what the dashboard shows

GET /sandbox/pre-launch-checklist and the launch panel on Developer → Sandbox read the same five-step evaluation. Each step reports complete or pending; what to do while it is pending: readyForLaunch is true when all five report complete. The full per-item breakdown is on the pre-launch checklist page.

How sandbox interacts with billing

Sandbox never bills. Magic-number sends report cost_usd_cents: 0, no balance is deducted, and the simulated traffic never reaches Stripe or your live wallet. Provisioning a test number, spawning a fixture, and injecting an inbound message are all free. When you flip to a live key (dv_live_sk_*), the same request shape bills normally — there is no sandbox-side billing config to clean up.

Reset discipline

Reset is the pressure valve between test runs, and it is deliberately narrow: Reset preserves — nothing changes outside data fixtures:
  • API keys and their settings (sandbox and live tokens, IP allowlists, webhook URLs) — resetting never invalidates a key.
  • Workspace and dashboard settings, integrations, and flows you built.
  • Anything on your paired live org — the sandbox org never reaches across the pair.
Reset wipes — the data layer, inside one transaction:
  • Contacts, conversations, and messages in the sandbox workspace.
  • Sandbox-provisioned numbers (provider = 'sandbox') — real-provider rows are untouched as a defensive guard.
Reset when a test run leaves debris you do not want in the next run: before asserting inbox state in CI, after an exploratory session, or when a fixture set has grown past what a script expects. Do not reset to rotate keys or to “clean up billing” — neither is affected. The 60 req/min authenticated-write limit covers every sandbox mutation endpoint — reset, provision, spawn, and inbound injection included — so a script that loops reset-plus-reseed in a tight retry will start seeing 429s and should respect Retry-After. Bursts of dozens of calls per run are fine; sustained hammering is not what the sandbox is for.

Sandbox to live transition — the swap matrix

Moving to production is a key swap, not a rewrite: the sandbox signal comes from the credential, never from request bodies. Four gates stand between sandbox and live traffic:
  1. KYC approved — complete organisation verification; required before live sending unlocks.
  2. Funded balance — live traffic debits your wallet; launch volume plus headroom must be funded.
  3. 10DLC registration (US A2P SMS) — brand and campaign registered before US SMS sends on live traffic, layered on top of KYC — see 10DLC registration.
  4. Production key swap — mint a live key (dv_live_sk_) under Settings → API Keys and swap the environment variable your integration reads. Nothing else in your request shape changes.
The endpoint surface mostly carries over verbatim; a few behaviors move with the key: There is no sandbox-side cleanup by design: sandbox fixtures never migrate into your live org, and the sandbox numbers, contacts, conversations, and messages you built stay quarantined in the sandbox workspace (or vanish with a reset you run yourself). The swapped key is the cutover — do not attempt to carry fixture data across; rebuild any seed data you want on live with the normal live endpoints. The production go-live checklist walks the human sign-off; the pre-launch checklist reports machine readiness with readyForLaunch: true.

Constants reference

The /sandbox/* routes are bounded by these constants:

Next steps