Sandbox Mode
Sandbox mode simulates a mutation request without executing it. When the request body of a supported endpoint contains "sandbox": true, the API authenticates and validates the request as usual, then returns a simulated response with sample data instead of performing the operation.
For test-suite, CI, and debugging workflows built on sandbox mode, see Testing with sandbox mode.
Request Field
| Field | Type | Location | Default |
|---|---|---|---|
sandbox | boolean | JSON request body | false |
sandbox must be the JSON boolean true; the string "true" is not a valid value. The API reads the field from the request body of the endpoints listed under Supported Endpoints, never from headers or query parameters.
Two endpoints also accept a multipart form body:
PATCH /v3/channels/sms/{country}/{type}: sendsandboxas its own form field with the text valuetrue.POST /v3/sender-profiles: include"sandbox": trueinside the JSON document in theprofileform field.
How It Works
For a request with "sandbox": true:
- Authentication runs. An invalid or missing API key is rejected with
401. - Request validation runs. A malformed payload returns the same
400errors as a live request. - Execution is skipped. The database, the send queue, downstream provider APIs, and your balance are all left untouched.
- A simulated response is returned. It uses the same envelope and schema as a live response, populated with sample data, and the same status code except where Supported Endpoints notes otherwise. Identifiers such as
message_idare newly generated and do not reference stored resources.
Sandbox mode does not look up stored resources for most endpoints. A sandbox send referencing a nonexistent template ID still returns 202; only request-level validation is applied. The exceptions are the channel and Sender Profile endpoints below. They read your stored account state first and return the same errors as a live request:
| Endpoint | Checks run before the simulated response |
|---|---|
PATCH /v3/sender-profiles/{id} | 404 if the Sender Profile does not exist. |
PATCH /v3/channels/voice/{number} | 404 if the account has no such voice number, and 404 if agent_id is not one of the account's voice agents. |
POST /v3/voice/agents | 409 if the account already holds as many voice agents as it may. |
PATCH /v3/voice/agents/{id} | 404 if the account has no such agent, and 400 if the body changes nothing. |
DELETE /v3/voice/agents/{id} | 404 if the account has no such agent, and 409 if a voice number routes to it or another agent lists it in transfer_targets. |
POST /v3/voice/agents/{id}/rotate-secret | 404 if the account has no such agent. |
PATCH /v3/channels/sms/{country}/{type} | 404 if the account does not hold the market, plus the same 400 and 409 compliance checks as a live request. |
POST /v3/channels/sms | 409 if the account already holds the market, unless the request adds numbers to a held LOCAL market or re-asks for area codes on a held market that has no sender yet. Also runs the same 400 and 422 compliance and identity checks as a live request. |
POST /v3/channels/rcs | 409 if the account already has an RCS agent. |
Response Headers
| Header | Value |
|---|---|
X-Sandbox | true when the sandbox field was detected. Present on success responses and on 400 validation errors. |
Example
POST /v3/messages
Content-Type: application/json
x-api-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
{
"sandbox": true,
"to": ["+14155550123"],
"channel": ["sms"],
"template": {
"id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"parameters": {
"name": "Test User"
}
}
}Response:
HTTP/1.1 202 Accepted
X-Sandbox: true
X-Request-Id: req_5f2c1b3a9d8e4f67
Content-Type: application/json
{
"success": true,
"data": {
"status": "QUEUED",
"template_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"template_name": "",
"recipients": [
{
"message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
"to": "+14155550123",
"channel": "sms",
"body": null
}
]
},
"meta": {
"request_id": "req_5f2c1b3a9d8e4f67",
"timestamp": "2026-07-25T10:30:00Z",
"version": "v3"
}
}In the simulated response, template_name is empty and body is null because the template is referenced by id and sandbox mode performs no template lookup. message_id is generated per recipient and does not correspond to a stored message.
Supported Endpoints
| Endpoint | Sandbox response |
|---|---|
POST /v3/messages | 202 with "status": "QUEUED" and one simulated recipient per to entry per channel, or one per to entry with no channel when channel is omitted. Nothing is queued or sent. |
POST /v3/messages/{id}/cancel | 200 with a simulated message reporting "status": "CANCELLED". No message is cancelled and no webhook is delivered. |
POST /v3/contacts | 201 with a simulated contact echoing phone_number. No contact is created. |
PATCH /v3/contacts/{id} | 200 with a simulated updated contact. |
POST /v3/templates | 201 with a simulated template in DRAFT status. |
PUT /v3/templates/{id} | 200 with a simulated updated template. |
DELETE /v3/templates/{id} | 204. The template is not deleted. |
POST /v3/webhooks | 201 with a simulated webhook and a placeholder signing secret. |
PUT /v3/webhooks/{id} | 200 with a simulated updated webhook. |
PATCH /v3/webhooks/{id}/toggle-status | 200 with a simulated webhook reflecting the requested is_active. |
POST /v3/webhooks/{id}/rotate-secret | 200 with a placeholder signing_secret. The real secret is unchanged. |
POST /v3/webhooks/{id}/test | 200 with a simulated delivery result. No test event is delivered. |
POST /v3/sender-profiles | 201 with a simulated Sender Profile, or 202 when the body includes a channels.sms block. Nothing is created and no number is provisioned. |
PATCH /v3/sender-profiles/{id} | 200 with the stored profile and the requested changes applied. Nothing is written. |
POST /v3/channels/sms | 202 with a simulated market in PROVISIONING status. When the request adds numbers to a held LOCAL market or re-asks for area codes, 202 with the market as currently stored. No number is claimed and no compliance data is written. |
PATCH /v3/channels/sms/{country}/{type} | 200 with the stored market and the requested compliance changes applied. Nothing is written and no documents are attached. |
POST /v3/channels/rcs | 202 with a simulated RCS agent echoing the request. No agent is requested. |
POST /v3/channels/voice | 202 with a simulated voice number and a callback secret that is not stored. No number is purchased. |
PATCH /v3/channels/voice/{number} | 200 with the stored voice number and the requested changes applied. Nothing is written. |
POST /v3/channels/voice/{number}/rotate-secret | 202 (a live request returns 200) with a new callback_secret that is not stored. The real secret is unchanged. |
POST /v3/channels/voice/{number}/test | 202 (a live request returns 200) with a passing result and a simulated call_id. No test request is sent to your callback URL. |
POST /v3/voice/tokens | 200 with a token prefixed voice_test_ that cannot be used to place calls. |
POST /v3/voice/calls/{id}/hangup | 204. The call is not ended. |
POST /v3/voice/calls/{id}/participants | 202 with a simulated participant leg in INITIATED status. Nobody is dialed. |
PATCH /v3/voice/calls/{id}/participants/{participantId} | 204. The participant is not muted or unmuted. |
DELETE /v3/voice/calls/{id}/participants/{participantId} | 204. The participant is not removed. |
DELETE /v3/voice/calls/{id}/participants | 204. No participants are removed. |
POST /v3/voice/calls/{id}/recordings | 204. Recording is not started or stopped. |
POST /v3/voice/agents | 202 with the agent as it would be stored, under an id and a signing_secret that are not stored. No agent is created. |
PATCH /v3/voice/agents/{id} | 200 with the stored agent and the requested changes applied. Nothing is written. |
DELETE /v3/voice/agents/{id} | 204. The agent is looked up and the answer is the one a real delete would give, but the agent is not deleted. |
POST /v3/voice/agents/{id}/rotate-secret | 202 (a live request returns 200) with a new signing_secret that is not stored. The real secret is unchanged. |
POST /v3/users | 201 with a simulated invited user. No invitation is sent. |
PATCH /v3/users/{userId} | 200 with a simulated updated user. |
DELETE /v3/users/{userId} | 204. The user is not removed. |
GET endpoints do not accept the sandbox field. The following endpoints do not support sandbox mode and always run the operation, even if the body contains "sandbox": true:
DELETE /v3/webhooks/{id}deletes the webhook.DELETE /v3/sender-profiles/{id}deletes the Sender Profile.POST /v3/channels/whatsappconnects the WhatsApp account. The response still carriesX-Sandbox: true.
Deprecated Endpoints
These endpoints are deprecated and will be removed in a future release. They still accept "sandbox": true, but new integrations should use the replacement. Responses from the /v3/profiles endpoints, sandbox or live, carry a Deprecation: true header and a Link header pointing to /v3/sender-profiles with rel="successor-version".
| Deprecated endpoint | Sandbox response | Replacement |
|---|---|---|
DELETE /v3/contacts/{id} (deprecated) | 204. The contact is not deleted. | PATCH /v3/contacts/{id} with {"opt_out": true} |
POST /v3/profiles (deprecated) | 201 with a simulated profile. | POST /v3/sender-profiles |
PATCH /v3/profiles/{profileId} (deprecated) | 200 with a simulated updated profile. | PATCH /v3/sender-profiles/{id} |
DELETE /v3/profiles/{profileId} (deprecated) | 204. The profile is not deleted. | DELETE /v3/sender-profiles/{id} (no sandbox support) |
POST /v3/profiles/{profileId}/complete (deprecated) | 202. No completion is started. The administrator role check still runs, so a caller without the required role gets 403 as in a live request. | /v3/sender-profiles |
POST /v3/profiles/{profileId}/campaigns (deprecated) | 201 with a simulated campaign. Nothing is submitted to TCR. | /v3/sender-profiles |
PUT /v3/profiles/{profileId}/campaigns/{campaignId} (deprecated) | 200 with a simulated updated campaign. | /v3/sender-profiles |
DELETE /v3/profiles/{profileId}/campaigns/{campaignId} (deprecated) | 204. The campaign is not deleted. | /v3/sender-profiles |
Sandbox Mode vs Idempotency
| Feature | Purpose | Side effects | Response data |
|---|---|---|---|
| Sandbox mode | Simulate a request | None | Sample data |
| Idempotency | Execute a request at most once | On the first request only | Real data, cached for replays |
Refer to Idempotency for key format, expiry, and replay headers.
Troubleshooting
| Symptom | Cause | Check |
|---|---|---|
Response has no X-Sandbox header and the operation executed | The sandbox field was not detected | Send sandbox as the JSON boolean true, not the string "true", in the request body of a supported endpoint. Headers and query parameters are not read. |
401 | Invalid or missing API key | Sandbox mode does not bypass authentication. The request must carry a valid key in x-api-key or Authorization: Bearer. |
400 with X-Sandbox: true | The payload failed validation | Sandbox requests return the same validation errors as live requests. Correct the fields listed in the error details. |
Idempotency
Idempotency-Key contract for the Sent API v3: supported methods, key format, response caching, replay headers, and concurrency behavior.
Data Models
Reference for the Sent API v3 response envelope, ApiError and ApiMeta objects, and pagination metadata, plus links to per-resource request and response schemas.