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 ยท Concepts

Mini and full hotel data

Use the compact mini data for search result lists and the full data only for the hotel page, with the size difference measured.

Hotel content comes in two sizes, and you can use both: the mini card for every hotel of a result list, and the full document for the one hotel the user opens. Mini is a compact card of 13 fields, about 5 KB a hotel. Full is the whole document, 35 to 45 KB a hotel on average and up to 150 KB: 7 to 9 times larger, and 92% of a mini card is photos and amenities. So build lists from mini data, fetch full data for one hotel when it is opened, and cache both.

Which one to use

You are buildingUseWhy
A list of results or map pinshotels_details[] of search_advanced, joined by hotel id with its hotels_pricingOne call gives the cards and the prices. Already mini.
Cards for hotel ids you already have (favourites, name search hits, a saved list)POST /hotels/search?detail_level=minimal with up to 200 idsOne call, from FX-Port's cache, no price.
The card of a hotel in missing_hotel_idsget_hotel_info with hotel_id and detail_level=minimalThe only call that asks the supplier when FX-Port does not hold the hotel yet.
The page of one hotel the user openedget_hotel_info (full), next to get_ratesDescription, every room type, policies, facts, contact details.
The pages of several hotels at once (a comparison)POST /hotels/search (full, snake_case) with their idsOne call, from the cache.
A copy of the hotel documents in your own cataloguePOST /hotels/detailsThe stored document, with images_ext and policy_struct.

Mini data

The mini card has 13 fields in snake_case. It is the same card wherever you get it:

  • hotels_details[] of search_advanced (automatic, with every search);
  • POST /hotels/search?detail_level=minimal (alias mini);
  • POST /hotels/details?detail_level=minimal;
  • get_hotel_info with detail_level=minimal.
FieldTypeWhat it holds
idstringThe FX-Port hotel id, a lower-case slug. Send it as hotel_id.
hidintegerThe supplier's numeric hotel id.
namestringName of the hotel.
kindstringType of property: Hotel, Apartment, Resort, Hostel ...
addressstringStreet address, in English.
latitude, longitudenumberPosition, for the map.
postal_codestring or nullPostal code; null for a minority of hotels.
regionobject{ id, country_code, iata, name, type, type_v2 }: the city.
star_ratinginteger0 to 5. 0 means the property is not rated.
imagesarray or nullPhoto URL templates, best first (see Images). null for a few hotels (62 of 1,818 around Paris).
serp_filtersarray of stringsFilter tags: has_internet, has_parking, has_pool, has_spa, has_fitness, has_pets, has_kids, has_meal ...
amenity_groupsarray[{ group_name, amenities[], non_free_amenities[] }].
{
  "id": "test_hotel_do_not_book",
  "hid": 8473727,
  "name": "Test Hotel (Do Not Book) test",
  "kind": "Apartment",
  "address": "Avenida Roble, Frente al Mall Multiplaza, Tegucigalpa",
  "latitude": 14.079872,
  "longitude": -87.21677,
  "postal_code": null,
  "region": { "id": 3421, "country_code": "HN", "iata": "TGU", "name": "Tegucigalpa", "type": "City", "type_v2": "city" },
  "star_rating": 0,
  "images": [
    "https://cdn.worldota.net/t/{size}/extranet/b5/d3/b5d3d33394494c68321246882c1bd93a6832dcd5.jpeg",
    "https://cdn.worldota.net/t/{size}/extranet/15/44/15440558c952ec4c3488730d1563dc9e8d25832c.JPEG"
  ],
  "serp_filters": ["has_internet", "has_parking", "has_spa", "has_pets", "has_jacuzzi", "kitchen"],
  "amenity_groups": [
    { "amenities": ["24-hour reception", "Early check-in", "Late check-out"], "non_free_amenities": [], "group_name": "General" }
  ]
}

The reference describes every field: The Hotel mini object.

A mini card has no price, no room and no rate. It is made for a list: where the hotel is, what it looks like, what it offers. The price of the cheapest rate is in hotels_pricing, and the rooms and cancellation terms come from get_rates.

Full data

The full document adds what the hotel page needs: the description, every room type with photos and amenities, the policies, the facts, the contact details and the times of check-in and check-out. It comes in three spellings:

CallKeysNotes
POST /hotels/search (default detail_level=full)snake_case31 fields, from the cache. The Hotel full object.
get_hotel_info (default)camelCaseThe same data with the room properties decoded into words. For a hotel that is not cached it asks the supplier.
POST /hotels/detailssnake_caseThe stored document: the 31 fields plus images_ext and policy_struct. About 27% larger.

The fields of the full document (snake_case names; the camelCase document has the same data):

FieldWhat it holds
id, hid, name, kind, hotel_chainIdentity. hotel_chain is No chain for an independent hotel.
address, postal_code, latitude, longitude, regionPlace.
star_rating, star_certificateStars, and the official certificate when there is one ({ certificate_id, valid_to }, else null).
email, phoneContact details of the hotel.
check_in_time, check_out_time, check_in_time_endTimes of day (14:00:00, local time), not dates. check_in_time_end only for hotels that state it.
front_desk_time_start, front_desk_time_endOpening hours of the front desk, or null.
images, amenity_groups, serp_filtersThe same as in the mini card.
description_structThe description in titled blocks: [{ title, paragraphs[] }].
metapolicy_structThe policies: internet, meal, children_meal, extra_bed, cot, pets, shuttle, parking, children, visa, deposit, no_show, add_fee, check_in_check_out. Read children before you assume a child stays free.
metapolicy_extra_infoThe house rules as free text, or null.
room_groupsEvery room type: room_group_id, name, name_struct, rg_ext (numeric codes), room_amenities, images. About 80% of the bytes of the document.
factsFloors, rooms, year built, year renovated, electricity, registration. Many are null.
payment_methodsPayment methods accepted at the property.
keys_pickupHow guests get the keys: type, is_contactless, phone, email, apartment_office_address, apartment_extra_information.
is_closed, is_gender_specification_required, deletedFlags. is_closed: true means get_rates finds no rate.
distance_center, giata_codeOptional: present for some hotels only.

get_hotel_info spells these keys in camelCase (starRating, amenityGroups, descriptionStruct, roomGroups), and differs in six places:

In the snake_case documentIn get_hotel_info
amenity_groups[].non_free_amenitiesamenityGroups[].paidAmenities
check_in_time, check_out_timecheckIn, checkOut (the snake_case names are returned as twins)
email, phonecontact: { email, phone }
front_desk_time_start, front_desk_time_endfrontDeskHours: { start, end }
room_groups[].rg_ext (12 numeric codes)decoded: roomClass, quality, bathroom, hasPrivateBathroom, capacity, balcony, view, floor, bedrooms, beddingType, isFamily, isClub, genderRestriction
description_struct[]the same blocks plus html

The entries inside metapolicyStruct keep their snake_case keys (age_start, price_unit, meal_type).

How much smaller is mini

Measured on the sandbox on 2026-11-01 (decoded JSON; "gzip" is what travels over the network):

WhatMiniFullFull / mini
One hotel, the test hotel (7 photos, 11 amenity groups, 9 room types)2.3 KB (0.9 KB gzip)11.9 KB (3.2 KB gzip), get_hotel_info 13.1 KB5.2x (5.7x)
15 hotels around Tegucigalpa79 KB, 5.3 KB a hotel (16.7 KB gzip)654 KB, 43.6 KB a hotel (72.8 KB gzip); get_hotel_info 723 KB8.3x decoded, 4.4x gzip
73 hotels around Los Angeles349 KB, 4.8 KB a hotel (75.7 KB gzip)2,493 KB, 34.2 KB a hotel (309 KB gzip); get_hotel_info 2,784 KB, 38.1 KB a hotel7.1x decoded, 4.1x gzip

A mini card is also predictable: in the 73 hotels it runs from 0.4 KB to 8.3 KB, 4.6 KB in the middle. A full document runs from 3.8 KB to 131 KB, 23 KB in the middle.

Where the bytes go (73 hotels): in a mini card, photos are 58% and amenities 34%, so 92% of it is photos and amenities. In a full document the room types are 79%, the photos 8% and the amenities 5%.

A mini card is still larger than what a list shows. What a result card needs is the id, name, kind, address, position, stars, filters, the city name and one photo: about 0.5 KB, or 1.0 KB with six photos (computed on the 15 hotels above; 5.1 KB a hotel in mini form). Keep those fields when you store or forward the cards, and do not send the amenity groups to a phone.

Mini saves bytes, not server time. Both forms read the same cache document and the answer takes 0.2 to 0.7 s for 15 to 73 hotels either way. The saving is on the network, in parsing, and in your own storage: 8.5 MB of search answer for 1,818 hotels around Paris is 93% mini cards.

Hotels without content

A search prices every hotel the supplier returns, but the content comes from FX-Port's own cache of hotel content. A hotel that is priced but not cached yet (a new property) is not in hotels_details. The search tells you:

{
  "status": "partial",
  "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 },
  "missing_hotel_ids": ["comfort_inn_tegucigalpa"],
  "hotels_pricing": { "comfort_inn_tegucigalpa": { "final_price": 109590.36, "b2b_price": 109371.61, "...": "..." } }
}
  • missing_hotel_ids lists the hotels that have a price and no card. The price is in hotels_pricing (and missing_hotels_pricing) as for any other hotel.
  • FX-Port queues them for loading in the background (queue_info): at most 50 ids a search. They are usually in the cache a few minutes later and then appear in hotels_details of the next search.
  • status is complete when every hotel has content, partial when some do not, cache_miss when none does.

It is rare: 1 hotel of 15 in the example (and cached 9 minutes later), none of 73 around Los Angeles, none of 1,818 around Paris.

To get the card of a missing hotel now, call get_hotel_info with that one id:

{ "hotel_id": "comfort_inn_tegucigalpa", "detail_level": "minimal" }

It is the only content call that asks the supplier when FX-Port does not hold the hotel (about 0.3 s), and it keeps the hotel for everybody afterwards. It does this for one id per call. The calls that read many ids (POST /hotels/search, POST /hotels/details, get_hotel_info with hotel_ids) read the cache only: they leave out the ids they do not hold and do not report them.

How to do it well:

  • Fill only the hotels the user can see, not all of them. Render the others with their price and a placeholder, or leave them out until their content is cached.
  • Make at most five calls at a time. The supplier answers about 30 hotel content calls a minute for the whole of FX-Port, so a 429, 502 or 503 answer means you went too fast or the supplier is busy: back off. A 503 supplier_busy was refused before anything was sent: wait the seconds of Retry-After and ask again.
  • Send "language": "en" when you fetch a hotel that is not cached. The content that is fetched is stored for every customer in the language of that call.
  • Remember the result for the hotel id, and remember a failure for a few minutes, so that the next search does not repeat the same calls.

Images

images holds URL templates. Replace {size} to get a picture:

https://cdn.worldota.net/t/{size}/extranet/b5/d3/b5d3d33394494c68321246882c1bd93a6832dcd5.jpeg
{size}UseNote
120x120Thumbnails, map pins
240x240Cards of a result list18 KB for the photo above
640x400A hotel page, the first screen
1024x768A gallery, a full-width image228 KB for the photo above
x500500 px high, the width follows

These sizes answer 200. orig does not (it redirects to a web page) and unknown sizes answer 400. Choose the size by what you display: one 1024x768 photo can weigh as much as 40 mini cards, so the size you ask the CDN for matters more than the card.

const photo = (template, size = "240x240") => template.replace("{size}", size);

const card = search.hotels_details[0];
const thumbnail = card.images ? photo(card.images[0]) : "/no-photo.svg"; // images can be null
def photo(template, size="240x240"):
    return template.replace("{size}", size)


card = search["hotels_details"][0]
thumbnail = photo(card["images"][0]) if card["images"] else "/no-photo.svg"  # images can be null

images_limit (0 to 30) on the content calls (POST /hotels/search, POST /hotels/details and get_hotel_info) keeps only the first N photos of each mini card: images_limit=1 removes about half of a mini card. It applies to the mini card only (detail_level=minimal, alias mini): with the full document it is ignored and the answer carries a coerced_value warning. It does not exist on search_advanced, which always returns every photo of every hotel.

Reading hotel content

Three calls read hotels by id. They read FX-Port's cache, they take up to 200 ids, and they answer a bare JSON array that does not follow the order of your ids; an id that is not cached is left out and queued for loading. An id can also be the supplier's numeric hid, as a JSON integer or as digits ([8473727]): POST /hotels/search and POST /hotels/details look an id up as an id and then as a hid, a hotel reached by both comes back once, and a digits-only id that matches nothing is not queued; get_hotel_info takes a hid too. Spaces around an id are trimmed.

CallBodyAnswer
POST /hotels/search{ "hotel_ids": [...] } (a bare list still works)Mini cards with ?detail_level=minimal, full documents otherwise
POST /hotels/detailsthe sameThe stored documents (?detail_level=minimal gives mini cards)
POST /hotels/get_hotel_info{ "hotel_id": "..." } or { "hotel_ids": [...] }{ "data": [...] }, camelCase (mini cards with detail_level)
curl 'https://api.fx-port.com/api/v1/hotels/search?detail_level=minimal&images_limit=1' \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "hotel_ids": [
    "test_hotel_do_not_book",
    "hotel_honduras_maya_6"
  ]
}'
Query parameterDefaultDescription
detail_levelfullminimal (alias mini) or full. Anything else answers 400 invalid_detail_level.
fieldsnoneA comma separated list of top-level fields (id,name,star_rating), at most 40. It replaces detail_level; id is always added. Nested paths are not supported.
images_limitnoneWith detail_level=minimal and no fields: the most photos of each hotel, 0 to 30 (another number answers 400 invalid_images_limit). In any other case it is ignored and the answer carries a coerced_value warning. POST /hotels/details reads it too, with detail_level=minimal.

The answer carries X-Results-Count, X-Detail-Level and X-Search-Time headers. A body that is not a list of ids, or that holds more than 200 ids, answers 400 invalid_body, and a malformed id 400 invalid_hotel_id. See the reference: POST /hotels/search, POST /hotels/details and POST /hotels/get_hotel_info.

Legacy names

These older spellings still work. The answer carries a deprecated_field warning that names the spelling to use (see Legacy names).

Older nameUse instead
hotelIds, ids (body)hotel_ids
detailLevel (query)detail_level
imagesLimit (query)images_limit

Caching

  • Cache content by hotel id. A hotel's content changes rarely: a day is a safe default for a card, and a result list can use content that is a week old. Do not put the language in the key: cached content is English whatever language you send (language only applies to the live fetch of a hotel that is not cached, and to room names in get_rates).
  • Never cache prices beyond the search that returned them. bookHash values live 30 minutes, and a price can change at any time.
  • Cache the card, not the answer. Keep the fields you show and drop amenity_groups and all but the photos you display.
  • Do not refetch on every search. Search again only for prices; read content for the ids you have not seen.

A results list in code

The answer of search_advanced has the cards in hotels_details and the prices in hotels_pricing. Join them by hotel id, and treat the ids of missing_hotel_ids apart:

search = call("POST", "/hotels/search_advanced", json=request)

cards = {card["id"]: card for card in search["hotels_details"]}
rows = []
for hotel_id, price in search["hotels_pricing"].items():
    card = cards.get(hotel_id)  # None for a hotel in missing_hotel_ids
    rows.append({
        "id": hotel_id,
        "name": card["name"] if card else None,
        "stars": card["star_rating"] if card else None,
        "photo": card["images"][0].replace("{size}", "240x240") if card and card["images"] else None,
        "price": price["final_price"],
        "currency": price["agency_currency"],
    })
rows.sort(key=lambda row: row["price"])

to_fill = [row["id"] for row in rows[:20] if row["name"] is None]  # only what is visible, five at a time
const search = await call("POST", "/hotels/search_advanced", request);

const cards = new Map(search.hotels_details.map((card) => [card.id, card]));
const rows = Object.entries(search.hotels_pricing).map(([id, price]) => {
  const card = cards.get(id); // undefined for a hotel in missing_hotel_ids
  return {
    id,
    name: card?.name ?? null,
    stars: card?.star_rating ?? null,
    photo: card?.images?.[0]?.replace("{size}", "240x240") ?? null,
    price: price.final_price,
    currency: price.agency_currency,
  };
});
rows.sort((a, b) => a.price - b.price);

const toFill = rows.slice(0, 20).filter((row) => row.name === null).map((row) => row.id); // only what is visible

Related

Geo searchThe search that returns the mini cards and the prices.Hotel detailsget_hotel_info, the call for the hotel page and for hotels without content.Get hotel ratesRooms, meals and cancellation terms for the stay.Rate limitsHow many calls a key may make, and how to stay below.
PreviousSandbox testingNext Guests and rooms

On this page

  • Which one to use
  • Mini data
  • Full data
  • How much smaller is mini
  • Hotels without content
  • Images
  • Reading hotel content
  • Legacy names
  • Caching
  • A results list in code
  • Related

ยฉ 2026 FX-Port

TermsPrivacy[email protected]