# Placing an order (/docs/place-an-order)



`POST /api/v1/public/orders/create` places an order at a location in one request: no tab to pre-create, no guest lookup, no payload assembly. A new tab is opened, a kitchen order is created, and an anonymous guest is generated unless you identify one.

The API key needs the `inventory.create` permission for the location.

## The simplest order [#the-simplest-order]

```bash
curl -X POST "$COMMON_API/api/v1/public/orders/create" \
  -H "X-API-Key: $KARMA_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "location_id": 1234,
    "items": [{ "id": 456, "quantity_cents": 100 }]
  }'
```

```json
{
  "status": "success",
  "data": {
    "orderId": "e01dcd36-cdec-41e5-8708-1b57d943367d",
    "tabId": "59e4ce7d-b801-4323-af9d-ba0ca4bd757a",
    "totalAmountCents": 9900
  }
}
```

`orderId` is the kitchen order, `tabId` the tab it was placed on. Keep the `tabId` to add more items to the same tab before payment.

## Common scenarios [#common-scenarios]

**Identify the guest.** Pass `ordering_member` with a `user_id`, an `email` or a `phone`, in that order of priority. An email or phone that matches an existing Karma account reuses it; otherwise a guest is created. `name` is used for new guests.

```json
{
  "location_id": 1234,
  "ordering_member": { "email": "guest@example.com", "name": "Alex" },
  "items": [{ "id": 456, "quantity_cents": 100 }]
}
```

**Variants, a voucher, takeaway.**

```json
{
  "location_id": 1234,
  "order_type": "TAKEAWAY",
  "items": [
    { "id": 456, "quantity_cents": 100, "variants": [{ "id": 12 }, { "id": 34 }] },
    { "id": 789, "quantity_cents": 100 }
  ],
  "vouchers": ["WELCOME10"]
}
```

**Add to an existing tab.** Use the `tabId` from an earlier response:

```json
{
  "location_id": 1234,
  "order_destination": "TAB",
  "order_destination_id": "59e4ce7d-b801-4323-af9d-ba0ca4bd757a",
  "items": [{ "id": 789, "quantity_cents": 100 }]
}
```

**Fractional quantities.** `quantity_cents` is the quantity times 100: `100` is one unit, `50` is half, `200` is two. Use it for weight-based or bulk items.

**Price override.** `price_cents` on an item replaces the catalogue price for this order only.

## Request reference [#request-reference]

| Field                    | Required | Description                                               |
| ------------------------ | -------- | --------------------------------------------------------- |
| `location_id`            | **yes**  | Location to place the order at                            |
| `items`                  | **yes**  | At least one item                                         |
| `items[].id`             | **yes**  | Inventory item id                                         |
| `items[].quantity_cents` | **yes**  | Quantity × 100                                            |
| `items[].variants`       | no       | Variant selections, `[{ "id": 12 }]`                      |
| `items[].price_cents`    | no       | Price override in cents                                   |
| `items[].coursing_order` | no       | Course number; `0` sends immediately                      |
| `ordering_member`        | no       | `user_id`, `email` or `phone`, plus `name` for new guests |
| `vouchers`               | no       | Voucher codes to apply                                    |
| `order_destination`      | no       | `NONE` (default, a fresh tab), `TAB`, `QR` or `KD`        |
| `order_destination_id`   | no       | UUID of the destination when it is not `NONE`             |
| `order_type`             | no       | `OTC` (default), `TAKEAWAY` or `TABLE`                    |

## Errors [#errors]

| Status | Code                       | When                                                                                      |
| ------ | -------------------------- | ----------------------------------------------------------------------------------------- |
| 400    | `INVALID_INPUT`            | Missing required fields or a bad type. The response `details.hint` names what is missing. |
| 401    | `MISSING_API_KEY`          | No `X-API-Key` header                                                                     |
| 403    | `INSUFFICIENT_PERMISSIONS` | The key lacks `inventory.create`                                                          |
| 403    | `SCOPE_VIOLATION`          | The key is not scoped to this location                                                    |
| 429    | `RATE_LIMIT_EXCEEDED`      | See [rate limits](/docs/rate-limits)                                                      |

Once the order exists, the location's `order.created` webhook fires, and `tab.payment_completed` follows when it is paid. See [Receiving webhooks](/docs/webhooks).
