---
title: "Create an order"
description: "Unipage creates the customer, validates everything again (slot and\ncapacity, products orderable and in stock, option rules, delivery zone\nand minimum, coupon) and places the order. An invalid coupon is a 422\n`COUPON_INVALID`, never silently dropped.\n\n`paymentType` picks who collects the money (see\n[Payments](/storefront/payments/)):\n\n- `online`: Unipage, with the restaurant's payment provider. The order is\n  `PENDING_PAYMENT` and `next.type` is `payment`: send the shopper's\n  browser to `next.url`. After the payment they are sent to\n  `returnUrl` with `?status=success` (or `?status=failed` for a failed\n  or cancelled payment). Read the outcome from `GET /orders/{id}`\n  (`isPaid`, `status`), never from the query string.\n- `offline`: the restaurant, at pickup or on delivery. The order is\n  `PLACED` and unpaid.\n- `external`: you, with your own payment provider, before you place\n  the order. The order is `PLACED` and paid at once.\n\nWith `customerNotifications: false`, Unipage sends the customer no\nmail, SMS or push message for this order, now or later: you send your\nown. See [Customer messages](/storefront/customer-messages/).\n"
---

> Documentation Index
> Fetch the complete documentation index at: https://developer.unipage.be/llms.txt
> Use this file to discover all available pages before exploring further.

Path: Storefront API › Orders

`POST /orders`

Unipage creates the customer, validates everything again (slot and
capacity, products orderable and in stock, option rules, delivery zone
and minimum, coupon) and places the order. An invalid coupon is a 422
`COUPON_INVALID`, never silently dropped.

`paymentType` picks who collects the money (see
[Payments](/storefront/payments/)):

- `online`: Unipage, with the restaurant's payment provider. The order is
  `PENDING_PAYMENT` and `next.type` is `payment`: send the shopper's
  browser to `next.url`. After the payment they are sent to
  `returnUrl` with `?status=success` (or `?status=failed` for a failed
  or cancelled payment). Read the outcome from `GET /orders/{id}`
  (`isPaid`, `status`), never from the query string.
- `offline`: the restaurant, at pickup or on delivery. The order is
  `PLACED` and unpaid.
- `external`: you, with your own payment provider, before you place
  the order. The order is `PLACED` and paid at once.

With `customerNotifications: false`, Unipage sends the customer no
mail, SMS or push message for this order, now or later: you send your
own. See [Customer messages](/storefront/customer-messages/).

## Authentication

- `secretKey`, http, header `Authorization`

## Request body

_Required._

- `orderType` (`createOrder.orderType`; string, required)
  - one of `"TAKEOUT"`, `"DELIVERY"`
- `slotAt` (`createOrder.slotAt`; integer, required) — Unix seconds of the chosen slot, from the schedule.
- `lines` (`createOrder.lines`; array<object>, required)
  - `productId` (`createOrder.lines.productId`; string, required)
  - `quantity` (`createOrder.lines.quantity`; integer, required)
    - min 1
  - `notes` (`createOrder.lines.notes`; string, optional)
    - maxLength 255
  - `options` (`createOrder.lines.options`; array<object>, optional)
    - `groupId` (`createOrder.lines.options.groupId`; string, required)
    - `optionId` (`createOrder.lines.options.optionId`; string, required)
    - `quantity` (`createOrder.lines.options.quantity`; integer, optional) — Only matters for `AMOUNT` groups.
      - default `1`
- `customer` (`createOrder.customer`; object, required) — Unipage finds or creates the restaurant's customer by e-mail address.
  - `firstName` (`createOrder.customer.firstName`; string, required)
    - maxLength 255
  - `lastName` (`createOrder.customer.lastName`; string, required)
    - maxLength 255
  - `email` (`createOrder.customer.email`; string, required)
    - format `email`; maxLength 255
  - `phone` (`createOrder.customer.phone`; string, required) — 6 to 20 digits, spaces and `+ ( ) . / -`.
- `paymentType` (`createOrder.paymentType`; string, required) — `online` or `offline`: one of the restaurant's `payment.methods`. `external`: you took the payment yourself; always accepted. See [Payments](/storefront/payments/).
  - one of `"online"`, `"offline"`, `"external"`
- `delivery` (`createOrder.delivery`; object, optional) — Required for `DELIVERY` (every field), ignored for `TAKEOUT`.
  - `zoneId` (`createOrder.delivery.zoneId`; string, optional) — A zone id from `GET /store`.
  - `street` (`createOrder.delivery.street`; string, optional)
    - maxLength 255
  - `number` (`createOrder.delivery.number`; string, optional)
    - maxLength 50
  - `postcode` (`createOrder.delivery.postcode`; string, optional)
    - maxLength 20
  - `city` (`createOrder.delivery.city`; string, optional)
    - maxLength 255
- `payment` (`createOrder.payment`; object, optional) — Only for `external`: where you took the payment, kept on the order for reconciliation.
  - `provider` (`createOrder.payment.provider`; string, optional)
    - maxLength 50
  - `reference` (`createOrder.payment.reference`; string, optional)
    - maxLength 255
- `customerNotifications` (`createOrder.customerNotifications`; boolean, optional) — `false`: Unipage sends the customer nothing for this order (no confirmation, status or review message, by mail, SMS or push), now or later. The restaurant's own notifications are not affected. See [Customer messages](/storefront/customer-messages/).
  - default `true`
- `notes` (`createOrder.notes`; string, optional) — Only shown to the restaurant when `notesEnabled` is true.
  - maxLength 1000
- `coupon` (`createOrder.coupon`; string | null, optional)
- `locale` (`createOrder.locale`; string, optional) — Default the restaurant language. Mails, SMS and the order `label` follow it.
  - one of `"nl"`, `"fr"`, `"en"`, `"de"`, `"es"`
- `returnUrl` (`createOrder.returnUrl`; string, optional) — Where the shopper lands after an online payment, with `?status=success` or `?status=failed` added. `{orderId}` is replaced by the order id. It must be on one of the origins of your storefront's public key or on a local origin (`localhost`, `127.0.0.1`, `[::1]`), otherwise 422 `VALIDATION` (`fields.returnUrl`). Without it, the shopper lands on the restaurant's own Unipage thank-you page.
  - maxLength 2000

## Example request

```json
{
  "orderType": "TAKEOUT",
  "slotAt": 1792750500,
  "lines": [
    {
      "productId": "p7d2x",
      "quantity": 1,
      "notes": "no salt",
      "options": [
        {
          "groupId": "g5t1r",
          "optionId": "o9w3e",
          "quantity": 1
        }
      ]
    }
  ],
  "customer": {
    "firstName": "Ann",
    "lastName": "Example",
    "email": "ann@example.com",
    "phone": "+32470000000"
  },
  "delivery": {
    "zoneId": "z81qm",
    "street": "Main Street",
    "number": "1",
    "postcode": "1000",
    "city": "Brussels"
  },
  "paymentType": "online",
  "payment": {
    "provider": "stripe",
    "reference": "pi_3Q8xYz"
  },
  "customerNotifications": true,
  "notes": "Ring twice",
  "coupon": "string",
  "locale": "nl",
  "returnUrl": "https://shop.example.com/order/{orderId}"
}
```

## Code samples

### cURL

```bash
curl --request POST \
  --url https://api.unipage.eu/storefront/v1/orders \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "orderType": "TAKEOUT",
  "slotAt": 1792750500,
  "lines": [
    {
      "productId": "p7d2x",
      "quantity": 1,
      "notes": "no salt",
      "options": [
        {
          "groupId": "g5t1r",
          "optionId": "o9w3e",
          "quantity": 1
        }
      ]
    }
  ],
  "customer": {
    "firstName": "Ann",
    "lastName": "Example",
    "email": "ann@example.com",
    "phone": "+32470000000"
  },
  "delivery": {
    "zoneId": "z81qm",
    "street": "Main Street",
    "number": "1",
    "postcode": "1000",
    "city": "Brussels"
  },
  "paymentType": "online",
  "payment": {
    "provider": "stripe",
    "reference": "pi_3Q8xYz"
  },
  "customerNotifications": true,
  "notes": "Ring twice",
  "coupon": "string",
  "locale": "nl",
  "returnUrl": "https://shop.example.com/order/{orderId}"
}
'
```

### TypeScript

```typescript
const url = 'https://api.unipage.eu/storefront/v1/orders';
const options = {
  method: 'POST',
  headers: {'Content-Type': 'application/json', Authorization: 'Bearer <token>'},
  body: JSON.stringify({
    orderType: 'TAKEOUT',
    slotAt: 1792750500,
    lines: [
      {
        productId: 'p7d2x',
        quantity: 1,
        notes: 'no salt',
        options: [{groupId: 'g5t1r', optionId: 'o9w3e', quantity: 1}]
      }
    ],
    customer: {
      firstName: 'Ann',
      lastName: 'Example',
      email: 'ann@example.com',
      phone: '+32470000000'
    },
    delivery: {
      zoneId: 'z81qm',
      street: 'Main Street',
      number: '1',
      postcode: '1000',
      city: 'Brussels'
    },
    paymentType: 'online',
    payment: {provider: 'stripe', reference: 'pi_3Q8xYz'},
    customerNotifications: true,
    notes: 'Ring twice',
    coupon: 'string',
    locale: 'nl',
    returnUrl: 'https://shop.example.com/order/{orderId}'
  })
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://api.unipage.eu/storefront/v1/orders"

payload = {
    "orderType": "TAKEOUT",
    "slotAt": 1792750500,
    "lines": [
        {
            "productId": "p7d2x",
            "quantity": 1,
            "notes": "no salt",
            "options": [
                {
                    "groupId": "g5t1r",
                    "optionId": "o9w3e",
                    "quantity": 1
                }
            ]
        }
    ],
    "customer": {
        "firstName": "Ann",
        "lastName": "Example",
        "email": "ann@example.com",
        "phone": "+32470000000"
    },
    "delivery": {
        "zoneId": "z81qm",
        "street": "Main Street",
        "number": "1",
        "postcode": "1000",
        "city": "Brussels"
    },
    "paymentType": "online",
    "payment": {
        "provider": "stripe",
        "reference": "pi_3Q8xYz"
    },
    "customerNotifications": True,
    "notes": "Ring twice",
    "coupon": "string",
    "locale": "nl",
    "returnUrl": "https://shop.example.com/order/{orderId}"
}
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer <token>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
```

## Responses

### 201

The order was placed.

#### Example

```json
{
  "order": {
    "id": "q4m8z",
    "status": "PLACED",
    "label": "Order received",
    "orderType": "TAKEOUT",
    "slotAt": 1792750500,
    "paymentType": "online",
    "isPaid": true,
    "customerNotifications": true,
    "lines": [
      {
        "productId": "p7d2x",
        "name": "Medium fries",
        "quantity": 1,
        "optionsLabel": "",
        "total": 4
      }
    ],
    "subtotal": 4,
    "deliveryCost": 0,
    "serviceCost": 0.5,
    "couponDiscount": 0,
    "total": 4.5,
    "currency": "EUR",
    "locale": "nl",
    "customer": {
      "firstName": "Ann",
      "lastName": "Example",
      "email": "ann@example.com"
    },
    "delivery": {
      "street": "string",
      "number": "string",
      "postcode": "string",
      "city": "string",
      "zone": "string"
    },
    "createdAt": 1792749000
  },
  "next": {
    "type": "payment",
    "url": "https://demo.unipage.eu/pay/mollie/x7k2p"
  }
}
```

- `order` (`createOrder.response.201.order`; object, optional)
  - `id` (`createOrder.response.201.order.id`; string, optional)
  - `status` (`createOrder.response.201.order.status`; string, optional) — - `PENDING_PAYMENT`: an `online` order, until the payment provider confirms. - `PAYMENT_FAILED`: the online payment failed or was cancelled. - `PLACED`: the restaurant received the order (`offline`, `external`, or a confirmed payment). - `CHEF_STARTED`: the restaurant started preparing it. - `CHEF_COMPLETED`: ready for pickup or out for delivery. - `DECLINED`, `CANCELED`: the restaurant refused or cancelled it. - `PENDING_PLACEMENT`: being placed; you will rarely see it. New statuses may be added: treat an unknown one as "in progress".
    - one of `"PENDING_PLACEMENT"`, `"PENDING_PAYMENT"`, `"PAYMENT_FAILED"`, `"PLACED"`, `"CHEF_STARTED"`, `"CHEF_COMPLETED"`, `"DECLINED"`, `"CANCELED"`
  - `label` (`createOrder.response.201.order.label`; string, optional) — The status in the order's locale.
  - `orderType` (`createOrder.response.201.order.orderType`; string, optional)
    - one of `"TAKEOUT"`, `"DELIVERY"`
  - `slotAt` (`createOrder.response.201.order.slotAt`; integer, optional)
  - `paymentType` (`createOrder.response.201.order.paymentType`; string, optional)
    - one of `"online"`, `"offline"`, `"external"`
  - `isPaid` (`createOrder.response.201.order.isPaid`; boolean, optional) — Read the payment outcome from this, never from the return URL.
  - `customerNotifications` (`createOrder.response.201.order.customerNotifications`; boolean, optional) — `false` when Unipage sends the customer nothing for this order.
  - `lines` (`createOrder.response.201.order.lines`; array<object>, optional) — Includes the restaurant's charges as lines with `productId: null`.
    - `productId` (`createOrder.response.201.order.lines.productId`; string | null, optional)
    - `name` (`createOrder.response.201.order.lines.name`; string, optional)
    - `quantity` (`createOrder.response.201.order.lines.quantity`; integer, optional)
    - `optionsLabel` (`createOrder.response.201.order.lines.optionsLabel`; string, optional)
    - `total` (`createOrder.response.201.order.lines.total`; number, optional)
  - `subtotal` (`createOrder.response.201.order.subtotal`; number, optional)
  - `deliveryCost` (`createOrder.response.201.order.deliveryCost`; number, optional)
  - `serviceCost` (`createOrder.response.201.order.serviceCost`; number, optional)
  - `couponDiscount` (`createOrder.response.201.order.couponDiscount`; number, optional) — Every reduction (coupon, promotional offer).
  - `total` (`createOrder.response.201.order.total`; number, optional)
  - `currency` (`createOrder.response.201.order.currency`; string, optional)
  - `locale` (`createOrder.response.201.order.locale`; string, optional)
  - `customer` (`createOrder.response.201.order.customer`; object, optional)
    - `firstName` (`createOrder.response.201.order.customer.firstName`; string, optional)
    - `lastName` (`createOrder.response.201.order.customer.lastName`; string, optional)
    - `email` (`createOrder.response.201.order.customer.email`; string, optional)
  - `delivery` (`createOrder.response.201.order.delivery`; object | null, optional) — For `DELIVERY` orders, else `null`.
    - `street` (`createOrder.response.201.order.delivery.street`; string, optional)
    - `number` (`createOrder.response.201.order.delivery.number`; string, optional)
    - `postcode` (`createOrder.response.201.order.delivery.postcode`; string, optional)
    - `city` (`createOrder.response.201.order.delivery.city`; string, optional)
    - `zone` (`createOrder.response.201.order.delivery.zone`; string, optional)
  - `createdAt` (`createOrder.response.201.order.createdAt`; integer, optional) — Unix seconds.
- `next` (`createOrder.response.201.next`; object, optional) — What to do with the shopper next.
  - `type` (`createOrder.response.201.next.type`; string, optional) — `payment` (`online`): send the shopper's browser to `next.url`. `confirmation` (`offline`, `external`): the order is placed, nothing is left to pay through Unipage.
    - one of `"payment"`, `"confirmation"`
  - `url` (`createOrder.response.201.next.url`; string, optional) — Only for `payment`. The payment page on the restaurant's Unipage webshop domain. Treat it as opaque: send the browser to it as is.

### 401

A missing, unknown or revoked key (`UNAUTHENTICATED`).

#### Example

```json
{
  "error": {
    "code": "UNAUTHENTICATED",
    "message": "No valid key."
  }
}
```

- `error` (`createOrder.response.401.error`; object, required)
  - `code` (`createOrder.response.401.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`createOrder.response.401.error.message`; string, required) — Human readable.
  - `fields` (`createOrder.response.401.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.

### 403

A public key on a secret route, or used from an origin that is not
registered for it (`FORBIDDEN`).

#### Example

```json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Origin not registered for this key."
  }
}
```

- `error` (`createOrder.response.403.error`; object, required)
  - `code` (`createOrder.response.403.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`createOrder.response.403.error.message`; string, required) — Human readable.
  - `fields` (`createOrder.response.403.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.

### 422

The order cannot be placed. `code` is one of `VALIDATION`,
`SLOT_UNAVAILABLE`, `DELIVERY_UNAVAILABLE`, `PRODUCT_UNAVAILABLE`,
`COUPON_INVALID` or `MINIMUM_NOT_REACHED`.

#### Example

```json
{
  "error": {
    "code": "SLOT_UNAVAILABLE",
    "message": "The chosen time slot is no longer available."
  }
}
```

- `error` (`createOrder.response.422.error`; object, required)
  - `code` (`createOrder.response.422.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`createOrder.response.422.error.message`; string, required) — Human readable.
  - `fields` (`createOrder.response.422.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.

### 429

Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds.

#### Example

```json
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests."
  }
}
```

- `error` (`createOrder.response.429.error`; object, required)
  - `code` (`createOrder.response.429.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`createOrder.response.429.error.message`; string, required) — Human readable.
  - `fields` (`createOrder.response.429.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.


Source: https://developer.unipage.be/storefront/api/orders/create/index.md
