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: theidof a passenger in GET /api/v1/manifest.
Request body (JSON):
status(one ofchecked_in,no_show,clear):checked_in,no_show, orclear(back to not seen).present(integer, optional): Withchecked_inonly: how many of the party turned up. Omit for everyone.
Response fields (200):
id(string)check_in(object)status(one ofchecked_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 ofdeferred_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 ofCONFIRMED,CANCELLED,REFUNDED): The booking's status now.REFUNDEDonce the money is back with the guest,CANCELLEDwhen nothing needed returning or a void is still settling,CONFIRMEDonly withrefund.outcomepending.already_cancelled(boolean): True when the booking was already cancelled before this call. Nothing changed.refund(object)outcome(one ofrefunded,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_idof an option from GET /api/v1/bookings/{id}/reschedule-options. Omit to keep the booking's time of day.rate_id(string, optional):rate_idof 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