# 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](https://docs.ventrata.com/octo-core/products):

* `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](https://docs.ventrata.com/octo-core/availability):

* `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](https://docs.ventrata.com/octo-core/bookings#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"
    }
  ]
}
```
