Skip to main content

Operating the Developer → Sandbox dashboard

The sandbox overview explains what sandbox mode is and which API routes back it. This guide is the operator-side walkthrough: the Developer → Sandbox page in the console drives those same endpoints with a click, so you can provision test numbers, seed fixture contacts, simulate inbound replies, and reset the workspace without writing a script. Open it under Developer → Sandbox. Access is restricted to the owner, admin, and developer roles — viewer roles don’t see the page.

What the page wraps

Every card on the page maps to one of the sandbox API routes: Two cards are console conveniences rather than sandbox endpoints: Default test recipient saves a per-user phone and email that send dialogs pre-fill while test mode is on, and Sandbox webhook URL is an org-scoped destination where sandbox event callbacks (message receipts) are delivered — set an https:// URL to receive the same webhook shape production delivers, or leave it blank to disable simulated callbacks. Test-mode API keys lists your dv_test_sk_* / dv_test_pk_* tokens. The Provider stub-status table shows which channels actually short- circuit in test mode (SMS, MMS, WhatsApp, Email) and which still hit the live stack (voice, RCS, push, fax — those bill and reach real upstreams even in test mode).

Turn on test mode first

The top Test mode card mirrors the Test/Live toggle in the topbar. Switch it on before touching anything else: while test mode is off, the Virtual Phone and the sandbox controls cards show a warning banner, and every mutation they drive is rejected by the backend. The toggle only affects your browser session — it puts X-Test-Mode: true on the console’s requests, it does not flip the organization or anyone else’s session. To confirm you’re in sandbox context, look for the Test mode ON badge on the card.

Per-action walkthrough

Provision test numbers

UI path: Sandbox controls → Provision test numbers — enter a count and click Provision numbers. API call: POST /api/v1/sandbox/provision-numbers with { "count": <n> }. Behavior and bounds: up to 25 numbers per call (MAX_PROVISION_NUMBERS — the console input caps at the same value). The numbers are deterministic: the same workspace always gets the same +1 (555) 01XX fictional numbers for the same count, so a reset-then-provision run sees an identical inventory. Re-provisioning the same count is idempotent — existing rows update in place rather than duplicate. Verify: the response counts the provisioned rows, and the numbers appear in your sandbox inventory under Numbers. Each carries provider: "sandbox" and monthly_cost: "0.0000" — no carrier was touched and nothing bills.

Spawn fixture contacts

UI path: Sandbox controls → Spawn fixture contacts — enter a count, choose whether to include seeded conversations (on by default), and click Spawn contacts. API call: POST /api/v1/sandbox/spawn-fixture-contacts with { "count": <n>, "with_conversations": true|false }. Behavior and bounds: up to 50 contacts per call (MAX_FIXTURE_CONTACTS). Each fixture is a deterministic persona determined by index (Ada Lovelace, Grace Hopper, …) whose phone number ends in the trailing digit of one of the 10 magic numbers — so every delivery scenario has a ready-made recipient. With conversations enabled, each contact gets one open inbound SMS conversation carrying a greeting message, which gives your inbox and auto-responder flows something to work against immediately. Verify: fixture contacts appear under Contacts tagged sandbox-fixture; when conversations were seeded, Inbox shows one open thread per contact.

Virtual phone (Inbound)

UI path: Virtual phone card — pick a channel (SMS, MMS, or WhatsApp), set the sender from in E.164 format, type a body and/or an MMS media URL, optionally set a contact display name, and click send. API call: POST /api/v1/sandbox/inbound with { "from", "channel", "body" | "media_url", "contact_name" }. Behavior and bounds: channel accepts only the three SANDBOX_INBOUND_CHANNELSsms, mms, whatsapp. The body caps at 1600 characters, and the call is rejected unless it carries a non-empty body or a media URL. The sender picker suggests numbers from the magic- number table so the “handset” matches a known scenario. The injection is a pure inbox write: it never reaches a carrier, and because it is inbound it never touches any outbound send path. Verify: open Inbox — the message arrives like a real reply. The contact resolves (or is created), the conversation opens or threads, the unread counter bumps, the inbox list live-refreshes, and a new conversation raises the new-conversation notification — exactly the shape a real inbound webhook produces.

Check the pre-launch gate

UI path: the Pre-launch checklist card, near the top of the page. API call: GET /api/v1/sandbox/pre-launch-checklist. Behavior: the card renders the shared five-step gate — sandbox workspace ready, live workspace ready, test API key minted, integration exercises the sandbox, IP allowlist configured — each as complete or pending, with a Ready for launch badge when all five complete. The evaluator always reports against your live organization, so the result is identical whether you read it from the sandbox or the live context, and the pre-launch checklist reference agrees with it item for item. Verify: reload the card after finishing a step; a pending item flips to complete without a page refresh.

Reset the sandbox

UI path: Sandbox controls → Reset sandbox — confirm the dialog that opens before the wipe runs. API call: POST /api/v1/sandbox/reset. What it wipes: every contact, conversation, and message in the sandbox workspace, in one transaction — plus it releases every sandbox- provisioned number (provider: "sandbox"). Your saved default test recipient, the sandbox webhook URL, and your API keys are console/ org settings and survive the reset. Numbers from a real provider (a sandbox workspace should never hold any) are untouched. Verify: the response reports per-class counts (contacts_deleted, conversations_deleted, messages_deleted, numbers_released) so you can confirm the slate is clean, and the sandbox inventory under Numbers lists only what you re-provision afterward.

One shared rate budget

Every sandbox mutation — reset, provision, spawn, and Virtual Phone injection — sits under the authenticated-write rate limit: 60 requests per minute per organization. Console clicks, your dashboard scripts, and direct API calls all draw from that same budget. Bulk test harnesses that provision and reset in a loop should stay well under it; if you hit 429, back off and retry once the minute window clears.

Organization switching and billing

Every account ships with a paired sandbox organization. Use the organization switcher in the topbar to flip between your live and sandbox workspaces — the pairing lets the pre-launch checklist evaluate both sides from either context. The Test/Live toggle on the sandbox page is a faster session-local way to get the same sandbox signal without leaving your current organization. Nothing on this page bills. Magic-number sends report cost_usd_cents: 0, provisioning returns monthly_cost: "0.0000", and fixture or inbound rows carry provider: "sandbox" — the billing system never sees them. When you flip to a live key or live mode, the identical request shapes bill normally; there is no sandbox-side billing state to clean up.

Troubleshooting

403 SANDBOX_ONLY on a mutation. The route rejected the call because your request context isn’t sandbox. You tripped the gate — which is the point, it protects live data. To fix, activate one of the three signals the overview lists:
  1. Use a sandbox API key (prefix dv_test_sk_*) — the usual cause when a script fails.
  2. Switch into the organization flagged as sandbox — the usual cause when the console organization switcher is on the live org.
  3. Turn on the Test mode toggle so the console session sends X-Test-Mode: true — the usual cause when you’re working from the dashboard.
Any one of the three is sufficient; none is tied to the others. The warning banners inside the Virtual Phone and Sandbox controls cards appear precisely when the third signal is missing. Reset returned zero counts. The workspace was already clean — reset only reports what it actually deleted. Re-provision or re-spawn after the wipe instead of assuming the wipes failed. Virtual Phone sent nothing. The form rejects a blank body and a blank media URL before it ever calls the API, and it validates the sender as E.164 — check the inline field errors on the card, not just the network tab. Voice/push/RCS/fax behave “live” in test mode. That’s expected — check the Provider stub-status table at the bottom of the page. Only SMS, MMS, WhatsApp, and Email short-circuit; the other channels hit their real stacks and bill accordingly even while test mode is on. Use +1 555 numbers for voice tests to avoid carrier fees.
  • Sandbox overview — the concepts, the three activation signals, and the route/constants catalog this page operates
  • Magic numbers — the 10 delivery scenarios the fixture phones and sender suggestions key off
  • Pre-launch checklist — the five-step gate the page’s checklist card renders
  • Sandbox and test mode — the API-side walkthrough with copy-pasteable curl examples