> ## 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.

# Order & Checkout Flow (Public)

Create bookings through the public cart and checkout flow. For admin-side creation (`orders/from-config`), see [Admin Booking Creation](https://docs.anny.co/developers/guides/admin/booking-creation.md).

For conventions, see [JSON-API Conventions](https://docs.anny.co/developers/guides/json-api-conventions.md). For slot search, see [Availability & Booking Search](https://docs.anny.co/developers/guides/availability.md).

***

## Prerequisites

* Resource + service configuration exists
* Cookies are enabled (cart and order authorization are cookie/query based)
* Optional customer auth for prefilled fields

***

## Canonical Path Docs

* `/api/v1/order*` cart/checkout endpoints: [../../paths/public/orders.yaml](https://github.com/anny-co/anny-api-docs/blob/main/paths/public/orders.yaml)

***

## Flow Overview

```text theme={null}
1. Add booking(s) to cart
2. Show recommendations in cart
3. Edit cart / voucher
4. Collect checkout data
5. Optional guest email verification
6. Submit checkout
7. Handle payment redirect (embedded checkout opens an external payment popup when the browser allows it)
8. Fetch last succeeded orders
```

***

**1. Add Booking to Cart**

```http theme={null}
POST /api/v1/order/bookings

{
  "resource_id": "{resource_id}",
  "service_id": {
    "{service_id}": 1
  },
  "start_date": "2026-04-15T09:00:00+02:00",
  "end_date": "2026-04-15T10:00:00+02:00",
  "add_ons_by_service": {
    "{service_id}": [
      [
        { "add_on_id": "{add_on_id}", "quantity": 2 }
      ]
    ]
  },
  "sub_bookings_by_service": {
    "{service_id}": [
      []
    ]
  }
}
```

`add_ons_by_service` and `sub_bookings_by_service` are mapped by service id and booking index:

| Field | Type | Notes |
| - | - | - |
| `service_id` | `string \| object` | Object map (`service_id -> quantity`) is current format |
| `add_ons_by_service` | `object` | `{service_id: AddOn[][]}` |
| `add_ons_by_service[service_id][booking_index][]` | array item | `{add_on_id, quantity}` |
| `sub_bookings_by_service` | `object` | `{service_id: SubBooking[][]}` |

> [!NOTE]
>
> Each add-on has `always_apply` (boolean, default `true`). When it is `true`, omitting the add-on or sending `quantity: 0` still includes it at `min_quantity`. When it is `false`, omit the add-on or send `0` to skip it; a quantity greater than `0` is still floored to `min_quantity`. See [Add-On](https://docs.anny.co/developers/models/add-ons.md).

Legacy inputs (`booking_add_ons`, `sub_bookings`) still exist but are deprecated.

Exact add-booking request fields (current request rules):

| Field | Type | Required | Notes |
| - | - | - | - |
| `start_date` | string (atom date-time) | yes | |
| `end_date` | string (atom date-time) | yes | `>= start_date` |
| `service_id` | string \| int \| object | yes | object map recommended (`service_id -> quantity`) |
| `resource_id` | string \| int \| array | yes | can be multi-resource |
| `description` | string | no | nullable |
| `customer_note` | string | no | nullable |
| `add_ons_by_service` | object | no | nullable |
| `sub_bookings_by_service` | object | no | nullable |
| `recurring` | object | no | nullable |
| `recurring.frequency` | enum | no | `daily`, `weekly`, `monthly` |
| `recurring.duration` | int | conditional | required when `end_date` not used by recurring flow |
| `recurring.until` | date | no | nullable |
| `recurring.interval` | int | no | |
| `recurring.days` | array | no | |
| `recurring.month_day` | int | no | |
| `recurring.week_start` | string | no | |
| `recurring.set_pos` | int | no | |
| `strategy` | enum | no | availability booking strategy |
| `timezone` | IANA timezone | no | |
| `create_sequence` | boolean | no | |
| `timeslot_series_id` | int | no | |
| `original_booking_id` | string | no | |
| `book_on_behalf` | string | no | customer account uuid |
| `account_pass` | string | no | pass code |
| `booking_quota_grant_id` | string \| int \| array | no | |
| `prevent_applying_default_quota` | boolean | no | |
| `booking_pass_code` | string | no | Shop booking-code field. When the value is a discount voucher, public add-booking and precalculate apply it as `voucher_code`. Booking passes still apply via `booking_quota_grant_id`. Unknown codes fail as `invalid_booking_pass_code`. Not used on admin `booking_code` or MCP. |
| `adhoc` | boolean | no | |

Path docs: `/api/v1/order/bookings` in [../../paths/public/orders.yaml](https://github.com/anny-co/anny-api-docs/blob/main/paths/public/orders.yaml).

Current cart:

```http theme={null}
GET /api/v1/order
```

**2. Recommendations in Cart (Before Checkout)**

Recommendations are a cart-time feature, not a post-order step.

```http theme={null}
GET /api/v1/order/recommendations
```

**3. Edit Cart and Voucher**

Edit add-ons for one booking:

```http theme={null}
POST /api/v1/order/bookings/{booking_id}/edit-addons

{
  "addons": [
    { "add_on_id": "{add_on_id}", "quantity": 1 }
  ]
}
```

Remove one booking:

```http theme={null}
DELETE /api/v1/order/bookings/{booking_id}
```

Remove multiple bookings:

```http theme={null}
DELETE /api/v1/order/bookings/delete-multiple

{
  "filter": {
    "id": ["{booking_id_1}", "{booking_id_2}"]
  }
}
```

Clear cart:

```http theme={null}
GET /api/v1/order/bookings/delete-all
```

Apply voucher:

```http theme={null}
POST /api/v1/order/voucher

{
  "code": "SUMMER20"
}
```

Remove voucher:

```http theme={null}
DELETE /api/v1/order/voucher
```

After the customer logs in or switches account at checkout, fetch the open cart again so member or community prices replace guest prices:

```http theme={null}
GET /api/v1/order
```

On that fetch, guest bookings without an `account_id` are adopted onto the logged-in customer and repriced. A later logged-out fetch releases those adopted bookings again so member prices do not survive logout. Delegated (on-behalf) bookings keep their owner.

`POST /api/v1/order/recalculate` has been removed. Do not call it.

`POST /api/v1/order` (finish) also recalculates when an authenticated customer is present.

Optional booking pre-calculation:

```http theme={null}
POST /api/v1/order/bookings/calculate
```

**4. Collect Checkout Data**

Collect the customer data, payment method, and legal document acceptances required for the current organization.

Public integrations should treat these requirements as organization-defined checkout configuration rather than depending on internal UI helper endpoints.

**5. Optional Guest Email Verification**

If guest email verification is enabled for the checkout flow, complete that verification before the final order submission.

The external contract is the `verify_guest_email` field on `POST /api/v1/order`. Helper endpoints used by anny-hosted UIs are internal-only and not part of the external integration surface.

**6. Submit Checkout**

Customer and address are submitted as separate objects. Keep customer fields under `customer.*` and address fields under
`address.*`.

```http theme={null}
POST /api/v1/order

{
  "customer": {
    "email": "jane@example.com",
    "given_name": "Jane",
    "family_name": "Doe"
  },
  "address": {
    "street_address": "123 Main St",
    "address_line_2": "Suite 4B",
    "zip_code": "94105",
    "city": "San Francisco",
    "area": "CA",
    "country_code": "US"
  },
  "payment_method": "{payment_method_slug}",
  "documents": {
    "{legal_doc_uuid}": true
  },
  "verify_guest_email": {
    "state": "{email_verification_state}",
    "code": "123456"
  }
}
```

Validation depends on the organization's checkout configuration and selected payment method. Address fields returned by
the form are organization-country aware (for example state/area is only returned where relevant).

**7. Handle Payment Redirect**

If `meta.payment.next_action.type = redirect`, send the customer to `meta.payment.next_action.url`.

On the hosted booking page and in **embedded widgets**, methods that leave anny (PayPal, iDEAL, giropay, Sofort, Bancontact, EPS, Przelewy24, TWINT) open that URL in a **centered popup**. Card and SEPA stay in the widget (Stripe overlay). If the browser blocks the popup, checkout continues in the widget itself.

After the provider returns the customer, proceed to Step 8 to fetch the completed order.

**8. Fetch Last Succeeded Orders**

`last-succeeded` authorization is token-pair based and requires matching `oids[]` and `oats[]` arrays.

```http theme={null}
GET /api/v1/order/last-succeeded?oids[]={order_id_1}&oats[]={access_token_1}&oids[]={order_id_2}&oats[]={access_token_2}
```

***

## Common Errors

| Error | Cause | Solution |
| - | - | - |
| `422` add-ons ignored or invalid | Wrong `add_ons_by_service` shape | Use `service_id -> booking_index -> [{add_on_id, quantity}]` |
| `422` slot unavailable | Race condition between selection and checkout | Re-check availability and re-add booking |
| `409` order already completed | Duplicate checkout submit | Block duplicate submit and refresh cart state |
| `422` verification code invalid | Wrong/expired guest email code | Request a new code |
| `400` — `service_and_resource_not_related` | `resource_id` / `service_id` are not linked | Pick a service the resource actually offers. The API no longer swaps in another service. |
| `400` — `invalid_booking_pass_code` | `booking_pass_code` is neither a pass nor a discount voucher | Correct the code, or omit it |


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