# Booking Window — Activity Agent API (Out API)

Version 1.0 · 2026-09-15

Server-to-server API for travel agents to search activities and transfers, check availability, book, cancel, and view their wallet ledger.

---

## 1. Basics

| Item | Value |
|---|---|
| Base URL | `https://<your-assigned-host>` (provided by Booking Window) |
| Path prefix | `/outApi/` |
| Method | `POST` for every endpoint |
| Request body | JSON, `Content-Type: application/json` |
| Response body | JSON |
| Auth | `x-api-key` header on **every** request |
| Dates | `YYYY-MM-DD` |
| Currency codes | ISO 4217, 3 letters (`INR`, `AED`, `USD`, …) |

### 1.1 Authentication

Send your Agent API Key in the `x-api-key` header:

```http
POST /outApi/activity_list HTTP/1.1
Host: <your-assigned-host>
Content-Type: application/json
x-api-key: AGT_XXXXXXXXXXXXXXXX
```

- No login, OTP or token is needed.
- **Keep the key secret.** Call the API from your server only, never from a browser or mobile app. Anyone holding the key can book against your wallet.
- If your key leaks, contact Booking Window to have it rotated.

### 1.2 Pricing

- All selling prices are returned in `guest_currency` (the request value, else your account currency, else `INR`).
- Your agency markup is **already applied**. Each priced row includes a `markup` object and a `conversion_charges` object that explain how the price was built.
- `create_booking` debits `total_price` from your wallet. Keep your wallet funded.

### 1.4 Inventory sources

Booking Window enables inventory sources per account. Your account can have one or both:

| Source | What it is | `source` on a result |
|---|---|---|
| `ACT` | Booking Window's own activities | `"ACT"` |
| `GT` | GlobalTix activities | `"GT"` |

- `activity_list` returns results only from the sources enabled on your account. Sending request parameters cannot add a source that is not enabled.
- Within your enabled sources, you can leave one out of a single request with `include_bw: 0` (own activities) or `include_gt: 0` (GlobalTix).
- `get_activity_by_id` serves both sources. Send `source: "ACT"` or `source: "GT"` to choose; see 3.4.
- `get_time_slots`, `get_transfer_by_ids` and `create_booking` currently serve `ACT` only.
- Where an endpoint takes `source` (`get_activity_by_id`, `get_time_slots`, `create_booking`, including `reference_object.source`), send the `source` value of the item you are acting on. Every endpoint handles it the same way:

| Situation | Response |
|---|---|
| Your account doesn't have the source | `403 source_not_granted` |
| You left the source out with `include_*: 0`, or it is temporarily off | `409 source_not_granted` |
| Your account has the source, but this endpoint doesn't serve it yet (for example `create_booking` with `source: "GT"`) | `400 source_not_supported` |
| `source` is not a known value | `400 bad_request` |
| No `source` sent | The endpoint uses the first source your account has that it serves (`ACT` before `GT`) |
- An account without `ACT` or `GT` in its settings gets no results from that source. A setting that has neither gets no results at all.
- To change which sources your account has, contact Booking Window.

### 1.5 Activity availability on your account

Separately from sources, Booking Window can withdraw an individual activity from
your account — for example when a supplier restricts a product to certain
resellers, or when an activity is taken off sale for everyone. Activities can
also be withdrawn by **country or city** (for example, every activity in one
city from one source). Either way, everything below applies in the same way.

- A withdrawn activity does not appear in `activity_list`, and `totalRecords`
  counts only what you can see, so paging stays consistent.
- `get_activity_by_id` answers `"No activity found for the given id."` and
  `get_activity_by_ids` returns the id under `missing_ids` — the same answer an
  id that does not exist gets. `get_time_slots` returns an empty list the same
  way.
- `reserve_booking` and `create_booking` answer `403 activity_not_permitted`, so
  a booking never fails silently or half-way.
- If an activity is withdrawn after you reserved it but before you confirmed,
  the confirm returns `403 activity_not_permitted` and the reservation is
  released for you — you do not need to call `release_booking`.
- Bookings you made **before** an activity was withdrawn are unaffected. They
  still appear in `bookings_list` and `booking_details`, and you can still call
  `cancellation_charges` and `cancel_booking` on them.
- If you expect an activity and cannot see it, quote the activity id and your
  `X-Request-Id` to Booking Window.

### 1.3 Standard response envelope

```json
{
  "status": true,
  "replyCode": "success",
  "replyMsg": "Human readable message",
  "cmd": "x_bookings_list",
  "data": [ ],
  "api_user_ref": "AGT_XXXXXXXXXXXXXXXX"
}
```

| Field | Meaning |
|---|---|
| `status` | `true` on success, `false` on failure |
| `replyCode` | Machine-readable result (see §4) |
| `replyMsg` | Human-readable message |
| `cmd` | Operation identifier |
| `data` | Payload (object or array) |
| `api_user_ref` | Your account reference (echoed on authenticated responses) |

Every response has an **`X-Request-Id`** header. Quote it when you contact support about a call.

---

## 2. Endpoint summary

| # | Endpoint | Purpose |
|---|---|---|
| 1 | `/outApi/country_list` | Countries (reference data) |
| 2 | `/outApi/city_list` | Cities (reference data) |
| 3 | `/outApi/activity_list` | Search activities |
| 4 | `/outApi/get_activity_by_id` | One activity's full detail for a date |
| 5 | `/outApi/get_time_slots` | Time slots for an activity |
| 6 | `/outApi/get_transfer_by_ids` | Transfer packages by id |
| 6a | `/outApi/reserve_booking` | Hold the stock for a few minutes, no charge |
| 7 | `/outApi/create_booking` | Book (debits wallet); with a `booking_ref_no` it confirms a hold |
| 7a | `/outApi/release_booking` | Give a hold back |
| 8 | `/outApi/bookings_list` | Your bookings |
| 9 | `/outApi/booking_details` | One booking |
| 10 | `/outApi/cancellation_charges` | Cost of cancelling today (read-only) |
| 11 | `/outApi/cancel_booking` | Cancel (refunds wallet) |
| 12 | `/outApi/transactions_list` | Your wallet ledger |

**Typical flow:** `country_list` → `city_list` → `activity_list` → `get_activity_by_id` → `get_time_slots` → **`reserve_booking` → `create_booking`** → `booking_details` → `cancellation_charges` → `cancel_booking` → `transactions_list`.

**Reserve first when you take payment from a customer.** `reserve_booking`
holds the stock and tells you today's price, so the seats are yours while the
customer pays and you are never charged for a booking you could not complete.
`create_booking` on its own still works and books in one step.

---

## 3. Endpoints

### 3.1 `POST /outApi/country_list`

| Param | Type | Req. | Description |
|---|---|---|---|
| `keyword` | string | No | Contains-match on country name |
| `page` | int | No | Default 1 |
| `limit` | int | No | Default 10 |

```json
{ "keyword": "ind", "page": 1, "limit": 10 }
```

Response: `data[]` of countries (with currency) and a `pagination` object `{ total, page, limit, total_pages }`.

---

### 3.2 `POST /outApi/city_list`

| Param | Type | Req. | Description |
|---|---|---|---|
| `keyword` | string | No | Prefix match on city name (`"dub"` → Dubai) |
| `country_id` | int \| string \| int[] | No | `1`, `"1,2,3"` or `[1,2]` |
| `featured` | string | No | `"1"` for featured cities only |
| `page` | int | No | Default 1 |
| `limit` | int | No | Default 10, max 100 |

```json
{ "keyword": "dub", "country_id": [1], "page": 1, "limit": 10 }
```

Response: `data[]` of cities, `pagination`.

---

### 3.3 `POST /outApi/activity_list`

| Param | Type | Req. | Description |
|---|---|---|---|
| `keyword` | string | No | Free-text search |
| `country` | string | No | Country name |
| `city` | string | No | City name |
| `from_date` | date | No | Travel date (alias `travel_date`); default today |
| `is_combo` | 0 \| 1 | No | Combo activities only |
| `guest_currency` | string | No | Currency for prices |
| `page` | int | No | Default 1 |
| `limit` | int | No | Page size; enables pagination |
| `include_bw` | 0 \| 1 | No | `0` leaves out own (`ACT`) activities; default 1 (see 1.4) |
| `include_gt` | 0 \| 1 | No | `0` leaves out GlobalTix activities; default 1 (see 1.4) |

```json
{
  "keyword": "Zoo",
  "country": "Singapore",
  "city": "Singapore",
  "from_date": "2026-10-10",
  "guest_currency": "INR",
  "page": 1,
  "limit": 20
}
```

Response (trimmed):

```json
{
  "status": true,
  "replyCode": "success",
  "replyMsg": "Successfully fetched activities",
  "data": [
    {
      "id": 123,
      "source": "ACT",
      "name": "Singapore Zoo",
      "country": "Singapore",
      "city": "Singapore",
      "adult_price": 2450.00,
      "child_price": 1650.00,
      "infant_price": 0,
      "currency": "SGD",
      "guest_currency": "INR",
      "is_bookable": 1,
      "non_bookable_reason": null,
      "inventory_source": "time_slot",
      "time_slots": [ ],
      "categories": [ ],
      "markup": { }
    }
  ],
  "totalRecords": 42,
  "ownTotalRecords": 42,
  "page": 1,
  "limit": 20,
  "guest_currency": "INR",
  "cmd": "x_activity_list"
}
```

Notes: rows with `is_bookable: 0` cannot be booked for `from_date`; `non_bookable_reason` says why.

Each list item is a short summary, and `ACT` and `GT` items have the same keys (a value is `null` when that source doesn't provide it). `time_slots` only includes slots for `from_date`. Each slot has a `category_id`: on `GT` slots it is the option the slot belongs to, and the slot's prices are that option's prices; on `ACT` slots it is `null`. Highlights, inclusions and exclusions, gallery and video URLs, cancellation policy items, guides and add-ons are returned only by `get_activity_by_id`.

Results contain only the sources enabled on your account (1.4). `limit` and `totalRecords`/`ownTotalRecords` apply to own activities. GlobalTix results are added on top of them for the same `page`, and GlobalTix does not report a total. If GlobalTix is slow or unavailable, the list is returned without GlobalTix results rather than waiting; retry later to include them.

---

### 3.4 `POST /outApi/get_activity_by_id`

| Param | Type | Req. | Description |
|---|---|---|---|
| `activity_id` | int | **Yes** | Activity id (for GT, the GlobalTix product id) |
| `source` | `ACT` \| `GT` | No | Which catalogue `activity_id` belongs to. Use the `source` from `activity_list`. See below for the default. |
| `daydate` | date | No | Travel date (aliases `from_date`, `travel_date`); default today |
| `guest_currency` | string | No | Currency for prices |
| `country`, `city`, `keyword`, `is_combo` | — | No | Optional extra filters (`ACT` only) |

```json
{ "activity_id": 4, "source": "ACT", "travel_date": "2026-09-17", "guest_currency": "INR", "country": "Thailand", "city": "Bangkok" }
```

```json
{ "activity_id": 53082, "source": "GT", "daydate": "2026-10-10", "guest_currency": "INR" }
```

Response: the `activity_list` item plus `highlights`, `inclusion`, `exclusion`, `restrictions`, `recommendation`, `zone`, `icon_url`, `other_image_urls`, `video_url`, `last_entry_time`, `meal_activity`, `cancellation_policy_id`, `cancellation_policy_items`, `activity_guides` and `addon`. `time_slots` only includes slots for the travel date, in the same shape as in `activity_list`.

**Supplier questions (`categories[].questions`, detail only).** Some GlobalTix options can't be reserved without answers, for example gender, name, date of birth or pickup point. Each category on this response has a `questions` array. It is `[]` when the option asks nothing, which is always the case for ACT. Show one input per question. Then send the answers once for the whole booking as `questions` on `reserve_booking`: `[{ "id": 661959, "questionCode": "GENDER", "answer": "M" }]`. If you reserve without answers to a category that has questions, you get `400 gt_questions_required`, and the error's `data.questions` has the same shape.

```json
"questions": [
  { "id": 661959, "question": "Gender", "type": "OPTION", "question_code": "GENDER",
    "options": ["M", "F"], "option_list": [{ "key": "M", "value": "Male" }, { "key": "F", "value": "Female" }] },
  { "id": 661960, "question": "First Name", "type": "FREETEXT", "question_code": "FIRST_NAME", "options": [], "option_list": [] }
]
```

`type` is `FREETEXT`, `DATE` or `OPTION`. For `OPTION`, answer with the `key`. `activity_list` does not include questions.

### Image URLs

`image_url` (and, on the detail response, `icon_url` and `other_image_urls`) is always a **complete, absolute URL** you can put straight into an `<img src>` — no base to prepend, for either source:

```json
"image_url": "https://testpackage.fdking.com/activity/uploads/gt/68f2c4a1-f76c-4c1e-98ec-efb33428481f.png"
"image_url": "https://bwdevuploads.blob.core.windows.net/activity/img_1749466103251.jpg"
```

Two things worth knowing:

- It is safe to cache or store the URL. A GlobalTix image occasionally comes back as `.../gt/image/<id>` instead — that is the same image, fetched on demand, and it keeps working; a later response may give you the `/uploads/gt/<id>.<ext>` form for it.
- GlobalTix images are served through us, never linked directly — the original URLs are token-protected and would not load for you.

An activity with no image returns `null`, not an empty string.

**How `source` is chosen when you don't send it:** own activities (`ACT`) if your account has them, otherwise GlobalTix (`GT`). The same number can be a valid id in both catalogues, so send `source` whenever your account has both.

| Situation | Result |
|---|---|
| `source` names a source your account doesn't have | `403 source_not_granted` |
| `source` is not `ACT` or `GT` | `400 bad_request` |
| `source: "GT"` with `include_gt: 0` | `409 source_not_granted` |
| id not found in that catalogue | `200` with `data: []` |

---

### 3.5 `POST /outApi/get_time_slots`

| Param | Type | Req. | Description |
|---|---|---|---|
| `ref_id` | int | **Yes** | Activity id |
| `ref_date` | date | No | Slots for this date; omitted = today onward |
| `guest_currency` | string | No | Currency for prices |

```json
{ "ref_id": 123, "ref_date": "2026-10-10", "guest_currency": "INR" }
```

Response: `data[]` of slots (id, times, date, availability, prices), `totalRecords`, `ref_id`, `guest_currency`. An unknown or unavailable activity returns `status: true` with an empty `data`.

Pass the slot `id` as `time_slot` (and the chosen `cancellation_policy_id`, if any) to `create_booking`.

---

### 3.6 `POST /outApi/get_transfer_by_ids`

| Param | Type | Req. | Description |
|---|---|---|---|
| `ids` | int[] \| string \| int | **Yes** | `[12,34]`, `"12,34"` or `12`; max 100 |
| `travel_date` | date | No | Alias `daydate` / `from_date`. Without it, base prices, no slot data |
| `guest_currency` | string | No | Currency for prices |

```json
{ "ids": [12, 34], "travel_date": "2026-10-10", "guest_currency": "INR" }
```

Response: `data[]` of transfer packages, plus `requested_ids` and `missing_ids`. A package closed for the date is still returned with `booking_closed: 1`.

---

### 3.7 `POST /outApi/create_booking`

Books an activity and **debits your wallet** by `total_price`.

**Confirming a hold.** If you reserved first (3.7a), send only the reference:

```json
{ "booking_ref_no": "ACTHOLD1", "total_price": 550 }
```

The stock is already held, so this only charges your wallet and confirms the
booking. `total_price` is optional; when sent it must equal the held price, or
the call is refused with `price_mismatch` (409) carrying the price we hold.
Other refusals: `already_confirmed`, `reservation_released`,
`reservation_expired` (the hold lapsed and its units went back — reserve again),
`booking_cancelled`, `insufficient_balance`, `not_found`.

**Booking in one step** — no `booking_ref_no` — uses the parameters below and is
unchanged.

| Param | Type | Req. | Description |
|---|---|---|---|
| `reference_type` | int | **Yes** | Always `1` (activity) |
| `reference_id` | int | **Yes** | Activity id (or `reference_object.id`) |
| `from_date` | date | **Yes** | Travel date (or `reference_object.activity_date`) |
| `to_date` | date | No | End date |
| `no_of_travellers` | object | **Yes** | `{ adult, child, infant, travellers }`; at least one adult or child |
| `total_price` | number | **Yes** | Selling price, > 0 — the amount debited |
| `currency` | string | No | Currency of `total_price` |
| `reference_object` | object | **Yes** | The activity snapshot you are booking (id, source, inventory_source, selected category, selected time slot, travellers) |
| `time_slot` | int | No | Explicit time-slot id |
| `category_id` | int | No | Explicit category id |
| `cancellation_policy_id` | int | No | Chosen cancellation policy |
| `supplier_cancellation_policy_id` | int | No | Supplier policy id, if given |
| `guest_info` | object | Recommended | `{ first_name, last_name, email, contact_no }` |
| `title`, `city`, `country`, `time_from`, `time_to`, `transfer_mode`, `pickup_location`, `drop_location`, `price`, `markup`, `discount` | — | No | Descriptive fields stored with the booking |

Units booked = `no_of_travellers.travellers`, otherwise `adult + child` (infants do not use a unit).

```json
{
  "reference_type": 1,
  "reference_id": 4,
  "from_date": "2026-10-10",
  "to_date": "2026-10-10",
  "currency": "INR",
  "time_slot": 133267,
  "cancellation_policy_id": 43,
  "no_of_travellers": { "adult": 1, "child": 0, "infant": 0, "travellers": 1 },
  "guest_info": {
    "first_name": "John", "last_name": "Doe",
    "email": "john.doe@example.com", "contact_no": "9999999999"
  },
  "total_price": 1349.95,
  "reference_object": {
    "id": "4",
    "source": "ACT",
    "activity_date": "2026-10-10",
    "inventory_source": "time_slot",
    "selected_category_id": 8,
    "isSelectedTimeSlotObject": { "id": 133267, "inventory_date": "2026-10-10" },
    "no_of_travellers": { "adult": 1, "child": 0, "infant": 0, "travellers": 1 }
  }
}
```

Success:

```json
{
  "status": true,
  "replyCode": "success",
  "replyMsg": "Activity booking created successfully",
  "cmd": "x_create_booking",
  "data": {
    "booking_id": 9001,
    "booking_ref_no": "ACTAB12CD",
    "reference_id": 4,
    "inventory_source": "time_slot",
    "category_id": 8,
    "time_slot_id": 133267,
    "from_date": "2026-10-10",
    "to_date": "2026-10-10",
    "units": 1,
    "total_price": 1349.95,
    "currency": "INR",
    "wallet_balance": 48650.05
  },
  "api_user_ref": "AGT_XXXXXXXXXXXXXXXX"
}
```

A confirmation email with the voucher is sent to your registered email.

Common failures: `insufficient_balance`, `duplicate_booking` (same booking within 1 minute), `bad_request` (validation), no inventory.

**Do not retry blindly.** If a call times out, check `bookings_list` before trying again.

---

### 3.7a `POST /outApi/reserve_booking`

Holds the stock for an activity **without charging you**, and re-prices it.

Takes the same body as `create_booking` (see 3.7). `travel_date` is accepted for
the date, as on the search routes.

What it does, in one step: checks your wallet can cover the price (nothing is
debited), freezes the units on the row you are booking, prices the activity
again from the live catalogue, and writes the booking in `RESERVED` state with
an expiry.

```json
{
  "reference_type": 1,
  "reference_id": 4,
  "source": "ACT",
  "travel_date": "2026-09-18",
  "no_of_travellers": { "adult": 1, "child": 0, "infant": 0, "travellers": 1 },
  "total_price": 550,
  "currency": "INR",
  "guest_info": { "first_name": "Harish", "last_name": "K", "email": "harish@example.com", "contact_no": "+919999999999" },
  "reference_object": { "id": 4, "source": "ACT", "activity_date": "2026-09-18", "inventory_source": "base", "isSelectedTimeSlotObject": { "id": 133449 } }
}
```

Response `data`:

| Field | Meaning |
|---|---|
| `booking_ref_no` | Send this to `create_booking` to confirm, or to `release_booking` |
| `hold_expires_at`, `hold_minutes` | When the hold lapses, and the window it was given |
| `total_price` | **What confirming will charge** |
| `requested_total_price` | What you sent |
| `price_changed` | `true` when the two differ, `null` when we could not re-price |
| `price_check` | `quoted` = re-priced from the catalogue; `unavailable` = your own figure is held |
| `units`, `time_slot_id`, `category_id`, `inventory_source` | The stock being held |
| `wallet_balance` | Your balance, unchanged |

A changed price does **not** cancel the hold: the seats stay yours and you
decide whether to go on at the new price.

Refusals: `insufficient_balance`, `insufficient_inventory` (with what is left),
`no_time_slot` / `no_inventory`, `duplicate_booking` (same activity and amount
within a minute), `source_not_granted`, `source_not_supported` (GlobalTix is not
bookable yet).

---

### 3.7b `POST /outApi/release_booking`

Gives a hold back before it expires. Nothing was charged, so nothing is
refunded.

| Param | Type | Req. | Description |
|---|---|---|---|
| `booking_ref_no` | string | **Yes** | The reservation to release |

Returns `booking_ref_no`, `reservation_status`, `units_released`,
`inventory_restored`. Releasing a hold that is already released or expired
answers `200` with `units_released: 0`, so retrying after a timeout is safe. A
confirmed booking cannot be released (`already_confirmed`) — use
`cancel_booking`, which prices the cancellation and refunds your wallet.

A hold nobody confirms is released automatically once `hold_expires_at` passes.

---

### 3.8 `POST /outApi/bookings_list`

| Param | Type | Req. | Description |
|---|---|---|---|
| `booking_ref_no` | string | No | Exact reference |
| `reference_id` | int | No | Activity id |
| `status` | string | No | `PENDING` \| `CONFIRMED` \| `CANCELLED` (or `0` \| `1` \| `2`), or a hold state: `RESERVED` \| `RELEASED` \| `EXPIRED` |
| `include_reservations` | 0 \| 1 | No | `1` lists holds alongside bookings. Default `0` — holds are left out |
| `from_date` / `to_date` | date | No | Date range |
| `date_type` | string | No | `booking_date` (default) \| `travel_date` |
| `search` | string | No | Free-text search |
| `sort_order` | string | No | `DESC` (default) \| `ASC` |
| `page` | int | No | Default 1 |
| `limit` | int | No | Default 20, max 100 |

```json
{ "status": "CONFIRMED", "from_date": "2026-10-01", "to_date": "2026-10-31", "date_type": "travel_date", "page": 1, "limit": 20 }
```

Response: `data[]` of bookings and `pagination { total, page, limit, total_pages }`.

Booking fields: `id, booking_ref_no, source, supplier, title, city, country, image, description, reference_type, reference_id, from_date, to_date, time_from, time_to, transfer_mode, pickup_location, drop_location, guest_info, no_of_travellers, price, price_inr, markup, discount, total_price, total_price_inr, currency, confirmation_no, confirmation_note, voucher_pdf, status, ops_confirmed, reservation_status, hold_expires_at, booking_status (PENDING | CONFIRMED | CANCELLED | RESERVED | RELEASED | EXPIRED), cancelation_note, cancellation_amount, refund_amount, refund_status, created`.

**`voucher_pdf`.** Every booking gets a guest-facing voucher URL in this field, whoever fulfils it — a GlobalTix e-ticket for `supplier: "GT"`, our own PDF voucher for `"ACT"`. It is also emailed to you when the booking is made.

### Emails we send

| When | Subject | Goes to |
|---|---|---|
| `create_booking` succeeds | `Booking for <activity> - Ref No <ref>` | you, and the guest when `guest_info.email` is set |
| A GlobalTix e-ticket is issued after that mail went out | `E-Ticket ready - …` | same |
| `cancel_booking`, or our operations team cancels | `Booking cancelled - …` | same |

The guest is added to `To`, not BCC, so you can see they were written to. Leave `guest_info.email` out and only you are mailed.

**For a `GT` booking the confirmation carries two documents**: the attached PDF is a **summary** of the booking, and the **e-ticket is what admits the guest**. GlobalTix sometimes issues the e-ticket a moment after the booking; when that happens the confirmation says so and a second short email follows with the ticket, so you do not need to poll.

It can be `null` on a booking that has just been created: GlobalTix issues its e-ticket asynchronously, and our own PDF is rendered just after the booking. In both cases the **next `booking_details` call produces it** — read the booking again a minute later rather than polling. Once stored the URL never changes, so it is safe to cache.

`source` is always `ACT` — it identifies the Out API channel, not the supplier. **`supplier` is who fulfils the booking**: `GT` for GlobalTix (its reference is in `confirmation_no`) or `ACT` for our own inventory. Bookings made before 2026-09-19 have `supplier: null`.


---

### 3.9 `POST /outApi/booking_details`

| Param | Type | Req. | Description |
|---|---|---|---|
| `booking_ref_no` | string | One of | Booking reference |
| `booking_id` | int | One of | Booking id |

```json
{ "booking_ref_no": "ACTAB12CD" }
```

Response: one booking with the list fields plus `reference_object`, `confirmed_pax`, `support_info`, `driver_details`, and `cancellation_charges` (same shape as §3.10 `data`; `null` for a cancelled booking).

Unknown booking → `404 not_found`.

---

### 3.10 `POST /outApi/cancellation_charges`

Read-only. Nothing is cancelled.

| Param | Type | Req. | Description |
|---|---|---|---|
| `booking_ref_no` | string | **Yes** | Booking reference |

```json
{ "booking_ref_no": "ACTAB12CD" }
```

Response:

```json
{
  "status": true,
  "replyCode": "success",
  "replyMsg": "Cancellation charges calculated successfully",
  "cmd": "x_cancellation_charges",
  "data": {
    "booking_ref_no": "ACTAB12CD",
    "booking_status": "CONFIRMED",
    "from_date": "2026-11-20",
    "currency": "INR",
    "total_price": 1000,
    "today": "2026-09-15",
    "days_before_travel": 66,
    "cancellation_policy_id": 43,
    "is_refundable": true,
    "cancellation_charge": 0,
    "refund_amount": 1000,
    "charge_reason": "POLICY",
    "applied_policy_item_id": 4,
    "policies": [
      {
        "id": 4, "from_day": 30, "to_day": 365,
        "cancel_type": 0, "deduction_type": "PERCENT", "deduction": 0,
        "charge_from_date": "2025-11-20", "charge_to_date": "2026-10-21",
        "cancellation_charge": 0, "refund_amount": 1000, "is_applicable": true
      }
    ]
  }
}
```

`deduction_type`: `PERCENT` | `FIXED` | `UNKNOWN` (unknown charges 100%).
`charge_reason`: `POLICY` | `NO_POLICY` | `TRAVEL_DATE_PASSED` | `NO_MATCHING_POLICY_ITEM` (the last three charge 100%).
Already cancelled → `400 already_cancelled`.

---

### 3.11 `POST /outApi/cancel_booking`

Cancels the booking. Today's charge is kept; the rest is **credited to your wallet immediately**.

| Param | Type | Req. | Description |
|---|---|---|---|
| `booking_ref_no` | string | **Yes** | Booking reference |
| `remarks` | string | No | Reason for cancellation |

```json
{ "booking_ref_no": "ACTAB12CD", "remarks": "Guest unwell" }
```

Success:

```json
{
  "status": true,
  "replyCode": "success",
  "replyMsg": "Booking cancelled successfully",
  "cmd": "x_cancel_booking",
  "data": {
    "booking_ref_no": "ACTAB12CD",
    "booking_status": "CANCELLED",
    "currency": "INR",
    "total_price": 1000,
    "cancellation_amount": 250,
    "refund_amount": 750,
    "charge_reason": "POLICY",
    "applied_policy_item_id": 3,
    "days_before_travel": 10,
    "wallet_transaction_id": 7777,
    "wallet_balance": 50750,
    "cancelled_by": { "id": 7, "name": "…", "role": "API_PARTNER", "cancelled_at": "…", "remarks": "Guest unwell" }
  }
}
```

Tip: call `cancellation_charges` first and show the charge to your user.

---

A hold is not a booking: cancelling a `RESERVED`, `RELEASED` or `EXPIRED`
reference is refused with `not_confirmed` (409). Use `release_booking` while the
hold is live; an expired or released hold needs nothing.

#### Cancelling a GlobalTix (`GT`) booking

Same request, same response shape, plus `supplier`, `supplier_reference` and
`supplier_cancelled: true`. Two things differ:

- **The tickets are cancelled at GlobalTix first.** Only once GlobalTix confirms
  does your wallet get credited, so a successful response means both happened.
- **The refund follows GlobalTix's terms for that product**, recorded when you
  reserved it — not our own cancellation policy. The response says
  `"cancellation_policy_source": "GT"`, and `cancellation_charges` prices the
  same way, so you can still show the figure before cancelling.

Refusals specific to GT, none of which move any money:

| `replyCode` | HTTP | Meaning |
|---|---|---|
| `gt_not_cancellable` | 409 | The product is non-cancellable at GlobalTix. Nothing to retry. |
| `gt_cancel_refused` | 409 | GlobalTix declined the cancellation. The booking is still live. |
| `gt_unavailable` | 503 | GlobalTix did not answer. Nothing changed — safe to retry. |
| `gt_no_reference` | 409 | The booking has no supplier reference; contact support. |
| `gt_cancelled_not_settled` | 500 | The tickets **were** cancelled but the refund did not complete. **Do not retry** — support is notified and will settle it. |

---

### 3.12 `POST /outApi/transactions_list`

| Param | Type | Req. | Description |
|---|---|---|---|
| `account_type` | string | No | Ledger account type |
| `drcr` | string | No | `DR` (debit) \| `CR` (credit) |
| `booking_id` | int | No | Filter by booking |
| `reference_id` | string | No | Filter by reference |
| `from_date` / `to_date` | date | No | Date range |
| `page` | int | No | Default 1 |
| `limit` | int | No | Default 20, max 100 |

```json
{ "drcr": "DR", "from_date": "2026-10-01", "to_date": "2026-10-31", "page": 1, "limit": 20 }
```

Response: `data[]` with `id, booking_id, booking_type, reference_id, account_type, drcr, amount, available_balance, currency, narration, createdAt`, and `pagination`. `currency` is your account (wallet) currency.

---

## 4. Errors

Error body:

```json
{ "status": false, "replyCode": "unauthorized", "replyMsg": "Invalid API key", "cmd": "x_activity_list" }
```

| HTTP | replyCode | When |
|---|---|---|
| 400 | `bad_request` | Missing/invalid parameter |
| 400 | `insufficient_balance` | Wallet balance below `total_price` |
| 400 | `duplicate_booking` | Same booking repeated within 1 minute |
| 400 | `already_cancelled` | Booking is already cancelled |
| 400 | `source_not_supported` | Your account has this source, but this endpoint doesn't serve it yet (see 1.4) |
| 403 | `source_not_granted` | Your account doesn't have this source (see 1.4) |
| 403 | `activity_not_permitted` | This activity is not available to sell on your account (see 1.5) |
| 409 | `source_not_granted` | The source was left out with `include_*: 0` or is temporarily unavailable |
| 401 | `unauthorized` | `x-api-key` missing or invalid |
| 403 | `unauthorized` | Account inactive or not verified |
| 404 | `not_found` | Booking not found |
| 409 | — | Booking/inventory state conflict during cancellation; retry later or contact support |
| 500 | `error` | Server error; contact support with `X-Request-Id` |

Auth error messages:

| HTTP | replyMsg |
|---|---|
| 401 | `Missing 'x-api-key' header.` |
| 401 | `Invalid API key` |
| 403 | `API User account is inactive` |
| 403 | `API User account is not verified` |

---

## 5. cURL example

```bash
curl -X POST "https://<your-assigned-host>/outApi/activity_list" \
  -H "Content-Type: application/json" \
  -H "x-api-key: AGT_XXXXXXXXXXXXXXXX" \
  -d '{"keyword":"Zoo","city":"Singapore","from_date":"2026-10-10","guest_currency":"INR","page":1,"limit":20}'
```

## 6. Postman

Import `Activity_Agent_Out_API.postman_collection.json`, set the collection variables `baseUrl` and `api_key`, then run the folders in order. `Create Booking` stores `booking_ref_no` for the next requests.

`Activity_ACT_Booking_API.postman_collection.json` is the reserve → confirm →
release flow on its own: `Reserve Booking` stores `booking_ref_no` and the held
price, so `Create Booking (confirm the hold)` and `Release Booking` run without
editing anything.

## 7. Support

Include the `X-Request-Id` header value, endpoint, and time of the call.
