Panion Docs

Reference

Writes

Check guests in, send payment links, cancel and reschedule.

Writes act on real bookings. Each has its own scope, off on a new key unless ticked. There are no bulk endpoints: one booking per request. No write can create a booking or take an amount: a cancel always refunds in full through the same path as Oracle, and a reschedule never changes the price.

Every write needs an Idempotency-Key header (missing: 400). Use a new UUID per action and reuse it only to retry that action. For 24 hours the same key with the same request replays the first response with Idempotent-Replayed: true; the same key with a different request answers 422; while the first request is still running a duplicate answers 409.

POST /api/v1/manifest/{id}/checkin

Check a manifest booking in, mark a no-show, or clear it.

Sets the check-in state of one manifest row. checked_in with present records a partial party (3 of 4). clear returns the row to not seen. Only the check-in is stored: the booking, its guests and its money are never changed and your booking system is not told.

Scope: checkins:write. Needs an Idempotency-Key header. Counts against the key's daily checkins limit.

Path parameters:

  • id (string): Manifest row id: the id of a passenger in GET /api/v1/manifest.

Request body (JSON):

  • status (one of checked_in, no_show, clear): checked_in, no_show, or clear (back to not seen).
  • present (integer, optional): With checked_in only: how many of the party turned up. Omit for everyone.

Response fields (200):

  • id (string)
  • check_in (object)
    • status (one of checked_in, no_show, nullable)
    • present (integer, nullable)

Example: Three of four turned up

curl -X POST "https://www.panion.travel/api/v1/manifest/5c4b3a29-1807-4f6e-9d5c-4b3a29180716/checkin" \
  -H "Authorization: Bearer $PANION_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"status":"checked_in","present":3}'
{
  "id": "5c4b3a29-1807-4f6e-9d5c-4b3a29180716",
  "check_in": {
    "status": "checked_in",
    "present": 3
  }
}

Responses:

  • 200: Done
  • 400: Invalid body, or the Idempotency-Key header is missing
  • 401: Missing, invalid, expired or revoked key
  • 403: The key lacks the scope
  • 404: Not found, or not yours
  • 409: A request with this Idempotency-Key is still running
  • 422: Not possible for this booking, or the Idempotency-Key was used for a different request
  • 429: Rate limited, or the key's daily limit for this action is reached (checkins). Retry-After says when to try again.
  • 503: A system this action depends on did not answer; retry with the same key

POST /api/v1/bookings/{id}/payment-link

Email the guest a link to pay the balance still owed.

Sends a balance payment link for an existing booking: the pay-later balance of a Panion booking, or the balance your booking system says is due on a direct booking. Panion decides the amount; the request takes none. A cancelled booking (which would need a rebook link, and so a new booking) and anything with nothing owed are refused with 422. Calling it again resends the same open link.

Scope: payment_links:write. Needs an Idempotency-Key header. Counts against the key's daily payment_links limit.

Path parameters:

  • id (string): A booking id from GET /api/v1/bookings, or a manifest row id from GET /api/v1/manifest for a booking that exists only in your booking system. Both are UUIDs and never collide.

Request body (JSON):

  • (no fields)

Response fields (200):

  • id (string): The id you sent.
  • kind (one of deferred_balance, provider_balance): deferred_balance: the balance of a Panion pay-later booking. provider_balance: the balance your booking system says is due.
  • url (string): The payment page. Safe to share with the guest only.
  • amount_minor (integer)
  • currency (string)
  • emailed (boolean): False when the link exists but the email did not go out; share the url another way.

Example: The pay-later balance

curl -X POST "https://www.panion.travel/api/v1/bookings/8f6d2c1e-4b7a-4e3f-9c2d-1a5b6c7d8e9f/payment-link" \
  -H "Authorization: Bearer $PANION_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
{
  "id": "8f6d2c1e-4b7a-4e3f-9c2d-1a5b6c7d8e9f",
  "kind": "deferred_balance",
  "url": "https://www.panion.travel/pay/abc123",
  "amount_minor": 179900,
  "currency": "NOK",
  "emailed": true
}

Responses:

  • 200: Done
  • 400: Invalid body, or the Idempotency-Key header is missing
  • 401: Missing, invalid, expired or revoked key
  • 403: The key lacks the scope
  • 404: Not found, or not yours
  • 409: A request with this Idempotency-Key is still running
  • 422: Not possible for this booking, or the Idempotency-Key was used for a different request
  • 429: Rate limited, or the key's daily limit for this action is reached (payment_links). Retry-After says when to try again.
  • 503: A system this action depends on did not answer; retry with the same key

POST /api/v1/bookings/{id}/cancel

Cancel a booking with a full refund.

Cancels a live (CONFIRMED or RESERVED) Panion booking exactly as Cancel and refund in Oracle: the seats are released in your booking system first, then the guest gets a full refund (the reservation fee on an unpaid pay-later booking), the booking is marked cancelled, the commission is voided, and the guest and your team are emailed. No free-cancellation window or departure check applies: your call is the refund decision. The request takes no amount. If releasing the seats fails nothing changes (503, retry with the same key). A booking that is already cancelled answers 200 with already_cancelled and does not count against the daily limit. Cannot be undone.

Scope: bookings.cancel:write. Needs an Idempotency-Key header. Counts against the key's daily cancels limit.

Path parameters:

  • id (string): Booking id (UUID) from GET /api/v1/bookings.

Request body (JSON):

  • reason (string): Why the booking is cancelled. Kept on the booking for your team.

Response fields (200):

  • id (string): The booking id.
  • status (one of CONFIRMED, CANCELLED, REFUNDED): The booking's status now. REFUNDED once the money is back with the guest, CANCELLED when nothing needed returning or a void is still settling, CONFIRMED only with refund.outcome pending.
  • already_cancelled (boolean): True when the booking was already cancelled before this call. Nothing changed.
  • refund (object)
    • outcome (one of refunded, none, pending): refunded: the payment provider confirmed the money is returned. none: nothing was returned by this call (nothing was paid, or it was already cancelled). pending: the seats were released but the refund did not complete; Panion has been alerted and completes it by hand.
    • amount_minor (integer): Refunded amount in minor units.
    • currency (string)

Example: Cancel with a full refund

curl -X POST "https://www.panion.travel/api/v1/bookings/8f6d2c1e-4b7a-4e3f-9c2d-1a5b6c7d8e9f/cancel" \
  -H "Authorization: Bearer $PANION_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Storm warning, the boat cannot sail"}'
{
  "id": "8f6d2c1e-4b7a-4e3f-9c2d-1a5b6c7d8e9f",
  "status": "REFUNDED",
  "already_cancelled": false,
  "refund": {
    "outcome": "refunded",
    "amount_minor": 179900,
    "currency": "NOK"
  }
}

Responses:

  • 200: Done
  • 400: Invalid body, or the Idempotency-Key header is missing
  • 401: Missing, invalid, expired or revoked key
  • 403: The key lacks the scope
  • 404: Not found, or not yours
  • 409: A request with this Idempotency-Key is still running
  • 422: Not possible for this booking, or the Idempotency-Key was used for a different request
  • 429: Rate limited, or the key's daily limit for this action is reached (cancels). Retry-After says when to try again.
  • 503: A system this action depends on did not answer; retry with the same key

POST /api/v1/bookings/{id}/reschedule

Move a booking to another date or departure.

Moves a live Panion booking in your booking system and on Panion, at the same price: the payment is never touched, so a departure with a different price is refused (422). The guest's free-change window does not apply to you, but a tour that has started cannot be moved, the new date must be available, and a pay-later booking still owing its balance cannot move too close to the charge deadline. Pick a departure with GET /api/v1/bookings/{id}/reschedule-options. The guest is emailed that you moved the booking and your team gets the usual reschedule email. A booking already on that date answers 200 with already_on_date and does not count against the daily limit.

Scope: bookings.reschedule:write. Needs an Idempotency-Key header. Counts against the key's daily reschedules limit.

Path parameters:

  • id (string): Booking id (UUID) from GET /api/v1/bookings.

Request body (JSON):

  • date (string, YYYY-MM-DD): The new departure day (YYYY-MM-DD).
  • start_time_id (string, optional): start_time_id of an option from GET /api/v1/bookings/{id}/reschedule-options. Omit to keep the booking's time of day.
  • rate_id (string, optional): rate_id of the same option, when it has one.

Response fields (200):

  • id (string): The booking id.
  • status (string)
  • date (string): The booking's departure day now.
  • start_time (string, nullable): Its start time now, when known.
  • previous_date (string, nullable)
  • previous_start_time (string, nullable)
  • already_on_date (boolean): True when the booking was already on that date. Nothing changed.

Example: Move to another evening

curl -X POST "https://www.panion.travel/api/v1/bookings/8f6d2c1e-4b7a-4e3f-9c2d-1a5b6c7d8e9f/reschedule" \
  -H "Authorization: Bearer $PANION_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-12-14","start_time_id":"4821","rate_id":"1093"}'
{
  "id": "8f6d2c1e-4b7a-4e3f-9c2d-1a5b6c7d8e9f",
  "status": "CONFIRMED",
  "date": "2026-12-14",
  "start_time": "18:00",
  "previous_date": "2026-12-12",
  "previous_start_time": "18:00",
  "already_on_date": false
}

Responses:

  • 200: Done
  • 400: Invalid body, or the Idempotency-Key header is missing
  • 401: Missing, invalid, expired or revoked key
  • 403: The key lacks the scope
  • 404: Not found, or not yours
  • 409: A request with this Idempotency-Key is still running
  • 422: Not possible for this booking, or the Idempotency-Key was used for a different request
  • 429: Rate limited, or the key's daily limit for this action is reached (reschedules). Retry-After says when to try again.
  • 503: A system this action depends on did not answer; retry with the same key

On this page