# Geo search: hotels around a point

> Geo search: finds and prices the hotels around a point for a stay.

Endpoint: `POST /hotels/search_advanced`

Source: https://fx-port.com/docs/api/search-hotels

`POST /api/v1/hotels/search_advanced`

Requires an API key with **Read** permission; a **Read+Write** key works too.

Send `latitude`, `longitude` and `radius` (meters, required) with the dates, the guests and the residency; the coordinates of a place come from the location search. The same call searches one hotel when you send `hotel_id` instead. The answer has two parts that you join by hotel id: `hotels_details[]` holds the MINI card of each hotel (identity, place, photos, amenities, no rates) and `hotels_pricing` holds the price of the cheapest rate of each hotel for the whole stay. Call `get_rates` for the rooms, meals and cancellation terms of the hotel the user picks. Hotels that are priced but whose content FX-Port has not cached yet are in `missing_hotel_ids`: read each one with `get_hotel_info`. The answer is always one JSON body: `stream=true` and `Accept: text/event-stream` are ignored for API keys. There is no pagination: a 5 km radius around a big city can return 1,800 hotels and 8 MB, so keep the radius to what the screen needs. A search answers at most 5,000 hotels: when there are more the answer says `truncated` and how many were found. A geo search takes 4 to 20 seconds, a search of one hotel less than a second. 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. Do not send `currency`. A value written in another well-defined way is read and reported in `warnings` (`coerced_value`): a number as text, a date with a midnight time, a sub-territory residency, a radius outside 100 to 70,000 meters, which is moved to the nearest bound. What cannot be read keeps its error code. In the sandbox the search runs on real hotels and prices, but only the supplier test hotels can be booked.

## Request

### Body

Required, JSON.

- `latitude` (number, optional): Geo search: the latitude of the center of the search, -90 to 90. Send it with `longitude` and `radius`; the coordinates of a place come from the locations search. A number written as text (`"48.8566"`) is read as the number, with a `coerced_value` warning. Between -90 and 90.
- `longitude` (number, optional): Geo search: the longitude of the center, -180 to 180. A number written as text (`"2.3522"`) is read as the number, with a `coerced_value` warning. Between -180 and 180.
- `radius` (integer, optional): Geo search: the radius around the center, in meters, 100 to 70,000. Required for a geo search; there is no default. A value outside the range is moved to the nearest bound and the answer carries a `coerced_value` warning. A number written as text (`"3000"`) is read as the number, with a `coerced_value` warning. Between 100 and 70000.
- `hotel_id` (string, optional): Search one hotel instead of the hotels around a point: its FX-Port id (1 to 128 characters: letters, digits and `_ . : -`; spaces around it are trimmed). A JSON integer is read as its text, but it is not looked up as the supplier's numeric `hid` here: send the FX-Port id. When it is sent, `latitude`, `longitude` and `radius` are ignored; a `null` `hotel_id` next to `latitude` and `longitude` is a geo search.
- `checkin` (string, required): Check-in date, `YYYY-MM-DD`: from yesterday (for time zones) to 400 days ahead. A date written with a midnight time (`2026-12-01T00:00:00Z`) is read as that day, with a `coerced_value` warning; any other time of day is `invalid_date`.
- `checkout` (string, required): Check-out date, `YYYY-MM-DD`, after `checkin`. A stay is 1 to 30 nights. A midnight time is read as in `checkin`.
- `guests` (array of object, required): The rooms: one `{adults, children}` object per room, 1 to 9 rooms.
  - `adults` (integer, required): Number of adults in the room, 1 to 6. A number written as text (`"2"`) is read as the number, with a `coerced_value` warning. Between 1 and 6.
  - `children` (array of integer, optional): One entry per child of the room: the age at check-in. `[]` or omitted when there is none. At most 4 children per room.
- `residency` (string, optional): Residency of the guests, a two-letter ISO 3166-1 country code (`gb`, `dz`, `fr`). A sub-territory code of the website's nationality list (`GB-SCT`, `USPR`, `ES-XCI`, `PT-MA`, `BQ-SA` ...) is read as its country, with a `coerced_value` warning: send the 2-letter country. Some rates exist for some residencies only. Not defaulted: always send it.
- `language` (string, optional): Language of the supplier's answer, such as `en`, `fr` or `zh_CN`. A longer tag (`zh-Hans-CN`, `es-419`, `sr-Latn-RS`) is read as the supplier's code, with a `coerced_value` warning. The hotel content in the answer is English whatever you send. Default `en`.
- `id` (string, deprecated, optional): Legacy name: use `hotel_id`. The hotel id, in its older spelling. Use `hotel_id`.
- `hotelId` (string, deprecated, optional): Legacy name: use `hotel_id`. The hotel id, in its older spelling. Use `hotel_id`.
- `checkinDate` (string, deprecated, optional): Legacy name: use `checkin`. The check-in date, in an older spelling. Use `checkin`.
- `checkin_date` (string, deprecated, optional): Legacy name: use `checkin`. The check-in date, in an older spelling. Use `checkin`.
- `check_in` (string, deprecated, optional): Legacy name: use `checkin`. The check-in date, in an older spelling. Use `checkin`.
- `checkoutDate` (string, deprecated, optional): Legacy name: use `checkout`. The check-out date, in an older spelling. Use `checkout`.
- `checkout_date` (string, deprecated, optional): Legacy name: use `checkout`. The check-out date, in an older spelling. Use `checkout`.
- `check_out` (string, deprecated, optional): Legacy name: use `checkout`. The check-out date, in an older spelling. Use `checkout`.

### Example request

```bash
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": []
    }
  ]
}'
```

## Response

### 200: The hotels that are available, with their mini cards and prices. `status` tells how many have content; an empty `hotels_details` with `no_hotels_found` means no hotel is available for the request (also the case when a hotel exists but not for this residency). The examples are real answers of the sandbox: a geo search around Nuuk, a search of one hotel, a geo search around Tegucigalpa where one priced hotel has no card yet, and an empty answer.

- `environment` (string): The environment of the key that made the call: `sandbox` for fxp_test_ keys, `live` for fxp_live_ keys. Present in every JSON body. One of `sandbox`, `live`.
- `request_id` (string): The id of the request, also in the `X-Request-Id` header.
- `warnings` (array of object): Present only when the request used a previous spelling of a field or something the endpoint ignored.
  - `code` (string): What the warning is about. One of `deprecated_field`, `ignored_field`, `coerced_value`, `deprecated_value`, `deprecated_endpoint`.
  - `field` (string): The request field the warning is about.
  - `replacement` (string): The name to use instead, for `deprecated_*` warnings.
  - `message` (string): A sentence that says what happened and what to do.
  - `documentation_url` (string): Where the naming rules are explained.
- `search_id` (string): The id of the search, also in the `X-Search-Id` header. Quote it to support.
- `status` (string): `complete`: every priced hotel has content. `partial`: some do not (see `missing_hotel_ids`). `cache_miss`: none has. `no_hotels_found`: no hotel is available for the request. One of `complete`, `partial`, `cache_miss`, `no_hotels_found`.
- `hotels_details` (array of HotelMini): The mini card of each hotel that has cached content. No rates here: the prices are in `hotels_pricing`.
- `processing_status` (object): How many hotels of the search have content.
  - `total_hotels` (integer): Hotels the supplier priced for the search.
  - `cached_hotels` (integer): Of them, the hotels whose content FX-Port holds: they are in `hotels_details`.
  - `queued_hotels` (integer): Of them, the hotels without cached content that were queued for loading: their ids are in `missing_hotel_ids`.
- `queue_info` (object): The hotels without cached content that FX-Port loads in the background.
  - `queued_hotel_ids` (array of string): The ids of the hotels whose content was queued for loading (at most 50 per search).
  - `estimated_completion` (string, nullable): A rough delay until the content is loaded, such as `about 9 minutes`. `null` when nothing was queued.
  - `estimated_completion_seconds` (integer): The same delay in seconds. Present only when hotels were queued.
- `timing` (object): Where the time of a search went, in seconds.
  - `total_duration` (number): Seconds the whole search took, in FX-Port.
  - `search_duration` (number): Seconds the supplier took to price the hotels.
  - `cache_check_duration` (number): Seconds FX-Port took to read the hotel content.
- `hotels_pricing` (object): The price of the cheapest rate of every priced hotel, by hotel id. Absent when `status` is `no_hotels_found`.
- `missing_hotel_ids` (array of string): The ids of the hotels that are priced but have no cached content yet. Get one with `get_hotel_info`. Absent when `status` is `no_hotels_found`.
- `missing_hotels_pricing` (object): The price entry of each hotel of `missing_hotel_ids`, with the same fields as `hotels_pricing`.
- `truncated` (boolean): `true` when the supplier found more hotels than one search answers and the rest was cut off. Present only then: narrow the search.
- `max_hotels` (integer): The most hotels one search answers. Present with `truncated`.
- `total_hotels_found` (integer): How many hotels the supplier found before the cut. Present with `truncated`.
- `note` (string): A hint about an empty answer, for example that `test_hotel` has no availability for the residency you sent. Present only when there is one.

```json
{
  "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": {}
}
```

## Errors

| Status | Code | Example message |
| --- | --- | --- |
| 400 | `stay_too_long` | A stay can be at most 30 nights ('checkout' is 35 nights after 'checkin'). |
| 400 | `missing_field` | Missing required fields for hotel_id search: ['guests'] |
| 400 | `missing_field` | Missing required fields for hotel_id search: ['checkin', 'guests'] |
| 400 | `bad_request` | Do not pass 'currency'. Currency is resolved automatically from the authenticated agency. |
| 400 | `missing_field` | 'radius' (meters) is required for a geo search. |
| 400 | `invalid_radius` | 'radius' must be a whole number of meters. |
| 422 | `supplier_rejected_request` | The hotel supplier rejected the request parameters. Check dates, guests, residency and radius. |
| 503 | `supplier_busy` | The supplier is temporarily at capacity; retry after the time given in the Retry-After header. |

See [Errors](https://fx-port.com/docs/errors.md) for every code and how to react.

## Related guides

- [Geo search](https://fx-port.com/docs/hotels/search.md)
- [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md)
- [Guests and rooms](https://fx-port.com/docs/hotels/guests-and-rooms.md)
