Panion Docs

API

Quick book: holds (optional)

Optional seat holds for scarce departures while the guest pays, and releasing them.

Restricted.

Needs a Panion-issued Quick book key. Not available with partner API keys. Contact hello@panion.travel for access.

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 sale reference.
  • 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, child and 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.
    • 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 in provider. For a SagaPay payment, create the order on payment.merchantId for totalMinor with these tax lines and service providers.
      • provider (one of sagapay): The payment provider these instructions are for; more may be added. Today only sagapay.
      • merchantId (string): For a SagaPay payment: create the order on payment.merchantId, the selling operator's SagaPay merchant, and set the order's referenceId to the sale reference.
      • 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)
      • serviceProviders (array): For a SagaPay payment: Panion's fee as a service-provider split. Pass it unchanged in the order's controlFunctions.serviceProviders.
        • each item:
          • serviceProviderId (string): Panion's SagaPay service provider id.
          • amount (object)
    • 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: the Quickbook-Terminal header 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 with WWW-Authenticate: Bearer realm="panion-quickbook".
  • 403: terminal_not_registered: The terminal in the Quickbook-Terminal header 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 past validUntil. From quote: 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 without reserve: take payment and call book.
  • 429: rate_limited: Too many requests from this terminal: 60 writes, 60 books and 300 reads per minute. Retry-After says when to try again.
  • 503: provider_unavailable: The tour operator's booking system did not answer. Try again in a moment.

Restricted.

Needs a Panion-issued Quick book key. Not available with partner API keys. Contact hello@panion.travel for access.

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): The holdRef from 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: the Quickbook-Terminal header 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 with WWW-Authenticate: Bearer realm="panion-quickbook".
  • 403: terminal_not_registered: The terminal in the Quickbook-Terminal header 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-After says when to try again.
  • 503: provider_unavailable: The tour operator's booking system did not answer. Try again in a moment.

On this page