---
title: Sandbox quickstart
description: Get a sandbox organization pre-seeded with fake FHIR data from a Huli admin, receive your bearer credential through a one-time link, and make your first authenticated call.
nav: Recipes
order: 5
version: v1
source: handwritten
updated: 2026-07-08
---

# Sandbox quickstart

Most recipes in this section assume you already have a Huli organization and an admin
who can mint you a bearer token in Practice Settings. A **sandbox** is the same idea,
pointed at synthetic data: a dedicated organization pre-seeded with fabricated
patients, appointments, encounters, and observations, so you can build and test your
integration before it touches anything that looks like production.

A sandbox is **created for you by someone with a Huli account** — an org admin at the
clinic you're integrating with, or your Huli contact. There is no anonymous self-serve
provisioning endpoint: the person creating the sandbox does it from inside
HuliPractice, and hands you the credential through a one-time link. Your side of the
flow needs nothing but that link and an HTTP client.

## Audience

You're an external developer evaluating the Huli Public FHIR API, building a proof
of concept, or developing an integration that isn't ready for real patient data. You
have a contact at Huli or at a clinic who can create the sandbox for you.

## You'll need

- Someone with an **admin-role HuliPractice login** willing to create the sandbox —
  see [the admin's side](#sandbox-quickstart.the-admins-side-creating-a-sandbox) below, which you can
  forward to them verbatim.
- The **one-time share link** they send you after creating it.
- `curl` (or any HTTP client). The [`huli` CLI](/v1/cli) works too — the credential is
  a plain bearer token, so `--token` is all it needs.

## End state

A dedicated sandbox organization seeded with fabricated clinical data; a bearer API
key bound to that org and to nothing else, valid for **14 days**; and one successful
`GET` against the same FHIR R4 surface every production integration uses.

## The admin's side: creating a sandbox

If you're the org admin or Huli operator creating the sandbox for a developer: a
sandbox is always attached to **one production API key**, from **Practice Settings →
Integrations**.

1. Creating a **new** integration? Leave **"Generar un entorno de pruebas
   (recomendado)"** checked in the create wizard — the production key and its paired
   sandbox are minted together and disclosed in one share link.
   For an **existing** key, open the key row's menu and choose **"Gestionar entorno
   de pruebas"** (Manage sandbox), then create the sandbox from there.
2. The sandbox's identity is derived from the production key itself — there is no
   email or name to fill in. Managing the sandbox again for the same key reuses the
   existing sandbox organization (and its data); refreshing just mints a fresh key —
   it never creates a duplicate org.
3. On success you get a **one-time share link** (valid for **24 hours**, single use)
   containing the developer's bearer credential. Send that link — not a pasted token —
   to the developer over a reasonably private channel. The secret itself is only ever
   revealed on the share page, exactly once.

The sandbox organization is created and seeded at that moment — fabricated patients,
practitioners, appointments, encounters, and observations, referentially consistent,
zero real data. The minted key is bound to the sandbox org and can never reach any
other organization's data: the credential _is_ the boundary.

## The developer's side

### 1. Open the share link and capture the token

The link your contact sends looks like `https://<practice-host>/api-keys/share/…`. It
works **once**, then self-destructs; the same page shows the API base URL. Store the
bearer token in a secrets manager or environment variable immediately — nobody,
including the admin who created it, can view it again. If you lose it, ask your
contact to refresh the sandbox from the key's **Gestionar entorno de pruebas** dialog:
you'll get a new key on the same organization.

### 2. Make your first call

The sandbox key is an ordinary bearer credential on the same FHIR R4 surface as
production — no separate sandbox auth mode, no token-exchange handshake:

```bash
export HULI_SANDBOX_TOKEN="<the token from the share page>"

curl "https://api.huli.ai/fhir/R4/Patient?_count=5" \
  -H "Authorization: Bearer ${HULI_SANDBOX_TOKEN}" \
  -H "Accept: application/fhir+json"
```

A `200 OK` returns the same FHIR `Bundle` shape as [Start](/v1/start) — just backed by
fabricated data instead of a clinic's real records.

Or run it right here — paste your sandbox token into the [playground](/playground) token bar
above the recipes nav (or on this page's Run button, the first time you use one) and press Run:

<RunnableRequest method="GET" path="/fhir/R4/Patient?_count=5" recipe="sandbox-quickstart" />

With the [`huli` CLI](/v1/cli), pass the token directly:

```bash
huli --token "${HULI_SANDBOX_TOKEN}" fhir patient search --count 5
```

## The sandbox indicator

There's no request-level "sandbox mode" flag to remember or forget — the mode lives in
the credential. A sandbox key can only ever resolve to its own sandbox org, so there's
no header or query param that could accidentally point it at real data. Every response
served by a sandbox key carries two identifying signals — one on the transport, one
inside the payload:

```http
X-Huli-Mode: sandbox
```

```json
{
  "resourceType": "Patient",
  "meta": {
    "tag": [
      {
        "system": "https://huli.io/tags",
        "code": "sandbox",
        "display": "Synthetic sandbox data"
      }
    ]
  }
}
```

The `meta.tag` is stamped on **every** resource a sandbox key reads or writes — single
resources, search `Bundle`s, and each Bundle entry alike — so even data copied out of a
response (into a fixture, a demo, a bug report) stays self-identifying as synthetic.
Use both as belt-and-suspenders checks in your own logs or tests: neither should ever
appear on a response your production key receives, and both should always appear on a
sandbox key's responses.

## Limits and expiry

Sandbox keys carry ceilings a production key doesn't:

| Ceiling               | Default         | Enforcement                                                                   |
| --------------------- | --------------- | ----------------------------------------------------------------------------- |
| Rate limit            | 60 req/min      | Same 1-minute sliding window as every other key.                              |
| Volume cap (lifetime) | 10,000 requests | Counted per request; never resets on its own.                                 |
| Key expiry            | 14 days         | Stamped at minting; an expired key is rejected. The org and its data persist. |
| Keys per sandbox      | 5               | Each sandbox-key refresh mints a new key on the same org, up to this cap.     |

<Callout variant="warning">
The volume cap is a lifetime counter, not a per-minute rate — it does not reset on a
timer the way the request-rate limit does. Once you hit it, every further request on
that key returns <StatusBadge code="429" /> `HPB-00121`. Resetting a sandbox key's
volume counter and extending a key's expiry are **operator-only** actions — reach out
to your Huli contact if you need a clean run or more time.
</Callout>

Only the **credential** expires — the sandbox organization and its seeded data are
never deleted. When a key lapses (or you simply want a fresh one), your contact opens
the production key's **Gestionar entorno de pruebas** dialog and refreshes the sandbox
key: same org, same data, new key, new one-time link.

## What to verify

- The share link opened exactly once and showed a bearer token plus the base URL.
- `GET /fhir/R4/Patient?_count=5` with the token returns a `200` with a `Bundle`, not
  an `OperationOutcome`.
- The response headers include `X-Huli-Mode: sandbox`, and each resource carries the
  `meta.tag` with `system: "https://huli.io/tags"` and `code: "sandbox"`.

## What can go wrong

<StatusBadge code="401" /> — the token is wrong, or the key **expired** (14 days after
minting). Ask your contact to refresh the sandbox key from the production key's
Manage-sandbox dialog — you'll get a fresh key on the same organization.

<StatusBadge code="429" /> `HPB-00105` — the per-key request-rate limit (see
[Rate Limiting](/v1/concepts/rate-limiting)); read `Retry-After`.

<StatusBadge code="429" /> `HPB-00121` — the lifetime volume cap is exhausted, not the
per-minute rate. `Retry-After` will not help here; resetting a sandbox key's volume
counter is operator-only — reach out to your Huli contact.

**The share link says it was already used or expired** — share links are single-use
with a 24-hour window. Ask your contact to refresh the sandbox key from the production
key's Manage-sandbox dialog; a new key and a new link are minted, and the existing org
and data are reused.

## Graduating to production

When your integration is ready for real patient data, the path is the standard partner
one: the clinic's admin mints you a **production** key from the same Practice Settings
surface — see [Creating and sharing an API key](/v1/recipes/creating-and-sharing-an-api-key)
— or your Huli contact walks you through the commercial onboarding. Your sandbox keeps
working alongside it; it's a separate organization, so nothing you built against it
needs to change.

## Next recipes

- **[Run your first authenticated Patient search](/v1/recipes/getting-started-patient-search)**
  — the same request shape, against a real (non-sandbox) organization.
- **[Creating and sharing an API key as a clinic admin](/v1/recipes/creating-and-sharing-an-api-key)**
  — the production-key flow this sandbox flow mirrors.
- **[Rate Limiting](/v1/concepts/rate-limiting)** — the per-key/per-org limits every
  key has, sandbox or not.
