---
title: "Inventory reports and audit log"
description: "Retrieve live inventory, configured booked-value snapshots, channel attribution, accessibility breakdowns, CSV exports, and event history."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.seatlayer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Inventory reports and audit log

Reports are private organizer data. They describe the event's durable inventory
record, including immutable configured-price snapshots captured when inventory
was booked. They are not a payment-provider or commercial-order ledger.

> **Booked value is not collected revenue**
>
> SDK/platform reports can show seat/category/section mix, booked percentage,
> sell-through, and the configured value of booked inventory. Your platform is
> authoritative for the amount charged, discounts, fees, tax, settlement, and
> refunds. If those differ from SeatLayer's configured prices, use your own
> commerce reports for money.

## JSON report

**GET /v1/events/:key/report** — Authentication: Secret key, dashboard session, or event:reports manage token

```bash
curl -s "https://api.seatlayer.io/v1/events/ev_9f3a/report" \
  -H "authorization: Bearer $SEATLAYER_SECRET_KEY"
```

The response is `{report, event, categories}`.

```json
{
  "report": {
    "byStatus": {
      "free": 40,
      "held": 3,
      "booked": 55,
      "not_for_sale": 2
    },
    "byCategory": [
      {
        "category": "vip",
        "total": 80,
        "free": 35,
        "held": 2,
        "booked": 41,
        "not_for_sale": 2,
        "bookedValue": 3075,
        "bookedRevenue": 3075
      }
    ],
    "bySection": [
      {
        "sectionId": "sec-stalls",
        "sectionLabel": "Stalls",
        "zoneId": "floor-main",
        "total": 400,
        "free": 180,
        "held": 4,
        "booked": 206,
        "not_for_sale": 10,
        "bookedValue": 15450,
        "bookedRevenue": 15450
      }
    ],
    "byAccessibility": [],
    "byWheelchairProvision": []
  },
  "event": {
    "key": "ev_9f3a",
    "name": "Opening Night",
    "status": "active",
    "seatTotal": 1200
  },
  "categories": [
    {
      "key": "vip",
      "label": "VIP",
      "color": "#d97706",
      "price": 75
    }
  ]
}
```

## Interpret each breakdown

| Field | Meaning |
|---|---|
| `byStatus` | Guest-weighted totals for the entire event |
| `byCategory` | Status and configured booked value by category key |
| `bySection` | Status and configured booked value by event section, with zone id |
| `byAccessibility` | One row per accommodation type |
| `byWheelchairProvision` | Physical accessible seat versus empty wheelchair space |

A grouped table or variable-occupancy object is guest-weighted, so its
`quantity` contributes to capacity rather than counting the object once.

Accessibility rows are not mutually exclusive: one seat with multiple
accommodations contributes to each matching row. Do not sum those rows to
derive whole-event inventory.

`bookedValue` is the sum of stored configured-price snapshots in major currency
units for inventory that is currently booked. It changes when bookings are
cancelled and does not prove that a payment was charged, settled, or refunded.
`bookedRevenue` returns the same number as a deprecated compatibility alias.

> **Host price overrides are separate**
>
> `SeatPicker.pricing` changes buyer presentation and the SDK handoff. It does
> not rewrite the price stored with the SeatLayer hold. If your server charges
> a host price book, its order and payment ledger remains the source of truth.

## CSV export

CSV downloads are dashboard/Control Room exports in this launch: they require a
dashboard session and reject secret keys. They are not published OpenAPI
operations or server-SDK methods. Server integrations should call the typed JSON
reports above and serialize the fields they need; do not depend on an internal
`.csv` route.

The response is a downloadable `text/csv` file with:

```text
category,label,price,total,free,held,booked,not_for_sale,booked_value
```

A `TOTALS` row follows the category rows. Accessibility, wheelchair-provision,
and section breakdowns remain available in JSON because they are not one
mutually exclusive category table.

## Sales channel report

**GET /v1/events/:key/channels/report** — Authentication: Secret key, dashboard session, or event:channels:view manage token

One row per [sales channel](/platform/sales-channels), plus Public sale. Each row
carries two things that are easy to confuse and that move independently.

| | |
|---|---|
| **`allocation`** | Where inventory is assigned **right now**, and its physical status. Changes when you move seats. |
| **`attribution`** | Which channel each unit was **sold from**, recorded at the moment of booking and never rewritten. |

```json
{
  "report": {
    "assignmentVersion": 7,
    "includesBookedValue": true,
    "includesRevenue": true,
    "methodology": {
      "allocation": "Current allocation: where inventory is assigned right now…",
      "attribution": "Sale attribution: the channel each unit was sold from…",
      "sellThrough": "Sell-through = attributed sold / current allocated…"
    },
    "rows": [
      {
        "channelId": "public",
        "name": "Public sale",
        "externalRef": null,
        "state": "active",
        "allocation": { "allocated": 800, "free": 612, "held": 4, "booked": 180, "blocked": 4, "units": 800 },
        "attribution": { "sold": 180, "units": 180, "bookedValue": 8100, "revenue": 8100 },
        "sellThrough": 0.225
      },
      {
        "channelId": "chn_7f2a",
        "name": "Travel Agency A",
        "externalRef": "travel-agency-a",
        "state": "active",
        "allocation": { "allocated": 120, "free": 78, "held": 2, "booked": 40, "blocked": 0, "units": 120 },
        "attribution": { "sold": 40, "units": 40, "bookedValue": 3000, "revenue": 3000 },
        "sellThrough": 0.3333333333333333
      }
    ],
    "totals": {
      "allocated": 1000, "free": 690, "held": 6, "booked": 220, "blocked": 4,
      "sold": 220, "bookedValue": 11100, "revenue": 11100
    }
  },
  "event": { "key": "ev_9f3a", "name": "Opening Night", "status": "active", "seatTotal": 1000 }
}
```

`bookedValue` is the canonical configured-value field. `revenue` and
`includesRevenue` return the same values as deprecated compatibility aliases.
These are inventory attribution snapshots, not proof of a charge or settlement.

When creating a channel report link, send `includesBookedValue: true` to include
these configured values. The older `includesRevenue` request field remains an
additive compatibility alias; if both fields are sent, they must agree.

### Sell-through, and why it can exceed 100%

`sellThrough` is **attributed sold divided by current allocated**. The numerator
is frozen at sale time; the denominator is live. So a channel whose remaining
inventory is moved out after it sold can report more than `1.0`, and a channel
with no allocation left reports `null` rather than `0` — there is genuinely
nothing to divide by, which is not the same as "sold nothing".

The same three sentences ship inside every response as `methodology`, so a
report you export or forward explains itself.

> **A sale never follows the seat**
>
> Moving or archiving a channel changes where its inventory sits, and where a
> cancelled seat would return to. It does not move the sales that channel
> already made. That is why archived channels stay in this report — drop them
> and the attributed totals stop adding up to what the event actually sold.

### Who sees what

| Capability | Result |
|---|---|
| `event:channels:view` | Every allocation and attribution figure, with configured booked value hidden as `bookedValue: null` and `includesBookedValue: false` |
| `event:channels:view` + `event:reports` | The same report, with configured booked value in `bookedValue` |
| `event:reports` alone | `403 missing_manage_capability` |

Configured booked value is `null`, never `0`, when you may not see it — a zero
would read as a channel that sold nothing.

### Channel CSV export

Channel CSV is likewise a dashboard export rather than a stable server-SDK
operation. Integrations should retrieve the typed channel report and generate
their own file.

```text
channel_id,channel,external_ref,state,allocated,free,held,booked,blocked,sold_by_attribution,sell_through,booked_value_by_attribution
```

A `TOTALS` row follows. `sell_through` is a ratio, not a percentage string, so a
spreadsheet can format it; an empty cell means there is no current allocation to
divide by. The `booked_value_by_attribution` column is absent entirely when the caller
lacks `event:reports`.

### Partner-facing channel reports

An organizer can share one channel without granting dashboard or API access.
In **Performance → Channel**, choose **Share partner report** for a private
channel. The generated URL opens a live, read-only projection containing one
channel row and the exact inventory currently assigned to it.

The projection deliberately excludes event totals, sibling channel names,
buyer identities, holds, and booking references. Configured booked value is
excluded unless the organizer explicitly enables it for that link. The partner
can search and filter the allocation or export the scoped inventory as CSV.

The URL capability appears after `#`, is removed from browser history before
the application starts, and is stored by SeatLayer only as a SHA-256 hash. The
organizer must copy it when it is created; later listings contain status, expiry,
view count, and last-viewed time, never the URL. Revoke the link to stop access
immediately.

> **Treat the URL like a password**
>
> Anyone who receives the full URL can open the report until it expires or is
> revoked. Use Partner access and server-minted buyer sessions
> when the recipient must be identified individually or needs to transact.

## Audit log

**GET /v1/events/:key/log** — Authentication: Secret key, dashboard session, or event:reports manage token

Entries are newest first. `limit` defaults to 50 and is capped at 200. Pass the
returned `nextBefore` as `before` to retrieve older entries.

```bash
curl -s \
  "https://api.seatlayer.io/v1/events/ev_9f3a/log?limit=50&before=405" \
  -H "authorization: Bearer $SEATLAYER_SECRET_KEY"
```

```json
{
  "entries": [
    {
      "id": 404,
      "at": 1736897900000,
      "action": "book",
      "labels": ["A-1", "A-2"],
      "ref": "order_5567"
    }
  ],
  "nextBefore": 354
}
```

Actions include hold, replace, extend, book, release, expiry, block, unblock,
unbook, reveal, and availability changes as those operations occur.

## Incremental booking reconciliation

**GET /v1/events/:key/booking-changes** — Authentication: Secret key, dashboard session, or event:view/event:reports manage token

Platform events expose an oldest-first, append-only change feed for inventory
bookings. Use it for a background reconciliation job that must discover later
cancellations to older booking references without rescanning every booking.

```bash
curl -s \
  "https://api.seatlayer.io/v1/events/ev_9f3a/booking-changes?after=418&limit=100" \
  -H "authorization: Bearer $SEATLAYER_SECRET_KEY"
```

```json
{
  "changes": [
    {
      "id": 419,
      "action": "partial_cancel",
      "at": 1786897900000,
      "labels": ["A-2"],
      "actor": "refund-worker",
      "source": "server_api",
      "booking": {
        "eventKey": "ev_9f3a",
        "bookingRef": "order_5567",
        "state": "partially_cancelled",
        "activeQuantity": 1,
        "activeConfiguredValue": 75,
        "currency": "USD"
      }
    }
  ],
  "nextAfter": null,
  "checkpoint": 419
}
```

The numeric activity id—not `bookedAt` or `updatedAt`—is the cursor. That
matters because cancelling a week-old booking changes its current record while
its original booking time stays the same, and multiple changes may share one
millisecond.

Drain pages while `nextAfter` is non-null. After the final page, persist
`checkpoint` and send it as the next `after`. An empty poll echoes the same
checkpoint. Actions are `book`, `replay`, `partial_cancel`, `cancel`, and
`reconcile`; each change includes the current inventory-only booking record.
The feed contains no buyer, payment, refund, ticket, email, or door data.

## Reporting checklist

- [ ] Keep configured booked value grouped by event currency.
- [ ] Do not label SeatLayer snapshots as payment-provider settlement.
- [ ] Do not sum overlapping accessibility rows.
- [ ] Export until `nextBefore` is null when a complete audit is required.
- [ ] Persist the final booking-change `checkpoint` only after your reconciliation write commits.
- [ ] Restrict browser manage tokens to the event and `event:reports`.
- [ ] Store your own order, refund, tax, fee, and payment identifiers.
- [ ] Retain the event key and booking reference in every reconciliation record.
- [ ] Reconcile channel booked value by attribution, never by current allocation.
- [ ] State the sell-through denominator anywhere you republish the figure.

Archived events continue to serve reports. See
[event lifecycle](/server-api/events) and
[webhook delivery](/webhooks/delivery-and-retries).

Source: https://docs.seatlayer.io/server-api/reports/index.mdx
