{
  "openapi": "3.0.3",
  "info": {
    "title": "Tratok Labs Developer API",
    "version": "1.3.0",
    "description": "Public API for the Tratok hospitality ecosystem. Hotel data (thousands of hotels), car rental data (118,000+ rental packages), translation and language detection in 44 languages, the Tara AI travel assistant, cruise search (26,000+ upcoming sailings), and live Tratok inventory and booking, billed in TRAT, the ERC-20 utility token.\n\nAll endpoints share a single multiplexed entrypoint: **`POST /data.php`**. The `request` body field selects the method.\n\n**Authentication.** Each call carries `id` (your App ID) and `secret` (your API key). Both are issued once an admin approves an application; see the [developer dashboard](https://developer.tratok.net/account.php).\n\n**Pricing.** 1,000 free requests per developer per month. Beyond that, 1 TRAT per 100 requests (i.e. 0.01 TRAT each).",
    "contact": {
      "name": "Tratok Labs",
      "url": "https://developer.tratok.net"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "https://developer.tratok.net",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Accommodation",
      "description": "Hotel discovery and details across 185 countries."
    },
    {
      "name": "Car Rental",
      "description": "Rental locations and car-class catalogue."
    },
    {
      "name": "Translation",
      "description": "LibreTranslate-backed text translation and language detection."
    },
    {
      "name": "Tara AI",
      "description": "Tratok's built-in AI assistant. Per-user conversation memory and a Tratok-locked identity. Billed per token: 1 TRAT per 1,000 input tokens, 4 TRAT per 1,000 output tokens. Not covered by the 1,000/month free tier: every call is paid in TRAT."
    },
    {
      "name": "Inventory and Booking",
      "description": "Live Tratok stays, activities and restaurants: search, quote and book on a member's behalf after they sign in with Tratok. The member pays from their own Tratok balance."
    },
    {
      "name": "Cruises",
      "description": "Search upcoming cruise sailings by region, line, port, month, length and price; get a cruise with its ship, itinerary and other departures. Display-only for now: booking opens on tratok.net shortly."
    }
  ],
  "paths": {
    "/data.php": {
      "post": {
        "summary": "Multiplexed API endpoint",
        "description": "Single endpoint that dispatches to the requested method based on the `request` body field. Set `Content-Type: application/x-www-form-urlencoded`.",
        "operationId": "dispatch",
        "tags": [
          "Accommodation",
          "Car Rental",
          "Translation",
          "Tara AI",
          "Inventory and Booking",
          "Cruises"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/HotelsInCountryRequest"
                  },
                  {
                    "$ref": "#/components/schemas/HotelsInCityRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetHotelInformationRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetHotelGalleryRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetListOfCarCitiesRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetListOfAllCarVenuesInCityRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetListOfCarsAtVenueRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SupportedLanguagesRequest"
                  },
                  {
                    "$ref": "#/components/schemas/DetectLanguageRequest"
                  },
                  {
                    "$ref": "#/components/schemas/TranslateRequest"
                  },
                  {
                    "$ref": "#/components/schemas/TaraChatRequest"
                  },
                  {
                    "$ref": "#/components/schemas/TaraResetRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SearchStaysRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SearchActivitiesRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SearchRestaurantsRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetListingRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetQuoteRequest"
                  },
                  {
                    "$ref": "#/components/schemas/BookListingRequest"
                  },
                  {
                    "$ref": "#/components/schemas/MyBookingsRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetBookingRequest"
                  },
                  {
                    "$ref": "#/components/schemas/CancelBookingRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SearchCruisesRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetCruiseRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetCruiseRegionsRequest"
                  },
                  {
                    "$ref": "#/components/schemas/GetCruiseLinesRequest"
                  }
                ],
                "discriminator": {
                  "propertyName": "request"
                }
              },
              "examples": {
                "hotelsInCountry": {
                  "summary": "Hotels in a country",
                  "value": {
                    "request": "hotelsInCountry",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "country": "United Arab Emirates"
                  }
                },
                "hotelsInCity": {
                  "summary": "Hotels in a city",
                  "value": {
                    "request": "hotelsInCity",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "city": "Dubai"
                  }
                },
                "getHotelInformation": {
                  "summary": "Single hotel details",
                  "value": {
                    "request": "getHotelInformation",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "hotelId": 12345
                  }
                },
                "getHotelGallery": {
                  "summary": "Hotel image gallery",
                  "value": {
                    "request": "getHotelGallery",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "hotelId": 12345
                  }
                },
                "getListOfCarCities": {
                  "summary": "Cities with rental locations",
                  "value": {
                    "request": "getListOfCarCities",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET"
                  }
                },
                "getListOfAllCarVenuesInCity": {
                  "summary": "Rental venues in a city",
                  "value": {
                    "request": "getListOfAllCarVenuesInCity",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "city": "Dubai"
                  }
                },
                "getListOfCarsAtVenue": {
                  "summary": "Cars available at a venue",
                  "value": {
                    "request": "getListOfCarsAtVenue",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "locationId": "DXB-001"
                  }
                },
                "supportedLanguages": {
                  "summary": "Supported translation languages",
                  "value": {
                    "request": "supportedLanguages",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET"
                  }
                },
                "detectLanguage": {
                  "summary": "Detect text language",
                  "value": {
                    "request": "detectLanguage",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "text": "Hola, mundo"
                  }
                },
                "translate": {
                  "summary": "Translate text",
                  "value": {
                    "request": "translate",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "text": "Hello, world",
                    "sourceLanguage": "en",
                    "targetLanguage": "fr"
                  }
                },
                "taraChat": {
                  "summary": "Chat with Tara",
                  "value": {
                    "request": "taraChat",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "message": "What hotels do you have in Dubai?",
                    "conversationId": "user-42-session-abc"
                  }
                },
                "taraReset": {
                  "summary": "Reset a Tara conversation",
                  "value": {
                    "request": "taraReset",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "conversationId": "user-42-session-abc"
                  }
                },
                "searchStays": {
                  "summary": "Search live stays",
                  "value": {
                    "request": "searchStays",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "city": "Dubai",
                    "limit": 10
                  }
                },
                "searchCruises": {
                  "summary": "Search cruises",
                  "value": {
                    "request": "searchCruises",
                    "id": "YOUR_APP_ID",
                    "secret": "YOUR_SECRET",
                    "region": "caribbean",
                    "nightsMin": 7,
                    "limit": 10
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success or expected error. The body is JSON. Errors take the form `{\"status\": \"Error\", \"message\": \"…\"}`.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Hotel"
                      }
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HotelImage"
                      }
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CarCity"
                      }
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CarVenue"
                      }
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CarClass"
                      }
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Language"
                      }
                    },
                    {
                      "$ref": "#/components/schemas/DetectLanguageResponse"
                    },
                    {
                      "$ref": "#/components/schemas/TranslateResponse"
                    },
                    {
                      "$ref": "#/components/schemas/TaraChatResponse"
                    },
                    {
                      "$ref": "#/components/schemas/TaraResetResponse"
                    },
                    {
                      "$ref": "#/components/schemas/CruiseSearchResponse"
                    },
                    {
                      "$ref": "#/components/schemas/CruiseDetailResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BaseRequest": {
        "type": "object",
        "required": [
          "request",
          "id",
          "secret"
        ],
        "properties": {
          "request": {
            "type": "string",
            "description": "Method selector."
          },
          "id": {
            "type": "string",
            "description": "Your App ID."
          },
          "secret": {
            "type": "string",
            "description": "Your API key."
          }
        }
      },
      "HotelsInCountryRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "country"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "hotelsInCountry"
                ]
              },
              "country": {
                "type": "string",
                "example": "United Arab Emirates"
              }
            }
          }
        ]
      },
      "HotelsInCityRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "city"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "hotelsInCity"
                ]
              },
              "city": {
                "type": "string",
                "example": "Dubai"
              }
            }
          }
        ]
      },
      "GetHotelInformationRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "hotelId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getHotelInformation"
                ]
              },
              "hotelId": {
                "type": "integer",
                "example": 12345
              }
            }
          }
        ]
      },
      "GetHotelGalleryRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "hotelId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getHotelGallery"
                ]
              },
              "hotelId": {
                "type": "integer",
                "example": 12345
              }
            }
          }
        ]
      },
      "GetListOfCarCitiesRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getListOfCarCities"
                ]
              }
            }
          }
        ]
      },
      "GetListOfAllCarVenuesInCityRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "city"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getListOfAllCarVenuesInCity"
                ]
              },
              "city": {
                "type": "string",
                "example": "Dubai"
              }
            }
          }
        ]
      },
      "GetListOfCarsAtVenueRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "locationId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getListOfCarsAtVenue"
                ]
              },
              "locationId": {
                "type": "string",
                "example": "DXB-001"
              }
            }
          }
        ]
      },
      "SupportedLanguagesRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "supportedLanguages"
                ]
              }
            }
          }
        ]
      },
      "DetectLanguageRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "text"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "detectLanguage"
                ]
              },
              "text": {
                "type": "string",
                "example": "Hola, mundo"
              }
            }
          }
        ]
      },
      "TranslateRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "text",
              "targetLanguage"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "translate"
                ]
              },
              "text": {
                "type": "string",
                "maxLength": 5000,
                "example": "Hello, where is the hotel?",
                "description": "Up to 5,000 characters per call."
              },
              "sourceLanguage": {
                "type": "string",
                "example": "en",
                "description": "A code from supportedLanguages, or `auto` to detect it."
              },
              "targetLanguage": {
                "type": "string",
                "example": "fr",
                "description": "A code from supportedLanguages."
              }
            }
          }
        ]
      },
      "TaraChatRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "message"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "taraChat"
                ]
              },
              "message": {
                "type": "string",
                "example": "What hotels do you have in Dubai?"
              },
              "conversationId": {
                "type": "string",
                "description": "Stable per-end-user thread id. Pass the same value across calls to give that user a persistent conversation with Tara.",
                "example": "user-42-session-abc"
              }
            }
          }
        ]
      },
      "TaraResetRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "conversationId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "taraReset"
                ]
              },
              "conversationId": {
                "type": "string",
                "example": "user-42-session-abc"
              }
            }
          }
        ]
      },
      "Hotel": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Burj Al Arab"
          },
          "id": {
            "type": "integer",
            "example": 12345
          },
          "city": {
            "type": "string",
            "example": "Dubai"
          },
          "country": {
            "type": "string",
            "example": "United Arab Emirates"
          },
          "address": {
            "type": "string",
            "example": "Jumeirah Beach Rd"
          },
          "description": {
            "type": "string"
          },
          "longitude": {
            "type": "string",
            "example": "55.1853"
          },
          "latitude": {
            "type": "string",
            "example": "25.1413"
          }
        }
      },
      "HotelImage": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://tratok.net/images/hotels/0f1e2d3c4b5a69788796a5b4c3d2e1f0.webp",
            "description": "Absolute URL. Only photos that are available are returned; a hotel without any answers with an error."
          }
        }
      },
      "CarCity": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "example": "Dubai"
          }
        }
      },
      "CarVenue": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "example": "Dubai"
          },
          "location": {
            "type": "string",
            "example": "Dubai International Airport"
          },
          "id": {
            "type": "string",
            "example": "DXB-001"
          }
        }
      },
      "CarClass": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "example": "Economy"
          },
          "example": {
            "type": "string",
            "example": "Toyota Yaris or similar"
          },
          "passengerCapacity": {
            "type": "integer",
            "example": 4
          },
          "bagCapacity": {
            "type": "integer",
            "example": 2
          },
          "transmission": {
            "type": "string",
            "example": "Automatic"
          },
          "fuelType": {
            "type": "string",
            "example": "Petrol"
          },
          "airConditioning": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "Language": {
        "type": "object",
        "properties": {
          "language": {
            "type": "string",
            "example": "English"
          },
          "code": {
            "type": "string",
            "example": "en"
          }
        }
      },
      "DetectLanguageResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Ok"
          },
          "score": {
            "type": "number",
            "format": "float",
            "example": 0.99
          },
          "language": {
            "type": "string",
            "example": "Spanish"
          },
          "language_code": {
            "type": "string",
            "example": "es"
          }
        }
      },
      "TranslateResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Ok"
          },
          "originalLanguage": {
            "type": "string",
            "example": "en"
          },
          "newLanguage": {
            "type": "string",
            "example": "fr"
          },
          "originalMessage": {
            "type": "string",
            "example": "Hello, world"
          },
          "translatedMessage": {
            "type": "string",
            "example": "Bonjour, le monde"
          }
        }
      },
      "TaraChatResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Ok"
          },
          "assistant": {
            "type": "string",
            "example": "Tara"
          },
          "reply": {
            "type": "string",
            "example": "Dubai has hundreds of properties on Tratok. Call hotelsInCity with city=\"Dubai\" to get them all."
          },
          "conversationId": {
            "type": "string",
            "example": "user-42-session-abc"
          },
          "usage": {
            "type": "object",
            "description": "Per-call token usage and billing. Tara is not part of the 1,000/month free tier: every call is paid in TRAT (`free` is always `false`).",
            "properties": {
              "inputTokens": {
                "type": "integer",
                "example": 412
              },
              "outputTokens": {
                "type": "integer",
                "example": 198
              },
              "totalTokens": {
                "type": "integer",
                "example": 610
              },
              "cost": {
                "type": "number",
                "format": "float",
                "example": 1.204,
                "description": "Amount charged in TRAT (0 if served from the free tier)."
              },
              "currency": {
                "type": "string",
                "example": "TRAT"
              },
              "free": {
                "type": "boolean",
                "example": false,
                "description": "Always false for Tara; kept in the schema for response-shape consistency with other endpoints."
              }
            }
          }
        }
      },
      "TaraResetResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Ok"
          },
          "cleared": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Error"
          },
          "message": {
            "type": "string",
            "example": "Validation failed. Invalid API provided."
          }
        }
      },
      "SearchStaysRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "searchStays"
                ]
              },
              "q": {
                "type": "string",
                "example": "beach",
                "description": "Free-text search."
              },
              "city": {
                "type": "string",
                "example": "Dubai"
              },
              "country": {
                "type": "string",
                "example": "United Arab Emirates"
              },
              "nearLat": {
                "type": "number",
                "example": 25.2048
              },
              "nearLng": {
                "type": "number",
                "example": 55.2708
              },
              "radiusKm": {
                "type": "number",
                "example": 10
              },
              "limit": {
                "type": "integer",
                "example": 20
              },
              "offset": {
                "type": "integer",
                "example": 0
              }
            }
          }
        ],
        "description": "Search live Tratok stays."
      },
      "SearchActivitiesRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "searchActivities"
                ]
              },
              "q": {
                "type": "string",
                "example": "beach",
                "description": "Free-text search."
              },
              "city": {
                "type": "string",
                "example": "Dubai"
              },
              "country": {
                "type": "string",
                "example": "United Arab Emirates"
              },
              "nearLat": {
                "type": "number",
                "example": 25.2048
              },
              "nearLng": {
                "type": "number",
                "example": 55.2708
              },
              "radiusKm": {
                "type": "number",
                "example": 10
              },
              "limit": {
                "type": "integer",
                "example": 20
              },
              "offset": {
                "type": "integer",
                "example": 0
              }
            }
          }
        ],
        "description": "Search live Tratok activities."
      },
      "SearchRestaurantsRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "searchRestaurants"
                ]
              },
              "q": {
                "type": "string",
                "example": "beach",
                "description": "Free-text search."
              },
              "city": {
                "type": "string",
                "example": "Dubai"
              },
              "country": {
                "type": "string",
                "example": "United Arab Emirates"
              },
              "nearLat": {
                "type": "number",
                "example": 25.2048
              },
              "nearLng": {
                "type": "number",
                "example": 55.2708
              },
              "radiusKm": {
                "type": "number",
                "example": 10
              },
              "limit": {
                "type": "integer",
                "example": 20
              },
              "offset": {
                "type": "integer",
                "example": 0
              }
            }
          }
        ],
        "description": "Search live Tratok restaurants."
      },
      "GetListingRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "listingId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getListing"
                ]
              },
              "vertical": {
                "type": "string",
                "enum": [
                  "stays",
                  "activities",
                  "dining"
                ]
              },
              "listingId": {
                "type": "integer",
                "example": 1234
              }
            }
          }
        ],
        "description": "One listing with its rooms, packages and photos."
      },
      "GetQuoteRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "accessToken",
              "vertical",
              "listingId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getQuote"
                ]
              },
              "accessToken": {
                "type": "string",
                "description": "The member's access token from Sign in with Tratok (book scope for quotes and bookings)."
              },
              "vertical": {
                "type": "string",
                "enum": [
                  "stays",
                  "activities",
                  "dining"
                ]
              },
              "listingId": {
                "type": "integer",
                "example": 1234
              },
              "roomId": {
                "type": "integer",
                "description": "Stays."
              },
              "packageId": {
                "type": "integer",
                "description": "Stays and dining, optional."
              },
              "checkIn": {
                "type": "string",
                "format": "date",
                "description": "Stays."
              },
              "checkOut": {
                "type": "string",
                "format": "date",
                "description": "Stays."
              },
              "guests": {
                "type": "integer",
                "description": "Stays."
              },
              "date": {
                "type": "string",
                "format": "date",
                "description": "Activities and dining."
              },
              "participants": {
                "type": "integer",
                "description": "Activities."
              },
              "time": {
                "type": "string",
                "example": "19:30",
                "description": "Dining."
              },
              "party": {
                "type": "integer",
                "description": "Dining."
              }
            }
          }
        ],
        "description": "A five-minute price in TRAT for the member."
      },
      "BookListingRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "accessToken",
              "quoteId",
              "idempotencyKey"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "bookListing"
                ]
              },
              "accessToken": {
                "type": "string",
                "description": "The member's access token from Sign in with Tratok (book scope for quotes and bookings)."
              },
              "vertical": {
                "type": "string",
                "enum": [
                  "stays",
                  "activities",
                  "dining"
                ]
              },
              "listingId": {
                "type": "integer",
                "example": 1234
              },
              "roomId": {
                "type": "integer",
                "description": "Stays."
              },
              "packageId": {
                "type": "integer",
                "description": "Stays and dining, optional."
              },
              "checkIn": {
                "type": "string",
                "format": "date",
                "description": "Stays."
              },
              "checkOut": {
                "type": "string",
                "format": "date",
                "description": "Stays."
              },
              "guests": {
                "type": "integer",
                "description": "Stays."
              },
              "date": {
                "type": "string",
                "format": "date",
                "description": "Activities and dining."
              },
              "participants": {
                "type": "integer",
                "description": "Activities."
              },
              "time": {
                "type": "string",
                "example": "19:30",
                "description": "Dining."
              },
              "party": {
                "type": "integer",
                "description": "Dining."
              },
              "quoteId": {
                "type": "string",
                "description": "From getQuote; book within five minutes."
              },
              "idempotencyKey": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{8,64}$",
                "description": "Unique per booking attempt."
              },
              "guestName": {
                "type": "string"
              },
              "guestPhone": {
                "type": "string"
              },
              "notes": {
                "type": "string"
              }
            }
          }
        ],
        "description": "Book the quote; the member pays from their own Tratok balance."
      },
      "MyBookingsRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "accessToken"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "myBookings"
                ]
              },
              "accessToken": {
                "type": "string",
                "description": "The member's access token from Sign in with Tratok (book scope for quotes and bookings)."
              },
              "tab": {
                "type": "string",
                "enum": [
                  "upcoming",
                  "past",
                  "cancelled"
                ],
                "default": "upcoming"
              }
            }
          }
        ],
        "description": "The member's bookings made through your app."
      },
      "GetBookingRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "accessToken",
              "bookingId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getBooking"
                ]
              },
              "accessToken": {
                "type": "string",
                "description": "The member's access token from Sign in with Tratok (book scope for quotes and bookings)."
              },
              "bookingId": {
                "type": "integer"
              }
            }
          }
        ],
        "description": "One booking made through your app."
      },
      "CancelBookingRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "accessToken",
              "bookingId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "cancelBooking"
                ]
              },
              "accessToken": {
                "type": "string",
                "description": "The member's access token from Sign in with Tratok (book scope for quotes and bookings)."
              },
              "bookingId": {
                "type": "integer"
              }
            }
          }
        ],
        "description": "Cancel under the listing's cancellation policy."
      },
      "SearchCruisesRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "searchCruises"
                ]
              },
              "q": {
                "type": "string",
                "example": "Alaska",
                "description": "Free text matched against the itinerary title, ship, cruise line and embarkation port."
              },
              "region": {
                "type": "string",
                "example": "caribbean",
                "description": "A region slug from getCruiseRegions."
              },
              "lineId": {
                "type": "integer",
                "example": 21,
                "description": "A lineId from getCruiseLines."
              },
              "port": {
                "type": "string",
                "example": "Barcelona",
                "description": "Embarkation or disembarkation port (text match)."
              },
              "month": {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                "example": "2027-03",
                "description": "Departure month."
              },
              "nightsMin": {
                "type": "integer",
                "example": 7
              },
              "nightsMax": {
                "type": "integer",
                "example": 14
              },
              "priceMax": {
                "type": "number",
                "example": 1500,
                "description": "Highest fare per guest in USD."
              },
              "sort": {
                "type": "string",
                "enum": [
                  "departure",
                  "price_low",
                  "price_high",
                  "nights"
                ],
                "default": "departure"
              },
              "limit": {
                "type": "integer",
                "example": 20,
                "description": "1 to 50, default 20."
              },
              "offset": {
                "type": "integer",
                "example": 0
              }
            }
          }
        ],
        "description": "Upcoming voyages, grouped by ship and itinerary: each item is the earliest matching departure and `departures` counts the matching dates."
      },
      "GetCruiseRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [
              "cruiseId"
            ],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getCruise"
                ]
              },
              "cruiseId": {
                "type": "integer",
                "example": 3842,
                "description": "A cruiseId from searchCruises or otherDepartures."
              }
            }
          }
        ],
        "description": "One sailing with its ship, port-by-port itinerary, fares and the other dates the same voyage sails."
      },
      "GetCruiseRegionsRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getCruiseRegions"
                ]
              }
            }
          }
        ],
        "description": "Cruise regions with their number of upcoming sailings."
      },
      "GetCruiseLinesRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseRequest"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "request": {
                "type": "string",
                "enum": [
                  "getCruiseLines"
                ]
              }
            }
          }
        ],
        "description": "Cruise lines with their number of upcoming sailings."
      },
      "Cruise": {
        "type": "object",
        "description": "A voyage in searchCruises results.",
        "properties": {
          "cruiseId": {
            "type": "integer",
            "example": 3842
          },
          "title": {
            "type": "string",
            "example": "7 Night Western Caribbean Cruise"
          },
          "line": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "ship": {
            "type": "string",
            "example": "Disney Fantasy"
          },
          "region": {
            "type": "object",
            "nullable": true,
            "properties": {
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "departureDate": {
            "type": "string",
            "format": "date"
          },
          "returnDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "nights": {
            "type": "integer"
          },
          "roundTrip": {
            "type": "boolean"
          },
          "embarkPort": {
            "type": "string"
          },
          "disembarkPort": {
            "type": "string"
          },
          "priceFromUsd": {
            "type": "number",
            "nullable": true,
            "description": "Lowest listed fare per guest, before taxes and fees."
          },
          "soldOut": {
            "type": "boolean"
          },
          "departures": {
            "type": "integer"
          },
          "hasItinerary": {
            "type": "boolean"
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The cruise on tratok.net."
          }
        }
      },
      "CruiseSearchResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Ok"
          },
          "count": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Cruise"
            }
          },
          "note": {
            "type": "string"
          }
        }
      },
      "CruiseDetailResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "Ok"
          },
          "cruise": {
            "type": "object",
            "description": "Cruise fields plus seaDays, portCount, price {from, currency, fromUsd, perNight, taxesAndFees, taxesCurrency}, ship {name, image, yearBuilt, lastRefurbished, passengers, crew, grossTonnage, decks, cabins, lengthM, flag, class}, itinerary [{day, port, country, lat, lng, when, embark, disembark}], itineraryBorrowed, otherDepartures, url, bookable (false for now)."
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "API reference with code samples",
    "url": "https://developer.tratok.net/docs.php"
  }
}
