# Testing in the sandbox

> Rehearse every gift outcome without sending a real gift.


The sandbox is a full copy of the API running against its own database. Orders you place there create real orders and real gifts, debit a real balance and return the same responses production does — but no card is charged, no message is sent, and no recipient is reached.

You reach it with a sandbox key. There is one base URL for both environments and your key decides which one serves the request, so moving an integration to production means swapping the key and changing nothing else.

> **Info: Getting a sandbox key** Switch the business portal to **Sandbox** mode, then create a key under **Account → Developers**. Sandbox keys are prefixed `omk-sandbox-`; live keys are prefixed `omk-`.

## Choosing what happens to a gift

In production, what becomes of a gift is up to its recipient: they open it, or they don't; the delivery lands, or it bounces. You cannot rehearse any of that against a real person.

So in the sandbox the **recipient address chooses the outcome**. Send a gift to one of these and you get that result immediately.

<SandboxTriggers />

The rest of your request is unchanged — same body, same headers, same code path. Only the recipient differs, which means the integration you test is the integration you ship.

```bash
curl https://api.onme.com/v1/orders \
  -H "Authorization: Bearer omk-sandbox-..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "gifts": [{
      "category": "coffee",
      "amount": 2500,
      "delivery": { "method": "email", "email": "delivery-failed@sandbox.onme.com" }
    }]
  }'
```

That order comes back `processed` with one gift whose `delivery.status` is `failed` — the state you would otherwise have to wait for a real bounce to see, and the one that unlocks `PUT /v1/gifts/:id/delivery`.

## Driving a gift that is already created

Triggers fire at order time. To move a gift that already exists — or one delivered by link, which has no recipient address to carry a trigger — use the sandbox endpoints. Open, claim and spend work on any gift in your sandbox. Deliver works only on gifts On Me sends by email or phone; a link gift returns `gift_delivery_not_simulatable`.

| Endpoint | What it does |
| --- | --- |
| `POST /v1/sandbox/gifts/:id/deliver` | Marks delivery delivered or failed |
| `POST /v1/sandbox/gifts/:id/open` | Marks the gift opened |
| `POST /v1/sandbox/gifts/:id/claim` | Claims it onto a card |
| `POST /v1/sandbox/gifts/:id/spend` | Records a first spend |

Order is enforced the way it is in production: a spend before a claim returns `gift_not_claimed`, and claiming twice returns `gift_already_redeemed`. Claiming also back-fills the opened timestamp, because a gift that was claimed without being opened is a state production never produces.

They are in the [API reference](https://business.onme.com/docs/reference) alongside everything else, tagged **Sandbox** and badged _Sandbox only_.

> **Warning: These answer only for a sandbox key** `/v1/sandbox/...` is documented because it shares a base URL with the rest of the API, not because it exists in production. A live key on one of these paths gets `403 sandbox_endpoint_only`. Code that calls them is test scaffolding by construction — which is exactly why they are namespaced rather than mixed into `/v1/gifts`.

## Testing an order that takes a while

Most orders finish before the response returns. Occasionally one does not: the order is paid for, some gifts are still being created, and the response says `processing`. Your integration has to poll until it settles.

Send to `processing@sandbox.onme.com` and the sandbox reproduces that: the order reports `processing` with no gifts for about twenty seconds, then completes on its own. It finishes by itself rather than waiting for you to nudge it, so the loop you write here is the loop that runs in production.

## Testing failures

Two triggers fail the whole order rather than the gift:

- `insufficient-funds@sandbox.onme.com` returns `402 insufficient_balance` whatever your balance is
- `order-error@sandbox.onme.com` returns `500 api_error`, for exercising retry handling

Both are rejected before anything is created, so there is no order and no debit to clean up.

## Starting over

**Account → Developers** has a reset control in sandbox mode. It deletes every sandbox order, gift and balance for your organization, including the balance ledger. Your API keys survive, and you will need to add funds again before you can order.

Reach for it when your sandbox has drifted into a state you can no longer account for. Everything in it is reproducible — a few calls recreate your fixtures — so starting clean is usually quicker than working out how it got there.
