{
  "name": "Hotels.nl Hotel API",
  "description": "Search for hotels by location, retrieve detailed hotel information with room availability and rates, make bookings, list and retrieve booking details, and cancel reservations. All endpoints are POST with a JSON body. The apikey is always sent inside the request body.",
  "version": "1.0.0",
  "contact": { "email": "info@hotels.nl" },
  "logo": "https://hotels.nl/logo.png",
  "legal": "https://hotels.nl/legal",
  "auth": {
    "type": "api_key",
    "in": "body",
    "name": "apikey",
    "registration_url": "https://hotels.nl/developer/register",
    "instructions": "Register for an API key at the URL above. Include it as the 'apikey' field in every POST JSON request body."
  },
  "rate_limits": {
    "description": "Rate limits apply to search.php and hotel.php only. All other endpoints are unlimited.",
    "search.php": { "per_minute": 5, "per_day": 200, "scope": "per IP, per endpoint" },
    "hotel.php": { "per_minute": 5, "per_day": 200, "scope": "per IP, per endpoint" },
    "increase_quota": "Contact info@hotels.nl with your API key and use case to request a higher daily limit."
  },
  "api": {
    "base_url": "https://hotels.nl/api",
    "openapi_url": "https://hotels.nl/api/openapi.json",
    "endpoints": [
      {
        "path": "/search.php",
        "method": "POST",
        "description": "Search hotels near a location. Returns up to 15 hotels with the cheapest available rate per hotel. Only nomeal and breakfast meal plans are available.",
        "parameters": [
          { "name": "apikey", "in": "body", "required": true, "type": "string", "description": "Your API key." },
          { "name": "checkin", "in": "body", "required": true, "type": "string", "description": "Check-in date (YYYY-MM-DD). Must be in the future." },
          { "name": "checkout", "in": "body", "required": true, "type": "string", "description": "Check-out date (YYYY-MM-DD). Must be after checkin. Max 30 nights." },
          { "name": "location", "in": "body", "required": false, "type": "string", "description": "City, address, landmark, airport, or POI. Auto-geocoded via Google. Required if latitude/longitude not provided." },
          { "name": "latitude", "in": "body", "required": false, "type": "number", "description": "Latitude of search center. Use with longitude as alternative to location (faster)." },
          { "name": "longitude", "in": "body", "required": false, "type": "number", "description": "Longitude of search center. Use with latitude." },
          { "name": "residency", "in": "body", "required": false, "type": "string", "description": "Guest country code (ISO 3166-1 alpha-2). Default: nl." },
          { "name": "language", "in": "body", "required": false, "type": "string", "description": "Response language code. Default: nl." },
          { "name": "currency", "in": "body", "required": false, "type": "string", "description": "ISO 4217 currency code. Default: EUR." },
          { "name": "radius", "in": "body", "required": false, "type": "integer", "description": "Search radius in meters (1–10000). Default: 5000." },
          { "name": "persons", "in": "body", "required": false, "type": "integer", "description": "Persons per room (1–5). Default: 2." },
          { "name": "star_rating", "in": "body", "required": false, "type": "array", "description": "Filter by star rating. Values: 0 (unrated), 1, 2, 3, 4, 5.", "items": "integer" },
          { "name": "kind", "in": "body", "required": false, "type": "array", "description": "Filter by property type: Hotel, Apartment, Resort, BNB, Hostel, Guesthouse, Apart-hotel, Boutique_and_Design, Camping, Castle, Cottages_and_Houses, Farm, Glamping, Mini-hotel, Sanatorium, Villas_and_Bungalows.", "items": "string" },
          { "name": "meal_type", "in": "body", "required": false, "type": "array", "description": "Filter by meal plan: nomeal, breakfast. Only these two are available.", "items": "string" },
          { "name": "price_from", "in": "body", "required": false, "type": "integer", "description": "Minimum total price filter." },
          { "name": "price_to", "in": "body", "required": false, "type": "integer", "description": "Maximum total price filter." },
          { "name": "include_description", "in": "body", "required": false, "type": "boolean", "description": "Include short_description per hotel. Default: false." },
          { "name": "include_amenities", "in": "body", "required": false, "type": "boolean", "description": "Include amenities per hotel. Default: true." }
        ],
        "responses": {
          "200": { "description": "Search results with search_id, search metadata, and hotels array. Each hotel includes id, name, address, star_rating, image, amenities, and the cheapest rate with hotelsnl_hash, pricing, meal, and cancellation info." },
          "400": { "description": "Missing or invalid parameters (e.g. no location, missing dates)." },
          "401": { "description": "API key is missing." },
          "403": { "description": "API key is invalid." },
          "405": { "description": "Method not allowed — only POST accepted." },
          "429": { "description": "Rate limit exceeded (5/min or 200/day). Retry-After header indicates wait time." }
        }
      },
      {
        "path": "/hotel.php",
        "method": "POST",
        "description": "Get detailed hotel information with room availability and rates. Requires a numeric hotel id from search results. Must provide search_id or checkin+checkout dates — static-only requests are not accepted.",
        "parameters": [
          { "name": "apikey", "in": "body", "required": true, "type": "string", "description": "Your API key." },
          { "name": "id", "in": "body", "required": true, "type": "integer", "description": "Hotel ID from search results (hotels[].id). Cannot search by name." },
          { "name": "search_id", "in": "body", "required": false, "type": "string", "description": "Search ID from a previous search. Preferred — returns cached rates instantly. Valid for 1 hour." },
          { "name": "checkin", "in": "body", "required": false, "type": "string", "description": "Check-in date (YYYY-MM-DD). Use with checkout for a fresh live lookup when no search_id is available." },
          { "name": "checkout", "in": "body", "required": false, "type": "string", "description": "Check-out date (YYYY-MM-DD). Use with checkin." },
          { "name": "persons", "in": "body", "required": false, "type": "integer", "description": "Persons per room (1–5). Default: 2. Only used with checkin/checkout." },
          { "name": "currency", "in": "body", "required": false, "type": "string", "description": "Price currency. Default: EUR. Only used with checkin/checkout." },
          { "name": "residency", "in": "body", "required": false, "type": "string", "description": "Guest country code. Default: nl. Only used with checkin/checkout." },
          { "name": "language", "in": "body", "required": false, "type": "string", "description": "Response language. Default: en." }
        ],
        "responses": {
          "200": { "description": "Full hotel details (name, location, descriptions, images, amenities, policies) plus rooms array with up to 4 rate options per room (nomeal/breakfast × refundable/non-refundable). Each rate includes hotelsnl_hash, pricing, cancellation, and booking metadata." },
          "400": { "description": "Missing parameters or no dates provided (search_id or checkin+checkout required)." },
          "401": { "description": "API key is missing." },
          "403": { "description": "API key is invalid." },
          "404": { "description": "Hotel not found." },
          "405": { "description": "Method not allowed — only POST accepted." },
          "429": { "description": "Rate limit exceeded (5/min or 200/day). Retry-After header indicates wait time." }
        }
      },
      {
        "path": "/booking.php",
        "method": "POST",
        "description": "Initiate a hotel reservation. Returns a finalization URL where the guest reviews and pays with one click. Guest details are pre-filled from the API account.",
        "parameters": [
          { "name": "apikey", "in": "body", "required": true, "type": "string", "description": "Your API key." },
          { "name": "hotelsnl_hash", "in": "body", "required": true, "type": "string", "description": "Booking reference from any rate. Encodes hotel, room, meal, refundability, dates, and persons." }
        ],
        "responses": {
          "200": { "description": "Booking initiated. Returns {status: 'ok', booking_url: '...'}. Direct the guest to the booking_url to finalize." },
          "400": { "description": "Missing or invalid hotelsnl_hash." },
          "401": { "description": "API key is missing." },
          "403": { "description": "API key is invalid." }
        }
      },
      {
        "path": "/booking_overview.php",
        "method": "POST",
        "description": "List all bookings for the API key. Only includes bookings where a payment was attempted. Sorted newest first.",
        "parameters": [
          { "name": "apikey", "in": "body", "required": true, "type": "string", "description": "Your API key." }
        ],
        "responses": {
          "200": { "description": "Returns {status: 'ok', count, bookings: [...]}. Each booking includes booking_id, bookhash, booked_at, arrival, departure, guest_name, hotel_name, room_name, and status (payment_failed, booking_failed, booking_confirmed, or booking_cancelled)." },
          "401": { "description": "API key is missing." },
          "403": { "description": "API key is invalid." }
        }
      },
      {
        "path": "/retrieve_booking.php",
        "method": "POST",
        "description": "Retrieve full details of a single booking. The booking must belong to the account associated with the API key.",
        "parameters": [
          { "name": "apikey", "in": "body", "required": true, "type": "string", "description": "Your API key." },
          { "name": "bookhash", "in": "body", "required": true, "type": "string", "description": "30-character booking reference hash from booking_overview or the original booking response." }
        ],
        "responses": {
          "200": { "description": "Returns {status: 'ok', booking: {...}} with hotel, guest, pricing, cancellation, and status details. Status is one of: new, processing, booking_failed, booking_confirmed, booking_cancelled." },
          "400": { "description": "Missing bookhash." },
          "401": { "description": "API key is missing." },
          "403": { "description": "API key is invalid or booking belongs to another account." },
          "404": { "description": "Booking not found." }
        }
      },
      {
        "path": "/book/cancel.php",
        "method": "POST",
        "description": "Cancel a confirmed reservation. Only works for refundable bookings within the free cancellation window. Always check eligibility with retrieve_booking.php first.",
        "parameters": [
          { "name": "apikey", "in": "body", "required": true, "type": "string", "description": "Your API key." },
          { "name": "bookhash", "in": "body", "required": true, "type": "string", "description": "30-character booking reference hash." }
        ],
        "responses": {
          "200": { "description": "Cancelled successfully. Returns {status: 'cancelled', booking_id, bookhash}. Confirmation email sent to guest. Payment settled within two working days." },
          "400": { "description": "Missing bookhash." },
          "401": { "description": "API key is missing." },
          "403": { "description": "API key is invalid, cancellation deadline has passed, or rate is non-refundable." },
          "404": { "description": "Booking not found." },
          "409": { "description": "Booking is not in a cancellable state (not booking_confirmed)." },
          "502": { "description": "Cancellation failed at the hotel supplier. Contact info@hotels.nl." }
        }
      }
    ]
  }
}
