# Integrating with HotelSync — start here

This page is the playbook for any developer or LLM agent integrating against the HotelSync API for the first time. Read this before the per-endpoint pages.

## TL;DR

1. Get a partner `token` from HotelSync support and a property's `username` + `password`.
2. `POST /api/user/auth/login` with `{ token, username, password, remember: 0 }` → store the returned `pkey`.
3. On every other request, send `key` (= the `pkey`), `id_properties`, and the partner `token` in the JSON body. Sessions live for a few hours; on `401` re-login.
4. Reads live under `/api/.../data/...`. Writes live under `/api/.../insert/...`, `/api/.../edit/...`, `/api/.../delete/...`.
5. Subscribe to webhooks via `POST /api/webhooks/insert/webhook` if you want push notifications. See [webhooks/events.md](../webhooks/events.md) for payload shapes.

---

## Authentication model

HotelSync uses a session-based auth model with a partner-token gate.

| Credential | What it is | Where you get it | Lifetime |
|---|---|---|---|
| `token` | 40-char hex partner token | HotelSync support | Long-lived (years) |
| `username` + `password` | The property user's login | Property owner / via signup at `https://app.hotelsync.com/register/` | Long-lived |
| `pkey` (returned by login as `pkey`, sent in subsequent requests as `key`) | Session key | `POST /api/user/auth/login` | A few hours; refresh on `401` |
| `id_properties` | The integer property ID | Returned in the login response under `properties[]` | Permanent for the property |

### Login

```bash
curl -X POST 'https://app.hotelsync.com/api/user/auth/login' \
  -H 'Content-Type: application/json' \
  --data '{
    "token":    "<your-40-char-partner-token>",
    "username": "owner@example.com",
    "password": "<plaintext-password>",
    "remember": 0
  }'
```

Response (success):

```json
{
  "id_users":   11,
  "username":   "owner@example.com",
  "email":      "owner@example.com",
  "pkey":       "<40-char-hex-session-key>",
  "properties": [
    { "id_properties": 42, "name": "My Hotel", ... }
  ]
}
```

A `511` response means the account has 2FA enabled and the device/IP isn't recognised — handle the 2FA challenge before retrying.

### On every subsequent request

Pass these three fields in the JSON body of **every** call:

```json
{
  "token":         "<partner-token>",
  "key":           "<pkey-from-login>",
  "id_properties": 42,
  "...":           "<endpoint-specific fields>"
}
```

Forgetting `key` → `401`. Forgetting `id_properties` → `403`. Forgetting `token` → `400` (`Action not available through API`).

---

## Environments

| Environment | Base URL | When to use |
|---|---|---|
| **Production** | `https://app.hotelsync.com` | Live customer data. All real reservations, payments, fiscal records. |
| **Staging (beta)** | `https://beta.hotelsync.com` | Pre-release / integration testing. Same API surface as production but isolated test data. Use this while you build your integration. |

Both environments expose the exact same endpoint surface, request/response shapes, and webhook semantics. Each needs its own partner `token` and its own property logins — credentials are not portable between environments.

## Endpoint URL shape

Every endpoint follows the same structure (swap the host between production and staging):

```
POST https://app.hotelsync.com/api/<feature>/<verb>/<action>     # production
POST https://beta.hotelsync.com/api/<feature>/<verb>/<action>    # staging
Content-Type: application/json
```

| Verb | Meaning | Examples |
|---|---|---|
| `data` | Read (list / fetch) | `/api/reservations/data/reservations`, `/api/room/data/rooms` |
| `insert` | Create | `/api/reservation/insert/reservation`, `/api/webhooks/insert/webhook` |
| `edit` | Update | `/api/reservation/edit/basics`, `/api/avail/edit/avail` |
| `delete` | Delete (usually soft-delete) | `/api/extras/delete/extra` |

> All endpoints are `POST`. Even reads use `POST` because credentials and filters live in the request body, not the querystring.

---

## Recommended bootstrap order for a new integration

When you first integrate, fetch the property's reference data in this order — most other endpoints depend on these IDs:

1. `POST /api/user/auth/login` → `pkey`, `id_properties`
2. `POST /api/property/data/property` → property currency, timezone, name, address
3. `POST /api/room/data/rooms` → room types and individual rooms
4. `POST /api/pricingPlan/data/pricing_plans` → pricing plans (`id_pricing_plans`)
5. `POST /api/restrictionPlan/data/restriction_plans` → restriction plans
6. `POST /api/boards/data/boards` → meal plans (RO, BB, HB, FB, AI)
7. `POST /api/channels/data/channels` → OTA channels
8. `POST /api/cityTax/data/city_taxes` → city tax rules
9. `POST /api/extras/data/extras` → add-on services
10. `POST /api/policies/data/policies` → cancellation / payment policies

After that you can read availability, prices, restrictions, and reservations using the IDs from steps 3–9.

For the order specific user-facing pages call API endpoints, see the per-domain workflow guides under [`_workflows/`](./README.md).

---

## Reading availability / prices / restrictions

These three are always queried by date range + room type. The shapes are parallel:

| Read | Endpoint | Returns |
|---|---|---|
| Availability | `POST /api/avail/data/avail` | `{ id_room_types: { date: count } }` |
| Prices | `POST /api/prices/data/prices` | `{ id_pricing_plans: { id_room_types: { date: price } } }` |
| Restrictions | `POST /api/restrictions/data/restrictions` | `{ id_restriction_plans: { id_room_types: { date: { min_stay, max_stay, cta, ctd, closed } } } }` |

Writes mirror the read shapes. You only send the (room-type, date) pairs you want to change — partial updates are fine.

---

## Creating a reservation

The recommended path:

1. Read the property's reference data (above).
2. Read availability for the date range to confirm rooms are bookable.
3. `POST /api/engine/insert/reservation` for direct bookings (engine/booking-engine flow), or `POST /api/reservation/insert/reservation` for manual entries.
4. The response has the full reservation object. The same shape fires as a `reservation.insert` webhook event.

For step-by-step API call sequences for specific Vue pages (calendar, new-reservation form, invoice editor, etc.), see [`_workflows/reservations.md`](./reservations.md) and the other domain guides in this folder.

---

## Errors

The server returns standard HTTP statuses and a JSON body of the shape:

```json
{ "code": 400, "message": "Reason text" }
```

| Status | Common causes |
|---|---|
| `400` | Required field missing, validation rule failed, or `Action not available through API` (missing `token`). |
| `401` | Session `key` invalid or expired — re-login. |
| `403` | `id_properties` not in this user's list, or partner token doesn't have write access. |
| `422` | Validation error on a typed field (e.g. `status must be confirmed or pending`). |
| `500` | Server / DB error — retry once, then escalate. Includes errors like `Database error insert into sessions` from infrastructure issues. |
| `511` | 2FA challenge during login. |

Each per-endpoint MD has a **Possible errors** section listing the specific error messages that endpoint can produce, plus a **Business rules** table showing the PHP `if (...)` condition that triggers each one.

---

## Webhooks

If you want push notifications instead of polling, register a webhook with `POST /api/webhooks/insert/webhook`. HotelSync will POST `{ data_type, action, data }` to your URL on every relevant change. Full event taxonomy and payload shapes: [webhooks/events.md](../webhooks/events.md).

Important caveats:
- **Best-effort delivery.** 1-second curl timeout, no retries. Reconcile via the read endpoints if you suspect missed events.
- **No request signing.** If you need authentication, embed a static secret in the registered URL (querystring or path).
- **No ordering guarantees.** A burst of grid edits may deliver `avail.edit`, `prices.edit`, `restrictions.edit` in any order.

---

## Conventions

- **Snake-case** keys on both request and response (`id_properties`, `date_arrival`, `total_price`).
- **Foreign keys** are named `id_<table_name>` (`id_pricing_plans` → `pricing_plans` table). Each per-endpoint MD lists the canonical "list-me" endpoint for every FK in the **DB origin / valid values** column of the Verified Parameters table.
- **Dates** are `YYYY-MM-DD`. Datetimes are `YYYY-MM-DD HH:MM:SS` (server timezone — read from `/api/property/data/property`).
- **Money** is double-precision in the property's currency. Currency is set per-property; multi-currency reservations carry an `exchange_rate`.
- **Soft deletes.** Most tables have `is_deleted` (0/1) + `date_deleted`; the row stays in the DB. Reads filter `is_deleted = 0` automatically.
- **Boolean-ish ints.** `0` / `1` is the convention. Some legacy fields are `"0"` / `"1"` strings — coerce defensively.
- **Free-form bodies.** ~65 endpoints unpack `$_POST` wholesale, so they may accept fields beyond what the docs list. The docs document the fields the official Vue frontend sends; if you need a field that isn't documented, ask support before relying on it.

## Free-form body — likely additional fields

_This endpoint unpacks the JSON body wholesale (no `checkPost` calls in the action block), so it accepts more fields than the Verified Parameters table lists. The columns below come from the `webhooks` table — the endpoint's primary write target. Treat this as a **superset hint, not a guarantee**: not every column is honoured, and unknown keys may be silently ignored or stored in the row depending on the SQL the action emits._

Primary table: `webhooks`

| Field | Type | Default |
|---|---|---|
| `id_webhooks` | int | — |
| `url` | varchar | — |

