openapi: 3.1.0
info:
  title: Simorgh API (HAPI v3)
  description: >-
    HAPI v3 is a clean, resource-oriented REST API for flights, hotels, and
    activities. Authenticate with the OAuth2 client-credentials grant to receive
    a bearer token, then send it on every request. JSON is snake_case, money is
    always an object in USD, ids are opaque and prefixed, and errors use real
    HTTP status codes. The environment (test or live) is chosen by which
    credential you authenticate with.
  version: "3.0.0"
servers:
  - url: https://api.simorghapi.com/v3/HAPI
    description: HAPI v3 base (environment selected by credential)
tags:
  - name: Authentication
  - name: Flights
  - name: Hotels
  - name: Activities
security:
  - BearerAuth: []
paths:
  /auth/tokens:
    post:
      tags: [Authentication]
      summary: Get an access token
      description: Exchange client credentials for a short-lived bearer token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [grant_type]
              properties:
                grant_type: { type: string, enum: [client_credentials] }
                client_id: { type: string, example: cid_live_9f3c81d24a5b4c6d9e0f1a2b }
                client_secret: { type: string, example: sk_live_… }
                scope: { type: string, description: Space-separated scopes to narrow the token. }
      responses:
        "201":
          description: A new access token.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AccessToken" }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
  /auth/introspect:
    get:
      tags: [Authentication]
      summary: Introspect the current token
      responses:
        "200":
          description: The token's office, environment, and scopes.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TokenIntrospection" }
        "401": { $ref: "#/components/responses/Error" }
  /wallet:
    get:
      tags: [Authentication]
      summary: Get the office wallet balance
      responses:
        "200":
          description: The wallet balance for the token's environment.
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: wallet }
                  environment: { type: string, enum: [test, live] }
                  balance: { $ref: "#/components/schemas/Money" }
                  held: { $ref: "#/components/schemas/Money" }
                  available: { $ref: "#/components/schemas/Money" }

  /flights/places:
    get:
      tags: [Flights]
      summary: Find origins/destinations (autocomplete)
      parameters:
        - { name: query, in: query, required: true, schema: { type: string, minLength: 2 }, example: istan }
        - { name: limit, in: query, schema: { type: integer, maximum: 20, default: 8 } }
      responses:
        "200":
          description: Matching cities and airports (flightable IATA stations).
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: flight_place_list }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        object: { type: string, example: flight_place }
                        code: { type: string, example: IST }
                        name: { type: string, example: Istanbul Airport }
                        type: { type: string, enum: [city, airport] }
                        city_code: { type: string, example: IST }
                        country_code: { type: string, example: TR }
                        country: { type: string, example: "T\u00fcrkiye" }
  /flights/searches:
    post:
      tags: [Flights]
      summary: Create a flight search
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FlightSearchRequest" }
      responses:
        "201":
          description: The search with its offers.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightSearch" }
        "422": { $ref: "#/components/responses/Error" }
  /flights/searches/stream:
    post:
      tags: [Flights]
      summary: Stream flight search results (SSE)
      description: >-
        Same body as the blocking search. Returns text/event-stream with
        `offers` events (each a full flight_offer_batch with replace=true),
        then a `done` event.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FlightSearchRequest" }
      responses:
        "200":
          description: A server-sent events stream.
          content:
            text/event-stream:
              schema: { type: string }
  /flights/offers/{id}:
    get:
      tags: [Flights]
      summary: Get and revalidate an offer
      parameters:
        - { name: id, in: path, required: true, schema: { type: string }, example: ofr_… }
      responses:
        "200":
          description: The re-priced offer.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightOffer" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /flights/offers/{id}/conditions:
    get:
      tags: [Flights]
      summary: Fare rules and baggage for an offer
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: The fare conditions.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightConditions" }
  /flights/orders:
    post:
      tags: [Flights]
      summary: Create a flight order (hold)
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FlightOrderRequest" }
      responses:
        "201":
          description: The held order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightOrder" }
        "402": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /flights/orders/{id}:
    get:
      tags: [Flights]
      summary: Get a flight order
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightOrder" }
        "404": { $ref: "#/components/responses/Error" }
  /flights/orders/{id}/ticket:
    post:
      tags: [Flights]
      summary: Request ticket issuance
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The order, now ticketing.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightOrder" }
  /flights/orders/{id}/cancellation:
    post:
      tags: [Flights]
      summary: Cancel a held order
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The cancelled order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightOrder" }
        "409": { $ref: "#/components/responses/Error" }
  /flights/orders/{id}/refund:
    get:
      tags: [Flights]
      summary: Preview a refund
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The refund quote.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RefundQuote" }
    post:
      tags: [Flights]
      summary: Request a refund
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                refund_type: { type: integer }
                ticket_numbers: { type: array, items: { type: string } }
                refund_payment_mode: { type: integer }
      responses:
        "200":
          description: The refund result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: refund }
                  order_id: { type: string }
                  status: { type: string, example: requested }
                  refunded_tickets: { type: array, items: { type: string } }
  /flights/orders/{id}/notes:
    post:
      tags: [Flights]
      summary: Add notes to an order
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [notes]
              properties:
                notes: { type: array, items: { type: string } }
      responses:
        "200":
          description: The order with the notes appended.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FlightOrder" }

  /hotels/places:
    get:
      tags: [Hotels]
      summary: Search destinations
      parameters:
        - { name: query, in: query, required: true, schema: { type: string }, example: dubai }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } }
        - { name: type, in: query, schema: { type: string, enum: [city, region, country, hotel, landmark, district] } }
      responses:
        "200":
          description: Matching places.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelPlaceList" }
  /hotels/searches:
    post:
      tags: [Hotels]
      summary: Search hotel availability
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/HotelSearchRequest" }
      responses:
        "201":
          description: The search with its rates.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelSearch" }
        "422": { $ref: "#/components/responses/Error" }
  /hotels/rates/{id}:
    get:
      tags: [Hotels]
      summary: Confirm a rate
      parameters: [ { name: id, in: path, required: true, schema: { type: string }, example: hrt_… } ]
      responses:
        "200":
          description: The confirmed rate.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelRate" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /hotels/orders:
    post:
      tags: [Hotels]
      summary: Create a hotel order (hold)
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/HotelOrderRequest" }
      responses:
        "201":
          description: The held order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelOrder" }
        "402": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /hotels/orders/{id}:
    get:
      tags: [Hotels]
      summary: Get a hotel order
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelOrder" }
        "404": { $ref: "#/components/responses/Error" }
  /hotels/orders/{id}/confirm:
    post:
      tags: [Hotels]
      summary: Confirm a hotel order
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The confirmed order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelOrder" }
  /hotels/orders/{id}/cancellation:
    get:
      tags: [Hotels]
      summary: Preview a cancellation
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The cancellation quote.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelCancellationQuote" }
    post:
      tags: [Hotels]
      summary: Cancel a hotel order
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The cancelled order.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HotelOrder" }
  /hotels/orders/{id}/extend-deadline:
    post:
      tags: [Hotels]
      summary: Extend the payment deadline
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The order with a new deadline.
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: hotel_order }
                  id: { type: string }
                  payment_deadline: { type: string, format: date-time }

  /activities/products:
    get:
      tags: [Activities]
      summary: Browse activity products
      parameters:
        - { name: query, in: query, schema: { type: string } }
        - { name: city, in: query, schema: { type: string } }
        - { name: country, in: query, schema: { type: string } }
        - { name: category, in: query, schema: { type: string } }
        - { name: language, in: query, schema: { type: string } }
        - { name: min_price, in: query, schema: { type: number } }
        - { name: max_price, in: query, schema: { type: number } }
        - { name: sort, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer } }
        - { name: after, in: query, schema: { type: string }, description: Cursor from a prior next_cursor. }
      responses:
        "200":
          description: A page of products.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityProductList" }
  /activities/products/{id}:
    get:
      tags: [Activities]
      summary: Get a product with its product-types
      parameters: [ { name: id, in: path, required: true, schema: { type: string }, example: act_… } ]
      responses:
        "200":
          description: The product.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityProduct" }
        "404": { $ref: "#/components/responses/Error" }
  /activities/product-types/{id}/availability:
    get:
      tags: [Activities]
      summary: Availability and pricing
      parameters:
        - { name: id, in: path, required: true, schema: { type: string }, example: apt_… }
        - { name: date, in: query, schema: { type: string, format: date }, description: Without it, bookable dates; with it, timeslots and rates. }
      responses:
        "200":
          description: The availability.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityAvailability" }
  /activities/bookings:
    get:
      tags: [Activities]
      summary: List activity bookings
      parameters:
        - { name: status, in: query, schema: { type: string } }
        - { name: email, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer } }
        - { name: after, in: query, schema: { type: string } }
      responses:
        "200":
          description: A page of bookings.
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: activity_booking_list }
                  data: { type: array, items: { $ref: "#/components/schemas/ActivityBookingSummary" } }
                  has_more: { type: boolean }
                  next_cursor: { type: string, nullable: true }
    post:
      tags: [Activities]
      summary: Create an activity booking (hold)
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ActivityBookingRequest" }
      responses:
        "201":
          description: The held booking.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityBooking" }
        "422": { $ref: "#/components/responses/Error" }
  /activities/bookings/{id}:
    get:
      tags: [Activities]
      summary: Get an activity booking
      parameters: [ { name: id, in: path, required: true, schema: { type: string }, example: abk_… } ]
      responses:
        "200":
          description: The booking.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityBooking" }
        "404": { $ref: "#/components/responses/Error" }
  /activities/bookings/{id}/confirm:
    post:
      tags: [Activities]
      summary: Confirm an activity booking
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The confirmed booking.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityBooking" }
  /activities/bookings/{id}/cancel:
    post:
      tags: [Activities]
      summary: Cancel an activity booking
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The cancelled booking.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityBooking" }
  /activities/bookings/{id}/vouchers:
    get:
      tags: [Activities]
      summary: List vouchers for a booking
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200":
          description: The vouchers.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ActivityVoucherList" }
  /activities/bookings/{id}/vouchers/{voucher}/download:
    get:
      tags: [Activities]
      summary: Download a voucher (PDF)
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
        - { name: voucher, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: The voucher file.
          content:
            application/pdf:
              schema: { type: string, format: binary }

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: The access_token from /auth/tokens.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema: { type: string }
      description: Retry-safe key; a repeat with the same key replays the first response.
  responses:
    Error:
      description: A typed error.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Money:
      type: object
      properties:
        amount: { type: string, example: "412.00" }
        currency: { type: string, example: USD }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type: { type: string, example: invalid_request_error }
            code: { type: string, example: validation_failed }
            message: { type: string }
            param: { type: string }
            doc_url: { type: string }
            request_id: { type: string }
    AccessToken:
      type: object
      properties:
        access_token: { type: string }
        token_type: { type: string, example: Bearer }
        expires_in: { type: integer, example: 3600 }
        expires_at: { type: string, format: date-time }
        scope: { type: string }
        environment: { type: string, enum: [test, live] }
    TokenIntrospection:
      type: object
      properties:
        active: { type: boolean }
        client_id: { type: string }
        environment: { type: string }
        office: { type: object, properties: { id: { type: string }, name: { type: string } } }
        scopes: { type: array, items: { type: string } }
        token_type: { type: string }
        expires_at: { type: string, format: date-time }
    FlightSearchRequest:
      type: object
      required: [slices, passengers]
      properties:
        slices:
          type: array
          items:
            type: object
            required: [origin, destination, departure_date]
            properties:
              origin: { type: string, example: IST }
              destination: { type: string, example: DXB }
              departure_date: { type: string, format: date }
        passengers:
          type: object
          required: [adults]
          properties:
            adults: { type: integer, minimum: 1 }
            children: { type: integer }
            infants: { type: integer }
        cabin_class: { type: string, enum: [economy, premium_economy, business, first] }
        provider: { type: string, enum: [gts, skyscanner, tp, parto, pgf] }
        max_offers: { type: integer }
    FlightSearch:
      type: object
      properties:
        object: { type: string, example: flight_search }
        id: { type: string }
        status: { type: string, example: completed }
        offer_count: { type: integer }
        offers: { type: array, items: { $ref: "#/components/schemas/FlightOffer" } }
    FlightOffer:
      type: object
      properties:
        object: { type: string, example: flight_offer }
        id: { type: string }
        owner: { type: string }
        validating_airline: { type: string }
        airline_name: { type: string }
        cabin_class: { type: string }
        total: { $ref: "#/components/schemas/Money" }
        base: { $ref: "#/components/schemas/Money" }
        tax: { $ref: "#/components/schemas/Money" }
        passengers: { type: array, items: { type: object } }
        slices:
          type: array
          items:
            type: object
            properties:
              origin: { type: string }
              destination: { type: string }
              duration_minutes: { type: integer }
              segments: { type: array, items: { $ref: "#/components/schemas/FlightSegment" } }
    FlightSegment:
      type: object
      properties:
        origin: { type: string }
        destination: { type: string }
        departing_at: { type: string }
        arriving_at: { type: string }
        marketing_carrier: { type: string }
        operating_carrier: { type: string }
        flight_number: { type: string }
        aircraft: { type: string }
        aircraft_name: { type: string }
        booking_class: { type: string }
        cabin_class: { type: string }
        stops: { type: integer }
        seats_remaining: { type: integer }
        baggage: { type: object, properties: { checked: { type: string }, cabin: { type: string } } }
        duration_minutes: { type: integer }
    FlightConditions:
      type: object
      properties:
        object: { type: string, example: flight_conditions }
        offer_id: { type: string }
        fare_type: { type: integer }
        fare_rules:
          type: array
          items:
            type: object
            properties:
              airline: { type: string }
              route: { type: string }
              rules: { type: array, items: { type: object, properties: { category: { type: string }, text: { type: string } } } }
        baggage:
          type: array
          items:
            type: object
            properties:
              origin: { type: string }
              destination: { type: string }
              flight_number: { type: string }
              allowance: { type: string }
    FlightOrderRequest:
      type: object
      required: [offer_id, passengers, contact]
      properties:
        offer_id: { type: string }
        passengers:
          type: array
          items:
            type: object
            required: [type, given_name, family_name, date_of_birth, gender]
            properties:
              type: { type: string, enum: [adult, child, infant] }
              given_name: { type: string }
              family_name: { type: string }
              date_of_birth: { type: string, format: date }
              gender: { type: string, enum: [male, female] }
              nationality: { type: string }
              national_id: { type: string }
              document:
                type: object
                properties:
                  number: { type: string }
                  expires_on: { type: string, format: date }
                  country_code: { type: string }
                  issued_on: { type: string, format: date }
        contact:
          type: object
          required: [email, phone]
          properties:
            email: { type: string }
            phone: { type: string }
        client_reference: { type: string }
    FlightOrder:
      type: object
      properties:
        object: { type: string, example: flight_order }
        id: { type: string }
        status: { type: string, enum: [held, ticketing, ticketed, cancelled, refunded] }
        ticketing_deadline: { type: string, format: date-time }
        validating_airline: { type: string }
        total: { $ref: "#/components/schemas/Money" }
        contact: { type: object }
        passengers: { type: array, items: { type: object } }
        segments: { type: array, items: { type: object } }
        notes: { type: array, items: { type: object } }
    RefundQuote:
      type: object
      properties:
        object: { type: string, example: refund_quote }
        order_id: { type: string }
        refund_type: { type: integer }
        tickets: { type: array, items: { type: object } }
    HotelPlaceList:
      type: object
      properties:
        object: { type: string, example: hotel_place_list }
        count: { type: integer }
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              name: { type: string }
              type: { type: string }
              country: { type: string }
              coordinates: { type: object, nullable: true }
    HotelSearchRequest:
      type: object
      required: [check_in, check_out, occupancies]
      properties:
        check_in: { type: string, format: date }
        check_out: { type: string, format: date }
        city_id: { type: integer }
        hotel_id: { type: integer }
        occupancies:
          type: array
          items:
            type: object
            required: [adults]
            properties:
              adults: { type: integer, minimum: 1, maximum: 8 }
              children: { type: array, items: { type: integer } }
        sort: { type: string, description: "City search only. A sort id from the response `sorts`: price, stars_desc, stars_asc, review_score, distance, popularity." }
        filters:
          type: object
          description: >-
            City search only. A map of facet `key` -> array of `value`s from the
            response `facets` (OR within a group, AND across groups). Fixed-value
            groups: `stars` (0-5), `review_score` (5/6/7/8/9 = minimum), `meals`
            (breakfast, breakfast_lunch, breakfast_dinner, full_board,
            all_inclusive, self_catering), `free_cancellation`/`adults_only`/`sustainable` (boolean true), `distance_km` (1/3/5),
            `min_beds` (1-5), `min_bedrooms` (1-4), `bed_type` (single/double).
            Catalogue groups (values from `facets`): `property_type`, `facility`,
            `room_facility`, `district`, `chain`, `landmark`.
            Example: {"stars":[4,5],"facility":[107,433],"meals":["breakfast"],"free_cancellation":[true]}.
          additionalProperties:
            type: array
            items: { oneOf: [ { type: integer }, { type: string }, { type: boolean } ] }
        page: { type: integer }
        page_size: { type: integer }
    HotelSearch:
      type: object
      description: >-
        A city search (city_id) lists hotel_summary offers — a lead-in price
        per property, nothing bookable yet. A hotel search (hotel_id) carries
        the property once under `hotel` and lists bookable hotel_rate offers.
      properties:
        object: { type: string, example: hotel_search }
        id: { type: string, example: hsr_485158995, nullable: true }
        mode: { type: string, enum: [city, hotel] }
        check_in: { type: string }
        check_out: { type: string }
        nights: { type: integer, nullable: true }
        hotel:
          allOf: [ { $ref: "#/components/schemas/Hotel" } ]
          description: Present in hotel mode only.
        total: { type: integer, description: "City mode: total matching properties across all pages." }
        sort: { type: string, nullable: true }
        filters_applied: { type: object }
        sorts:
          type: array
          description: "City mode: available sort options."
          items:
            type: object
            properties: { id: { type: string }, name: { type: string } }
        facets:
          type: array
          description: "City mode: available filter groups. Send a group's key + option values back in `filters`."
          items:
            type: object
            properties:
              key: { type: string, example: facility }
              title: { type: string, example: Facilities }
              type: { type: string, enum: [range, enum, boolean] }
              options:
                type: array
                items:
                  type: object
                  properties:
                    value: { oneOf: [ { type: integer }, { type: string }, { type: boolean } ] }
                    name: { type: string }
                    count: { type: integer }
        pagination:
          type: object
          nullable: true
          properties:
            page: { type: integer }
            page_size: { type: integer }
            total_pages: { type: integer }
            has_next: { type: boolean }
            has_prev: { type: boolean }
        offer_count: { type: integer, description: "Offers on this page (city) or total rates (hotel)." }
        offers:
          type: array
          items:
            oneOf:
              - $ref: "#/components/schemas/HotelSummary"
              - $ref: "#/components/schemas/HotelRate"
    HotelSummary:
      type: object
      description: One property in a city search, with its cheapest available price.
      properties:
        object: { type: string, example: hotel_summary }
        hotel_id: { type: string }
        name: { type: string }
        lead_rate: { $ref: "#/components/schemas/Money" }
        lead_rate_before_discount:
          allOf: [ { $ref: "#/components/schemas/Money" } ]
          nullable: true
        discount_percent: { type: number }
        deal_badges: { type: array, items: { type: string } }
        meal_plan: { type: string, nullable: true }
        refundable: { type: boolean }
        free_cancellation: { type: boolean }
        check_in: { type: string }
        check_out: { type: string }
        nights: { type: integer }
        occupancy:
          type: object
          properties:
            adults: { type: integer }
            children: { type: integer }
        rating: { type: number, nullable: true }
        review_score: { type: number, nullable: true }
        review_count: { type: integer, nullable: true }
        review_word: { type: string, nullable: true }
        accommodation: { type: string, nullable: true }
        address: { type: object }
        location: { type: object, nullable: true }
        images: { type: array, items: { type: string } }
    HotelRate:
      type: object
      description: >-
        A bookable rate. From a hotel search it also carries board details,
        remaining rooms and payment terms; from GET /hotels/rates/{id} it
        carries the hotel and full room details.
      properties:
        object: { type: string, example: hotel_rate }
        id: { type: string }
        check_in: { type: string, nullable: true }
        check_out: { type: string, nullable: true }
        nights: { type: integer, nullable: true }
        total: { $ref: "#/components/schemas/Money" }
        total_before_discount:
          allOf: [ { $ref: "#/components/schemas/Money" } ]
          nullable: true
        discount_percent: { type: number }
        deal_badges: { type: array, items: { type: string } }
        refundable: { type: boolean }
        refundable_until: { type: string, nullable: true }
        available_rooms: { type: integer, nullable: true }
        units: { type: integer, nullable: true }
        board: { type: string, nullable: true }
        board_basis: { type: string, nullable: true }
        board_included: { type: array, items: { type: string } }
        hotel: { $ref: "#/components/schemas/Hotel" }
        rooms: { type: array, items: { type: object } }
        cancellation: { $ref: "#/components/schemas/Cancellation" }
    Hotel:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        rating: { type: number }
        review_score: { type: number }
        chain: { type: string }
        address: { type: object }
        contact: { type: object }
        location: { type: object }
        images: { type: array, items: { type: string } }
    Cancellation:
      type: object
      properties:
        non_refundable: { type: boolean }
        policy_text: { type: string }
        policies: { type: array, items: { type: object } }
    HotelOrderRequest:
      type: object
      required: [rate_id, rooms, contact]
      properties:
        rate_id: { type: string }
        rooms:
          type: array
          items:
            type: object
            required: [guests]
            properties:
              guests:
                type: array
                items:
                  type: object
                  required: [given_name, family_name]
                  properties:
                    given_name: { type: string }
                    family_name: { type: string }
                    type: { type: string, enum: [adult, child] }
                    title: { type: string }
                    national_id: { type: string }
                    passport_number: { type: string }
        contact:
          type: object
          required: [email, phone]
          properties:
            email: { type: string }
            phone: { type: string }
        nationality: { type: string }
        note: { type: string }
        client_reference: { type: string }
    HotelOrder:
      type: object
      properties:
        object: { type: string, example: hotel_order }
        id: { type: string }
        status: { type: string, enum: [held, confirming, confirmed, cancelled] }
        payment_deadline: { type: string, format: date-time }
        check_in: { type: string }
        check_out: { type: string }
        nights: { type: integer }
        total: { $ref: "#/components/schemas/Money" }
        confirmation_number: { type: string, nullable: true }
        voucher_number: { type: string, nullable: true }
        hotel: { $ref: "#/components/schemas/Hotel" }
        contact: { type: object }
        rooms: { type: array, items: { type: object } }
        cancellation: { $ref: "#/components/schemas/Cancellation" }
    HotelCancellationQuote:
      type: object
      properties:
        object: { type: string, example: hotel_cancellation_quote }
        order_id: { type: string }
        penalty: { $ref: "#/components/schemas/Money" }
        status: { type: integer }
        expires_at: { type: string, format: date-time }
    ActivityProductList:
      type: object
      properties:
        object: { type: string, example: activity_product_list }
        data: { type: array, items: { $ref: "#/components/schemas/ActivityProduct" } }
        total: { type: integer }
        has_more: { type: boolean }
        next_cursor: { type: string, nullable: true }
    ActivityProduct:
      type: object
      properties:
        object: { type: string, example: activity_product }
        id: { type: string }
        title: { type: string }
        title_translated: { type: string }
        description: { type: string }
        type: { type: string }
        city: { type: string }
        country: { type: string }
        from: { $ref: "#/components/schemas/Money" }
        image_url: { type: string }
        images: { type: array, items: { type: string } }
        categories: { type: array, items: { type: string } }
        product_types: { type: array, items: { $ref: "#/components/schemas/ActivityProductType" } }
    ActivityProductType:
      type: object
      properties:
        object: { type: string, example: activity_product_type }
        id: { type: string }
        title: { type: string }
        title_translated: { type: string }
    ActivityAvailability:
      type: object
      properties:
        object: { type: string, example: activity_availability }
        product_type_id: { type: string }
        timezone: { type: string }
        date: { type: string, format: date }
        weekday: { type: string, nullable: true }
        available: { type: boolean }
        capacity:
          type: array
          description: Remaining capacity per traveller category; 0 = sold out.
          items:
            type: object
            properties:
              category: { type: string }
              quantity: { type: integer, nullable: true }
        dates: { type: array, items: { type: object, properties: { date: { type: string } } } }
        timeslots: { type: array, items: { type: object, properties: { start_time: { type: string } } } }
        rates:
          type: array
          items:
            type: object
            properties:
              category: { type: string }
              price: { $ref: "#/components/schemas/Money" }
        booking_options:
          type: object
          description: >-
            Extra info to collect from the guest at booking time. Submit the
            answers under `options` when creating the booking.
          properties:
            per_booking:
              type: array
              description: Answered once for the whole booking.
              items: { $ref: "#/components/schemas/ActivityBookingOption" }
            per_pax:
              type: array
              description: Answered once per traveller (adults, then children, then seniors).
              items: { $ref: "#/components/schemas/ActivityBookingOption" }
    ActivityBookingOption:
      type: object
      properties:
        id: { type: string, description: Option id; send it back as the answer's `id`. }
        name: { type: string }
        name_translated: { type: string, nullable: true }
        description: { type: string, nullable: true }
        description_translated: { type: string, nullable: true }
        required: { type: boolean }
        add_on: { type: boolean }
        input_type: { type: integer, description: "1 list, 2 list_multiple, 3 number, 4 string, 5 boolean, 6 date, 7 file, 8 image, 9 address, 10 time, 11 datetime, 12 country, 13 phone, 14 flight_number" }
        input_type_name: { type: string }
        format_regex: { type: string, nullable: true }
        valid_from: { type: string, nullable: true }
        valid_to: { type: string, nullable: true }
        price: { $ref: "#/components/schemas/Money" }
        items:
          type: array
          description: Selectable choices for list input types.
          items:
            type: object
            properties:
              label: { type: string }
              label_translated: { type: string, nullable: true }
              value: {}
              price: { $ref: "#/components/schemas/Money" }
    ActivityBookingSummary:
      type: object
      description: >-
        A list item. The upstream list carries only the booking's identity and
        status; fetch GET /activities/bookings/{id} for amounts, dates and the
        customer.
      properties:
        object: { type: string, example: activity_booking_summary }
        id: { type: string }
        status: { type: string }
        code: { type: string, nullable: true }
        partner_reference: { type: string, nullable: true }
        product_type_id: { type: string, nullable: true }
        product_id: { type: string, nullable: true }
        created_at: { type: string, nullable: true }
        updated_at: { type: string, nullable: true }
    ActivityBookingRequest:
      type: object
      required: [product_type_id, date, travelers, customer]
      properties:
        product_type_id: { type: string }
        date: { type: string, format: date }
        timeslot: { type: string }
        travelers:
          type: object
          required: [adults]
          properties:
            adults: { type: integer, minimum: 1 }
            children: { type: integer }
            seniors: { type: integer }
        customer:
          type: object
          required: [given_name, family_name, email]
          properties:
            salutation: { type: string }
            given_name: { type: string }
            family_name: { type: string }
            email: { type: string }
            phone: { type: string }
        client_reference: { type: string }
        options:
          type: object
          description: >-
            Answers to the product-type's booking options (from the availability
            response's `booking_options`). Required when the activity has any
            `required` option.
          properties:
            per_booking:
              type: array
              items:
                type: object
                required: [id, value]
                properties:
                  id: { type: string }
                  value: {}
            per_pax:
              type: array
              description: One array per traveller (adults, then children, then seniors).
              items:
                type: array
                items:
                  type: object
                  required: [id, value]
                  properties:
                    id: { type: string }
                    value: {}
    ActivityBooking:
      type: object
      properties:
        object: { type: string, example: activity_booking }
        id: { type: string }
        status: { type: string, enum: [held, confirmed, issued, cancelled, refunded] }
        code: { type: string }
        product_type_id: { type: string }
        title: { type: string }
        date: { type: string }
        timeslot: { type: string }
        total: { $ref: "#/components/schemas/Money" }
        breakdown: { type: array, items: { type: object } }
        customer: { type: object }
        created_at: { type: string, format: date-time }
    ActivityVoucherList:
      type: object
      properties:
        object: { type: string, example: activity_voucher_list }
        booking_id: { type: string }
        data:
          type: array
          items:
            type: object
            properties:
              object: { type: string, example: activity_voucher }
              id: { type: string }
              visual_id: { type: string }
              generated_at: { type: string }
              download_url: { type: string }
