Skip to content
FX-Port/Docs
GuidesAPI referenceChangelog
Get API keys
  • Introduction
  • Quickstart
  • Security
  • Authentication
  • Environments and Postman
  • Sandbox
  • Conventions and naming
  • Legacy names and twins
  • Errors
  • Rate limits and fair use
  • Streaming search results

Concepts

  • Overview
  • Sandbox testing
  • Mini and full data
  • Guests and rooms
  • Pricing

Search

  • Geo search
  • Locations and coordinates
  • Name search
  • Hotel details

Rates and booking

  • Rates
  • Pre-book
  • Book

After booking

  • Get a booking
  • Voucher
  • Cancel
  • Use these docs with AI

Hotels ยท Search

Geo search

Search and price every hotel around a point (latitude, longitude and radius), or one hotel by id: a compact card and the cheapest price per hotel.

POST/api/v1/hotels/search_advanced

Geo search lists the hotels around a point with their prices. Send a latitude, a longitude and a radius with the dates and the guests: the answer has a compact mini card for each hotel and the price of its cheapest rate for the stay. The same call, POST /api/v1/hotels/search_advanced, also searches one hotel by id. It returns no rooms, meals or cancellation terms: call get_rates for the hotel the user picks.

Permission: Read

Sandbox: any hotel can be searched, only the test hotels can be booked

The search runs on real hotels and real prices in the sandbox. Booking is possible only on test_hotel_do_not_book and test_hotel: see Sandbox testing.

Search hotels around a point

POST /api/v1/hotels/search_advanced

curl https://api.fx-port.com/api/v1/hotels/search_advanced \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "latitude": 48.8566,
  "longitude": 2.3522,
  "radius": 1000,
  "checkin": "2026-12-01",
  "checkout": "2026-12-04",
  "residency": "gb",
  "guests": [
    {
      "adults": 2,
      "children": []
    }
  ]
}'

These seven fields are all it needs. radius is in meters (100 to 70,000) and has no default. A big city is a big answer: 5 km around the center of Paris measured 1,818 hotels and 8.5 MB. Keep the radius to what your map shows (Size and speed).

The answer is one JSON body with two parts that you join by hotel id:

status              complete | partial | cache_miss | no_hotels_found
hotels_details[]    the mini card of each hotel: id, name, address, latitude, longitude, star_rating, images, amenities (no price)
hotels_pricing      { "<hotel id>": { final_price, b2b_price, agency_commission, agency_currency, exchange_rate } }
                    the cheapest rate of each hotel for the whole stay
missing_hotel_ids   the hotels that have a price but no card yet
search_id, processing_status, queue_info, timing

Every field is described in Response, the cards in Mini and full hotel data.

How to get the coordinates

Ask locations/search for the place the user typed, then take the latitude and longitude of the place they pick (for example locations[0]):

curl 'https://api.fx-port.com/api/v1/hotels/locations/search?query=Paris' \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"

Search locations shows the answer and how to let the user choose between several places. To search around a hotel you know, use its own latitude and longitude (in its mini card).

Search one hotel by id

To price one hotel whose id you know (from name-search, an earlier search or a booking), send hotel_id instead of the coordinates and the radius:

curl https://api.fx-port.com/api/v1/hotels/search_advanced \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "hotel_id": "test_hotel_do_not_book",
  "checkin": "2026-12-01",
  "checkout": "2026-12-04",
  "residency": "gb",
  "guests": [
    {
      "adults": 2,
      "children": []
    }
  ]
}'

The answer has the same shape with one card and one price, in under a second. If you send both a hotel_id and coordinates, hotel_id wins and the coordinates are ignored. A search of test_hotel with a residency other than dz, ru or us answers no_hotels_found.

Request body

FieldTypeRequiredDefaultDescription
latitudenumberFor a geo search-90 to 90, from locations/search.
longitudenumberFor a geo search-180 to 180.
radiusintegerFor a geo searchnoneMeters, 100 to 70,000. There is no default: a geo search without a radius cannot be priced. A value outside the range is moved to the nearest bound, and the answer carries a coerced_value warning.
hotel_idstringFor a search of one hotelThe FX-Port id of the hotel, from name-search or from an earlier search.
checkinstringYesCheck-in date, YYYY-MM-DD. From yesterday (for time zones) to 400 days ahead.
checkoutstringYesCheck-out date, after checkin. A stay is 1 to 30 nights.
guestsarrayYesOne { adults, children } object per room, 1 to 9 rooms. See Guests and rooms.
residencystringNo, but send itnoneThe residency of the guests, a two-letter country code (gb, dz, fr). It decides which rates exist: some are for some residencies only. The search does not default it, unlike get_rates, which defaults to gb.
languagestringNoenLanguage of the supplier's answer. The hotel content in the answer is English whatever you send.

Do not send a currency field: it is refused with 400. Prices come in the currency of your agency, resolved from your key.

Several rooms: one { adults, children } object per room, with the age of each child:

curl https://api.fx-port.com/api/v1/hotels/search_advanced \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "latitude": 48.8566,
  "longitude": 2.3522,
  "radius": 1500,
  "checkin": "2026-12-10",
  "checkout": "2026-12-12",
  "residency": "gb",
  "guests": [
    {
      "adults": 2,
      "children": [
        8
      ]
    },
    {
      "adults": 1,
      "children": []
    }
  ]
}'

Legacy names

These older spellings still work. The answer carries a warnings entry with the code deprecated_field that names the spelling to use (and the headers Deprecation and X-FX-Deprecated-Fields). See Legacy names.

Older nameUse instead
id, hotelIdhotel_id
check_in, checkin_date, checkinDatecheckin
check_out, checkout_date, checkoutDatecheckout

Values the API reads for you

A value that a real client writes in another well-defined way is read, not refused, and the answer carries a coerced_value warning that says what was read (see Conventions). Send the documented form to avoid the warning.

You sendThe API reads
"radius": 600000 or 5070000 or 100: a radius outside 100 to 70,000 meters is moved to the nearest bound
A number written as text: "radius": "15000", "latitude": "14.09", "adults": "2", a child age "8"The number. An integral float (2.0) is the same number and has no warning
"checkin": "2026-12-01T00:00:00Z", a date with a midnight time2026-12-01. Any other time of day is 400 invalid_date
"residency": "GB-SCT", "USPR", "ES-XCI", "PT-MA", the sub-territories of the website's nationality listThe country: gb, us, es, pt. Send the 2-letter country
"language": "zh-Hans-CN", "es-419", "sr-Latn-RS"zh_CN, es, sr
"hotel_id": 8473727, a JSON integer, or an id with spaces around itThe text "8473727", or the trimmed id. A number is not looked up as the supplier's numeric hid here: send the FX-Port id (the content calls do look a hid up)
"hotel_id": null next to latitude and longitudeA search around a point (a null alone is 400 missing_field)

What cannot be read keeps its error code: a boolean or NaN where a number is wanted, 1e3, a fraction where a whole number is wanted, usa, gbr, en-USA, another time of day. get_rates reads the same values.

Response

The answer is always one JSON body. It is a real, trimmed answer of the sandbox (a geo search with a 4 km radius around Nuuk: five hotels were priced, the answer is cut to two here). A search of one hotel answers the same keys with one card:

{
  "environment": "sandbox",
  "search_id": "71498fa9-d3a7-4b6c-81bd-76693be726d1",
  "status": "complete",
  "hotels_details": [
    {
      "id": "hotel_nordbo_2",
      "hid": 10048834,
      "name": "Hotel Nordbo",
      "kind": "Hotel",
      "address": "Vandsovej 13, Nuuk, 3900, Greenland, Nuuk",
      "latitude": 64.17923,
      "longitude": -51.73019,
      "postal_code": null,
      "region": {
        "id": 1359,
        "country_code": "GL",
        "iata": "GOH",
        "name": "Nuuk",
        "type": "City",
        "type_v2": "city"
      },
      "star_rating": 0,
      "images": [
        "https://cdn.worldota.net/t/{size}/content/16/be/16be5aea51c0060686286cf8c886e4e07e1e5dce.jpeg",
        "https://cdn.worldota.net/t/{size}/content/b1/98/b198dca0393650833ee449c3e460b2af85a04dda.jpeg"
      ],
      "serp_filters": [
        "has_parking",
        "has_meal",
        "has_pets"
      ],
      "amenity_groups": [
        {
          "amenities": [
            "24-hour reception",
            "Smoke-free property",
            "Ticket assistance"
          ],
          "non_free_amenities": [],
          "group_name": "General"
        },
        {
          "amenities": [
            "Breakfast",
            "Restaurant"
          ],
          "non_free_amenities": [],
          "group_name": "Meals"
        }
      ]
    },
    {
      "id": "hhe_express",
      "hid": 9168363,
      "name": "HHE Express Hotel",
      "kind": "Hotel",
      "address": "Aqqusinersuaq 14B, Nuuk",
      "latitude": 64.17208,
      "longitude": -51.73649,
      "postal_code": "3900",
      "region": {
        "id": 1359,
        "country_code": "GL",
        "iata": "GOH",
        "name": "Nuuk",
        "type": "City",
        "type_v2": "city"
      },
      "star_rating": 3,
      "images": [
        "https://cdn.worldota.net/t/{size}/content/b5/39/b539a20fd8349ef419e73802ced9a04215155205.JPEG",
        "https://cdn.worldota.net/t/{size}/content/41/97/4197c95a76a3ada2539a0fe169e7e3d7df3e80f9.jpeg"
      ],
      "serp_filters": [
        "has_internet",
        "has_parking"
      ],
      "amenity_groups": [
        {
          "amenities": [
            "24-hour reception",
            "Elevator/lift",
            "Smoke-free property"
          ],
          "non_free_amenities": [],
          "group_name": "General"
        },
        {
          "amenities": [
            "Free Wi-Fi"
          ],
          "non_free_amenities": [],
          "group_name": "Internet"
        }
      ]
    }
  ],
  "processing_status": {
    "total_hotels": 5,
    "cached_hotels": 5,
    "queued_hotels": 0
  },
  "queue_info": {
    "queued_hotel_ids": [],
    "estimated_completion": null
  },
  "timing": {
    "total_duration": 0.84,
    "search_duration": 0.82,
    "cache_check_duration": 0.02
  },
  "hotels_pricing": {
    "hotel_nordbo_2": {
      "exchange_rate": 285,
      "agency_currency": "DZD",
      "agency_commission": 438.68,
      "final_price": 219774.68,
      "b2b_price": 219336
    },
    "hhe_express": {
      "exchange_rate": 285,
      "agency_currency": "DZD",
      "agency_commission": 228.83,
      "final_price": 114639.23,
      "b2b_price": 114410.4
    }
  },
  "missing_hotel_ids": [],
  "missing_hotels_pricing": {}
}
FieldDescription
search_idThe id of the search, also in the X-Search-Id header. Quote it when you write to support. There is nothing to pass on to the next call.
statuscomplete: every priced hotel has a card. partial: some do not (see missing_hotel_ids). cache_miss: none has. no_hotels_found: nothing is available for the request.
hotels_details[]The mini card of each hotel that has cached content: 13 fields (id, name, address, position, stars, photos, filters, amenities). No rate, no room, no price. See Mini and full hotel data and The Hotel mini object.
hotels_pricingThe price of the cheapest rate of every priced hotel, by hotel id: final_price (what the traveler pays for the whole stay and all rooms), agency_commission, b2b_price (final_price - agency_commission, what your agency is debited), agency_currency and exchange_rate. See Hotel pricing.
missing_hotel_idsThe hotels that are priced but have no card yet.
missing_hotels_pricingThe price entry of each of them, the same fields as hotels_pricing.
processing_statustotal_hotels, cached_hotels and queued_hotels.
queue_infoThe ids that FX-Port is loading in the background (queued_hotel_ids) and a rough delay (estimated_completion, estimated_completion_seconds).
timingSeconds spent: total_duration, search_duration (the supplier) and cache_check_duration.
truncated, max_hotels, total_hotels_foundPresent only when the supplier found more than 5,000 hotels and the rest was cut off. Narrow the search.
noteA hint about an empty answer, for example that test_hotel has no availability for the residency.

The answer carries the headers X-Search-Id, X-Search-Status, X-Search-Type (hotel_id or geo), X-Total-Hotels, X-Available-Hotels, X-Search-Time and X-Environment.

Join the cards and the prices

hotels_details and hotels_pricing are separate: join them by hotel id. A hotel that has a price and no card is in missing_hotel_ids:

{
  "environment": "sandbox",
  "search_id": "4a6877dc-96da-4f32-8c1b-4aaed8b60fe4",
  "status": "partial",
  "hotels_details": [
    {
      "id": "hotel_honduras_maya_6",
      "hid": 6329711,
      "name": "Hotel Honduras Maya",
      "kind": "Hotel",
      "address": "Colonia Palmira 3era calle ave, Tegucigalpa",
      "latitude": 14.103492,
      "longitude": -87.19721,
      "postal_code": "11101",
      "region": {
        "id": 3421,
        "country_code": "HN",
        "iata": "TGU",
        "name": "Tegucigalpa",
        "type": "City",
        "type_v2": "city"
      },
      "star_rating": 3,
      "images": [
        "https://cdn.worldota.net/t/{size}/content/1d/67/1d67eefb8685fb4b809624bb4f6c437592722183.JPEG",
        "https://cdn.worldota.net/t/{size}/content/6a/0e/6a0ebfe39089a02e1e6da4a03eb6200047545ba1.JPEG"
      ],
      "serp_filters": [
        "has_internet",
        "has_airport_transfer",
        "has_parking",
        "has_kids",
        "has_pool"
      ],
      "amenity_groups": [
        {
          "amenities": [
            "ATM",
            "Air conditioning",
            "24-hour reception"
          ],
          "non_free_amenities": [],
          "group_name": "General"
        },
        {
          "amenities": [
            "Bridal suite",
            "Room service",
            "Interconnecting rooms available"
          ],
          "non_free_amenities": [],
          "group_name": "Rooms"
        }
      ]
    }
  ],
  "processing_status": {
    "total_hotels": 15,
    "cached_hotels": 14,
    "queued_hotels": 1
  },
  "queue_info": {
    "queued_hotel_ids": [
      "comfort_inn_tegucigalpa"
    ],
    "estimated_completion": "about 9 minutes",
    "estimated_completion_seconds": 541
  },
  "timing": {
    "total_duration": 4.38,
    "search_duration": 4.33,
    "cache_check_duration": 0.05
  },
  "hotels_pricing": {
    "hotel_honduras_maya_6": {
      "exchange_rate": 285,
      "agency_currency": "DZD",
      "agency_commission": 285.14,
      "final_price": 142853.54,
      "b2b_price": 142568.4
    },
    "comfort_inn_tegucigalpa": {
      "exchange_rate": 285,
      "agency_currency": "DZD",
      "agency_commission": 218.75,
      "final_price": 109590.36,
      "b2b_price": 109371.61
    }
  },
  "missing_hotel_ids": [
    "comfort_inn_tegucigalpa"
  ],
  "missing_hotels_pricing": {
    "comfort_inn_tegucigalpa": {
      "exchange_rate": 285,
      "agency_currency": "DZD",
      "agency_commission": 218.75,
      "final_price": 109590.36,
      "b2b_price": 109371.61
    }
  }
}

Show such a hotel with its price and a placeholder, or fetch its card with get_hotel_info (detail_level minimal, one id per call, five at a time). The recipe is in Mini and full hotel data.

Size and speed

There is no pagination, and the price list is part of the same answer. Measured on the sandbox (2026-11-01, the search call only):

SearchHotelsAnswerTime
One hotel12.9 KB (1.2 KB gzip)0.4 to 0.7 s
Nuuk, 4 km520 KB (5.7 KB gzip)1.5 s
Tegucigalpa, 15 km1582 KB (17 KB gzip)4.1 s
Los Angeles, 5 km73362 KB (79 KB gzip)9.6 s
Paris, 5 km1,8188.5 MB (1.7 MB gzip)19.3 s

About 93% of a large answer is hotels_details. So: keep the radius to what the screen needs, send Accept-Encoding: gzip, show the first hotels and read the rest in your own pages, and do not run the same search again for each page. One search answers at most 5,000 hotels. Identical searches of your agency that arrive at the same moment are merged into one supplier call; each caller still gets its own request_id and headers.

No streaming for API keys

?stream=true and Accept: text/event-stream are ignored for API keys: the answer is always one JSON body (Content-Type: application/json). Server-sent events exist for FX-Port's own dashboard only. Streaming of flight searches is described in Streaming.

Errors

HTTPCodeCause
400missing_fieldA required field is missing: checkin, checkout or guests; there is neither hotel_id nor latitude and longitude; or a search around a point has no radius. errors[0].field names the first missing field (always the current name: hotel_id, not id) and errors[0].details.missing_fields lists all of them when several are missing. The message is the older sentence (Missing required fields for hotel_id search: ['guests']).
400bad_requestYou sent a currency field.
400invalid_date, date_in_past, date_too_far, checkout_before_checkin, stay_too_longThe dates are not a real stay: a wrong date, in the past, more than 400 days ahead (errors[0].details.max_days_ahead says 400), checkout not after checkin, or more than 30 nights.
400invalid_guests, invalid_adult_count, too_many_children, invalid_child_age, too_many_guests, too_many_roomsThe rooms do not follow the rules of guests and rooms.
400invalid_latitude, invalid_longitude, invalid_radiusA coordinate is out of range, or the radius is not a whole number.
400invalid_residency, invalid_language, invalid_hotel_idThe code is not one the supplier knows.
400invalid_json, invalid_bodyThe body is not a JSON object.
403, 401The key is missing, wrong or not allowed: Authentication.
422supplier_rejected_requestThe supplier refused well formed parameters (errors[0].type is supplier_error; errors[0].details.supplier_code has its code when it is a plain one). Change the dates, the guests, the residency or the radius.
429rate_limit_exceededMore than 6,000 searches a minute for the key: Rate limits. With errors[0].type supplier_error the code says that the hotel supplier is receiving too many searches: retry in a minute.
502bad_gatewayThe supplier could not complete the search (errors[0].type is supplier_error). Retry in a few minutes with backoff.
503supplier_busyThe supplier is at capacity for searches. The call was refused before anything was sent: retry after the seconds in Retry-After.

A 200 with status: "no_hotels_found" is not an error: nothing is available for the request (also when a hotel exists but not for this residency).

Gotchas

  • hotels_pricing is the cheapest rate, not a quote. It has no room, meal or cancellation terms. The rates of the hotel come from get_rates, which returns prepaid rates only, so in rare cases the cheapest rate there is higher.
  • The prices are for the whole stay and all rooms, in the currency of your agency. There is no price per night.
  • Send the same residency and guests to get_rates as to the search, or the rates will differ.
  • A search is not a booking step. get_rates makes its own search of the hotel; no id of the search is needed to book.

Related

Mini and full hotel dataThe cards of the answer, the full document and the hotels without a card.Get hotel ratesThe rooms and rates of the hotel the user picked.Search locationsCoordinates for a typed destination, the input of the geo search.API reference: Geo searchEvery field and error.
PreviousHotel pricingNext Search locations

On this page

  • Search hotels around a point
  • How to get the coordinates
  • Search one hotel by id
  • Request body
  • Legacy names
  • Values the API reads for you
  • Response
  • Join the cards and the prices
  • Size and speed
  • No streaming for API keys
  • Errors
  • Gotchas
  • Related

ยฉ 2026 FX-Port

TermsPrivacy[email protected]