﻿# ACT Travels Agent Developer Quickstart

This is the document we should share with approved agent developers who want to connect their own portal with ACT Travels inventory.

The goal is simple:

1. connect with the approved API key
2. load ACT Travels route and airline catalogs for filters
3. load grouped inventory for listing pages
4. search tickets live for the selected route/date
5. verify seats before booking
6. create the booking safely
7. track the booking status from your own portal

## What ACT Travels Provides

ACT Travels exposes a partner-facing B2B API for:

- sectors catalog
- airlines catalog
- grouped ticket listing for portal pages
- ticket search
- seat verification
- booking creation
- booking status tracking

This API is intended for approved agencies integrating ACT Travels inventory into their own sales portal, CRM, or booking workflow.

## Base URL

Production:

```text
https://api-actgroups.duckdns.org
```

Local example:

```text
http://localhost:3001
```

All partner endpoints live under:

```text
/api/b2b
```

## Authentication

All requests must include an API key in the `x-api-key` header.

Example:

```http
x-api-key: act_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Booking creation also requires an idempotency header:

```http
idempotency-key: booking-20260705-0001
```

## Step-by-Step Integration Guide

### Step 1. Check integration health

```http
GET /api/b2b/health
```

Use this first to confirm:

- the API key is active
- the organization is linked correctly
- the granted scopes are correct
- the integration is ready for requests

Example:

```bash
curl -H "x-api-key: act_your_key_here" \
  https://api-actgroups.duckdns.org/api/b2b/health
```

What to confirm from the response:

- integration status is `READY`
- your organization name is correct
- the expected scopes are present

### Step 2. Load sectors for your portal filters

```http
GET /api/b2b/sectors
```

Use this endpoint if your portal has a route dropdown, shortcuts, or a search form that should show ACT Travels-supported sectors.

Example:

```bash
curl -H "x-api-key: act_your_key_here" \
  https://api-actgroups.duckdns.org/api/b2b/sectors
```

Example response:

```json
{
  "status": "success",
  "count": 3,
  "data": [
    {
      "from": "LHE",
      "to": "DXB",
      "routePath": "LHE-DXB",
      "label": "LHE -> DXB"
    }
  ]
}
```

Important:

- this is a catalog helper endpoint
- it does not mean seats are currently available
- real availability always comes from `POST /api/b2b/tickets/search`

### Step 3. Load airlines for filters and branding

```http
GET /api/b2b/airlines
```

Use this endpoint if your portal needs airline filters, airline badges, or airline logos.

Example:

```bash
curl -H "x-api-key: act_your_key_here" \
  https://api-actgroups.duckdns.org/api/b2b/airlines
```

Example response:

```json
{
  "status": "success",
  "count": 2,
  "data": [
    {
      "id": "default-pf",
      "code": "PF",
      "name": "AirSial",
      "logoUrl": "https://cdn.example.com/airlines/airsial.webp"
    }
  ]
}
```

### Step 4. Load grouped inventory for your group tickets page

```http
GET /api/b2b/tickets/grouped
```

Use this endpoint when your portal needs to display many ACT Travels tickets in grouped route blocks like the main ACT Travels group tickets page.

Example:

```bash
curl -H "x-api-key: act_your_key_here" \
  "https://api-actgroups.duckdns.org/api/b2b/tickets/grouped?subCategoryId=YOUR_SUBCATEGORY_UUID&page=1&pageSize=50"
```

What you get:

- `groups` for route sections such as `ISB-BAH` or `ISB-DMM`
- `rows` inside each group for actual fare rows
- `items` if your portal also wants a flat list
- `meta` for pagination and counts

Recommended usage:

- use this for the group tickets listing page
- use `categoryId`, `subCategoryId`, or `subSubCategoryId` for product tabs
- use `airline` for airline filters
- use `from`, `to`, and `date` when narrowing a specific route view
- use `tripType=ROUND_TRIP` together with `returnDate` when grouped results should only show return-ticket itineraries matching a return leg date
- use `page` and `pageSize` for pagination or infinite scroll

### Step 5. Search tickets

```http
POST /api/b2b/tickets/search
```

Example request:

```json
{
  "from": "LHE",
  "to": "DXB",
  "date": "2026-07-12",
  "passengers": 1
}
```

Return-ticket request:

```json
{
  "from": "LHE",
  "to": "DXB",
  "date": "2026-07-12",
  "returnDate": "2026-07-20",
  "tripType": "ROUND_TRIP",
  "passengers": 1
}
```

Recommended usage:

- call this when the user selects route + date + passengers
- store the returned `id`
- for live fares, also keep the same search input because you will reuse it as `searchContext`
- if `tripType` is `ROUND_TRIP`, read `outbound` and `inbound` separately; for backward compatibility the top-level `data` array remains the outbound leg
- in partner UI language, this usually maps to a return-ticket flow even though the API flag name is `ROUND_TRIP`

### Step 6. Verify seats

```http
POST /api/b2b/tickets/seats
```

Use this before booking.

For live fares, always send the original `searchContext`.

Example request:

```json
{
  "flightId": "FND-7D4A7B2C9F4D1A6C8E210B3F",
  "passengers": 1,
  "searchContext": {
    "from": "LHE",
    "to": "DXB",
    "date": "2026-07-12"
  }
}
```

Rule:

- if seat verification fails, do not create the booking
- refresh search results and let the user choose again

### Step 7. Create booking

```http
POST /api/b2b/bookings
```

Required headers:

- `x-api-key`
- `idempotency-key`

Example request:

```json
{
  "flightId": "FND-7D4A7B2C9F4D1A6C8E210B3F",
  "isUrgent": false,
  "passengers": [
    {
      "title": "MR",
      "passengerType": "ADULT",
      "firstName": "Ali",
      "lastName": "Khan",
      "passportNo": "P1234567",
      "nationality": "PK",
      "dob": "1995-01-01",
      "passportExpiry": "2031-01-01"
    }
  ],
  "searchContext": {
    "from": "LHE",
    "to": "DXB",
    "date": "2026-07-12"
  }
}
```

Important rules:

- use a fresh idempotency key for every new booking attempt
- reusing the same idempotency key with the same payload safely replays the original response
- reusing the same key with a different payload returns `409`
- for live fares, `searchContext` must match the original search flow

Typical booking create response fields to save:

- `bookingRef`
- `status`
- `pnrConfirmation`
- `externalPNR`
- `supplierReference`
- optionally `pnr` as a generic partner-facing reference field

### Step 8. Track booking status

```http
GET /api/b2b/bookings/{bookingRef}
```

Example:

```bash
curl -H "x-api-key: act_your_key_here" \
  https://api-actgroups.duckdns.org/api/b2b/bookings/FND-2E58CF997B4DD234
```

PNR / reference rule:

- treat `GET /api/b2b/bookings/{bookingRef}` as the authoritative source for the latest supplier-synced reference
- for an airline-facing or supplier-facing PNR display, use `pnrConfirmation || externalPNR || supplierReference`
- treat `pnr` as a generic partner-facing convenience field, not the primary airline-PNR display field
- some booking-create responses may show an earlier reservation reference before the first status refresh

## First Website Checklist

If a developer is starting from zero and building a fresh portal, this is the recommended order:

1. Create a simple login or admin settings page where your API key can be stored securely on the server side.
2. Add a listing page that loads:
   - route/product tabs using your own chosen category mapping
   - grouped inventory from `GET /api/b2b/tickets/grouped`
   - optional airline filter from `GET /api/b2b/airlines`
3. Add a search form with:
   - sector dropdown from `GET /api/b2b/sectors`
   - optional airline filter from `GET /api/b2b/airlines`
   - travel date
   - passengers count
4. On search submit, call `POST /api/b2b/tickets/search`.
5. Render the returned tickets exactly as ACT Travels fares:
   - airline
   - flight number
   - route
   - departure and arrival
   - total price
   - seats available
6. When the user clicks a ticket, save:
   - `flightId`
   - original route/date input as `searchContext`
7. Before showing the passenger form as final, call `POST /api/b2b/tickets/seats`.
8. Build a passenger form that collects:
   - title
   - passenger type
   - first and last name
   - passport number
   - nationality
   - date of birth
   - passport expiry when required
9. On final submit, create a unique `idempotency-key` and call `POST /api/b2b/bookings`.
10. Save the returned `bookingRef` in your portal database.
11. Build a booking status page that polls `GET /api/b2b/bookings/{bookingRef}`.

## Implementation Advice

- Keep the API key on your backend, not inside browser JavaScript.
- Treat `tickets/search` results as short-lived inventory.
- Always perform seat verification before booking.
- For live fares, reuse the same `searchContext` in seat verification and booking.
- Do not assume a ticket remains available after a long delay.
- Save `bookingRef` as your permanent ACT Travels reference.
- Handle `409` idempotency conflicts carefully to avoid duplicate bookings.

## Suggested UI Pages

- Search page
- Search results page
- Passenger details page
- Booking success page
- Booking tracking page
- Internal settings page for API key rotation and integration testing

## Recommended Integration Flow

1. Call `GET /api/b2b/health`
2. Optionally preload `GET /api/b2b/sectors`
3. Optionally preload `GET /api/b2b/airlines`
4. Call `POST /api/b2b/tickets/search`
5. Call `POST /api/b2b/tickets/seats`
6. Call `POST /api/b2b/bookings`
7. Poll `GET /api/b2b/bookings/{bookingRef}`

## Happy Path Example

This is the simplest professional integration sequence for a one-way booking:

1. Load filters with `GET /api/b2b/sectors` and `GET /api/b2b/airlines`.
2. Run `POST /api/b2b/tickets/search` with route, date, and passengers.
3. Store the returned fare `id` plus the original route/date as `searchContext`.
4. Run `POST /api/b2b/tickets/seats` before the passenger submits payment or final confirmation.
5. If seats are available, generate a fresh `idempotency-key` and call `POST /api/b2b/bookings`.
6. Save `bookingRef` in your own portal immediately.
7. Call `GET /api/b2b/bookings/{bookingRef}` right after create, then keep polling from your booking page until the supplier-facing PNR settles.

For a return-ticket flow, the sequence is the same, but your search request should include:

- `tripType: ROUND_TRIP`
- `returnDate`
- and your UI should read `outbound` plus `inbound` separately

## Common Integration Mistakes

- Treating grouped listing as final real-time availability instead of using direct search plus seat verification.
- Skipping `searchContext` for live fares during seat check or booking.
- Reusing the same `idempotency-key` for different booking attempts.
- Treating the booking create response as the final airline PNR instead of polling booking status.
- Displaying the generic `pnr` field as the only airline PNR without checking `pnrConfirmation || externalPNR || supplierReference`.
- Assuming `ROUND_TRIP` must always be presented with the exact same wording in UI instead of mapping it to a return-ticket experience for agents.

## Error Model

Standard error shape:

```json
{
  "status": "error",
  "message": "Human-readable error"
}
```

Validation errors may return:

```json
{
  "status": "error",
  "message": "Validation failed",
  "errors": [
    {
      "path": "passengers.0.firstName",
      "message": "First name or givenName is required and must be at least 2 characters."
    }
  ]
}
```

Common status codes:

- `200` success
- `201` booking created
- `400` invalid request or business precondition failure
- `401` invalid or insufficient-scope API key
- `404` quote or booking not found
- `409` idempotency conflict
- `422` booking failed after validation
- `429` rate limit exceeded

## Rate Limits

Current implementation:

- read and search routes: `120` requests per minute per API key
- write and booking routes: `30` requests per minute per API key

## Supported Scopes

- `B2B_TICKETS_READ`
- `B2B_BOOKINGS_WRITE`
- `B2B_BOOKINGS_READ`

## Interactive Docs and Postman

Swagger UI:

```text
https://api-actgroups.duckdns.org/api-docs
```

OpenAPI JSON:

```text
https://api-actgroups.duckdns.org/api-docs/openapi.json
```

Postman collection:

```text
backend/docs/postman/acttravels-b2b-v1.postman_collection.json
```

## Commercial Boundary

This API intentionally exposes only the minimum required partner features.

It does not expose:

- admin APIs
- ERP controls
- internal inventory routing rules
- internal analytics
- wallet or finance-only APIs
- internal provider identity or supplier routing
