# Quotes and orders

> Price the delivery options for an address, then book the one your shopper picks.

Source: https://docs.delivr.no/guides/quotes-and-orders/

Booking a delivery takes two calls: a quote prices the options, and an order books one of them.

## 1. Show Delivr in the cart (optional)

Before the shopper has entered their whole address, ask [`GET /v1/merchant/availability`](/api/operations/getavailability/) for a cheap hint by postal code:

```bash title="Request"
curl "https://driver-staging-api.delivr.no/v1/merchant/availability?pickupExternalStoreId=oslo-sentrum&postalCode=0026" \
  -H "Authorization: Bearer $DELIVR_API_KEY"
```

```json title="Response · 200"
{
  "postalCode": "0026",
  "onDemand": { "available": true, "eta": { "fromMinutes": 35, "toMinutes": 60 } },
  "windows": { "available": true },
  "priceFrom": { "amountMinor": 9900, "vatMinor": 1980, "exVatMinor": 7920, "currency": "NOK" },
  "timeZone": "Europe/Oslo",
  "checkedAt": "2026-11-03T13:00:00Z"
}
```

Use it for messages like "Delivered today from NOK 99". It promises nothing and is safe to cache for 60 seconds.

## 2. Quote

[`POST /v1/merchant/quotes`](/api/operations/createquote/) prices every option from a store to an address.

| Field                                         | Required    | Notes                                                                                                                                                           |
| --------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pickupLocationId` or `pickupExternalStoreId` | One of them | The store.                                                                                                                                                      |
| `delivery.address`                            | Yes         | `street`, `postalCode`, `city`; optional `line2` (apartment, such as `H0101`) and coordinates.                                                                  |
| `delivery.contact`                            | No          | The recipient. Required later, on the order.                                                                                                                    |
| `parcels`                                     | Yes         | Groups of identical parcels: `count`, `weightKg`, and all three dimensions or none (none uses the store's `defaultParcel`; without one, `422 invalid_request`). |
| `items`                                       | No          | Products bought (`title`, `quantity`, optional `imageUrl`). Shown to the recipient and driver, never priced.                                                    |
| `readyBy`                                     | No          | When the goods are ready at the latest, within 24 hours.                                                                                                        |

The answer lists `options`, with `on_demand` first when there is driver capacity, then windows in time order:

- **On demand** has an `eta` band: when the recipient can expect the delivery.
- **Windows** have a `window` and a `cutoffAt`. Book and cancel for free before the cutoff.
- Every option has a stable `key`, such as `on_demand` or `window:2026-11-03T17:00:00+01:00/2026-11-03T19:00:00+01:00`. The same delivery quoted again gives the same keys, so you can store a key with the shopper's cart.
- `price` is what Delivr bills you, VAT included. You decide what the shopper pays.

`options` may be empty, for example far outside Delivr's area. If nothing at all can be offered you get `422 no_delivery_options`. See [Showing options at checkout](/guides/checkout-options/) for how to present them.

## 3. Order

When the shopper pays, [`POST /v1/merchant/orders`](/api/operations/createorder/) with:

| Field              | Notes                                                                                                                                                        |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `quoteId`          | The quote.                                                                                                                                                   |
| `option`           | The `key` of the option chosen.                                                                                                                              |
| `externalRef`      | Your order id, unique among your live orders.                                                                                                                |
| `delivery`         | `address`, **`contact`** (the recipient's name and phone, required) and optional `dropoff` (`instructions`, `doorCode`, `floor`, `entrance`, `leaveAtDoor`). |
| `pickup.note`      | Optional note for the driver at the store, for this order only.                                                                                              |
| `parcels`, `items` | Optional. Replace the quote's.                                                                                                                               |
| `readyBy`          | Optional, on demand. When the goods are due.                                                                                                                 |
| `onWindowFull`     | Optional, windows. `reject` (default) or `reschedule`. See below.                                                                                            |

The answer is `201` with the order. Its `id` equals the `quoteId`, and `reference` (`DD-…`) is the short id for people. The placement answer also carries `eta` (on demand) or `rescheduled` (window); these two fields appear only there, so store them if you need them.

### Placing on an older quote

A quote is valid for **5 minutes** (`expiresAt`), plus the market's quote grace when you place (10 minutes by default). After that, or when the address, the parcels or the store changed since the quote, Delivr quotes again behind the scenes and books the same key at **no more than the quoted price**.

| Answer                   | What happened                                                                                             | What to do                          |
| ------------------------ | --------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `201`                    | Booked, at or below the quoted price.                                                                     | Nothing.                            |
| `409 price_changed`      | It would cost more now. `details.option` has the new price.                                               | Show the new price, or quote again. |
| `409 option_unavailable` | The option is gone, for example no driver capacity. `details.alternatives` lists what is offered instead. | Offer an alternative.               |
| `409 window_full`        | The window filled up. `details.alternatives` has the nearest free window.                                 | Offer it, or set `onWindowFull`.    |

With `"onWindowFull": "reschedule"`, a full window books the nearest free one instead (no dearer than quoted). The answer has `rescheduled: true`, and you get an `order.window_changed` webhook.

### Booking without a quote

If your shopper does not pick between options, you can skip the quote: send the store (`pickupLocationId` or `pickupExternalStoreId`), `parcels` and the option key, with no `quoteId`. The order is quoted and booked in one call.

### Retries

Placing is idempotent twice over: by the `Idempotency-Key`, and by `quoteId` (or `externalRef` without a quote). A retry answers the order already placed instead of booking a second delivery.

- A repeat on the same quote answers the order already placed, **even if it names another option**. To book another option, quote again.
- The same quote placed under another `externalRef` answers `409 quote_already_used`.

## Who sees what

| Data                                      | Driver | Recipient (SMS, tracking) | Webhooks |
| ----------------------------------------- | ------ | ------------------------- | -------- |
| `items`                                   | Yes    | Yes                       | No       |
| `delivery.dropoff.doorCode`               | Yes    | No                        | No       |
| `pickup.note`, store `pickupInstructions` | Yes    | No                        | No       |