curl --request POST \
--url https://api.example.com/v1/hotel_cancel_preview \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"booking_ref": "JNK-H1ZK90",
"last_name": "Carrard"
}
'import requests
url = "https://api.example.com/v1/hotel_cancel_preview"
payload = {
"booking_ref": "JNK-H1ZK90",
"last_name": "Carrard"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({booking_ref: 'JNK-H1ZK90', last_name: 'Carrard'})
};
fetch('https://api.example.com/v1/hotel_cancel_preview', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/hotel_cancel_preview",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'booking_ref' => 'JNK-H1ZK90',
'last_name' => 'Carrard'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/hotel_cancel_preview"
payload := strings.NewReader("{\n \"booking_ref\": \"JNK-H1ZK90\",\n \"last_name\": \"Carrard\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/hotel_cancel_preview")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"booking_ref\": \"JNK-H1ZK90\",\n \"last_name\": \"Carrard\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/hotel_cancel_preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"booking_ref\": \"JNK-H1ZK90\",\n \"last_name\": \"Carrard\"\n}"
response = http.request(request)
puts response.read_body{
"quote": "svq_01J7ZR3M8FKX2P9C",
"state": "completed",
"commitable": true,
"support_level": "AUTO",
"expires_at": "2026-09-03T12:15:00Z",
"item": "itm_7f2c9a4e8b1d",
"refund": {
"value": 41250,
"amount": 123,
"currency": "USD",
"decimal_places": 2,
"basis": "sell_minus_penalty"
},
"penalty": {
"value": 41250,
"amount": 123,
"currency": "USD",
"decimal_places": 2,
"fee_known": true
},
"policy": {
"free_cancel_until": "2026-09-01T00:00:00Z",
"is_refundable_now": true,
"tiers": [
{
"from": "2026-09-01T00:00:00Z",
"amount": {
"amount": 123,
"value": 41250,
"currency": "USD",
"decimal_places": 2
},
"fraction": 0.5
}
]
},
"provider_figures": {
"refund_net": {
"amount": 123,
"value": 41250,
"currency": "USD",
"decimal_places": 2
},
"penalty_net": {
"amount": 123,
"value": 41250,
"currency": "USD",
"decimal_places": 2
},
"currency": "USD"
},
"not_commitable_reason": "not_cancellable"
}{
"error": {
"code": "BAD_REQUEST",
"message": "Malformed JSON in request body.",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "AUTH_REQUIRED",
"message": "Invalid or expired API key.",
"doc_url": "https://docs.gojinko.com/api-reference/authentication"
}
}{
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Insufficient balance — this call costs $0.0150 and your organization has $0.0000 available. Top up at https://dashboard.gojinko.com/developers/billing/topup",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "booking not found"
}
}{
"error": {
"code": "CONFLICT",
"message": "an exchange is already in progress for this booking",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "BAD_REQUEST",
"message": "origins: origins is required; trip_type: trip_type is required",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit or quota exceeded.",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "UPSTREAM_REJECTED",
"message": "sabre-rest BargainFinderMaxRQ failed with status 400: 27131 - Number of connection locations exceeds maximum allowed",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "UPSTREAM_UNAVAILABLE",
"message": "All flight providers are temporarily unable to serve this search. Please retry later. Provider reasons: sabre-rest: provider temporarily closed; travelfusion: quota exhausted",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "UPSTREAM_TIMEOUT",
"message": "sabre-rest BargainFinderMaxRQ timed out after 30s",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}hotel-cancel-preview
Preview what cancelling a hotel booking would cost
curl --request POST \
--url https://api.example.com/v1/hotel_cancel_preview \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"booking_ref": "JNK-H1ZK90",
"last_name": "Carrard"
}
'import requests
url = "https://api.example.com/v1/hotel_cancel_preview"
payload = {
"booking_ref": "JNK-H1ZK90",
"last_name": "Carrard"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({booking_ref: 'JNK-H1ZK90', last_name: 'Carrard'})
};
fetch('https://api.example.com/v1/hotel_cancel_preview', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/hotel_cancel_preview",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'booking_ref' => 'JNK-H1ZK90',
'last_name' => 'Carrard'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/hotel_cancel_preview"
payload := strings.NewReader("{\n \"booking_ref\": \"JNK-H1ZK90\",\n \"last_name\": \"Carrard\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/hotel_cancel_preview")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"booking_ref\": \"JNK-H1ZK90\",\n \"last_name\": \"Carrard\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/hotel_cancel_preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"booking_ref\": \"JNK-H1ZK90\",\n \"last_name\": \"Carrard\"\n}"
response = http.request(request)
puts response.read_body{
"quote": "svq_01J7ZR3M8FKX2P9C",
"state": "completed",
"commitable": true,
"support_level": "AUTO",
"expires_at": "2026-09-03T12:15:00Z",
"item": "itm_7f2c9a4e8b1d",
"refund": {
"value": 41250,
"amount": 123,
"currency": "USD",
"decimal_places": 2,
"basis": "sell_minus_penalty"
},
"penalty": {
"value": 41250,
"amount": 123,
"currency": "USD",
"decimal_places": 2,
"fee_known": true
},
"policy": {
"free_cancel_until": "2026-09-01T00:00:00Z",
"is_refundable_now": true,
"tiers": [
{
"from": "2026-09-01T00:00:00Z",
"amount": {
"amount": 123,
"value": 41250,
"currency": "USD",
"decimal_places": 2
},
"fraction": 0.5
}
]
},
"provider_figures": {
"refund_net": {
"amount": 123,
"value": 41250,
"currency": "USD",
"decimal_places": 2
},
"penalty_net": {
"amount": 123,
"value": 41250,
"currency": "USD",
"decimal_places": 2
},
"currency": "USD"
},
"not_commitable_reason": "not_cancellable"
}{
"error": {
"code": "BAD_REQUEST",
"message": "Malformed JSON in request body.",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "AUTH_REQUIRED",
"message": "Invalid or expired API key.",
"doc_url": "https://docs.gojinko.com/api-reference/authentication"
}
}{
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Insufficient balance — this call costs $0.0150 and your organization has $0.0000 available. Top up at https://dashboard.gojinko.com/developers/billing/topup",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "booking not found"
}
}{
"error": {
"code": "CONFLICT",
"message": "an exchange is already in progress for this booking",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "BAD_REQUEST",
"message": "origins: origins is required; trip_type: trip_type is required",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit or quota exceeded.",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "UPSTREAM_REJECTED",
"message": "sabre-rest BargainFinderMaxRQ failed with status 400: 27131 - Number of connection locations exceeds maximum allowed",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "UPSTREAM_UNAVAILABLE",
"message": "All flight providers are temporarily unable to serve this search. Please retry later. Provider reasons: sabre-rest: provider temporarily closed; travelfusion: quota exhausted",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}{
"error": {
"code": "UPSTREAM_TIMEOUT",
"message": "sabre-rest BargainFinderMaxRQ timed out after 30s",
"doc_url": "https://docs.gojinko.com/concepts/errors"
}
}refund is the customer figure, what they paid for the item less the penalty; the supplier’s own net figures are under provider_figures and are not what the customer receives. commitable: false means a commit would be refused and not_commitable_reason says why. The handle this returns is named quote: it is what POST /v1/hotel_cancel_commit consumes, and it stops binding at expires_at. Every call here needs API authentication; this is about which mode identifies the booking. Use EXACTLY ONE: provider_reference, which additionally requires a credential that OWNS the booking, or booking_ref + last_name, which identifies it without one. Sending both is a 422.Authorizations
Body
The supplier's own booking reference. OWNER mode: beyond the API authentication every call needs, this one requires a credential that OWNS the booking — a tenant-bound key reaches its whole tenant, any other credential must belong to the booking's own user. Mutually exclusive with booking_ref + last_name; sending both is rejected with 422. An unknown reference, another tenant's booking and another user's booking are all answered identically, so this field cannot be used to discover which supplier references exist.
"NUITEE-88231"
The Jinko reference. GUEST mode: pair it with last_name and the pair identifies the booking on its own — API authentication is still required, as on every call here, but the credential does not have to own the booking. Mutually exclusive with provider_reference; one without the other is rejected with 422.
"JNK-H1ZK90"
The lead traveller's surname. Required with booking_ref, and only with it.
"Carrard"
Show child attributes
Show child attributes
Response
What cancelling would cost
This quote ("svq_…"). Pass it to POST /v1/hotel_cancel_commit — it binds the commit to the figures below. Opaque; the format may evolve.
"svq_01J7ZR3M8FKX2P9C"
Lifecycle of the QUOTE, not of a cancellation — nothing has been cancelled by this call. completed means the figures are final until expires_at.
"completed"
Whether a commit against this quote would be accepted right now. When false, not_commitable_reason says why and committing is pointless.
true
How this cancellation would be carried out. AUTO — the platform completes it end to end. MANUAL_REQUIRED — it is possible, but a Jinko agent has to act; expect a delay rather than an immediate result, and do not treat it as a failure. There is no "unsupported" level: a supplier that cannot be cancelled through the API arrives as commitable: false with not_commitable_reason: provider_unsupported, or as a 409 not_cancellable on commit.
AUTO, MANUAL_REQUIRED When this quote stops binding. Committing after it answers 409 quote_expired; take a fresh quote and show the customer the new figure before committing again.
"2026-09-03T12:15:00Z"
The booked item this quote would cancel ("itm_…"). A booking holding several items is quoted and cancelled one item at a time.
"itm_7f2c9a4e8b1d"
THE CUSTOMER FIGURE: what would go back to the payment method, on the basis named in basis. Show this one. Absent is not zero.
Show child attributes
Show child attributes
What the customer would forfeit — read fee_known before showing it.
Show child attributes
Show child attributes
The supplier's cancellation policy as it stands, for explaining the figures to the customer.
Show child attributes
Show child attributes
The supplier's own net figures, for reconciliation — never what the customer receives. refund_net is what the supplier returns to Jinko and penalty_net what it withheld from Jinko; on a booking sold at a margin both differ from the customer figures. Show the customer the top-level refund_amount / penalty_amount, never these. currency is the currency the supplier settles in, which can differ from the currency charged.
Show child attributes
Show child attributes
Why commitable is false. Today: provider_unsupported (the supplier offers no cancellation through the API), not_cancellable (this booking cannot be cancelled as it stands), penalty_exceeds_sell (the penalty is at least what the customer paid), multi_currency_basis (the booking was paid across currencies and no single refund figure exists), funds_in_flight (the original payment is still moving). New reasons may be added, so treat an unrecognised value as "not right now".
"not_cancellable"
