# Analytics

> How each tour sells on every site and channel, and site traffic.

Source: https://www.panion.travel/docs/api/reference/analytics

Needs the `analytics:read` scope. Money is EUR in minor units; days are UTC.

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.

A surface is either a `site` (a Panion-powered website: sessions per funnel step, bookings, revenue and conversion rates) or a `channel` (another sales channel from your booking system: bookings, participants and revenue only, because a channel sale has no session to divide by). Every tour row says why you see it in `basis`: `own_tour` or `site_owner`.

Limits: at most 400 days per request and 2 analytics requests at a time per key (more answer 429 with `Retry-After`). A request that takes longer than 20 seconds answers 503; retry or ask for a shorter range.

### GET /api/v1/analytics/tours

Tour funnel and sales per surface and day.

Your tours on every surface they are sold on: each Panion-powered site (sessions that viewed the tour, checked availability, opened checkout and tried to pay, then bookings, revenue in EUR and conversion rates) and each other sales channel in your booking system, such as GetYourGuide or Viator (bookings, participants and revenue only: a channel sale has no session, so no funnel and no rate). If your operator owns a site, every operator's tours sold on it are included too, marked `basis: site_owner`. Days are UTC. At most 400 days per request.

Scope: `analytics:read`.

Query parameters:

- `from` (string, YYYY-MM-DD): First UTC day (YYYY-MM-DD).
- `to` (string, YYYY-MM-DD): Last UTC day, inclusive (YYYY-MM-DD). At most 400 days from `from`.
- `granularity` (one of `day`, `total`, optional, default "day"): `day`: one row per tour, surface and UTC day. `total`: one row per tour and surface for the whole range.
- `tour_id` (string, optional): Only this tour.
- `surface` (string, optional): Only this surface: a site domain or a channel id from `surface.id`.
- `partner_id` (string, optional): Only this operator: one of the key's operators (see GET /api/v1/me). Any other id answers 404. What you see follows that operator's sites and tours.

Response fields (200):

- `from` (string, YYYY-MM-DD)
- `to` (string, YYYY-MM-DD)
- `granularity` (one of `day`, `total`)
- `currency` (`EUR`)
- `partners` (array): The operators this answer is for (the key's, or `partner_id`).
  - each item:
    - `id` (string)
    - `name` (string)
- `rows` (array)
  - each item:
    - `date` (string, YYYY-MM-DD, nullable): UTC day. Null with `granularity=total` (the whole range).
    - `surface` (object)
      - `type` (one of `site`, `channel`): `site`: a Panion-powered website (sessions, funnel and sales). `channel`: another sales channel from your booking system, such as GetYourGuide or Viator (sales and revenue only, never a funnel).
      - `id` (string): The site's domain, or the channel's id (e.g. `getyourguide`).
      - `name` (string)
    - `tour` (object)
      - `id` (string, nullable)
      - `name` (string, nullable)
    - `operator` (object, nullable): The tour's operator. Null when the tour is not matched to one.
      - `id` (string)
      - `name` (string)
    - `basis` (one of `own_tour`, `site_owner`): Why you see this row: `own_tour` (a tour of one of your operators, shown on every surface) or `site_owner` (another operator's tour sold on a site you own).
    - `funnel` (object, nullable): Null on a channel.
      - `views` (integer): Sessions that viewed the tour.
      - `availability_checks` (integer): Sessions that checked availability.
      - `checkout_opens` (integer): Sessions that opened checkout.
      - `checkout_attempts` (integer): Sessions that tried to pay.
    - `bookings` (integer)
    - `participants` (integer, nullable): Channels only; null on a site.
    - `revenue_minor` (integer): Integer minor units (cents) of `currency` (EUR).
    - `conversion` (object, nullable): Null on a channel.
      - `view_to_check` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `check_to_open` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `open_to_attempt` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `attempt_to_booking` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `view_to_booking` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
- `surfaces` (array): Every row of `rows` summed per surface, largest revenue first.
  - each item:
    - `surface` (object)
      - `type` (one of `site`, `channel`): `site`: a Panion-powered website (sessions, funnel and sales). `channel`: another sales channel from your booking system, such as GetYourGuide or Viator (sales and revenue only, never a funnel).
      - `id` (string): The site's domain, or the channel's id (e.g. `getyourguide`).
      - `name` (string)
    - `funnel` (object, nullable): Distinct sessions per step. Sites only.
      - `views` (integer): Sessions that viewed the tour.
      - `availability_checks` (integer): Sessions that checked availability.
      - `checkout_opens` (integer): Sessions that opened checkout.
      - `checkout_attempts` (integer): Sessions that tried to pay.
    - `bookings` (integer)
    - `participants` (integer, nullable)
    - `revenue_minor` (integer): Integer minor units (cents) of `currency` (EUR).
    - `revenue_share` (number): This surface's share of the revenue in `surfaces` (0 to 1).
    - `conversion` (object, nullable): Step-through rates. Sites only.
      - `view_to_check` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `check_to_open` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `open_to_attempt` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `attempt_to_booking` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
      - `view_to_booking` (number, nullable): A ratio (0.25 = 25%). Null when the step before it is zero.
- `other_channels_available` (boolean): False when other-channel sales could not be read this time; the site rows are still complete.

Example: One tour on a site and on GetYourGuide

```sh
curl "https://www.panion.travel/api/v1/analytics/tours?from=2026-11-01&to=2026-11-30&granularity=total" \
  -H "Authorization: Bearer $PANION_API_KEY"
```

```json
{
  "from": "2026-11-01",
  "to": "2026-11-30",
  "granularity": "total",
  "currency": "EUR",
  "partners": [
    {
      "id": "2b7e1516-28ae-4d2a-9f15-88094f3c4f3c",
      "name": "Arctic Tours"
    }
  ],
  "rows": [
    {
      "date": null,
      "surface": {
        "type": "site",
        "id": "destinationtromso.com",
        "name": "Destination Tromsø"
      },
      "tour": {
        "id": "3a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "name": "Northern Lights Chase"
      },
      "operator": {
        "id": "2b7e1516-28ae-4d2a-9f15-88094f3c4f3c",
        "name": "Arctic Tours"
      },
      "basis": "own_tour",
      "funnel": {
        "views": 120,
        "availability_checks": 48,
        "checkout_opens": 20,
        "checkout_attempts": 9
      },
      "bookings": 6,
      "participants": null,
      "revenue_minor": 74400,
      "conversion": {
        "view_to_check": 0.4,
        "check_to_open": 0.4167,
        "open_to_attempt": 0.45,
        "attempt_to_booking": 0.6667,
        "view_to_booking": 0.05
      }
    },
    {
      "date": null,
      "surface": {
        "type": "channel",
        "id": "getyourguide",
        "name": "GetYourGuide"
      },
      "tour": {
        "id": "3a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "name": "Northern Lights Chase"
      },
      "operator": {
        "id": "2b7e1516-28ae-4d2a-9f15-88094f3c4f3c",
        "name": "Arctic Tours"
      },
      "basis": "own_tour",
      "funnel": null,
      "bookings": 14,
      "participants": 31,
      "revenue_minor": 190500,
      "conversion": null
    }
  ],
  "surfaces": [
    {
      "surface": {
        "type": "channel",
        "id": "getyourguide",
        "name": "GetYourGuide"
      },
      "funnel": null,
      "bookings": 14,
      "participants": 31,
      "revenue_minor": 190500,
      "revenue_share": 0.7192,
      "conversion": null
    },
    {
      "surface": {
        "type": "site",
        "id": "destinationtromso.com",
        "name": "Destination Tromsø"
      },
      "funnel": {
        "views": 120,
        "availability_checks": 48,
        "checkout_opens": 20,
        "checkout_attempts": 9
      },
      "bookings": 6,
      "participants": null,
      "revenue_minor": 74400,
      "revenue_share": 0.2808,
      "conversion": {
        "view_to_check": 0.4,
        "check_to_open": 0.4167,
        "open_to_attempt": 0.45,
        "attempt_to_booking": 0.6667,
        "view_to_booking": 0.05
      }
    }
  ],
  "other_channels_available": true
}
```

Responses:

- 200: OK
- 400: Invalid request
- 401: Missing, invalid, expired or revoked key
- 403: The key lacks the scope
- 429: Rate limited (300 requests per minute per key), or 2 analytics requests from this key are already running
- 503: The data did not load in time; retry, or ask for a shorter range

### GET /api/v1/analytics/sites

Traffic, sources, funnel and sales per site.

Whole-site traffic for every site your operators are on: visitors, sessions, pageviews, bounce rate, a daily series and the top sources. On a site your operator owns you also get the whole-site funnel, conversions per source and every tour's sales; on a site you are only added to, sales cover your own tours. Days are UTC; money is EUR. At most 400 days per request.

Scope: `analytics:read`.

Query parameters:

- `from` (string, YYYY-MM-DD): First UTC day (YYYY-MM-DD).
- `to` (string, YYYY-MM-DD): Last UTC day, inclusive (YYYY-MM-DD). At most 400 days from `from`.
- `site` (string, optional): Only this site (its domain). A site the key cannot see traffic for answers 404.
- `partner_id` (string, optional): Only this operator: one of the key's operators (see GET /api/v1/me). Any other id answers 404. What you see follows that operator's sites and tours.

Response fields (200):

- `from` (string, YYYY-MM-DD)
- `to` (string, YYYY-MM-DD)
- `currency` (`EUR`)
- `sites` (array)
  - each item:
    - `domain` (string)
    - `name` (string)
    - `role` (one of `owner`, `added`): `owner`: your operator owns the site, so sales cover every tour sold on it. `added`: your operator is on the site but does not own it, so sales cover your own tours only.
    - `sales_scope` (one of `all_tours`, `own_tours`)
    - `traffic` (object): The whole site, every tour and page.
      - `visitors` (integer)
      - `new_visitors` (integer)
      - `sessions` (integer)
      - `pageviews` (integer)
      - `bounce_rate` (number, nullable)
      - `avg_session_seconds` (number)
    - `days` (array)
      - each item:
        - `date` (string, YYYY-MM-DD)
        - `visitors` (integer)
        - `new_visitors` (integer)
        - `pageviews` (integer)
        - `bookings` (integer): Within `sales_scope`.
        - `revenue_minor` (integer): Integer minor units (cents) of `currency` (EUR).
    - `sources` (array): Top 25 per dimension, by visitors.
      - each item:
        - `dimension` (one of `channel`, `referrer`, `utm_source`, `utm_medium`, `utm_campaign`, `country`, `device`, `browser`, `os`, `path`)
        - `label` (string)
        - `visitors` (integer)
        - `conversions` (integer, nullable): Owners only; null otherwise.
        - `revenue_minor` (integer, nullable): Owners only; null otherwise.
    - `funnel` (array, nullable): The whole site's funnel. Owners only; null otherwise.
      - each item:
        - `step` (string)
        - `unit` (one of `visitors`, `bookings`)
        - `count` (integer)
    - `sales` (object): Panion sales on this site within `sales_scope`.
      - `bookings` (integer)
      - `revenue_minor` (integer): Integer minor units (cents) of `currency` (EUR).

Example: One site the operator owns

```sh
curl "https://www.panion.travel/api/v1/analytics/sites?from=2026-11-01&to=2026-11-01&site=destinationtromso.com" \
  -H "Authorization: Bearer $PANION_API_KEY"
```

```json
{
  "from": "2026-11-01",
  "to": "2026-11-01",
  "currency": "EUR",
  "sites": [
    {
      "domain": "destinationtromso.com",
      "name": "Destination Tromsø",
      "role": "owner",
      "sales_scope": "all_tours",
      "traffic": {
        "visitors": 410,
        "new_visitors": 352,
        "sessions": 468,
        "pageviews": 1290,
        "bounce_rate": 0.41,
        "avg_session_seconds": 96
      },
      "days": [
        {
          "date": "2026-11-01",
          "visitors": 410,
          "new_visitors": 352,
          "pageviews": 1290,
          "bookings": 7,
          "revenue_minor": 86800
        }
      ],
      "sources": [
        {
          "dimension": "channel",
          "label": "Organic Search",
          "visitors": 220,
          "conversions": 4,
          "revenue_minor": 49600
        }
      ],
      "funnel": [
        {
          "step": "view",
          "unit": "visitors",
          "count": 300
        },
        {
          "step": "conversion",
          "unit": "bookings",
          "count": 7
        }
      ],
      "sales": {
        "bookings": 7,
        "revenue_minor": 86800
      }
    }
  ]
}
```

Responses:

- 200: OK
- 400: Invalid request
- 401: Missing, invalid, expired or revoked key
- 403: The key lacks the scope
- 404: Not found, or not yours
- 429: Rate limited (300 requests per minute per key), or 2 analytics requests from this key are already running
- 503: The data did not load in time; retry, or ask for a shorter range
