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

# OCTO Protocol

> Data model and field requirements for your OCTO-compatible API.

Seyaha uses a subset of the [OCTO standard](https://github.com/open-connectivity-standard/octo)
as its catalog and booking protocol. This page explains exactly which fields Seyaha reads
and how they map to the Seyaha data model.

## Products → Activities

Each object in your `GET /products` response creates or updates one Seyaha activity.

| Your field                    | Seyaha stores it as              | Notes                                      |
| ----------------------------- | -------------------------------- | ------------------------------------------ |
| `product.id`                  | `external_sync.external_id`      | Permanent deduplication key — never change |
| `product.internalName`        | `activity_title` (initial)       | Editable by Seyaha admins after first sync |
| `product.instantConfirmation` | `variation.instant_confirmation` | Applied to all variations                  |

<Note>
  `product.internalName` seeds the activity title at creation time. Seyaha admins can
  edit the title, description, media, and pricing after the first sync without those
  changes being overwritten on the next sync. Only structural fields (title, pricing,
  availability) are refreshed from OCTO.
</Note>

## Options → Variations

Each option under a product creates or updates one variation.

| Your field                               | Seyaha stores it as                          | Notes                                           |
| ---------------------------------------- | -------------------------------------------- | ----------------------------------------------- |
| `option.id`                              | `variation.external_sync.external_option_id` | Permanent key — never change                    |
| `option.internalName`                    | `variation.title` (initial)                  |                                                 |
| `option.availabilityType`                | `variation.availability_type`                | `START_TIME` or `OPENING_HOURS`                 |
| `option.units[].type`                    | Pricing unit type                            | `ADULT`, `CHILD`, `INFANT`, `SENIOR`, `STUDENT` |
| `option.units[].pricingFrom[0].original` | `variation.pricing.units[].price`            | Base price in smallest currency unit            |

### Pricing units

The `type` field on each unit maps to Seyaha's guest type enum:

| Unit `type` | Seyaha guest type |
| ----------- | ----------------- |
| `ADULT`     | adult             |
| `CHILD`     | child             |
| `INFANT`    | infant            |
| `SENIOR`    | senior            |
| `STUDENT`   | student           |

Prices in `pricingFrom[].original` are in the **smallest currency unit** — halalas for SAR,
cents for USD. Seyaha divides by `10^currencyPrecision` when displaying prices.

## Availability → Slots

Each object returned by `POST /availability` creates or updates one time slot.

| Your field                            | Seyaha stores it as                  | Notes                                   |
| ------------------------------------- | ------------------------------------ | --------------------------------------- |
| `availability.id`                     | `slot.availability_id`               | Used at booking time to call reserve    |
| `availability.localDateTimeStart`     | `slot.date` + `slot.time`            | Parsed with timezone                    |
| `availability.status`                 | `slot.status`                        | See status table below                  |
| `availability.vacancies`              | `slot.vacancies`                     |                                         |
| `availability.capacity`               | `slot.capacity` + `slot.max_booking` |                                         |
| `availability.unitPricing[].original` | `slot.pricing[].price`               | Overrides option-level price if present |

### Availability status values

| Status      | Meaning in Seyaha                      |
| ----------- | -------------------------------------- |
| `AVAILABLE` | Bookable                               |
| `FREESALE`  | Bookable, unlimited capacity           |
| `LIMITED`   | Bookable, low vacancies                |
| `SOLD_OUT`  | Not bookable, shown as full            |
| `CLOSED`    | Hidden (slot set to `isActive: false`) |

## Booking lifecycle fields

When Seyaha creates a reservation, it sends:

| Field               | Source                                                   |
| ------------------- | -------------------------------------------------------- |
| `productId`         | `activity.external_sync.external_id`                     |
| `optionId`          | `variation.external_sync.external_option_id`             |
| `availabilityId`    | Cached `slot.availability_id` (or resolved in real time) |
| `unitItems`         | Derived from guest counts in the booking                 |
| `contact`           | Customer's name, email, phone                            |
| `resellerReference` | Seyaha's internal booking `_id`                          |

Your `POST /bookings/reserve` response must include `reservationConfirmationCode` and
`expiresAt`. Your `POST /bookings/confirm` response must include `externalBookingId`.
Both values are stored by Seyaha and used in subsequent cancel requests.
