Cancel a hotel booking
Cancel a confirmed hotel booking owned by your agency. Requires Read+Write permission.
POST/api/v1/hotels/cancel_hotel
Cancel a confirmed hotel booking of your agency. FX-Port cancels it at the supplier and gives back what the cancellation schedule of the rate allows: everything before the free cancellation deadline, less (or nothing) after it.
Permission: Read+Write (a read key answers 403 read_write_permission_required)
Request body
POST /
| Field | Type | Required | Description |
|---|---|---|---|
booking_id | string | Yes | The bookingId of book_hotel. |
curl https://api.fx-port.com/api/v1/hotels/cancel_hotel \
-H "Authorization: Bearer fxp_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"booking_id": "HMVXX4ZQ3"
}'Legacy names
These older spellings still work. The answer carries a deprecated_field warning that names the spelling to use (see Legacy names).
| Older name | Use instead |
|---|---|
partner_order_id, partnerOrderId, bookingId | booking_id |
Response
A real answer of the sandbox, in 3.5 to 4 seconds (1 to 4 seconds is usual):
{
"environment": "sandbox",
"success": true,
"status": "ok",
"bookingId": "HMVXX4ZQ3",
"bookingStatus": "refunded",
"adminCancel": false,
"refund": {
"cancellationType": "free",
"type": "Free cancellation",
"agencyAmount": 1482,
"travelerAmount": 1484.97,
"currency": "DZD",
"providerAmount": 3,
"providerCurrency": "EUR"
},
"refundProcessed": true,
"webhookSent": true,
"processingTime": 3.488
}| Field | Description |
|---|---|
bookingStatus | The new status of the booking: refunded (free cancellation), partial_refund (a penalty applied) or cancelled (nothing refunded). Also as booking_status. |
refund.cancellationType | free (the supplier refunded everything), partial (a penalty applied) or none (no refund). Also as cancellation_type. |
refund.type | The same as a label: Free cancellation, Partial cancellation or No refund. |
refund.agencyAmount | What is credited back to your agency, in your currency. For a free cancellation it is everything you were debited (totalPrice - agencyCommission). Also as agency_refund_amount. |
refund.travelerAmount | What the traveler gets back, in your currency. For a free cancellation it is the whole totalPrice. Also as traveler_refund_amount. |
refund.currency | Your agency currency. Also as agency_currency. |
refund.providerAmount, providerCurrency | What the supplier refunded, in its own currency (EUR). For information. |
refundProcessed | true when the refund was handled. In the sandbox nothing moves and it is still true. Also as refund_processed. |
adminCancel | true when FX-Port staff cancelled the booking. Always false for your call. |
webhookSent | An internal step of FX-Port, not a message to you: FX-Port sends no webhooks to customers. |
processingTime | Seconds the call took. |
The keys are camelCase. Twins in snake_case sit next to them: booking_id, booking_status, refund_processed and processing_time at the top, and cancellation_type, agency_refund_amount, traveler_refund_amount, supplier_refund_amount, agency_currency and supplier_currency inside refund. See Legacy names.
What you get back
| When you cancel | bookingStatus | cancellationType | Your refund |
|---|---|---|---|
| Before the free cancellation deadline | refunded | free | Everything you were debited. |
| After it, with a penalty | partial_refund | partial | What was debited minus the penalty of the rate. |
| After it, on a non-refundable rate | cancelled | none | Nothing. |
When the deadline is, and what each window costs, is in the cancellationPolicy of the rate (freeCancellationBefore and policies[]): see Get hotel rates. Cancelling after the free window still succeeds; it is the refund that is smaller. We could not exercise the penalty windows in the sandbox, because the test hotels refund in full until the check-in day: the lines above for partial and none come from the refund rules of the code, not from a sandbox run.
If the call times out
The supplier sometimes takes about 30 seconds to answer a cancellation. The call then fails with 503 supplier_timeout, and the cancellation may or may not have happened. So:
- Read the booking:
GET /.bookings/ {booking_id}?product=hotels refunded,partial_refundorcancelled: it went through.- Still
confirmed: callcancel_hotelagain. In our tests the second call answered in under a second.
for attempt in range(3):
try:
response = requests.post(f"{API}/hotels/cancel_hotel", headers=HEADERS, json={"booking_id": booking_id}, timeout=60)
except requests.Timeout:
continue # read the booking if you can; it stays confirmed until the supplier answers
if response.ok:
break
time.sleep(3) # a 5xx once in a while: the booking is still confirmed, ask againErrors
| HTTP | Code | Cause |
|---|---|---|
400 | invalid_status | The booking is not confirmed. |
400 | missing_field, invalid_booking_id | booking_id is missing (errors[0].field is booking_id), or is not 1 to 256 characters of letters, digits and _ . : -. |
400 | the supplier's code | The supplier refused the cancellation (errors[0].type is supplier_error). The booking is unchanged. |
403 | read_write_permission_required, forbidden | A read key, or a booking of another agency. |
404 | booking_not_found | No booking of your agency has this id in this environment. |
502 | supplier_invalid_response | The supplier answered in a form that could not be used. The cancellation may or may not have happened: read the booking, and call again only if it is still confirmed. |
503 | supplier_timeout | The supplier did not answer in time. Read the booking, then retry. |