Sandbox and Test Mode
Every endpoint page in the API reference lists the sameX-Test-Mode
header parameter. This guide is the authoritative explanation of what it
does: when test mode applies, how the header and your credential
interact, and worked samples for every auth type. It then walks the full
sandbox flow — magic-number sends, test numbers, simulated inbound
messages, deterministic verify codes, and the go-live gate.
When test-mode applies
Test mode is resolved per request, before any route handler runs, from your credential and theX-Test-Mode header:
- Session-authenticated (Clerk) requests. A browser signed into the
dashboard sends session auth — the dashboard’s Test mode toggle in
the topbar works this way, and it stamps
X-Test-Mode: trueon every call it makes. For session auth the header is honored:trueopts that request into test mode, absent orfalseruns it live. This is the only auth type the header can influence. - API key auth. A live key (
dv_live_sk_*) is always live; a test key (dv_test_sk_*) is always sandbox. The prefix of the key you minted under Settings → API Keys decides — request headers cannot override it. - Sandbox orgs. An org flagged as a sandbox org resolves every credential on it to test mode, regardless of key prefix or header.
The routing matrix
The header is honored only when there is no API key in play — a partner
or reseller holding your live key cannot flip it into sandbox from the
wire, and a live dashboard session can sandbox itself without touching
your shared integration credentials. When a request engages test mode,
the response envelope carries
meta.test_mode: true; assert that marker
rather than trusting the header you sent or the key prefix you pulled
from config.
Session auth: Clerk only
“Session-authenticated” means the caller authenticated with a Clerk browser-session token and sent no API key — the auth model the dashboard itself uses, in this authority order:- API key auth wins whenever a key (
dv_live_sk_*ordv_test_sk_*) is present — the key prefix decides the mode. - Only when no key is attached does a Clerk session authenticate the
request, and only then is
X-Test-Mode: trueread at all.
Worked samples per auth type
Live key — header does not sandbox it
SendingX-Test-Mode: true with a live key still hits the provider and
still charges your balance. The response envelope shows no test_mode
marker:
Test key — unconditionally sandbox
Adv_test_sk_* key engages test mode on every request, with or without
the header:
test_sent, no provider is called, no balance
moves. Add X-Test-Mode: false to the same request and the result is
identical — the key prefix already decided.
Session auth with the toggle on — header decides
From a Clerk session — authenticated cookie-style by the browser, no API key attached — the header is the switch. With it on, the call sandboxes exactly like a test key:Session auth with the toggle off — provider delivery
With the header absent orfalse, the same session-authenticated call
runs live:
Common pitfalls
- Retrying a live key with
X-Test-Mode: true. The header is ignored on key auth, so the retry still delivers and still charges. The fix is adv_test_sk_*key, not a header value sweep. Symptom: the response keeps coming back with the livequeuedstatus and nometa.test_modemarker. - A dashboard session that never goes live. If a colleague left
Test mode on in the dashboard, every browser call sandboxes silently
— sends look healthy (
test_sent) but no recipient ever receives one. Check the topbar toggle before checking the number. - Asserting the header or key prefix instead of the marker. Config
drift between environments means “we sent the test key” and “the
request sandboxed” disagree eventually. Gate the assertion on
meta.test_mode: truein the response; it is the only reliable signal. - Setting
metadata.test_mode: truein a request body. That field is an output stamp the platform writes, not an input — the request remains live and your body field is stored verbatim.
What the sandbox gives you once it is on
Orbit sandbox is a complete simulation environment. Your integration code runs unchanged against the real API — the same endpoints, the same request shapes, the same webhook format — and Orbit short-circuits every send, purchase, and verify call before it touches a carrier. Nothing bills, no real phone rings, no audit rows you need to clean up later. Everything below is keyed off the same signal defined above. When you flip to a live key (dv_live_sk_*), the same request bodies hit
production paths.
Simulate sends and receipts with magic numbers
POST to the Messages API aiming at a magic test number, and Orbit returns a deterministic delivery receipt (DLR). The trailing digit of the recipient picks the scenario —2 delivered, 3 undelivered, 7
rejected — so you can drive every retry, dead-letter, and alerting
branch you have.
message.status webhooks to your
sandbox webhook URL, tagged sandbox: true. The full digit-to-scenario
table lives at Sandbox magic numbers.
Buy test numbers without touching a carrier
POST /api/v1/numbers/purchase with a sandbox credential returns a
deterministic fictional number from the reserved +1 555 01XX range.
There is no provider call, no wallet charge, and the number is marked
provider: "sandbox" in your tenant inventory.
from in your send tests; the
trailing-digit table then drives the recipient-side outcome.
Inject a simulated inbound message
To test the receive side — auto-responders, STOP keywords, inbox routing — you don’t need to hand-forge a provider webhook. POST a synthetic inbound (mobile-originated) message into your sandbox inbox:403 SANDBOX_ONLY, so a
live inbox can never be polluted with synthetic traffic.
Deterministic verification codes
The Verify API honourscustom_code only on sandbox credentials, so
your QA and CI flows can assert a fixed OTP end to end without parsing
a real message.
custom_code returns 403 CUSTOM_CODE_FORBIDDEN — pinning
the OTP is a sandbox privilege, never a production one. A live verify
send still generates a random code server-side.
Reset and re-seed the sandbox
When a test run leaves debris, reset the whole sandbox workspace or seed deterministic fixtures:
All three reject non-sandbox requests with
403 SANDBOX_ONLY before a
single row changes.
Pre-launch gate
Before go-live, Orbit evaluates a shared checklist against your org — callable from either the live or the sandbox context, with the same result either way:readyForLaunch flips to true when every item is complete. Wire it
into your launch script to fail CI on an unfinished gate, then walk the
human sign-off in the production go-live checklist.
Graduating to production
- Finish the pre-launch checklist.
- Complete KYC and fund your balance — these two unlock live sending.
- Mint a live key (
dv_live_sk_) under Settings → API Keys. - Walk the production go-live checklist for numbers, webhooks, guardrails, and compliance.
Cross-references
- Sandbox model — what the sandbox workspace is and how it relates to your production tenant.
- Sandbox, test mode, and provisioning
— the org-level sandbox flag, metrics hygiene for
test_sentrows, and theTENANT_PROVISIONING503. - Sandbox magic numbers — the full trailing-digit to delivery-outcome table.
- Sandbox API samples — copy-paste request/response pairs across channels.