API
Quick book: holds (optional)
Optional seat holds for scarce departures while the guest pays, and releasing them.
Restricted.
POST /api/quickbook/v1/reserve
Optional: hold the seats of a quoted sale while the guest pays.
Optional. The main path books after payment without a hold. Use reserve for scarce departures (few seats left, popular evening departures), where a seat could go to the web while the guest pays. Takes a hold in the operator's booking system and returns holdRef (the same value as reference), expiresAt and the cart line checked again. A tour whose booking system cannot hold answers hold_not_supported: carry on with the main path.
Headers: Authorization: Bearer pqb_live_... and Quickbook-Terminal: <terminal id>. Rate limit: writes.
Request body (JSON):
reference(string): The Panion sale reference,PAN-and 10 uppercase letters or digits. Pass it to the payment provider as the payment's reference.idempotencyKey(string): A new value (a UUID) per hold. A retry with the same value returns the same hold.
Response fields (200):
holdRef(string): The hold, the same value as the salereference.expiresAt(string, ISO 8601 date-time): The hold is released then. Show a timer.cartLine(object)operatorName(string)tourName(string)start(object)date(string, YYYY-MM-DD): Local date, YYYY-MM-DD.time(string): Local wall-clock start time, HH:MM (24 hour).timeZone(string): IANA time zone, e.g. Europe/Oslo.
guests(array)- each item:
type(string): Guest type key from GET /tours/{activityId}/slots (adult,childand so on, from the operator's pricing categories).label(string)count(integer)unitMinor(integer): Price per guest, in minor units.lineMinor(integer): unitMinor times count, in minor units.
- each item:
totalMinor(integer): The amount to charge, VAT included, in minor units.currency(string): ISO 4217 currency code.payment(object): Payment instructions for the provider named inprovider. For a SagaPay payment, create the order on payment.merchantId fortotalMinorwith these tax lines and service providers.provider(one ofsagapay): The payment provider these instructions are for; more may be added. Today onlysagapay.merchantId(string): For a SagaPay payment: create the order on payment.merchantId, the selling operator's SagaPay merchant, and set the order'sreferenceIdto the salereference.taxLines(array): For a SagaPay payment: the order's tax array, the VAT included in the total, per rate. Pass it unchanged.- each item:
amount(integer): VAT included in the total at this rate, in minor units.percentage(number): VAT rate in percent, e.g. 12.type(VAT)
- each item:
serviceProviders(array): For a SagaPay payment: Panion's fee as a service-provider split. Pass it unchanged in the order'scontrolFunctions.serviceProviders.- each item:
serviceProviderId(string): Panion's SagaPay service provider id.amount(object)
- each item:
requiresPhone(boolean)orderLineText(string): Short label for the receipt and the order: tour name, date and time, at most 40 characters.
Example: Hold a quoted sale
curl -X POST "https://www.panion.travel/api/quickbook/v1/reserve" \
-H "Authorization: Bearer $PANION_QUICKBOOK_KEY" \
-H "Quickbook-Terminal: $TERMINAL_ID" \
-H "Content-Type: application/json" \
-d '{"reference":"PAN-3F9A1C2B7D","idempotencyKey":"2d1c6f0e-8a4b-4e3c-9b7a-5f6e4d3c2b1a"}'{
"holdRef": "PAN-3F9A1C2B7D",
"expiresAt": "2026-11-13T10:20:00+01:00",
"cartLine": {
"operatorName": "Destination Tromsø",
"tourName": "Fjord and Whale Safari",
"start": {
"date": "2026-11-14",
"time": "09:00",
"timeZone": "Europe/Oslo"
},
"guests": [
{
"type": "adult",
"label": "Adult",
"count": 2,
"unitMinor": 189000,
"lineMinor": 378000
},
{
"type": "child",
"label": "Child",
"count": 1,
"unitMinor": 94500,
"lineMinor": 94500
}
],
"totalMinor": 472500,
"currency": "NOK",
"requiresPhone": true,
"orderLineText": "Fjord and Whale Safari 2026-11-14 09:00",
"payment": {
"provider": "sagapay",
"merchantId": "MERCHANT_ID",
"taxLines": [
{
"amount": 50625,
"percentage": 12,
"type": "VAT"
}
],
"serviceProviders": [
{
"serviceProviderId": "PANION_SERVICE_PROVIDER_ID",
"amount": {
"percentage": 12.5
}
}
]
}
}
}Errors:
- 400:
terminal_not_registered: theQuickbook-Terminalheader is missing or malformed.invalid_request: The body or query does not validate (a missing field, a bad date or number). - 401:
unauthorized: The Quick book key is missing, not valid, expired or revoked, or was sent in the URL. Answers withWWW-Authenticate: Bearer realm="panion-quickbook". - 403:
terminal_not_registered: The terminal in theQuickbook-Terminalheader is not registered, not active or not bound to this key. A missing or malformed header answers 400 with this code.tour_not_sellable_here: This terminal cannot charge for this tour's operator, so it cannot sell it. - 409:
sold_out: Not enough seats left on this departure.price_changed: The live price differs from the quote, and the quote is pastvalidUntil. Fromquote: a guest type the tour does not sell (read the slots again). - 410:
hold_expired: The seat hold ran out. - 422:
hold_not_supported: This tour's booking system cannot hold seats. Carry on withoutreserve: take payment and callbook. - 429:
rate_limited: Too many requests from this terminal: 60 writes, 60 books and 300 reads per minute.Retry-Aftersays when to try again. - 503:
provider_unavailable: The tour operator's booking system did not answer. Try again in a moment.
Restricted.
DELETE /api/quickbook/v1/reserve/{holdRef}
Optional: release a hold.
Optional, only after reserve. Releases the seats when the guest does not pay. Idempotent: releasing a hold that is already released or expired answers the same.
Headers: Authorization: Bearer pqb_live_... and Quickbook-Terminal: <terminal id>. Rate limit: writes.
Path parameters:
holdRef(string): TheholdReffrom POST /reserve.
Response fields (200):
holdRef(string): The Panion sale reference,PAN-and 10 uppercase letters or digits. Pass it to the payment provider as the payment's reference.released(true)
Example: Release a hold
curl -X DELETE "https://www.panion.travel/api/quickbook/v1/reserve/PAN-3F9A1C2B7D" \
-H "Authorization: Bearer $PANION_QUICKBOOK_KEY" \
-H "Quickbook-Terminal: $TERMINAL_ID"{
"holdRef": "PAN-3F9A1C2B7D",
"released": true
}Errors:
- 400:
terminal_not_registered: theQuickbook-Terminalheader is missing or malformed.invalid_request: The body or query does not validate (a missing field, a bad date or number). - 401:
unauthorized: The Quick book key is missing, not valid, expired or revoked, or was sent in the URL. Answers withWWW-Authenticate: Bearer realm="panion-quickbook". - 403:
terminal_not_registered: The terminal in theQuickbook-Terminalheader is not registered, not active or not bound to this key. A missing or malformed header answers 400 with this code. - 404:
not_found: No such sale or hold for this terminal: the reference is unknown or belongs to another terminal. - 429:
rate_limited: Too many requests from this terminal: 60 writes, 60 books and 300 reads per minute.Retry-Aftersays when to try again. - 503:
provider_unavailable: The tour operator's booking system did not answer. Try again in a moment.