# Extras

To use this capability, add `octo/extras` to your `Octo-Capabilities` header.

Extras are upsell items that can be configured either on the booking (option-level) or per unit item (unit-level). An extra might be a lunch package, fast track entrance, souvenir photo, or another optional add-on.

## Product Extras

This capability extends product routes documented in [Products](/octo-core/products.md):

* `GET /products`
* `GET /products/{productId}`

Available extras are returned in the product response as:

* `option.extras[]` for booking-level extras
* `unit.extras[]` for unit-level extras

When combined with `octo/pricing` and `octo/content`, extra objects also include pricing and content fields.

Extra IDs are returned as `extra_<uuid>` in responses. For request payloads, both `extra_<uuid>` and raw UUID values are accepted.

For item-based products, item objects still include `extras`, but it is always an empty array.

## Extra Availability

This capability extends availability routes documented in [Availability](/octo-core/availability.md):

* `POST /availability`
* `POST /availability/calendar`
* `POST /availability/batch`
* `POST /availability/calendar/batch`

Extras do not require availability by themselves, but you can include extra quantities in availability queries to calculate total pricing.

For the normal case, availability requests use `extras[]` items with:

* `id`
* `quantity`

If an extra quantity exceeds configured limits, the API responds with:

* `EXTRAS_QTY_LIMIT` for booking-level extras
* `EXTRAS_UNIT_QTY_LIMIT` for unit-level extras

When used with `octo/pricing`, availability pricing includes both extra pricing breakdowns and totals.

`extraPricingFrom` is used instead of `extraPricing` on "from" payloads.

### Custom Retail Extras

A small number of extras may use reseller-supplied pricing. These extras are identified by `customRetail: true` on the extra object.

For custom retail extras:

* include `retail` in minor units on availability `extras[]`
* include `retail` in minor units on booking `extraItems[]`
* use `customRetailOptions` as suggested values if provided
* enforce `restrictions.minCustomRetail` and `restrictions.maxCustomRetail`

If a custom retail extra is used incorrectly, the API responds with:

* `EXTRAS_RETAIL_REQUIRED`
* `EXTRAS_RETAIL_BELOW_MINIMUM`
* `EXTRAS_RETAIL_ABOVE_MAXIMUM`

When `octo/pricing` is also enabled and a `currency` is selected, custom retail amounts are returned in that currency's minor units.

## Booking Reservation

This capability extends booking write routes documented in [Bookings](/octo-core/bookings.md#endpoints):

* `POST /bookings`
* `PATCH /bookings/{uuid}`

Use `extraItems[]` to reserve extras on bookings:

* booking-level extras: set `extraItems[]` on the booking object
* unit-level extras: set `extraItems[]` inside each `unitItems[]` object

When `extraItems` is present it must be an array.

For the normal case, each `extraItems[]` object contains:

* `extraId`
* optional `uuid`
* optional `resellerReference`

Send one `extraItems[]` object per reserved extra. If the same extra is reserved multiple times, repeat the same `extraId` in multiple array entries.

For custom retail extras, include `retail` on each `extraItems[]` object.

Booking and unit item responses both include `extraItems[]` with the same object schema.

When using the `octo/pricing` capability, booking and unit-item pricing totals include the sum of attached extra items. For purchase unit items, `extraItems` is always an empty array.

## Schema Additions (JSON)

These are additive fragments showing only fields introduced by this capability.

### `Option`

```json
{
  "// ...rest of option object": "...",
  "extras": [
    {
      "id": "e7cc8bb4-8d1c-4848-8824-5dbedb718681",
      "internalName": "Fast Track Entry",
      "reference": "fast_track_entry",
      "title": "Fast Track Entry",
      "customRetail": false,
      "customRetailOptions": [],
      "restrictions": {
        "required": false,
        "default": false,
        "idRequired": false,
        "minQuantity": 0,
        "maxQuantity": 2,
        "minCustomRetail": 0,
        "maxCustomRetail": null,
        "paxCount": 0,
        "accompaniedBy": [],
        "accompaniedByRatio": null,
        "accompaniedByRatioDenominator": null,
        "notAccompaniedBy": []
      }
    }
  ]
}
```

### `Unit`

```json
{
  "// ...rest of unit object": "...",
  "extras": [
    {
      "id": "4b8cb6fb-c7c5-4f0b-8ed9-c0fd7bb0aa26",
      "internalName": "Souvenir Photo",
      "reference": "souvenir_photo",
      "title": "Souvenir Photo",
      "customRetail": false,
      "customRetailOptions": [],
      "restrictions": {
        "required": false,
        "default": false,
        "idRequired": false,
        "minQuantity": 0,
        "maxQuantity": 1,
        "minCustomRetail": 0,
        "maxCustomRetail": null,
        "paxCount": 0,
        "accompaniedBy": [],
        "accompaniedByRatio": null,
        "accompaniedByRatioDenominator": null,
        "notAccompaniedBy": []
      }
    }
  ]
}
```

### `Option (Custom Retail Extra)`

```json
{
  "// ...rest of option object": "...",
  "extras": [
    {
      "id": "3d6f0a3a-59d4-4b16-a0c5-11d2d8a4e6b7",
      "internalName": "Guide Gratuity",
      "reference": "guide_gratuity",
      "title": "Guide Gratuity",
      "customRetail": true,
      "customRetailOptions": [500, 1000, 1500],
      "restrictions": {
        "required": false,
        "default": false,
        "idRequired": false,
        "minQuantity": 1,
        "maxQuantity": 1,
        "minCustomRetail": 500,
        "maxCustomRetail": 5000,
        "paxCount": 0,
        "accompaniedBy": [],
        "accompaniedByRatio": null,
        "accompaniedByRatioDenominator": null,
        "notAccompaniedBy": []
      }
    }
  ]
}
```

### `AvailabilityRequest`

The same `extras[]` selector fragment also applies to `AvailabilityCalendarRequest`, `AvailabilityBatchRequest`, and `AvailabilityCalendarBatchRequest`.

```json
{
  "// ...rest of availability request object": "...",
  "extras": [
    {
      "id": "e7cc8bb4-8d1c-4848-8824-5dbedb718681",
      "quantity": 2
    }
  ]
}
```

### `AvailabilityRequest (Custom Retail Extra)`

```json
{
  "// ...rest of availability request object": "...",
  "extras": [
    {
      "id": "3d6f0a3a-59d4-4b16-a0c5-11d2d8a4e6b7",
      "quantity": 1,
      "retail": 1500
    }
  ]
}
```

### `BookingWriteRequest`

```json
{
  "// ...rest of booking write request object": "...",
  "extraItems": [
    {
      "uuid": "89fe0192-ddcd-430a-b285-e1396a4725d2",
      "extraId": "e7cc8bb4-8d1c-4848-8824-5dbedb718681",
      "resellerReference": "RES-BOOK-10045"
    },
    {
      "uuid": "a0d0f264-e244-461f-8612-4b175f8fa9e6",
      "extraId": "e7cc8bb4-8d1c-4848-8824-5dbedb718681",
      "resellerReference": "RES-BOOK-10046"
    }
  ]
}
```

### `BookingWriteRequest (Custom Retail Extra)`

```json
{
  "// ...rest of booking write request object": "...",
  "extraItems": [
    {
      "uuid": "6a4f3d0f-d514-4db5-9c13-0d27d5f49a44",
      "extraId": "3d6f0a3a-59d4-4b16-a0c5-11d2d8a4e6b7",
      "retail": 1500,
      "resellerReference": "RES-BOOK-10047"
    }
  ]
}
```

### `BookingUnitItemWriteRequest`

```json
{
  "// ...rest of booking unit item write request object": "...",
  "extraItems": [
    {
      "uuid": "3f8b16ff-8e6d-49b4-a4ca-c0b4a137cc1b",
      "extraId": "4b8cb6fb-c7c5-4f0b-8ed9-c0fd7bb0aa26",
      "resellerReference": "RES-BOOK-10045-1"
    }
  ]
}
```

### `Booking`

```json
{
  "// ...rest of booking object": "...",
  "extraItems": [
    {
      "id": "89fe0192-ddcd-430a-b285-e1396a4725d2",
      "uuid": "89fe0192-ddcd-430a-b285-e1396a4725d2",
      "resellerReference": "RES-BOOK-10045",
      "supplierReference": "SUP-BOOK-7782",
      "extraId": "extra_e7cc8bb4-8d1c-4848-8824-5dbedb718681",
      "status": "CONFIRMED"
    }
  ]
}
```

### `Booking (Custom Retail Extra)`

```json
{
  "// ...rest of booking object": "...",
  "extraItems": [
    {
      "id": "6a4f3d0f-d514-4db5-9c13-0d27d5f49a44",
      "uuid": "6a4f3d0f-d514-4db5-9c13-0d27d5f49a44",
      "extraId": "extra_3d6f0a3a-59d4-4b16-a0c5-11d2d8a4e6b7",
      "retail": 1500,
      "status": "CONFIRMED"
    }
  ]
}
```

### `BookingUnitItem`

```json
{
  "// ...rest of booking unit item object": "...",
  "extraItems": [
    {
      "id": "a0d0f264-e244-461f-8612-4b175f8fa9e6",
      "uuid": "3f8b16ff-8e6d-49b4-a4ca-c0b4a137cc1b",
      "extraId": "extra_4b8cb6fb-c7c5-4f0b-8ed9-c0fd7bb0aa26",
      "status": "CONFIRMED"
    }
  ]
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ventrata.com/capabilities/extras.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
