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 ยท Rates and booking

Pre-book a rate

Lock in the price of a chosen rate and get a partner_order_id and the booking form ahead of book_hotel. Requires Read+Write permission.

POST/api/v1/hotels/pre_book

Lock the price of the rate the user chose. pre_book asks the supplier to confirm the rate and the price, opens an order at the supplier, and keeps the priced offer on FX-Port's side. It answers a partnerOrderId: the booking id you send to book_hotel next.

Permission: Read+Write (a read key answers 403 read_write_permission_required)

In the sandbox only the supplier test hotels can be booked

Pre-book test_hotel_do_not_book (every residency) or test_hotel (residency dz, ru or us). Any other hotel answers 409 hotel_not_bookable_in_sandbox and lists the test hotels in errors[0].details.test_hotels: the supplier opens no order for it, formData.orderId is null, and book_hotel would fail with order_not_found. Searching and pricing work for every hotel. See Sandbox testing.

pre_book stores the priced offer (with your commission) on FX-Port's side, under the partnerOrderId. book_hotel reads the price, the hotel, the dates and the residency from that offer, not from your request, so the amount debited is the one you were shown here. Call pre_book right before book_hotel.

Request body

POST /api/v1/hotels/pre_book

FieldTypeRequiredDefaultDescription
book_hashstringYesThe bookHash of the rate, from get_rates. It starts with h- and is valid for 30 minutes.
price_increase_percentnumberNo0The price rise, in percent, you accept if the supplier raised the price since get_rates: 0 to 100. With 0 the call fails if the price moved. null means not set (0); a number written as text ("5") is read, with a coerced_value warning.
languagestringNoenLanguage of the hotel name in the answer.

The matchHash of a rate is not a booking token: pre_book does not read it, and a call that sends only a match_hash fails with 400 missing_field. Send the bookHash as book_hash.

curl https://api.fx-port.com/api/v1/hotels/pre_book \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "book_hash": "h-14574656-7ce9-5a21-b673-8654c5ebd416",
  "price_increase_percent": 0
}'

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
hash, bookHashbook_hash

Response

A real answer of the sandbox, in 4 to 5 seconds (the supplier does the work):

{
  "environment": "sandbox",
  "success": true,
  "status": "ok",
  "partnerOrderId": "HMVXX4ZQ3",
  "priceChanged": false,
  "data": {
    "hotelId": "test_hotel_do_not_book",
    "hid": 8473727,
    "rates": [
      {
        "bookHash": "p-58bfbfa0-fd1c-4be9-8a2a-54942cae6627",
        "matchHash": "m-a1f40b22-c086-57c9-b2b5-70c8e59cb27c",
        "room": {
          "name": "Standard room",
          "beddingType": "single bed",
          "miscRoomType": null,
          "capacity": null,
          "bathroom": "private-bathroom",
          "hasPrivateBathroom": true,
          "balcony": false,
          "roomClass": "room",
          "quality": "standard",
          "view": null,
          "floor": null,
          "bedrooms": null,
          "isFamily": false,
          "isClub": false,
          "genderRestriction": null,
          "nonSmoking": true
        },
        "meal": {
          "type": "nomeal",
          "included": false,
          "childMealIncluded": false
        },
        "dailyPrices": [
          1,
          1,
          1
        ],
        "allotment": 77,
        "isPackage": false,
        "anyResidency": true,
        "deposit": null,
        "noShow": null,
        "cancellationPolicy": {
          "freeCancellationBefore": "2026-12-01T21:00:00",
          "policies": [
            {
              "from": null,
              "until": "2026-12-01T21:00:00",
              "originalAmount": 0,
              "originalCurrency": "EUR",
              "currency": "DZD"
            },
            {
              "from": "2026-12-01T21:00:00",
              "until": null,
              "originalAmount": 3,
              "originalCurrency": "EUR",
              "currency": "DZD"
            }
          ]
        },
        "pricing": {
          "supplierCurrency": "EUR",
          "agencyCurrency": "DZD",
          "originalPrice": 3,
          "agencyCommission": 2.97,
          "totalPrice": 1484.97,
          "taxes": {
            "cityTax": {
              "includedInPrice": false,
              "amount": 23.1,
              "currency": "HNL"
            },
            "electricityFee": {
              "includedInPrice": true,
              "amount": 0.6,
              "currency": "EUR"
            },
            "resortFee": {
              "includedInPrice": true,
              "amount": 0.68,
              "currency": "EUR"
            }
          }
        },
        "exchangeRate": 285
      }
    ]
  },
  "formData": {
    "partnerOrderId": "HMVXX4ZQ3",
    "orderId": 757581298,
    "itemId": 232430616,
    "paymentTypes": [
      {
        "amount": "3.00",
        "currencyCode": "EUR",
        "isNeedCreditCard": false,
        "isNeedCvc": false,
        "type": "deposit"
      },
      {
        "amount": "3.00",
        "currencyCode": "USD",
        "isNeedCreditCard": true,
        "isNeedCvc": true,
        "type": "now"
      }
    ]
  }
}
FieldDescription
partnerOrderIdThe booking id. Send it as booking_id to book_hotel, and later to the booking detail, get_voucher and cancel_hotel. It comes twice more as booking_id and partner_order_id.
priceChangedtrue when the supplier's price moved since get_rates, within price_increase_percent.
data.rates[0]The rate you locked, with the price as it stands now (pricing.totalPrice). Its bookHash is a new one that starts with p-: do not reuse it, book_hotel needs only the booking id.
formData.orderId, itemIdThe order opened at the supplier. orderId: null means the supplier opened no order: the hotel cannot be booked, do not call book_hotel.
formData.paymentTypesThe ways the supplier offers to pay: one deposit and about 40 card options, one per currency. Informational: FX-Port always pays with deposit, from your agency balance.

Check the price

When priceChanged is true, or whenever you want to be sure, compare data.rates[0].pricing.totalPrice with the price the traveler accepted. If it is higher, show the new price and let the traveler decide before you call book_hotel. With price_increase_percent at 0 the call fails instead of locking a higher price (see below).

What lives how long

WhatHow long
The bookHash from get_rates30 minutes
The priced offer behind partnerOrderId1 hour: after that book_hotel answers 409 cached_offer_required on live and you must pre_book again
The order at the supplier60 minutes; an order you never book costs nothing and expires

Calling pre_book again with the same rate and the same price_increase_percent within a minute returns the same answer (the same partnerOrderId), so a retry after a timeout does not open a second order. The key of the replay is your agency, the rate and the percentage as read: "5", 5 and 5.0 are one request, another price_increase_percent is a new request. The supplier limits all pre_book calls of FX-Port to about 30 a minute: do not pre-book for many rates "just in case".

Errors

HTTPCodeCause
400missing_field, invalid_rate_hash, invalid_price_increasebook_hash is missing (Missing required field: hash, errors[0].field is book_hash) or malformed, or price_increase_percent is not a number from 0 to 100.
400rate_not_foundThe supplier does not know the rate any more: it expired (30 minutes), or the price rose by more than price_increase_percent. Call get_rates again and choose again. errors[0].type is supplier_error: the refusal comes from the supplier, and any other refusal of the supplier comes the same way, with its own code.
400supplier_invalid_responseThe supplier priced the rate but returned no usable offer (the message says No rates in pre-book response). Call get_rates again.
403read_write_permission_requiredThe key has read permission only.
409hotel_not_bookable_in_sandboxSandbox only: the hotel is not a supplier test hotel.
500internal_errorAn unexpected failure, for example the supplier did not answer. Nothing is booked and you can repeat the call: within a minute it replays the answer of the first call if that one succeeded, otherwise it opens a new order (an order you never book costs nothing).
502supplier_invalid_responseThe supplier answered in a form that could not be used. Nothing is locked. Retry in a few minutes.
503supplier_busyThe supplier is at capacity. The call was refused before anything was sent: nothing is locked, nothing is debited. Retry after the seconds in Retry-After.

The 409 carries the test hotels:

{
  "test_hotels": [
    {
      "id": "test_hotel_do_not_book",
      "hid": 8473727,
      "residency": "any"
    },
    {
      "id": "test_hotel",
      "hid": 8526976,
      "residency": [
        "dz",
        "ru",
        "us"
      ]
    }
  ]
}

Next step

Call book_hotel with the partnerOrderId and the guests of each room.

Related

Get hotel ratesWhere the bookHash comes from.Book a hotelFinish the booking.Hotel pricingWhat totalPrice and what you are debited mean.API reference: Lock a rate before bookingEvery field and error.
PreviousGet hotel ratesNext Book a hotel

On this page

  • Request body
  • Legacy names
  • Response
  • Check the price
  • What lives how long
  • Errors
  • Next step
  • Related

ยฉ 2026 FX-Port

TermsPrivacy[email protected]