# Panion Partner API > REST API for tour operators on Panion: your bookings, your daily manifest (every channel), your sales and your analytics, plus narrow writes (check guests in, send a balance payment link, cancel, reschedule). It never creates bookings. ## Quickstart for agents 1. Ask the operator for an API key. Owners and admins create keys in Oracle: Settings, API keys. Keys look like `pnn_live_...` and are shown once. 2. Send it as a header on every request: `Authorization: Bearer pnn_live_...`. Never put it in a URL. 3. Call `GET https://www.panion.travel/api/v1/me` first to see the key's operators (`partners`) and scopes. 4. Read the OpenAPI document for exact shapes: https://www.panion.travel/api/v1/openapi.json ## Operators on a key One key can cover several operators: the ones its creator picked from those they own or administer. Every request acts for the key's operators where its creator is still owner or admin; if none are left the key answers 401. - Reads return data across all of the key's operators. Every booking, manifest passenger and sales row has a `partner` object (`id`, `name`). - Add `partner_id=` to any read (bookings, one booking, manifest, sales) for one operator only. An id that is not one of the key's operators answers 404. - Operators sharing one booking system account appear once on the manifest and in other-channel sales, never once per operator. - Writes act only on bookings of the key's operators; any other id answers 404. ## Endpoints - GET https://www.panion.travel/api/v1/me: The key and the operators it acts for. - GET https://www.panion.travel/api/v1/bookings: List bookings. Scope: bookings:read. - GET https://www.panion.travel/api/v1/bookings/{id}: Get one booking. Scope: bookings:read. - GET https://www.panion.travel/api/v1/manifest: Departures and passengers for one day. Scope: manifest:read. - GET https://www.panion.travel/api/v1/sales: Sales per day and channel. Scope: finance:read. - GET https://www.panion.travel/api/v1/analytics/tours: Tour funnel and sales per surface and day. Scope: analytics:read. - GET https://www.panion.travel/api/v1/analytics/sites: Traffic, sources, funnel and sales per site. Scope: analytics:read. - GET https://www.panion.travel/api/v1/bookings/{id}/reschedule-options: Departures a booking can move to on one day. Scope: bookings.reschedule:write. - POST https://www.panion.travel/api/v1/manifest/{id}/checkin: Check a manifest booking in, mark a no-show, or clear it. Scope: checkins:write. - POST https://www.panion.travel/api/v1/bookings/{id}/payment-link: Email the guest a link to pay the balance still owed. Scope: payment_links:write. - POST https://www.panion.travel/api/v1/bookings/{id}/cancel: Cancel a booking with a full refund. Scope: bookings.cancel:write. - POST https://www.panion.travel/api/v1/bookings/{id}/reschedule: Move a booking to another date or departure. Scope: bookings.reschedule:write. ## Analytics Needs analytics:read. Analytics follow one visibility rule, the same one Oracle uses. (1) Your own tours on every surface they are sold on: each Panion-powered site (funnel and sales) and each other sales channel in your booking system, such as GetYourGuide or Viator (sales and revenue only). (2) Whole-site traffic for the sites your operators are on. (3) Sales of other operators' tours only on a site your operator owns; on a site you are only added to, sales cover your own tours. Analytics are read from pre-computed daily rollups that refresh every five minutes, never from raw events. - GET /api/v1/analytics/tours?from=YYYY-MM-DD&to=YYYY-MM-DD[&granularity=day|total][&tour_id=][&surface=]: one row per tour x surface (x UTC day). A `site` surface has `funnel` (sessions that viewed, checked availability, opened checkout, tried to pay), `bookings`, `revenue_minor` (EUR) and `conversion` rates. A `channel` surface (GetYourGuide, Viator, ...) has `bookings`, `participants` and `revenue_minor`, with `funnel` and `conversion` null. `basis` says why you see a row: `own_tour` or `site_owner`. `surfaces` sums revenue per surface. - GET /api/v1/analytics/sites?from=YYYY-MM-DD&to=YYYY-MM-DD[&site=domain]: traffic, a daily series, top sources and sales per site. `role` is `owner` or `added`; only owners get the whole-site `funnel` and conversions per source, and `sales_scope` says whose sales are counted. - At most 400 days per request and 2 analytics requests at a time per key (429 with `Retry-After`); a slow request answers 503. ## Scopes - bookings:read: List and read your Panion bookings. No guest email or phone. - manifest:read: Departures for a day with passengers and check-in state, from every channel. - guests.contact:read: Adds guest email and phone to bookings and the manifest. Off unless you need it. - finance:read: Daily sales totals per channel. - analytics:read: Your tours' funnel and sales on every site and channel they are sold on, plus traffic for the sites you are on. Owners of a site also see every tour sold there. - checkins:write: Mark a manifest booking checked in or a no-show, or clear it. Never changes the booking. - payment_links:write: Email the guest a link to pay the balance still owed. Panion decides the amount. Never a rebook link. - bookings.cancel:write: Cancel a live booking: the seats are released in your booking system and the guest gets a full refund, exactly as Cancel and refund in Oracle. Cannot be undone. - bookings.reschedule:write: Move a live booking to another available date or departure at the same price. Never charges or refunds; a different price is refused. Guest email and phone appear only with guests.contact:read. Without it those keys are absent. ## Writes Writes act on real bookings. Each needs its own scope, and write scopes are off on a new key unless the operator ticks them. - POST /api/v1/manifest/{id}/checkin (checkins:write): body `{"status": "checked_in" | "no_show" | "clear", "present"?: number}`. `{id}` is a passenger `id` from GET /api/v1/manifest. Only the check-in is stored; the booking is never changed. - POST /api/v1/bookings/{id}/payment-link (payment_links:write): empty body `{}`. `{id}` is a booking id from GET /api/v1/bookings, or a manifest passenger id for a booking that exists only in the operator's booking system. Sends a BALANCE link only; Panion decides the amount. Cancelled bookings (a rebook would create a booking) and bookings with nothing owed answer 422. The response has the link `url` and `emailed`. - POST /api/v1/bookings/{id}/cancel (bookings.cancel:write): body `{"reason": string}` (3 to 500 characters), no amount. Cancels a live booking exactly as Cancel and refund in Oracle: seats released in the booking system first, then a FULL refund (the reservation fee on an unpaid pay-later booking), commission voided, guest and operator emailed. No free-cancellation window or departure check applies. `refund.outcome` is `refunded` (money confirmed back), `none` or `pending` (seats released, refund stuck, Panion finishes it by hand). Seats not released: 503, nothing changed, retry with the same Idempotency-Key. Already cancelled: 200 with `already_cancelled`, not counted. Cannot be undone. There is no partial refund endpoint. - GET /api/v1/bookings/{id}/reschedule-options?date=YYYY-MM-DD (bookings.reschedule:write, read only): whether the booking can move to that day and its departures (`start_time_id`, `rate_id`). `price_differs` means room only at another price, which a move refuses. - POST /api/v1/bookings/{id}/reschedule (bookings.reschedule:write): body `{"date": "YYYY-MM-DD", "start_time_id"?: string, "rate_id"?: string}`. Moves the booking in the booking system and on Panion at the SAME price: the payment is never touched and a different price answers 422. The guest free-change window does not apply to the operator; a started tour, an unavailable date and a pay-later booking too close to its balance deadline answer 422. The guest is emailed that the operator moved the booking. Already on that date: 200 with `already_on_date`, not counted. Rules for every write: - Idempotency-Key header is required (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. - Daily limits per key and UTC day (defaults: 2000 check-ins, 50 payment links, 20 cancels, 20 reschedules). Past a limit: 429 with `Retry-After`. Refused actions do not count. There are no bulk endpoints: one booking per request. - Never booking creation: no endpoint reserves, books, rebooks, checks out or creates share links. Bookings are made through Panion checkout or the operator's own booking system. ## Conventions - Errors: RFC 9457 `application/problem+json` with `request_id`. A booking that is not yours answers 404. - Money: integer minor units (`amount_minor`) plus an ISO 4217 `currency`. - Time: ISO 8601 timestamps. Departure times are local `HH:MM` with an IANA `timezone`. - Lists: pass `next_cursor` back as `cursor`; use `updated_since` for incremental sync. - Rate limit: 300 requests per minute per key. A 429 carries `Retry-After` and `RateLimit-*` headers. - Freshness: the manifest reads a mirror of your booking system, refreshed nightly and every two hours for near-term departures. ## Changelog - 2026-10-10 [added] MCP server at https://mcp.panion.travel for AI assistants (Claude, ChatGPT, Cursor, VS Code). It signs in with your Panion account (OAuth), you pick the operators and permissions on Panion's consent page, and its read tools are generated from these same contracts: me, bookings, one booking, manifest, sales and analytics. Calls count against the same rate limits and show in the same request log. - 2026-10-10 [added] Analytics: GET /api/v1/analytics/tours (your tours' funnel, bookings, revenue and conversion per site, plus bookings and revenue per other sales channel such as GetYourGuide and Viator, per UTC day) and GET /api/v1/analytics/sites (traffic, sources, funnel and sales per site), with the new analytics:read scope. Site owners also see every tour sold on their site. At most 400 days per request and 2 analytics requests at a time per key. - 2026-10-10 [added] Booking changes: POST /api/v1/bookings/{id}/cancel (bookings.cancel:write, full refund through the same path as Cancel and refund in Oracle), POST /api/v1/bookings/{id}/reschedule and GET /api/v1/bookings/{id}/reschedule-options (bookings.reschedule:write, same price only). Both writes need an Idempotency-Key and have daily limits (20 cancels and 20 reschedules per key per UTC day by default). There is no refund endpoint. - 2026-10-09 [added] Agent-readable docs generated from the contracts: /api/v1/llms-full.txt, Markdown sections at /api/v1/docs/
.md, Markdown from /api/v1/docs with Accept: text/markdown, this changelog at /api/v1/changelog, worked examples in the OpenAPI document, and Deprecation and Sunset headers for deprecated endpoints. - 2026-10-09 [changed] One key can cover several operators. GET /api/v1/me lists them in `partners`; every booking, manifest passenger and sales row carries its `partner`; reads take an optional `partner_id` (404 outside the key's operators). Oracle shows each key's usage. - 2026-10-09 [added] Writes: POST /api/v1/manifest/{id}/checkin and POST /api/v1/bookings/{id}/payment-link (balance links only), each with its own write scope, a required Idempotency-Key header and per-key daily limits. - 2026-10-09 [added] Read API: GET /api/v1/me, /bookings, /bookings/{id}, /manifest and /sales with scoped keys, rate limits, the OpenAPI document, the reference page and llms.txt. Full list: https://www.panion.travel/api/v1/changelog. A deprecated endpoint answers with `Deprecation` and `Sunset` headers and keeps working until the sunset date. ## Docs - Reference: https://www.panion.travel/api/v1/docs - OpenAPI: https://www.panion.travel/api/v1/openapi.json - Changelog: https://www.panion.travel/api/v1/changelog