# Hotel name search

> Find a hotel by name. Partial names and accents are tolerated; typos only when the rest of the name is long enough.

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

Find a hotel by the name a person types, and get its **FX-Port hotel id** to use in [`search_advanced`](https://fx-port.com/docs/hotels/search.md), [`get_rates`](https://fx-port.com/docs/hotels/rates.md) and [`get_hotel_info`](https://fx-port.com/docs/hotels/hotel-details.md). The search compares your text with hotel **names** over every hotel FX-Port knows. It does not price anything and does not need dates.

**Permission:** Read


> [!NOTE]
> Names are not unique: `Hilton` is the name of hundreds of hotels in the world. Always show the city and the country of each hit (they are on by default) and let the user choose. To search around a place instead of by name, use [Search locations](https://fx-port.com/docs/hotels/locations.md) and the [geo search](https://fx-port.com/docs/hotels/search.md).


## Two forms, one search

Use the form that fits your client. They run the same search and return the same answer.


**GET**

`GET /api/v1/hotels/name-search`


**Request**

```bash
curl 'https://api.fx-port.com/api/v1/hotels/name-search?query=Sofitel%20Algiers&limit=3&min_score=60' \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"
```

**POST**

`POST /api/v1/hotels/search-by-name`


**Request**

```bash
curl https://api.fx-port.com/api/v1/hotels/search-by-name \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "Sofitel Algiers",
  "limit": 3,
  "min_score": 60,
  "include_location": true,
  "include_details": false
}'
```


| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `query` | string | Yes | | The hotel name or a part of it, 2 to 100 characters. Control characters are removed. |
| `limit` | integer | No | `10` | Most hits to return, 1 to 100. The POST form caps a larger value at 100 and says so in `warnings` (`coerced_value`); the GET form refuses it with `400 invalid_limit`. A whole number written as text (`"10"`) is read; zero, a negative number, a fraction or another text is `400 invalid_limit` on both forms. |
| `min_score` | number | No | `60` | Lowest match score to return, 0 to 100. A number written as text is read; a number outside that range, or another text, is `400 invalid_min_score`. See [Reading the score](#reading-the-score). |
| `include_location` | boolean | No | `true` | Add the address, city, country code and postal code to each hit. In the POST body the text `true` or `false` (also `1`, `0`, `yes`, `no`, `on`, `off`) is read, with a `coerced_value` warning, and `null` means the default. |
| `include_details` | boolean | No | `false` | Return the whole stored hotel document of each hit instead of the summary. See [Details of each hit](#details-of-each-hit): you almost never want it. |

## Response

A real answer of the sandbox:

~~~json
{
  "environment": "sandbox",
  "query": "Sofitel Algiers",
  "results_count": 2,
  "search_time": "0.060s",
  "hotels": [
    {
      "id": "sofitel_algiers_hamma_garden",
      "name": "Sofitel Algiers Hamma Garden",
      "match_score": 83.39,
      "match_type": "partial",
      "address": "172, rue Hassiba Benbouali, Algiers",
      "city": "Algiers",
      "country_code": "DZ",
      "postal_code": "16014"
    },
    {
      "id": "algiers_hotel",
      "name": "Algiers",
      "match_score": 76.67,
      "match_type": "partial",
      "address": "2845 South Las Vegas Boulevard Las Vegas, South Las Vegas Boulevard, Las Vegas",
      "city": "Las Vegas",
      "country_code": "US",
      "postal_code": "89109"
    }
  ]
}
~~~

| Field | Description |
|---|---|
| `results_count` | How many hotels are in `hotels`. |
| `search_time` | The time the search itself took, **as text** with its unit (`0.060s`). The call takes longer because of the network: 0.2 to 0.8 s in our measurements. |
| `hotels[].id` | The FX-Port hotel id. Send it as `hotel_id`. |
| `hotels[].name` | The name of the hotel. |
| `hotels[].match_score` | How well the name matches, 0 to 100, two decimals. The hits are sorted by it. |
| `hotels[].match_type` | `exact` (the whole name), `partial` (your text is a part of the name, or the name a part of your text) or `fuzzy` (the text is close to the name but not the same). |
| `hotels[].address`, `city`, `country_code`, `postal_code` | Where the hotel is. `postal_code` is an empty string when unknown. Absent with `include_location=false`. |

## Reading the score

The score says how close the **name** is to your text. It does not say that it is the hotel you mean. What the search returned in our probes (sandbox, 2026-11-01):

| You type | Best hit | Score | Type |
|---|---|---|---|
| `Sofitel Algiers Hamma Garden` | Sofitel Algiers Hamma Garden (Algiers, DZ) | 100 | `exact` |
| `sofitel algiers hamma garden hotel` | the same hotel | 85.59 | `partial` |
| `sofitel algirs hamma garden` (a typo) | the same hotel | 86.43 | `fuzzy` |
| `Hamma Garden` (a fragment) | `A garden` (Sevastopol, RU); the hotel you want is second | 81.67 (80.71) | `partial` |
| `sofitel algers` (a typo in a short text) | `Sofi` (Perm, RU): the hotel is **not** found | 72.14 | `partial` |
| `Hilton` | many hotels called `Hilton`, in St Helens, Thanesar, Windsor ... | 100 | `exact` |
| `pereme` and `peremé` | the same hits with the same scores (78.33 at best): accents are ignored | 78.33 | `partial` |

What to do with it:

- **Treat a score of 80 or more as a likely match and anything below as a suggestion.** In our probes, every hit from 80 up was the hotel we typed, and nearly every hit below was another hotel (`Sofitel Algiers` also finds a hotel called `Algiers` in Las Vegas at 76.67). Raise `min_score` to 80 to hide the weak ones, or lower it to 50 when you would rather show too much.
- **Typos are tolerated only when the rest of the text is long enough to carry the match.** A typo in a short text finds another hotel.
- **Do not book the first hit without showing it.** Show the name, the city and the country.

## Details of each hit

With `include_details=true` each hit is the whole stored document of the hotel (snake_case, with `images_ext`, `policy_struct` and every room type) plus `match_score` and `match_type`, and **no** `city` or `country_code` at the top (they are in `region`). It is large: 48 KB for one hotel in our measurement (26 KB for the smallest we measured). It is not the shape of any other call, so a client that wants the hotel page should take the `id` of the hit and call [`get_hotel_info`](https://fx-port.com/docs/hotels/hotel-details.md) for that one hotel. For a result list use the summary, or the compact card of [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md).

## Good practice

- **Debounce a typeahead** at about 300 ms and cancel the request in flight. The search engine runs a few searches at a time, and when it is busy the call answers `503` with `service_busy` and a `Retry-After` of 2 seconds: wait that long and ask again.
- **The ids are the same in the sandbox and on live**, but a hit is not necessarily bookable in the sandbox: see [Sandbox testing](https://fx-port.com/docs/hotels/sandbox-testing.md). The test hotel `test_hotel` (`Test Hotel (Do Not Book)`) is found by name; `test_hotel_do_not_book` is not, so use its id as it is written there.
- **The keys are snake_case** and have no camelCase twin.

## Errors

| HTTP | Code | Cause |
|---|---|---|
| `400` | `missing_field` | `query` is missing from the POST body (`Missing required field: 'query'`; `errors[0].field` is `query`). |
| `400` | `invalid_query` | `query` is not a text of 2 to 100 characters (POST and GET). |
| `400` | `invalid_limit`, `invalid_min_score` | `limit` is not a whole number from 1 to 100 (a larger number is capped on the POST form and refused on the GET form), or `min_score` is not a number from 0 to 100. `errors[0].field` names it and `errors[0].details` carries `min` and `max` when the number is out of range. |
| `400` | `bad_request` | The query is blank, or `include_details` or `include_location` of the POST body is not a boolean or one of the texts above (the message names it). |
| `422` | `validation_error` | The GET form has no `query`, or `include_details` or `include_location` is not a boolean. |
| `503` | `service_busy` | The search engine is busy. Retry after `Retry-After` seconds. |
| `401`, `403` | | The key is missing, wrong or not allowed: [Authentication](https://fx-port.com/docs/authentication.md). |
| `429` | `rate_limit_exceeded` | Too many calls: [Rate limits](https://fx-port.com/docs/rate-limits.md). |

A search that finds nothing is not an error: the answer is `200` with `results_count: 0` and an empty `hotels`.

## Related


- [Geo search](https://fx-port.com/docs/hotels/search.md): Price the hotel you found, for a stay (search it by its id).
- [Hotel details](https://fx-port.com/docs/hotels/hotel-details.md): The page of the hotel, by its id.
- [Mini and full hotel data](https://fx-port.com/docs/hotels/hotel-data.md): The compact card for a list and the full document for one hotel.
- [API reference: Find a hotel by name](https://fx-port.com/docs/api/get-hotel-name-search.md): Every field and error.
