> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anny.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Booking Creation

Create bookings and orders directly from the admin API using the `orders/from-config` endpoint. This bypasses the public checkout flow and is designed for back-office operations.

The same endpoint also **edits** an existing order when the config carries an `order_id` — see [Editing an Existing Order](#editing-an-existing-order). To change one confirmed booking in place (resource, dates, fields, add-ons, sub-bookings) without rewriting the whole order, use [Admin Booking Edit](https://docs.anny.co/developers/guides/admin/booking-edit.md).

For the public checkout flow, see [Order & Checkout Flow](https://docs.anny.co/developers/guides/customer/checkout.md). For general conventions, see [JSON-API Conventions](https://docs.anny.co/developers/guides/json-api-conventions.md).

***

## Prerequisites

* A valid Bearer token with admin access — see [Authentication](https://docs.anny.co/developers/guides/authentication.md)
* Organization context (`?o={organization_id}`)
* At least one resource and service configured in the organization

***

## Recommended Flow (Admin UI Forms)

For Admin UI integrations, prefer the dynamic form flow over manually assembling all custom-field and participant payloads.

### Step A - Build a Dynamic Checkout Form

```http theme={null}
POST /api/v1/ui/admin/checkout-form?o={org_id}

{
  "booking_config": {
    "order_customer_id": null,
    "bookings": [
      {
        "customer_id": null,
        "service_id": { "{service_uuid}": 1 },
        "resource_id": "{resource_uuid}",
        "start_date": "2026-04-15T09:00:00+02:00",
        "end_date": "2026-04-15T10:00:00+02:00"
      },
      {
        "customer_id": "{optional_existing_customer_uuid}",
        "service_id": { "{service_uuid}": 1 },
        "resource_id": "{resource_uuid}",
        "start_date": "2026-04-15T10:00:00+02:00",
        "end_date": "2026-04-15T11:00:00+02:00"
      }
    ]
  }
}
```

This returns a form object (`components`, `validations`, `default`) that includes:

* customer group (`customer-group`)
* customer custom fields
* order custom fields
* per-booking personalization groups (booking custom fields, host, participants)

### Step B - Apply Submitted Form Data

```http theme={null}
POST /api/v1/ui/admin/checkout-form/apply?o={org_id}

{
  "form_data": {
    "customer": {
      "email": "newcustomer@example.com",
      "given_name": "Test",
      "family_name": "Customer"
    },
    "bookings_data": {
      "b_205": {
        "service_custom_fields": {
          "{custom_field_uuid}": "Wheelchair access"
        },
        "participants": [
          {
            "email": "participant@example.com",
            "given_name": "Alex",
            "family_name": "Example"
          }
        ]
      }
    }
  },
  "booking_config": {
    "bookings": [
      {
        "_booking_id": 205,
        "resource_id": "{resource_uuid}",
        "service_id": { "{service_uuid}": 1 },
        "start_date": "2026-04-15T09:00:00+02:00",
        "end_date": "2026-04-15T10:00:00+02:00"
      }
    ]
  }
}
```

The response is an enriched config that is directly compatible with `POST /api/v1/orders/from-config`.

### Step C - Create the Order

Use the enriched payload from Step B as input for `POST /api/v1/orders/from-config`.

### Standalone Forms for Existing Records

You can also fetch standalone admin UI forms for existing records, such as customer, order, and booking edit forms.

The payload structure and persistence rules for those admin UI form endpoints are documented in [Admin UI Forms](https://docs.anny.co/developers/guides/internal/admin-ui-forms.md), not in this booking-creation guide.

***

## When to Use

| Scenario | Use |
| - | - |
| Customer books through the website | [Public Checkout](https://docs.anny.co/developers/guides/customer/checkout.md) |
| Admin creates a booking on behalf of a customer | **This guide** |
| Bulk/batch booking creation | **This guide** |
| Booking without availability check (override) | **This guide** |
| Admin edits an existing order | [Editing an Existing Order](#editing-an-existing-order) |
| Admin edits one confirmed booking | [Admin Booking Edit](https://docs.anny.co/developers/guides/admin/booking-edit.md) |

***

## Step 1 — Pre-Calculate Pricing (Optional)

Request path documentation:

* `paths/admin/orders.yaml` for `POST /api/v1/orders/calculate`

Before creating the order, preview the pricing and resolve available booking quota grants:

```http theme={null}
POST /api/v1/orders/calculate?o={org_id}

{
  "order_customer_id": "{customer_uuid}",
  "bookings": [
    {
      "booking_ref": "bk-1",
      "resource_id": "{resource_uuid}",
      "service_id": { "{service_uuid}": 1 },
      "start_date": "2026-04-15T09:00:00+02:00",
      "end_date": "2026-04-15T10:00:00+02:00"
    }
  ],
  "voucher_code": "SUMMER20",
  "booking_code": "PASS123"
}
```

Returns calculated totals, taxes, and per-booking pricing without persisting anything.

Each booking in the response includes `services_label`: the concatenated service list the admin calendar already shows for sequenced services (for example `1 x Cut, 1 x Wash`). For a single service it matches `service.name`.

Each booking also includes a `booking_quota_grants` array with all eligible grants, their applicability, projected usage, and which one is preselected. This allows the UI to display a grant selection dropdown directly from the calculate response — no separate endpoint call needed.

When the user selects a different grant, re-call calculate with `booking_quota_grant_id` set on the booking. Set `prevent_applying_default_quota: true` to deselect all grants.

> [!NOTE]
>
> With an `order_id`, `orders/calculate` previews an **edit** of that order instead: it runs the same resolution and planning as `orders/from-config` without writing anything. See [Preview an edit](#step-2-preview-the-edit-optional) under Editing an Existing Order.

***

## Step 2 — Create the Order

Request path documentation:

* `paths/admin/orders.yaml` for `POST /api/v1/orders/from-config`

```http theme={null}
POST /api/v1/orders/from-config?o={org_id}

{
  "order_customer_id": "{customer_uuid}",
  "bookings": [
    {
      "resource_id": "{resource_uuid}",
      "service_id": { "{service_uuid}": 1 },
      "start_date": "2026-04-15T09:00:00+02:00",
      "end_date": "2026-04-15T10:00:00+02:00",
      "quantity": 1,
      "description": "Team meeting",
      "booking_add_ons": [
        { "add_on_id": "{add_on_uuid}", "quantity": 2 }
      ]
    }
  ],
  "notify_customer": true,
  "complete_order": true,
  "check_availability": true,
  "timezone": "Europe/Berlin"
}
```

### Top-Level Fields

| Field | Type | Required | Default | Description |
| - | - | - | - | - |
| `order_id` | string | No | — | Existing order to update. Absent creates a new order — see [Editing an Existing Order](#editing-an-existing-order). |
| `order_customer_id` | string | No | — | Customer UUID for the entire order |
| `bookings` | array | Yes\* | — | Array of booking configurations. \*May be empty or omitted when `add_ons` or `booking_passes` are present. |
| `add_ons` | array | No | — | Standalone add-ons `{ add_on_id, quantity, custom_price }`, billed on the order invoice. `custom_price` replaces the per-unit price. |
| `booking_passes` | array | No | — | Booking passes from bundles `{ booking_bundle_id, quantity, custom_amount }`, billed on the order invoice |
| `voucher_code` | string | No | — | Discount voucher code |
| `discount_relative` | number | No | — | Ad-hoc percentage discount (0.01–100). Mutually exclusive with `voucher_code`. |
| `discount_absolute` | number | No | — | Ad-hoc fixed-amount discount. Mutually exclusive with `voucher_code`. |
| `notify_customer` | boolean | No | `true` | Send confirmation email to customer |
| `complete_order` | boolean | No | `true` | `false` creates a draft order |
| `check_availability` | boolean | No | `true` | `false` skips availability validation |
| `timezone` | string | No | — | Timezone for date interpretation |

### Booking Object Fields

| Field | Type | Required | Description |
| - | - | - | - |
| `resource_id` | string/array | Yes | Resource UUID(s). Array for multi-resource bookings. |
| `service_id` | object | Yes | Map of `service_uuid → quantity` (e.g. `{"uuid": 1}`) |
| `start_date` | string | Yes\* | Booking start (Atom format). \*Optional when `booking_id` is set; the booking keeps its current period. |
| `end_date` | string | Yes\* | Booking end (Atom format). \*Optional when `booking_id` is set. |
| `customer_id` | string | No | Per-booking customer (overrides `order_customer_id`) |
| `quantity` | integer | No | Number of slots/participants |
| `booking_id` | string | No | Persisted booking this entry updates. Only read when the config carries an `order_id` — see [Editing an Existing Order](#editing-an-existing-order). |
| `custom_price` | number | No | Admin override of the booking subtotal. Add-ons are unaffected. |
| `booking_quota_grant_id` | string | No | Quota grant to charge this booking against |
| `availability_override` | boolean | No | Skip the availability check for this booking only |
| `host` | string | No | Host email address |
| `participants` | array | No | Array of `{ email, name, can_edit, attending_virtually }` |
| `description` | string | No | Internal description |
| `note` | string | No | Admin-visible note |
| `customer_note` | string | No | Customer-visible note |
| `booking_add_ons` | array | No | Array of `{ add_on_id, quantity }` |
| `sub_bookings` | array | No | Array of `{ service_id, resource_id, quantity }` for linked sub-bookings |
| `service_custom_fields` | object | No | Custom field values (UUID → value) |
| `order_custom_fields` | object | No | Order-level custom field values |
| `recurring` | object | No | Recurring rules (frequency, interval, until, count) |
| `timeslot_series_id` | string | No | Link to a specific timeslot series |

### Response

Returns a standard JSON-API `orders` resource with the created bookings:

```json theme={null}
{
  "data": {
    "type": "orders",
    "id": "order-uuid",
    "attributes": {
      "status": "completed",
      "manually_created": true,
      "total": 5000,
      "currency": "EUR"
    },
    "relationships": {
      "bookings": {
        "data": [{ "type": "bookings", "id": "b1" }]
      },
      "customer": {
        "data": { "type": "customers", "id": "c1" }
      }
    }
  }
}
```

***

## Multiple Bookings in One Order

Add multiple booking objects to create them in a single order:

```json theme={null}
{
  "order_customer_id": "{customer_uuid}",
  "bookings": [
    {
      "resource_id": "{room_uuid}",
      "service_id": { "{meeting_service}": 1 },
      "start_date": "2026-04-15T09:00:00+02:00",
      "end_date": "2026-04-15T10:00:00+02:00"
    },
    {
      "resource_id": "{desk_uuid}",
      "service_id": { "{desk_service}": 1 },
      "start_date": "2026-04-15T10:00:00+02:00",
      "end_date": "2026-04-15T17:00:00+02:00"
    }
  ]
}
```

***

## Editing an Existing Order

An order that already exists can be read back as a config, edited, and posted to the very same `orders/from-config` endpoint. The config carries an `order_id`, and that is what tells the endpoint to update that order instead of creating a new one. The config is the **complete desired state** of the order, so one document drives updates, additions and removals in a single request.

To change **one** confirmed booking without rewriting the order, use the per-booking actions in [Admin Booking Edit](https://docs.anny.co/developers/guides/admin/booking-edit.md) instead.

### Step 1 — Read the Current Config

```http theme={null}
GET /api/v1/orders/{order_id}/config?o={org_id}
```

The response is the same document `orders/from-config` accepts, with the order's own `order_id` at the top level and the persisted `booking_id` on every entry in `bookings`. Removed and cancelled bookings are omitted, and sub-bookings and sequence members are nested inside their parent entry rather than listed on their own.

`check_availability` and `notify_customer` come back as `false`, so posting an unchanged config straight back is a no-op.

### Step 2 — Preview the Edit (Optional)

```http theme={null}
POST /api/v1/orders/calculate?o={org_id}

{ "order_id": "{order_id}", "bookings": [ ... ], ... }
```

The same document, sent to `orders/calculate` with its `order_id`, is priced as an edit of that order: the request goes through the same reference resolution and planning as the save below, against the persisted state, and nothing is written, reserved or consumed. The target order has to be configurable (see the errors below); a missing or foreign `order_id` is a `404`.

The response carries a `calculation_scope`:

| `calculation_scope` | When | What the amounts mean |
| - | - | - |
| `order` | The order's invoice is still a draft | The regenerated totals of the whole order after the edit |
| `correction` | The order's invoice is already issued | The **delta** the edit adds — what the parked correction draft will carry. Amounts can be negative. |

Per booking the response still lists the resolved prices, `services_label`, `resource` (with its `parent`), the extras and `booking_quota_grants`, in the order the entries were sent. A preview is not a reservation: availability and quota are validated again on save.

### Step 3 — Post the Edited Config Back

```http theme={null}
POST /api/v1/orders/from-config?o={org_id}

{ "order_id": "{order_id}", "bookings": [ ... ], ... }
```

| Booking entry | Effect |
| - | - |
| has `booking_id` | Updates that booking |
| no `booking_id` | Added to the order and confirmed, like a newly created admin booking |
| missing from the config | Soft-deleted, on confirmed orders as well as drafts |

> [!WARNING]
>
> The body is the complete desired state, not a patch. Sending only the bookings you changed will soft-delete every booking you left out.

Updatable per booking: dates, resource, service configuration (`service_id`), quota grant, add-ons, sub-bookings, customer, participants and host, custom fields, `custom_price`, and the note fields. Order-level fields behave as on create: `order_customer_id`, `voucher_code` / `discount_relative` / `discount_absolute`, `add_ons` and `booking_passes`.

The response is the updated order; `meta.calculation` carries the full normalized totals of the order after the update — the whole order, not the delta of the edit.

### Behaviour to Know

* Pricing follows the admin booking edit: changes apply immediately. Vouchers, credit quota, payment-method codes, and gift cards on the invoice **recap** to the new totals so payable stays at or above zero. While the order's invoice is still a draft it is **regenerated in place**; once issued, the change produces a correction that stays an **unlocked draft invoice** on the order for an admin to issue.
* Applies are **serialized per order** — concurrent requests queue instead of diffing against a half-updated order. They are **not atomic**: a step that fails part-way leaves the earlier steps applied, so re-read the config before retrying.
* Removals are soft deletes: the bookings are priced out of the order and the rows stay recoverable.
* `notify_customer` only reaches **added** bookings. Removals are silent — soft-deleting a booking sends the customer nothing, so tell them separately if the change warrants it.
* Changing `add_ons` rewrites the standalone lines rather than patching them, so their ids change. An unchanged `add_ons` array is left untouched.
* Standalone `add_ons` and `booking_passes` are both synced to the config. A pass is one model per purchased unit, so lowering a bundle's `quantity` soft-deletes the surplus passes (newest first, so the oldest codes survive) along with their booking quota grants. A pass whose grants have already been used is rejected with `booking_pass_in_use` rather than orphaning the bookings it paid for.
* A `recurring` block on an entry that carries a `booking_id` is rejected — an existing booking cannot be expanded into a series. Series members round-trip as individual entries.
* An empty config is rejected rather than dissolving the order.
* Shop checkout drafts (`status=draft` and `manually_created=false`) cannot be configured. Admin-created drafts and reserved admin carts can. The API still returns `cannot_modify_reserved_booking` for a customer checkout cart that is still mid-flow.
* Without an `order_id` the very same document creates a new order, so dropping the key is how you duplicate an existing order.

***

## Skipping Availability Checks

Set `check_availability: false` to override availability constraints. Use this for:

* Overbooking scenarios
* Creating bookings in the past
* Special arrangements outside normal schedules

> **Warning:** Skipping availability checks can create conflicting bookings. Use with care.

***

## Re-Sending Order Notifications

If the customer didn't receive the confirmation:

```http theme={null}
GET /api/v1/orders/{order_id}/send-notification?o={org_id}
```

Triggers the order confirmation notification to the associated customer.

***

## Common Errors

| Error | Cause | Solution |
| - | - | - |
| `422` — resource\_id required | Missing resource in booking object | Include `resource_id` for each booking |
| `422` — service\_id required | Missing service mapping | Provide `service_id` as `{ "uuid": quantity }` |
| `422` — slot unavailable | Time conflict with existing booking | Set `check_availability: false` to override, or pick a different time |
| `422` — customer not found | Invalid `order_customer_id` | Verify the customer UUID exists in the organization |
| `403` — unauthorized | Insufficient permissions | Requires order creation permission, or order update permission when `order_id` is set |
| `404` — booking not part of order | A `booking_id` that does not belong to the order in `order_id` | Re-read the config from `GET /orders/{id}/config` |
| `400` — order empty | The config lists no bookings, add-ons or passes | Send at least one item, or cancel the order explicitly |
| `400` — cannot modify reserved booking | `order_id` points at a shop customer checkout draft (`status=draft` and not admin-created) | Wait until checkout finishes, or edit an admin-created order |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.