# Sandbox API reference

> The sandbox operations of the Fax for Agents API. Choose the outcome of the next sends, create a synthetic inbound fax and reset an organization

Page: https://faxforagents.com/docs/api/reference/sandbox/
Last changed: 2026-11-13

Updated Nov 13, 2026

4 operations of the Fax for Agents API, from the OpenAPI document version 1.20.0, snapshot of 2026-11-13. The base URL is `https://api.faxforagents.com/v1`. This is one group of the [full reference](https://faxforagents.com/docs/api/reference/).

- A synthetic inbound fax goes through the same pipeline as a real one.
- A scenario selects the outcome of the next sends, for one destination or for every send.
- The sandbox routes are for test keys, and a live key is refused with sandbox_only.

## Operations

- `DELETE /v1/sandbox/data` Reset this organization's test data
- `POST /v1/sandbox/inbound-faxes` Create a synthetic inbound fax (the same pipeline as a real one)
- `GET /v1/sandbox/scenarios` The scenario catalog and active fixtures
- `POST /v1/sandbox/scenarios` Select the outcome of the next sends

## What does the sandbox group add to a test key?

Control. Test keys already send to the sandbox: no carrier, no cost, and the outcome follows from the destination number. The four operations here let a test choose the outcome, receive a fax it did not have to send, and start again from clean data.

The rest of the API is the same in the sandbox. Documents, quotes, faxes, webhooks and the wallet behave as they do live, with test credit in place of money.

## How do I choose the outcome of a send?

`POST /v1/sandbox/scenarios` takes a `scenario` and, when you want one, a `destination`. With a destination the fixture applies to sends to that number. Without one it applies to every send of the organization that has no more specific fixture. The answer has the fixture's id and `expires_at`.

`GET /v1/sandbox/scenarios` returns the catalog: the scenario names, the scripted destinations and the fixtures that are active now. The names cover every way a fax can end, including the two a test rarely meets by chance, `partial` and `unknown`. The [sandbox guide](https://faxforagents.com/docs/sandbox/) has the table of numbers and what each one does.

## How do I rehearse receiving?

`POST /v1/sandbox/inbound-faxes` creates a synthetic inbound fax. The body takes `to`, and optionally `from`, `pages` and `partial_content`, which rehearses a cut-off transmission. The summary says the fax goes through the same pipeline as a real one. A private document is stored, an inbox item appears, and a `fax.received` event fires with its download reference.

A receiver built against this call is finished before the organization owns a number. The item is read through the [inbox group](https://faxforagents.com/docs/api/reference/inbox/) like any other.

## What does a reset remove?

`DELETE /v1/sandbox/data` resets the organization's test data. The answer has `deleted`, which counts what was removed, and `wallet_available_micro`, the test wallet after the reset. Live data is not touched: the call works on the test environment only.

Run it between test suites, so that one run's faxes and fixtures do not leak into the next.

## Which credential do sandbox calls take?

A test key with `sandbox:write`, which the default API key carries. The four operations all name that scope, the catalog read included. A live key is refused with 403 `sandbox_only`. A signed-in session reaches the sandbox when it sends `X-FFA-Environment: test`, which is also the default for a session.

## What does the sandbox not simulate?

A real fax line. A scripted outcome arrives at once or on the worker's schedule, not after a real call. Timing measured against the sandbox says nothing about a carrier. What the sandbox proves is your handling of each state, and of the events and holds that go with it.

## How do sandbox events reach a webhook?

The same way live ones do. A test send fires `fax.queued`, `fax.started` and its ending event, with the same signature scheme as a live delivery. Register the receiver in the [webhooks group](https://faxforagents.com/docs/api/reference/webhooks/) with the test key, and it gets the events of test faxes only.

## Each operation in detail

### DELETE `/v1/sandbox/data`

Reset this organization's test data

Credential: an API key or a signed-in session. Scope: `sandbox:write`.

#### Parameters

DELETE /v1/sandbox/data parameters

| Name | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |

#### Responses

DELETE /v1/sandbox/data responses

| Status | Meaning |
| --- | --- |
| `200` | Successful Response |
| `401` | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
| `403` | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
| `409` | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
| `422` | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
| `429` | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |

#### Response fields

DELETE /v1/sandbox/data response fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | object | yes |  |
| `reset` | boolean | yes |  |
| `wallet_available_micro` | integer | yes |  |

#### Example

```
`curl https://api.faxforagents.com/v1/sandbox/data \ -X DELETE \ -H "Authorization: Bearer ffa_test_..."`
```

### POST `/v1/sandbox/inbound-faxes`

Create a synthetic inbound fax (the same pipeline as a real one)

A synthetic PDF goes through ``inbox.service.receive_now``: the private document, the inbox item, the ``fax.received`` event with its download reference, exactly as a carrier delivery (M8).

Credential: an API key or a signed-in session. Scope: `sandbox:write`.

#### Parameters

POST /v1/sandbox/inbound-faxes parameters

| Name | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |

#### Request body application/json, required

POST /v1/sandbox/inbound-faxes request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `from` | string or null | no |  |
| `pages` | integer | no |  |
| `partial_content` | boolean | no | Rehearse a cut-off transmission. |
| `to` | string | yes |  |

#### Responses

POST /v1/sandbox/inbound-faxes responses

| Status | Meaning |
| --- | --- |
| `201` | Successful Response |
| `401` | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
| `403` | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
| `409` | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
| `422` | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
| `429` | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |

#### Response fields

POST /v1/sandbox/inbound-faxes response fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amount_micro` | integer | no |  |
| `document_id` | string or null | yes |  |
| `from` | string or null | no |  |
| `id` | string | yes |  |
| `note` | string or null | no |  |
| `pages` | integer | yes |  |
| `received_at` | string | yes |  |
| `status` | string | yes |  |
| `to` | string | yes |  |

#### Example

```
`curl https://api.faxforagents.com/v1/sandbox/inbound-faxes \ -X POST \ -H "Authorization: Bearer ffa_test_..." \ -H "Content-Type: application/json" \ -d '{"to":"+15550000001"}'`
```

### GET `/v1/sandbox/scenarios`

The scenario catalog and active fixtures

Credential: an API key or a signed-in session. Scope: `sandbox:write`.

#### Responses

GET /v1/sandbox/scenarios responses

| Status | Meaning |
| --- | --- |
| `200` | Successful Response |
| `401` | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
| `403` | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
| `429` | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |

#### Response fields

GET /v1/sandbox/scenarios response fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `destinations` | array of object | yes |  |
| `fixtures` | array of object SandboxFixture | yes |  |
| `scenarios` | array of object | yes |  |

#### Example

```
`curl https://api.faxforagents.com/v1/sandbox/scenarios \ -H "Authorization: Bearer ffa_test_..."`
```

### POST `/v1/sandbox/scenarios`

Select the outcome of the next sends

Credential: an API key or a signed-in session. Scope: `sandbox:write`.

#### Parameters

POST /v1/sandbox/scenarios parameters

| Name | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | Replay the first successful response for the same request within 24 hours. Reusing the idempotency header with a different request returns 409 idempotency_key_conflict. Responses include Idempotent-Replayed: true on replay. |

#### Request body application/json, required

POST /v1/sandbox/scenarios request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `destination` | string or null | no | Apply to one destination; omit for every send. |
| `scenario` | string | yes |  |

#### Responses

POST /v1/sandbox/scenarios responses

| Status | Meaning |
| --- | --- |
| `201` | Successful Response |
| `401` | No credential, or one that was not accepted: invalid_token for an API key, unauthenticated where a signed-in session is needed. error.details.reason says why a key was refused. |
| `403` | The credential is valid and is not allowed to do this. scope_missing puts the scope in error.details.scope. Other codes are agent_forbidden and approval_required. A session can also meet csrf_invalid and reauth_required. |
| `409` | The resource is in a state that refuses the call, or the Idempotency-Key was used with a different request (idempotency_key_conflict). error.code names the state. |
| `422` | A field failed validation: invalid_request, with one entry per field in error.details, or a code that names the rule. |
| `429` | rate_limited. Retry-After and the RateLimit-* headers say when to try again. |

#### Response fields

POST /v1/sandbox/scenarios response fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `created_at` | string | yes |  |
| `destination` | string or null | yes |  |
| `expires_at` | string | yes |  |
| `id` | string | yes |  |
| `scenario` | string | yes |  |

#### Example

```
`curl https://api.faxforagents.com/v1/sandbox/scenarios \ -X POST \ -H "Authorization: Bearer ffa_test_..." \ -H "Content-Type: application/json" \ -d '{"scenario":"delivered"}'`
```
