# Sandbox and testing

> Test your integration end to end with robo drivers, no real SMS and directives for every failure path.

Source: https://docs.delivr.no/sandbox/

The sandbox is a separate Delivr environment to build and test against. It behaves like production, with three differences:

- **No real SMS.** Recipient messages are recorded, not sent. Use any valid phone number of the market.
- **Robo drivers.** Test drivers deliver your on-demand orders in about two minutes.
- **Test data only.** Never send real customers' names, phones or addresses.

|                |                                                                                 |
| -------------- | ------------------------------------------------------------------------------- |
| Base URL       | `https://driver-staging-api.delivr.no`                                          |
| Tracking pages | `https://tracking-stage.delivr.no`                                              |
| Keys           | Sandbox keys (`dmk_…`) from your Delivr contact. They never work in production. |

## Getting set up

1. Ask your Delivr contact for a sandbox merchant and API key. Give them your webhook URL if you want webhooks; it must be public HTTPS. For local development, use a tunnel such as `cloudflared` or `ngrok`.
2. Add your stores with [`POST /v1/merchant/locations`](/guides/locations/). Robo drivers park at a new store within a minute.
3. Check your webhook endpoint with `POST /v1/merchant/webhooks/test`, then `GET /v1/merchant/webhooks/deliveries` to see what your server answered.

## Robo drivers

Every store in the sandbox has robo drivers parked outside, online. Because they are nearby, quotes for your store offer `on_demand`, and when an order's goods are released one of them accepts it.

A robo driver behaves like a real driver using the Delivr driver app:

| Step                                   | Takes                                 | Status and webhook                                    |
| -------------------------------------- | ------------------------------------- | ----------------------------------------------------- |
| Accepts the offer                      | A few seconds after the search starts | `driver_assigned`, `order.driver_assigned`            |
| Drives to the store                    | About 45 seconds                      |                                                       |
| Arrives and loads, with a pickup photo | 5 to 20 seconds                       | `picked_up`, `order.picked_up`                        |
| Drives to the recipient                | About 45 seconds                      |                                                       |
| Delivers, with a drop-off photo        |                                       | `delivered`, `order.delivered` with `proofOfDelivery` |

The recipient's tracking page shows the robo driver moving on the map, just as a shopper would see a real one.

<Aside>
  Robo drivers only take Merchant API orders. Up to two park at each store, so a third order placed while both are busy
  may get no `on_demand` option until one is back. They return to the store after each delivery.
</Aside>

## Testing failure paths with directives

Real drivers read `pickup.note`. Robo drivers do too, and they follow **directives** written there. Put one anywhere in the note of the order you place:

```json {5}
{
  "quoteId": "…",
  "option": "on_demand",
  "externalRef": "test-no-driver-1",
  "pickup": { "note": "sandbox:no_driver" },
  "delivery": { "…": "…" }
}
```

| Directive                | What the robo driver does                                                                   | What you can test                                                                                                    |
| ------------------------ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| _(none)_                 | Accepts and delivers.                                                                       | The happy path and every status webhook.                                                                             |
| `sandbox:no_driver`      | Every robo driver declines, and the search ends at once instead of after 15 minutes.        | `failed` with reason `no_driver`, and your [fallback policy](/guides/fallbacks/).                                    |
| `sandbox:hand_back`      | The first driver accepts, sets off and gives the job back. Another driver then delivers it. | A driver handing a job back. For [assisted checkout](/guides/assisted-checkout/), `sale.driver` shows `reassigning`. |
| `sandbox:stop_en_route`  | Accepts and stays on the way to the store.                                                  | Cancelling at the `driver_en_route` fee.                                                                             |
| `sandbox:stop_at_pickup` | Accepts, arrives at the store and waits there.                                              | Cancelling at the `driver_arrived` fee, and goods that are not ready when the driver arrives.                        |

Directives are not case-sensitive and can sit next to normal text, like `"Ask for Kari. sandbox:stop_at_pickup"`. A store's `pickupInstructions` work too, which is handy for a store you only use to test one path. In production, directives are ordinary text that drivers ignore.

A held driver (`stop_*`) gives the job back after an hour if you do nothing, and the order then searches again.

## More things to test

| Scenario          | How                                                                                                                                                                                                                                                      |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Goods not ready   | Set your store's `readySignal` to `required` and send no `ready` or `processing`. You get `order.ready_overdue` at `readyBy`, then the order fails with `goods_not_ready` after the grace period. Send a `readyBy` a few minutes ahead to make it quick. |
| Packing time      | Send `POST /orders/{id}/processing {readyAt}` a few minutes ahead. The driver search starts so that the driver arrives at about `readyAt`.                                                                                                               |
| Price changes     | Place an order on a quote older than five minutes. You get either the order at no more than the quoted price, or `409 price_changed`.                                                                                                                    |
| Free cancellation | Cancel before a driver accepts, with `maxFeeMinor: 0`.                                                                                                                                                                                                   |
| Webhook retries   | Answer `500` from your endpoint, then look at `GET /v1/merchant/webhooks/deliveries` for the retry schedule. Replay an event with `POST /v1/merchant/webhooks/events/{id}/replay`.                                                                       |
| Assisted checkout | Place with `sale: "pending"`, poll until `sale.driver` is `assigned`, then complete with `POST /orders/{id}/sale`.                                                                                                                                       |

## Window orders

Window orders can be quoted, placed, rescheduled and cancelled in the sandbox. Robo drivers do not run window routes, so a window order usually stays `scheduled`. If you need to see `picked_up` and `delivered` on a window order, ask your Delivr contact to run one for you.

## Limits and data

- The same [rate limits](/conventions/rate-limits/) apply as in production.
- The sandbox is shared by every integrator, and Delivr may reset its data. Do not depend on old orders being there.
- Prices come from a sandbox agreement and are not your production prices.