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

# GET /products

> Return your full product catalog. Seyaha calls this endpoint to sync activities into the marketplace.

Seyaha calls `GET /products` on your `base_url` in three situations:

| Trigger                | Frequency      | Notes                                                       |
| ---------------------- | -------------- | ----------------------------------------------------------- |
| Scheduled nightly sync | Once per night | Full catalog pull                                           |
| Manual sync trigger    | On demand      | Full catalog pull                                           |
| Health probe           | Periodically   | Sends `?limit=1` — content is ignored, only the 200 matters |

***

## Request

```http theme={null}
GET /products
Authorization: Bearer <auth_key>
```

If your `auth_type` is `api_key`:

```http theme={null}
GET /products
Authorization: <auth_key>
```

### Query parameters

| Parameter       | Type              | Required | Description                                                                                                          |
| --------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `updated_since` | ISO 8601 datetime | No       | Return only products modified after this time. Seyaha sets this to the previous `last_sync_at` on incremental syncs. |
| `limit`         | integer           | No       | Maximum products to return. Seyaha sends `limit=1` for health probes.                                                |

***

## Response

Return a JSON array of product objects. You may also return `{ "products": [...] }` — Seyaha handles both shapes.

```http theme={null}
200 OK
Content-Type: application/json
```

### Product object

| Field                 | Type    | Required | Description                                                                                              |
| --------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `id`                  | string  | **Yes**  | Permanent unique identifier. **Never change this.** Seyaha uses it to deduplicate products across syncs. |
| `internalName`        | string  | **Yes**  | Seeds the activity title when Seyaha first creates the activity. Admins can edit it after creation.      |
| `locale`              | string  | **Yes**  | BCP 47 locale (e.g. `"en"`, `"ar"`)                                                                      |
| `timeZone`            | string  | **Yes**  | IANA timezone for all datetimes (e.g. `"Asia/Riyadh"`)                                                   |
| `instantConfirmation` | boolean | **Yes**  | `true` if bookings confirm without manual partner review                                                 |
| `allowFreesale`       | boolean | No       | `true` for unlimited capacity — Seyaha skips vacancy checks                                              |
| `options`             | array   | **Yes**  | Booking options — maps to Seyaha variations. At least one required.                                      |
| `reference`           | string  | No       | Optional external reference code                                                                         |

### Option object

Each option maps to a Seyaha **variation**.

| Field                | Type   | Required | Description                                                                                     |
| -------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------- |
| `id`                 | string | **Yes**  | Permanent unique identifier. Seyaha stores this as `external_option_id`. **Never change this.** |
| `internalName`       | string | **Yes**  | Seeds the variation title                                                                       |
| `availabilityType`   | string | **Yes**  | `"START_TIME"` — fixed start times. `"OPENING_HOURS"` — open during a window.                   |
| `units`              | array  | **Yes**  | Guest types and their base prices (see below)                                                   |
| `cancellationCutoff` | string | No       | Human-readable cancellation policy, e.g. `"24 hours before"`                                    |

### Unit object

Each unit is a guest type with a base price.

| Field                 | Type    | Required | Description                                                                                                                                             |
| --------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                  | string  | **Yes**  | Stable unit ID. Use `"unit-adult"`, `"unit-child"`, `"unit-infant"`, `"unit-senior"` — Seyaha sends exactly these IDs back in `POST /bookings/reserve`. |
| `internalName`        | string  | **Yes**  | Display label, e.g. `"Adult"`, `"Child (3–12)"`                                                                                                         |
| `type`                | string  | **Yes**  | One of: `ADULT`, `CHILD`, `INFANT`, `SENIOR`, `STUDENT`                                                                                                 |
| `pricingFrom`         | array   | **Yes**  | At least one pricing entry                                                                                                                              |
| `restrictions.minAge` | integer | No       | Minimum age for this guest type                                                                                                                         |
| `restrictions.maxAge` | integer | No       | Maximum age for this guest type                                                                                                                         |

### Pricing entry

| Field               | Type    | Required | Description                                                                                                          |
| ------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `original`          | integer | **Yes**  | Base price in the **smallest currency unit** (halalas for SAR, cents for USD). `25000` at precision `2` = SAR 250.00 |
| `currency`          | string  | **Yes**  | ISO 4217 currency code (e.g. `"SAR"`)                                                                                |
| `currencyPrecision` | integer | No       | Decimal places. Default 2.                                                                                           |
| `retail`            | integer | No       | Retail-facing price if different from `original`                                                                     |
| `net`               | integer | No       | Net/wholesale price                                                                                                  |

***

## Full example

```json theme={null}
[
  {
    "id": "product_abc",
    "internalName": "Desert Safari Adventure",
    "locale": "en",
    "timeZone": "Asia/Riyadh",
    "instantConfirmation": true,
    "allowFreesale": false,
    "options": [
      {
        "id": "option_standard",
        "internalName": "Standard Package",
        "availabilityType": "START_TIME",
        "cancellationCutoff": "24 hours before",
        "units": [
          {
            "id": "unit-adult",
            "internalName": "Adult",
            "type": "ADULT",
            "restrictions": { "minAge": 12 },
            "pricingFrom": [
              { "original": 25000, "currency": "SAR", "currencyPrecision": 2 }
            ]
          },
          {
            "id": "unit-child",
            "internalName": "Child (3–11)",
            "type": "CHILD",
            "restrictions": { "minAge": 3, "maxAge": 11 },
            "pricingFrom": [
              { "original": 15000, "currency": "SAR", "currencyPrecision": 2 }
            ]
          }
        ]
      },
      {
        "id": "option_premium",
        "internalName": "Premium Package",
        "availabilityType": "START_TIME",
        "units": [
          {
            "id": "unit-adult",
            "internalName": "Adult",
            "type": "ADULT",
            "pricingFrom": [
              { "original": 45000, "currency": "SAR", "currencyPrecision": 2 }
            ]
          }
        ]
      }
    ]
  }
]
```

***

## Implementation notes

<Warning>
  Never change `product.id` or `option.id` after your first sync. Seyaha uses these
  as permanent keys to match incoming products to existing activities. Changing them
  causes Seyaha to treat the product as new and create a duplicate.
</Warning>

<Note>
  If you support incremental sync, return only products where `updatedAt > updated_since`.
  If not, ignore the parameter and return the full catalog every time — Seyaha's hash
  deduplication will skip unchanged responses without writing anything to the database.
</Note>

* Timeout: **15 seconds** for sync calls, **5 seconds** for health probes
* Any non-2xx response increments the health failure counter. Return `401` for invalid credentials.
* For health probes (`?limit=1`), an empty array `[]` is a perfectly valid response.
