Skip to main content

Sandbox API Cookbook

Every sandbox route the sandbox overview catalogs, shown as a runnable request/response pair. The sandbox and test mode guide walks the concepts end to end; this page is the recipe sheet you keep open while you script against the sandbox. All examples use your sandbox secret key, exported as an environment variable:

1. Setup — what puts a request in sandbox mode

Any one of these three signals activates sandbox mode; the mutation routes below gate on them before touching data. You usually need exactly one. Signal A — a sandbox API key (the normal one for scripts):
Signal B — a sandbox organization. Every account ships with a paired sandbox org; pick it in the console organization switcher and an API key minted on that org is implicitly sandbox-scoped. Signal C — the X-Test-Mode: true header (dashboard sessions only — the Test/Live toggle sets it for you):
GET endpoints like /sandbox/numbers and /sandbox/pre-launch-checklist do not gate on the signal (any authenticated key can read them), but every POST below refuses a non-sandbox context with 403 SANDBOX_ONLY. See the failure modes section for the envelope.

2. Provision test numbers

Mint deterministic fictional +1 (555) 01XX numbers into your sandbox inventory. No carrier is touched — the rows write straight into your tenant with provider: "sandbox" and monthly_cost: "0.0000".
Bounds and behaviour:
  • count caps at 25 per call (MAX_PROVISION_NUMBERS); country_code (2-letter, default US) and capabilities (a non-empty subset of sms / voice / mms) are optional.
  • The numbers are deterministic per tenant — a reset-then-provision run gets the identical inventory, and re-provisioning the same count is idempotent (the deterministic number is the conflict key, so it upserts rather than duplicates).
Rejections: count above 25 (or 0, or non-integer) fails validation with a 400; an omitted body defaults count to 5.

3. Spawn fixture contacts

Seed deterministic contacts (Ada Lovelace, Grace Hopper, …) so your inbox, auto-responder, and segment flows have something to work against. Each fixture’s phone ends in the trailing digit of one of the 10 magic numbers, so every delivery scenario has a ready-made recipient.
Bounds and behaviour:
  • count caps at 50 per call (MAX_FIXTURE_CONTACTS); an omitted body defaults to 10 contacts.
  • with_conversations defaults to true. With it on, each contact gets one open inbound SMS conversation carrying a greeting message, so the Inbox has a live thread immediately. Set it to false when you want bare contacts.
  • Fixture contacts are tagged sandbox-fixture, which is what POST /sandbox/reset sweeps away with the rest of the workspace.

4. Virtual Phone — inject an inbound message

Simulate the handset replying to you: the message lands in your sandbox inbox exactly like a real inbound provider webhook, without forging one. Use it to drive auto-responders, STOP keywords, inbox routing, and the unassigned-notification path.
Request rules:
  • channel accepts only sms, mms, or whatsapp (default sms).
  • The call is rejected (400 VALIDATION_ERROR) unless it carries a non-empty body or a media_url (an https:// URL, max 2048 chars, for an MMS / WhatsApp media message). body caps at 1600 characters.
  • from must be E.164; to is optional (defaults to the sandbox sentinel). contact_name sets the display name on a freshly created contact.
  • Repeat injections from the same sender on the same channel thread into the open conversation (created_conversation: false), matching the real inbound path.
The response’s created_contact / created_conversation flags tell you whether this injection minted rows or threaded into existing ones — the same distinction the live recorder reports.

5. Reset the sandbox

Wipe the sandbox workspace back to a clean slate in one transaction — every contact, conversation, and message, plus every sandbox-provisioned number released. Your API keys, saved test recipient, and sandbox webhook URL survive (they are org settings, not workspace data).
Zero counts mean the workspace was already clean — reset reports what it actually deleted. Real-provider numbers (which a sandbox workspace should never hold) are untouched.

6. Magic numbers catalog

The 10 delivery-state scenarios, keyed by the recipient’s trailing digit. Any authenticated key reads it; the dashboard’s magic-number table renders exactly this list.
Target sends at these numbers to drive your retry, dead-letter, and alerting branches; the full digit-to-scenario semantics live at magic numbers.

7. Pre-launch checklist

The shared five-step go-live gate — callable with either a live or sandbox key, and always evaluated against your live workspace so both contexts agree on the result.
Parse readyForLaunch — it flips to true only when every item reports status: "complete". Wire it into launch scripts to fail CI on an unfinished gate; the pre-launch checklist reference explains each item.

8. Failure modes

The gate every mutation route enforces — shown here with a live key:
Other errors you will hit, and what they mean:

Honest limits

  • Number inventory is fictional. Provisioned numbers come from the NANP-reserved +1 (555) 01XX range and carry provider: "sandbox" — they can send to and receive from other sandbox fixtures only. No carrier ever sees them.
  • Channels that still run live. Only SMS, MMS, WhatsApp, and Email short-circuit in test mode. Voice, RCS, push, and fax still hit their real stacks and bill normally even with a sandbox key — route those tests at +1 555 numbers to avoid carrier fees, and expect no sandbox inbound support for them (the endpoint accepts only the three messaging channels above).
  • Sandbox POSTs write real rows into your sandbox tenant. Reset is the only cleanup — there is no per-route undo.
  • Rate budget is shared. Console clicks on the Developer → Sandbox page and your scripts draw from the same 60 req/min write limit.