> For the complete documentation index, see [llms.txt](https://syticks.gitbook.io/merpi-by-syticks/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://syticks.gitbook.io/merpi-by-syticks/api-reference/hold-reserve-ticket-inventory/hold-reserve-transport-tickets.md).

# Hold/Reserve Transport Tickets

### Endpoint

```
POST /api/v1/merpi/validate
```

### Description

The validation endpoint creates temporary reservations (holds) on bus tickets, preventing other customers from purchasing the same seats or boarding slots while your customer completes their transaction. This is essential for maintaining seat inventory accuracy and providing a seamless booking experience.

#### How It Works

1. **Reserve**: Customer selects seats/route → Your system calls `/validate` → Inventory is held
2. **Checkout**: Customer completes payment details (3-minute window)
3. **Complete**: Submit hold to buy endpoint → Reservation converts to confirmed booking
4. **Auto-expire**: If no purchase is made, hold automatically releases after TTL

#### Common Use Cases

* **Seat Selection**: Reserve specific seats on timed schedules while customer enters payment information
* **Route Booking**: Hold boarding slots on random schedules for flexible departure times
* **Multi-Passenger Reservations**: Reserve multiple seats simultaneously for group travel
* **Multi-Step Checkout**: Maintain seat locks across multiple payment/form pages

***

### Understanding Schedule Types

Transport tickets support two schedule types, each with different reservation requirements:

#### Timed Schedules

**What it is**: Buses with fixed departure times and assigned seating (e.g., luxury coaches, intercity buses).

**When to use**: When customers need to select specific seats and travel at a precise departure time.

**Required information**: Specific seat numbers, exact departure timestamp.

#### Random Schedules

**What it is**: Buses with flexible boarding throughout the day without pre-assigned seating (e.g., commuter shuttles, frequent city routes).

**When to use**: When customers book general boarding rights for a specific route and date, without needing specific seats or exact times.

**Required information**: Bus ID, route ID, departure date.

***

### Request Parameters

#### Headers

| Parameter       | Type   | Required | Description                   |
| --------------- | ------ | -------- | ----------------------------- |
| `Content-Type`  | string | Yes      | Must be `application/json`    |
| `Authorization` | string | Yes      | Your API authentication token |

#### Request Body

| Field                        | Type    | Required    | Description                                                                                                              |
| ---------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `tickets`                    | array   | Yes         | Array of ticket reservation requests. Minimum 1, maximum 10 items.                                                       |
| `tickets[].ticket_type`      | string  | Yes         | Must be `"bus"` for transport reservations.                                                                              |
| `tickets[].resource_id`      | integer | Yes         | Schedule identifier. References `schedules.id` from the transport schedule listing endpoint. Must be a positive integer. |
| `tickets[].quantity`         | integer | Yes         | Number of tickets to reserve. Minimum value: 1.                                                                          |
| `tickets[].metadata`         | object  | **Yes**     | **Required for bus tickets.** Contains schedule-specific reservation details (see below).                                |
| `customer_info`              | object  | Yes         | Customer identification for hold ownership verification.                                                                 |
| `customer_info.email`        | string  | Conditional | Customer email address. Required if `phone_number` is not provided.                                                      |
| `customer_info.phone_number` | string  | Conditional | Customer phone number (11-13 digits). Required if `email` is not provided.                                               |

#### Metadata Structure by Schedule Type

**Timed Schedule Metadata**

| Field                | Type              | Required | Description                                                                                                                              |
| -------------------- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `schedule_type`      | string            | Yes      | Must be `"timed"`                                                                                                                        |
| `schedule_timestamp` | string            | Yes      | Departure date and time in `Y-m-d H:i` format (e.g., `"2026-03-30 07:00"`). Must match an actual schedule departure time.                |
| `seat_ids`           | array of integers | Yes      | Array of seat IDs to reserve. Must contain at least one seat ID. Each ID must correspond to an available seat on the specified schedule. |

**Example:**

```json
{
  "schedule_type": "timed",
  "schedule_timestamp": "2026-03-30 07:00",
  "seat_ids": [72, 73]
}
```

**Random Schedule Metadata**

| Field            | Type    | Required | Description                                                                    |
| ---------------- | ------- | -------- | ------------------------------------------------------------------------------ |
| `schedule_type`  | string  | Yes      | Must be `"random"`                                                             |
| `bus_id`         | integer | Yes      | Bus identifier. References `transport_buses.id` from the bus listing endpoint. |
| `route_id`       | integer | Yes      | Route identifier. References `routes.id` from the route listing endpoint.      |
| `departure_date` | string  | Yes      | Travel date in `Y-m-d` format (e.g., `"2026-04-02"`).                          |

**Example:**

```json
{
  "schedule_type": "random",
  "bus_id": 4,
  "route_id": 27,
  "departure_date": "2026-04-02"
}
```

**Important Notes**

* **Customer Info**: At least one of `email` or `phone_number` must be provided. Both can be included.
* **Resource ID Format**: For bus tickets, `resource_id` must be a positive integer matching a schedule ID.
* **Metadata Requirement**: Unlike entertainment tickets, bus tickets **always require metadata** with complete schedule information.
* **Ownership Verification**: The `customer_info` values act as a session token—they must match exactly when using the hold in a buy request or when releasing the hold early.
* **Seat Availability**: For timed schedules, all specified `seat_ids` must be available. If any seat is already taken or held, the entire reservation fails.

***

### Response Structure

#### Success Response (201 Created)

| Field               | Type    | Description                            |
| ------------------- | ------- | -------------------------------------- |
| `success`           | boolean | Always `true` for successful requests. |
| `status`            | integer | HTTP status code (201).                |
| `message`           | string  | Human-readable success message.        |
| `data`              | object  | Contains the reservation details.      |
| `data.reservations` | array   | Array of created reservation objects.  |

**Reservation Object Fields**

| Field               | Type              | Description                                                                            |
| ------------------- | ----------------- | -------------------------------------------------------------------------------------- |
| `reservation_id`    | string (UUID)     | Unique identifier for this hold. **Use this value in buy requests.**                   |
| `ticket_type`       | string            | Type of ticket reserved (will be `"bus"`).                                             |
| `resource_id`       | string            | The schedule ID that was reserved (returned as string even though input is integer).   |
| `quantity`          | integer           | Number of tickets reserved.                                                            |
| `status`            | string            | Current hold status. Values: `"active"`, `"converted"`, `"released"`, `"expired"`.     |
| `expires_at`        | string (datetime) | ISO 8601 timestamp when this hold will automatically expire. Includes timezone offset. |
| `seconds_remaining` | integer           | Number of seconds until expiration (calculated at response time).                      |

***

### Example Requests & Responses

#### Example 1: Reserve Timed Schedule Seat

**Request:**

```json
POST /api/v1/merpi/validate
Content-Type: application/json

{
  "tickets": [
    {
      "ticket_type": "bus",
      "resource_id": 2,
      "quantity": 1,
      "metadata": {
        "schedule_type": "timed",
        "schedule_timestamp": "2026-03-30 07:00",
        "seat_ids": [72]
      }
    }
  ],
  "customer_info": {
    "email": "user@example.com",
    "phone_number": "08085825362"
  }
}
```

**Response (201 Created):**

```json
{
  "success": true,
  "status": 201,
  "message": "Holds created",
  "data": {
    "reservations": [
      {
        "reservation_id": "e6011a19-97ea-48cc-823f-a0fc909cec6c",
        "ticket_type": "bus",
        "resource_id": "2",
        "quantity": 1,
        "status": "active",
        "expires_at": "2026-04-02T10:29:26+01:00",
        "seconds_remaining": 179
      }
    ]
  }
}
```

***

#### Example 2: Reserve Multiple Timed Schedule Seats

**Request:**

```json
POST /api/v1/merpi/validate
Content-Type: application/json

{
  "tickets": [
    {
      "ticket_type": "bus",
      "resource_id": 2,
      "quantity": 1,
      "metadata": {
        "schedule_type": "timed",
        "schedule_timestamp": "2026-03-30 07:00",
        "seat_ids": [72, 73, 74]
      }
    }
  ],
  "customer_info": {
    "email": "family@example.com",
    "phone_number": "08085825362"
  }
}
```

**Response (201 Created):**

```json
{
  "success": true,
  "status": 201,
  "message": "Holds created",
  "data": {
    "reservations": [
      {
        "reservation_id": "f7122b2a-08fb-59dd-934e-b1gd010ffd7d",
        "ticket_type": "bus",
        "resource_id": "2",
        "quantity": 1,
        "status": "active",
        "expires_at": "2026-03-30T07:03:00+01:00",
        "seconds_remaining": 180
      }
    ]
  }
}
```

***

#### Example 3: Reserve Random Schedule Boarding Slot

**Request:**

```json
POST /api/v1/merpi/validate
Content-Type: application/json

{
  "tickets": [
    {
      "ticket_type": "bus",
      "resource_id": 16,
      "quantity": 1,
      "metadata": {
        "schedule_type": "random",
        "bus_id": 4,
        "route_id": 27,
        "departure_date": "2026-04-02"
      }
    }
  ],
  "customer_info": {
    "email": "usera@example.com",
    "phone_number": "080858253624"
  }
}
```

**Response (201 Created):**

```json
{
  "success": true,
  "status": 201,
  "message": "Holds created",
  "data": {
    "reservations": [
      {
        "reservation_id": "a8133c3b-19gc-60ee-045f-c2he121ggf8e",
        "ticket_type": "bus",
        "resource_id": "16",
        "quantity": 1,
        "status": "active",
        "expires_at": "2026-04-02T14:05:30+01:00",
        "seconds_remaining": 180
      }
    ]
  }
}
```

***

### Error Responses

#### 409 Conflict - Insufficient Inventory

Occurs when the requested seats/slots are no longer available or the schedule has reached capacity.

**Response:**

```json
{
  "success": false,
  "message": "Insufficient inventory. Only 8 available.",
  "status": 409,
}
```

**Common Causes:**

* Requested seats already taken or held by another customer
* Schedule has reached capacity (for random schedules)
* Seats sold out between listing view and hold attempt
* Invalid seat IDs that don't exist on the schedule

**Important**: When a batch request fails at any index, **all previously created holds in that same request are automatically released** (batch rollback).

***

#### 422 Unprocessable Entity - Validation Error

Occurs when the request body contains invalid or missing required fields.

**Response:**

```json
{
  "success": false,
  "status": 422,
  "message": "The seat_ids field is required when schedule_type is timed.",
}
```

**Common Causes:**

* Missing required metadata fields for schedule type
* Missing `seat_ids` for timed schedules
* Missing `bus_id`, `route_id`, or `departure_date` for random schedules
* Invalid date/time format in `schedule_timestamp` or `departure_date`
* Invalid `resource_id` format (must be positive integer)
* Missing both `email` and `phone_number` in `customer_info`
* Invalid `phone_number` format (must be 11-13 digits)
* `quantity` less than 1
* More than 10 items in `tickets` array
* Empty `seat_ids` array for timed schedules

***

### Using Holds in Purchase Requests

Once you've successfully created a hold, use the `reservation_id` values to complete the purchase via the transport buy endpoint.

**Critical Requirements:**

* The `customer_info.email` or `customer_info.phone_number` in the buy request **must exactly match** what was used when creating the hold
* This matching is how the system verifies hold ownership
* Mismatched customer info will result in a 404 error (hold not found)
* All `reservation_ids` in a single buy request must belong to the same customer

***

### Hold Lifecycle & Behavior

#### Status Transitions

```
POST /validate  →  status: "active"  (TTL timer starts)
                         │
          ┌──────────────┼──────────────────┐
          │              │                  │
    buy request    DELETE /validate    TTL expires
          │              │                  │
   status: converted  status: released  status: expired
```

#### Key Behaviors

**Idempotency**: If you submit the exact same hold parameters while an active hold already exists for that customer, the API returns the existing hold instead of creating a duplicate.

**Time-to-Live (TTL)**: Holds automatically expire after 3 minutes. The `expires_at` and `seconds_remaining` fields in the response help you display countdown timers to customers.

**Batch Rollback**: When creating multiple holds in a single request, if any individual hold fails (e.g., seat already taken), **all previously created holds in that same request are automatically released**. This ensures transactional consistency—either all holds succeed or none do.

**Seat Locking**: For timed schedules, all specified seats in `seat_ids` are locked atomically—either all seats are held or none are. Partial seat holds are not possible.

***

### Early Release (Optional)

If a customer abandons their checkout or you need to release holds programmatically, use the hold release endpoint:

```
DELETE /api/v1/merpi/validate/{reservation_id}
```

**See Documentation**: [Release Hold Endpoint](https://claude.ai/chat/fc6fb558-b6df-4d86-bf40-5bbf020fd0f4#) for detailed usage instructions.

**Note**: Holds automatically release after TTL expiration, so manual release is only necessary for early cleanup or improved user experience.

***

### Best Practices

#### 1. Display Countdown Timer

Show the `seconds_remaining` or `expires_at` to customers so they know how much time they have to complete checkout. This is especially important for popular routes where seats sell out quickly.

#### 2. Handle Seat Selection Carefully

For timed schedules, always validate seat selection on the client side before calling the hold endpoint. Show customers which seats are already taken to minimize 409 errors.

#### 3. Schedule Type Validation

Ensure your application correctly identifies schedule type before building the metadata object. Sending timed metadata to a random schedule (or vice versa) will result in validation errors.

#### 4. Graceful 409 Handling

If seats/slots are insufficient, immediately:

* Notify the customer clearly
* Refresh the schedule/seat map to show current availability
* Suggest alternative seats or departure times
* Do NOT retry the same seats—they're already taken

#### 5. Retry Logic

Implement exponential backoff for transient failures, but **do not retry** on 409 errors (insufficient inventory).

#### 6. Customer Info Consistency

Store the `customer_info` values (email/phone) in your session and use them consistently across hold creation, buy requests, and release calls.

#### 7. Monitor TTL

The default 3-minute TTL is suitable for most checkouts, but consider your average checkout duration. If customers frequently lose holds, optimize your checkout flow or request a longer TTL from your account manager.

#### 8. Date/Time Format Compliance

Always use the exact formats specified:

* `schedule_timestamp`: `Y-m-d H:i` (e.g., `"2026-03-30 07:00"`)
* `departure_date`: `Y-m-d` (e.g., `"2026-04-02"`)

Incorrect formats will result in 422 validation errors.

#### 10. Multi-Passenger UX

For group bookings with seat selection, consider allowing the customer to select all seats first, then hold them together in one request. This provides atomic seat locking and better UX than sequential individual holds.

***

### Related Endpoints

* [**Get Transport Schedules**](/merpi-by-syticks/api-reference/bus-ticketing-routes-terminals-schedules-buses-purchase-etc/get-schedules-endpoint-with-extra-information-v2.md): Retrieve schedule information and seat availability before creating holds
* [Buy Transport Tickets](/merpi-by-syticks/api-reference/bus-ticketing-routes-terminals-schedules-buses-purchase-etc/buy-bus-ticket-endpoint-v2.md): Complete purchase using `reservation_ids` from successful holds
* [**Release Hold/Reservation**](/merpi-by-syticks/api-reference/hold-reserve-ticket-inventory/release-a-hold-reservation-early.md): Manually release holds before TTL expiration
* [Get Route Listings](/merpi-by-syticks/api-reference/bus-ticketing-routes-terminals-schedules-buses-purchase-etc/get-bus-routes-endpoint.md): Browse available routes to present to customers
