> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tavrn.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox

> Self-serve test environment for the Enterprise API v2: same contract, synthetic data, test keys that never touch production.

The sandbox at `https://sandbox.tavrn.ai` is a separate deployment of the Enterprise API v2 for integration work before you have a Tavrn account.

| | Sandbox | Production |
| - | - | - |
| Base URL | `https://sandbox.tavrn.ai/v2` | `https://enterprise.tavrn.ai/v2` |
| Key prefix | `tavrn_sk_test_` | `tavrn_sk_live_` |
| Who gets a key | Anyone with a work email, self-serve | Organization admins in the [Tavrn Platform](https://platform.tavrn.ai/dashboard/api) after onboarding |
| Data | Synthetic fixtures, watermarked PDFs | Real retrievals |
| Provider outreach | None: lifecycles are scripted | Tavrn contacts the facility |
| Billing | None | Per retrieval |
| Contract | Identical: same paths, schemas, scopes, error envelope, webhook signing | — |

Each host rejects the other host's keys: a `tavrn_sk_live_` key on the sandbox returns `400 wrong_environment`; a `tavrn_sk_test_` key on production returns `401 invalid_api_key`.

## Get a key

1. Sign up at [sandbox.tavrn.ai/sign-up](https://sandbox.tavrn.ai/sign-up) with a work email (code by email) or a Google work account. Free-mail and disposable addresses are rejected; there is a bot check.
2. Answer three questions (company, role, what you want to try) and accept the sandbox terms.
3. Copy the key. It is shown once; afterwards the page shows only the last 4 characters. **Rotate key** issues a new one (the old key keeps working for 10 minutes).

```bash theme={null}
export TAVRN_API_KEY=tavrn_sk_test_...
curl https://sandbox.tavrn.ai/v2/ping -H "Authorization: Bearer $TAVRN_API_KEY"
```

```json theme={null}
{"status":"ok","environment":"sandbox","sandbox":true,"requests_remaining":499,"expires_at":"2026-10-31T05:37:33.945Z","timestamp":"2026-10-17T05:39:49.587Z"}
```

The same page shows usage against the [limits](#limits), lets you rotate or revoke the key, extend the sandbox once by 14 days, export everything as JSON, or delete the sandbox.

## Quickstart

Five calls: create a case, upload a (synthetic) authorization form, open a retrieval request, read it, list its documents. Every call is the production call with the sandbox host and key.

```bash theme={null}
export TAVRN_API_KEY=tavrn_sk_test_...
B=https://sandbox.tavrn.ai

# 1. Case
CASE=$(curl -s -X POST $B/v2/cases \
  -H "Authorization: Bearer $TAVRN_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Jordan Sandbox v. Example Insurance","client_name":"Jordan Sandbox","external_id":"demo-001"}' | jq -r .id)

# 2. Authorization form: sign an upload, then PUT the file (any PDF; this makes an empty one)
[ -f auth_form.pdf ] || printf '%%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0 obj<</Type/Pages/Kids[]/Count 0>>endobj\ntrailer<</Root 1 0 R>>\n%%%%EOF\n' > auth_form.pdf
SIGN=$(curl -s -X POST $B/v2/cases/$CASE/sign-upload \
  -H "Authorization: Bearer $TAVRN_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"file_name":"auth_form.pdf","file_type":"application/pdf"}')
curl -s -X PUT "$(echo $SIGN | jq -r .upload_url)" \
  -H 'Content-Type: application/pdf' --data-binary @auth_form.pdf
UPLOAD=$(echo $SIGN | jq -r .upload_id)

# 3. Retrieval request (scenario header is optional; "complete" is the default)
RR=$(curl -s -X POST $B/v2/cases/$CASE/retrieval-requests \
  -H "Authorization: Bearer $TAVRN_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'X-Tavrn-Sandbox-Scenario: complete' \
  -d '{"patient":{"name":"Jordan Sandbox","date_of_birth":"1985-06-15"},
       "facility":{"name":"Sandbox General Hospital","address_line1":"100 Example Way","city":"Testville","state":"CA","zip_code":"00001"},
       "requested_records":[{"type":"medical_records"},{"type":"billings","deliverable_format":"itemized"}],
       "auth_form":{"upload_id":"'$UPLOAD'","file_name":"auth_form.pdf","signer_name":"Jordan Sandbox","signer_relationship":"self"}}' | jq -r .id)

# 4. Read it (or skip the 8-minute wait: see "Advance" below)
curl -s $B/v2/retrieval-requests/$RR -H "Authorization: Bearer $TAVRN_API_KEY" | jq '{stage, status, costs, sandbox}'

# 5. Documents (empty until stage=completed: wait ~8 min, send X-Tavrn-Sandbox-Speed: fast on step 3, or advance)
curl -s -X POST $B/v2/retrieval-requests/$RR/advance -H "Authorization: Bearer $TAVRN_API_KEY" -d '{"to_stage":"completed"}'
curl -s $B/v2/retrieval-requests/$RR/documents -H "Authorization: Bearer $TAVRN_API_KEY"
```

Step 3 returns `201`:

```json theme={null}
{"id":"8cf1c10eb43b029f7198047a","order_number":"T-SBX-00001","case_id":"52dfcb5f51755ff5211e9091","status":"received","created_at":"2026-10-17T05:39:53.000Z","updated_at":"2026-10-17T05:39:53.000Z","requestor_id":"39f8fe9617fc30f42a511d2c"}
```

Step 4, once the lifecycle has run:

```json theme={null}
{
  "stage": "completed",
  "status": "completed",
  "costs": {"provider_fees": 85, "tavrn_credits_consumed": 1, "tavrn_fee": 35, "total_cost": 120},
  "sandbox": {"scenario": "complete", "modifiers": [], "speed": "realtime"}
}
```

Step 5 lists one `invoice` (`sandbox_provider_invoice.pdf`, `metadata.provider_fee_amount: 85`) and one `records` document (`sandbox_medical_records.pdf`, 12 pages, `record_types: ["medical_records","billings"]`). `GET /v2/retrieval-requests/{id}/documents/{document_id}/download` returns JSON `{"download_url","expires_in"}` (presigned, 3600 s); fetch `download_url` to get the watermarked PDF.

Only the `sandbox` object on retrieval requests and the `X-Tavrn-Sandbox-*` headers are sandbox-specific; everything else is the production response shape. The same requests work with the [JavaScript and Python snippets in Authentication](/guides/v2/authentication#making-authenticated-requests) after swapping the host and key.

## Scenarios

A retrieval request follows a scripted lifecycle chosen at creation, in this order of precedence:

| How | Example |
| - | - |
| `X-Tavrn-Sandbox-Scenario` header | `X-Tavrn-Sandbox-Scenario: action_required_payment,slow` |
| `sandbox:<scenario>` token in `notes` | `"notes": "sandbox:no_records"` |
| Fixture facility name (table below) | `"facility": {"name": "Sandbox Contact Missing Clinic", ...}` |
| Patient `date_of_birth` `1900-01-01` | selects `no_records` |
| Default | `complete` |

The header and notes token accept a scenario plus comma-separated modifiers. The chosen scenario is echoed back in `sandbox.scenario` / `sandbox.modifiers` on every `GET /v2/retrieval-requests/{id}`.

| Scenario | Lifecycle | Ends in | `costs.provider_fees` |
| - | - | - | - |
| `complete` | all common stages → `invoice_received` (6:00) → `completed` (8:00) | `completed`, 1 invoice + 1 records PDF | 85 |
| `action_required_payment` (alias `payment_needed`) | common stages → `action_required` (5:00) with `action_required_reason: payment_needed` (invoice 185 > the requestor's 100 limit). After your `action-response`: `invoice_received` (+1:00) → `completed` (+3:00) | `completed` | 185 |
| `contact_missing` (alias `action_required_contact_missing`) | `submitted` → `locating_contact` → `action_required` (3:00) asking for a contact. After your `action-response`: `contact_found` (+0:30) … `completed` (+7:00) | `completed` | 85 |
| `request_issue` (alias `action_required_request_issue`) | common stages up to `confirming_receipt` → `action_required` (4:30), provider rejected the form. After your `action-response`: `request_sent` (+0:30) … `completed` (+6:30) | `completed` | 85 |
| `no_records` | common stages → `completed` (6:00) with a no-records letter, no invoice | `completed` | 0 |
| `stalled` | common stages → `acknowledged` (4:00), then nothing until you `advance` or cancel | `acknowledged` | 0 |

Common stages and their offsets from creation (real time): `submitted` 0:00 → `locating_contact` 0:45 → `contact_found` 1:30 → `request_sent` 2:00 → `confirming_receipt` 3:00 → `acknowledged` 4:00. The full stage vocabulary (`submitted`, `locating_contact`, `contact_found`, `request_sent`, `confirming_receipt`, `acknowledged`, `invoice_received`, `action_required`, `completed`, `cancelled`) and the `status` values are the production ones documented in the [Retrieval guide](/guides/v2/retrieval-guide).

| Modifier | Effect |
| - | - |
| `slow` | Every delay × 6 (`complete` takes 48 minutes) |
| `invoice_pending_download` | The invoice document is listed at `invoice_received` but its `download` returns `404 resource_not_found` for the next 60 seconds (production invoices can lag the event) |

Answer an `action_required` stage exactly as in production: `POST /v2/retrieval-requests/{id}/action-response` with `{"message": "..."}` returns `200 {"status":"responded", ...}` and the lifecycle resumes.

### Speed and advance

| Control | Effect |
| - | - |
| `X-Tavrn-Sandbox-Speed: fast` on create | All delays ÷ 20 (`complete` in about 24 seconds) |
| `X-Tavrn-Sandbox-Speed: frozen` on create | No automatic progress; move it with `advance` |
| `POST /v2/retrieval-requests/{id}/advance` | Run the lifecycle forward now. Body `{"to_stage": "completed"}` (any stage in the scenario) or empty for one stage. Returns the retrieval request. Sandbox only; `404` on production |

```bash theme={null}
curl -s -X POST $B/v2/retrieval-requests/$RR/advance \
  -H "Authorization: Bearer $TAVRN_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"to_stage":"completed"}' | jq '{stage, status, costs}'
```

Webhook events for the stages passed are emitted as the request advances.

### Fixture facilities and patients

Any facility name is accepted; these six carry a default scenario. All of them are fictional (`555` phone numbers, `example.com`).

| Facility | State | Default scenario |
| - | - | - |
| `Sandbox General Hospital` | CA | `complete` |
| `Sandbox Payment Needed Clinic` | TX | `action_required_payment` |
| `Sandbox Contact Missing Clinic` | NY | `contact_missing` |
| `Sandbox Request Issue Clinic` | FL | `request_issue` |
| `Sandbox Imaging Center` | IL | `complete` (imaging) |
| `Sandbox Billing Office` | WA | `complete` (billing records) |

Synthetic patients: `Alex Sandbox` (1990-01-01), `Jordan Sandbox` (1985-06-15), `Sam Noexist` (1900-01-01, always `no_records`). Never send real patient data to the sandbox.

`GET /v2/facilities` searches only these fixtures.

## Limits

| Limit | Value | When exceeded |
| - | - | - |
| Requests per key | 500 | `429 sandbox_cap_reached` (below) |
| Retrieval requests | 25 | `409 sandbox_limit_reached` |
| Webhook endpoints | 3 | `409 sandbox_limit_reached` |
| Uploads | 20 | `409 sandbox_limit_reached` |
| Rate limit | 300 requests / minute | `429 rate_limited` with `RateLimit-*` headers and `Retry-After` |
| Sandbox lifetime | 14 days from sign-up, extendable once by 14 days on the key page | `401 sandbox_key_expired` |

Every authenticated response carries:

```http theme={null}
Tavrn-Environment: sandbox
X-Tavrn-Sandbox-Cap: 500
X-Tavrn-Sandbox-Remaining: 499
X-Tavrn-Sandbox-Expires-At: 2026-10-31T05:37:33.945Z
Link: <https://www.tavrn.ai/demo>; rel="help"; title="Book a 30-minute demo to get a production account", <https://docs.tavrn.ai/guides/v2/sandbox#request-cap>; rel="describedby"
Cache-Control: no-store
```

With 50 or fewer requests left, `X-Tavrn-Sandbox-Notice` is added. Production sends none of these headers.

### Request cap

After the 500th request every call returns `429` with the standard error envelope plus a `sandbox` object:

```json theme={null}
{
  "error": {
    "code": "sandbox_cap_reached",
    "message": "This sandbox key has used all 500 requests. Book a demo to get a production account and keep this sandbox as your test environment: https://www.tavrn.ai/demo",
    "request_id": "req_..."
  },
  "sandbox": {
    "request_cap": 500,
    "requests_used": 500,
    "expires_at": "2026-10-31T05:37:33.945Z",
    "demo_url": "https://www.tavrn.ai/demo",
    "booking_api": "https://www.tavrn.ai/api/agents/demo",
    "fallback_booking_url": "https://tavrn.chilipiper.com/shared/intro-call-30-min-4726d531-25ac-4774-9d99-c13fa8105fc7",
    "docs_url": "https://docs.tavrn.ai/guides/v2/sandbox",
    "usage_token": "<signed JWT describing this sandbox's usage>"
  }
}
```

The cap is per key for the life of the sandbox; rotating the key does not reset it. Keep hammering a capped key and it is locked (`403 sandbox_key_locked`). `sandbox_*` and `wrong_environment` codes exist only on the sandbox; the [Errors](/guides/v2/errors) page lists the production vocabulary.

### Coverage

Every operation in the [OpenAPI contract](https://docs.tavrn.ai/api-reference/v2/openapi.json) is implemented on the sandbox; an operation that is documented but not yet implemented would return `501 not_implemented`. The only addition is `advance` above.

## Webhooks

[Webhooks](/guides/v2/webhooks) work as in production: same events, same payloads, same `X-Tavrn-Timestamp` / `X-Tavrn-Signature-V1` HMAC-SHA256 signature, same `POST /v2/webhooks/{id}/test`. Differences:

| | Sandbox | Production |
| - | - | - |
| Endpoint URL | `http://` or `https://` (local tunnels work) | `https://` only |
| Retry ladder after a failed attempt | +10 s, +30 s, +2 min, +5 min, +10 min (6 attempts, about 18 minutes) | 1 min … 28 h (9 attempts, about 72 hours) |
| Delivery timeout | 5 s, no redirects | same |
| Automatic disable | after 5 consecutive failed events (`webhook_endpoint.disabled`) | same |

Signature verification code written against the sandbox runs unchanged in production.

## Going to production

Production keys are issued to customer organizations: [book a demo](https://www.tavrn.ai/demo), and after onboarding an organization admin creates `tavrn_sk_live_` keys in the [Tavrn Platform](https://platform.tavrn.ai/dashboard/api) ([Account and API key](/guides/v2/get-started/account)).

| Changes | Stays the same |
| - | - |
| Host `enterprise.tavrn.ai`, key prefix `tavrn_sk_live_` | Every path, request and response schema, scope, and error envelope |
| Real facilities, real turnaround, provider invoices under your approval limit | Webhook events, payloads and signing |
| Your organization's rate limits; no sandbox caps or `X-Tavrn-Sandbox-*` headers | Your client code, apart from host and key |
| No `X-Tavrn-Sandbox-Scenario`, `X-Tavrn-Sandbox-Speed` or `advance` | |

Keep the sandbox after you go live; it stays your test environment.

## For AI agents

Sign-up is deliberately human-gated: a work email, a bot check and acceptance of the sandbox terms. An agent cannot create a sandbox on its own. Ask your operator to sign up at [sandbox.tavrn.ai/sign-up](https://sandbox.tavrn.ai/sign-up) and hand you the `tavrn_sk_test_` key; from there every call on this page is scriptable, and the key page's **Rotate key** is the only other step that needs the operator.

Machine-readable entry points:

| Resource | URL |
| - | - |
| OpenAPI 3 contract (production + sandbox in `servers`) | `https://docs.tavrn.ai/api-reference/v2/openapi.json` |
| OpenAPI as served by the sandbox host, including `advance` | `https://sandbox.tavrn.ai/openapi.json` |
| Docs index for LLMs | `https://docs.tavrn.ai/llms.txt`, `https://sandbox.tavrn.ai/llms.txt` |
| Liveness and remaining quota | `GET https://sandbox.tavrn.ai/v2/ping` |

Production access is a human decision too: the `429` cap body and the `Link: rel="help"` header point at the demo booking page for your operator.
