Skip to content

Buyer Access Sessions API

Mint, list, and revoke short-lived event-and-origin-bound bse_ tokens for private and scoped buyer audiences.

Updated View as Markdown

A buyer access session is the credential that lets one browser see and select one event audience’s inventory. Use this server API when access depends on a login, invitation, presale, package purchase, partner handshake, or named sales channel. Your backend decides who deserves that scope and then asks SeatLayer to mint the session.

Read Sales channels and allocations for the access model and the organizer private-sales guide for the complete browser-to-booking flow. This page owns the exact session contract.

For ordinary Public sale on a Platform/SDK event, pass publicKey to the Buyer SDK instead. SeatLayer validates the account, event mode, and exact registered browser origin, then returns a Public-only in-memory bearer together with the chart and compact inventory status. That direct bootstrap cannot expose private inventory. If buyerAccessTokenProvider or buyerAccessToken is also present, the explicit scoped credential always takes precedence over publicKey.

Your server authorizes; SeatLayer mints; the widget consumes. These endpoints hand out authority to spend an allocation, so they accept a secret key and nothing else. A bse_ token can never book: booking stays with your backend.

The same session also authorizes SeatLayer-hosted view-from-seat image bytes. The current SDK fetches those Event-scoped assets with the bearer in the Authorization header and renders an in-memory blob URL. Do not append the token to an image URL or proxy it through query parameters.

Private audience flow

Use this flow only after you have chosen a private audience. It is not part of the ordinary Public-sale load path.

  1. The buyer opens your page and signs in, enters a presale code, follows an invitation, or otherwise proves eligibility to your application.
  2. Your backend decides which private channel(s), if any, that buyer may see; it also decides whether the same buyer may see Public sale.
  3. Your backend calls this secret-key endpoint with that exact scope, the browser’s exact origin, a short expiry, and a retry-safe clientRequestId.
  4. Return only { token, expiresAt } to the browser. Keep it in memory and supply it through buyerAccessTokenProvider so the SDK can refresh it.
  5. The browser creates holds with that scoped access. Your backend inspects the hold and books it; neither the token nor the browser can book directly.

For an ordinary Platform/SDK Public sale, stop before step 1: pass publicKey to the SDK and let SeatLayer perform the public, exact-origin bootstrap.

Mint a session

POST/v1/events/:key/buyer-access-sessionsSecret key
curl -s -X POST "https://api.seatlayer.io/v1/events/ev_9f3a/buyer-access-sessions" \
  -H "authorization: Bearer $SEATLAYER_SECRET_KEY" \
  -H "content-type: application/json" \
  -d '{
    "channelIds": ["chn_9f1c"],
    "includePublic": false,
    "allowedOrigin": "https://booking.travel-agency.example",
    "expiresInSeconds": 1800,
    "maxQuantity": 4,
    "buyerRef": "buyer_8372",
    "partnerRef": "travel-agency-a",
    "clientRequestId": "agency-login-01J8F2A7MRQ4"
  }'
Field Type Required Default Notes
allowedOrigin string Yes One canonical HTTPS origin, checked on every request
includePublic boolean Yes, explicitly none No default. See below
channelIds string[] No [] Up to 20. Each must belong to this event and be active
expiresInSeconds integer No 1800 60 to 43,200 (12 hours)
maxQuantity integer | null No null 1 to 100. Guest-weighted, summed across all this buyer’s live holds
buyerRef string No null Your opaque, pseudonymous buyer reference. Max 120 characters
partnerRef string No null Your opaque partner reference. Its presence marks the sale partner rather than promoter
clientRequestId string No null Makes a retry safe. See below

Mode comes from the event, not the body. A key whose mode differs from the event’s gets 403 buyer_access_mode_mismatch.

includePublic has no default

You must send it. includePublic: true can expose public inventory to a partner; includePublic: false can leave a VIP with an empty map. SeatLayer refuses to guess. Omitting it returns 422 include_public_required.

An empty channelIds and includePublic: false is an empty scope and returns 422 invalid_channel_scope. A session that can see nothing is a bug, not a configuration.

Response

{
  "sessionId": "bas_2f4c",
  "token": "bse_ZXZ0X2FiYw_9d3f",
  "expiresAt": 1764601800000,
  "eventKey": "ev_9f3a",
  "includePublic": false,
  "maxQuantity": 4
}

Keep sessionId for audit and revocation. Return only token and expiresAt to the browser.

The response deliberately carries no channel names, no allocation labels, no partner details, and no internal notes.

Retries are safe, and never replay a token

Send a clientRequestId. On a repeat for the same issuer and id, SeatLayer revokes any earlier session for that pair and issues a fresh one. The response contains a new sessionId and bearer rather than a stored plaintext token. The earlier token stops working immediately.

This is why buyer access sessions do not use the platform’s generic idempotency replay: that mechanism persists complete response JSON, and this response contains a bearer.

List sessions

GET/v1/events/:key/buyer-access-sessionsSecret key

Newest first. limit defaults to 50, capped at 200.

{
  "sessions": [
    {
      "sessionId": "bas_2f4c",
      "channelIds": ["chn_9f1c"],
      "includePublic": false,
      "allowedOrigin": "https://booking.travel-agency.example",
      "mode": "live",
      "expiresAt": 1764601800000,
      "maxQuantity": 4,
      "buyerRef": null,
      "partnerRef": null,
      "accessSource": "promoter",
      "state": "active",
      "createdAt": 1764600000000,
      "revokedAt": null,
      "accessLinkId": null
    }
  ]
}

state is active or revoked. Expiry is derived from expiresAt, never stored as a state, so an expired session still lists as active with a past timestamp. accessSource is promoter, partner, or hosted_link. accessLinkId is set when the session came from a hosted access link rather than a direct mint.

The token is never here, in any form.

Revoke a session

DELETE/v1/events/:key/buyer-access-sessions/:sessionIdSecret key
{ "ok": true, "sessionId": "bas_2f4c", "grantVersion": 7 }

Revoking twice is not an error.

Revocation is immediate and ordered

By the time this call returns, no further hold can use that session. The event’s own inventory authority marks the grant revoked and bumps a monotonic grant version before acknowledging, and any live map still on screen is disconnected in the same instant with WebSocket close code 4401.

A revocation racing a hold therefore has one order, not a race: if the hold lands first it exists under the ordinary hold policy; once the revoke is acknowledged, nothing later can use that grant.

Revocation stops new availability sessions, holds, replacement holds, resumes, and extensions. It does not:

  • change historical booking attribution;
  • prevent the buyer releasing a hold they already have;
  • stop your trusted backend booking an already-valid hold until its normal expiry.

What a session cannot do

Situation Result
Valid publicKey bootstrap on a Platform/SDK event Public-only session plus chart and compact inventory status
No Authorization or public bootstrap on a Platform/SDK event 404 not_found; an event key is routing, not audience authority
No Authorization on a Managed public/unlisted event Existing anonymous Public sale behavior is unchanged
Expired, revoked, wrong origin, or wrong event A specific typed error; no fallback to public
Browser claims a channelId it was not granted Ignored. The server derives scope from the credential
Buyer opens a second tab maxQuantity is summed across all live holds, so the allowance does not double
Session expires while a hold is active The buyer can still release it; your backend can still book it

The API fails closed rather than degrading to public. This prevents a private buyer from receiving a wider scope.

Errors

Status Code What to do
401 buyer_access_invalid Get a new session; do not retry the same bearer
401 buyer_access_expired Run your refresh flow
403 buyer_access_origin_mismatch Stop; check the configured origin
403 buyer_access_event_mismatch Stop; do not reuse a token across events
403 buyer_access_mode_mismatch Match test and live
403 channel_access_denied Return the buyer to inventory they may see. Do not reveal channel details
404 not_found Unknown or cross-tenant event, session, or channel
409 allocation_exhausted This private allocation has no inventory left. Do not say “sold out”; the event may still have public inventory
422 invalid_channel_scope Empty scope, more than 20 channels, or a paused/archived channel
422 include_public_required Send includePublic explicitly
422 invalid_allowed_origin One canonical HTTPS origin
422 invalid_expiry 60 to 43,200 seconds
422 invalid_max_quantity 1 to 100
422 invalid_reference A reference exceeded 120 characters

Server SDK

server/access.tsts
import SeatLayer from "@seatlayer/server";

const seatlayer = new SeatLayer({ secretKey: process.env.SEATLAYER_SECRET_KEY! });

export async function grantAgencyAccess(eventKey: string, buyerId: string) {
  const session = await seatlayer.channels.createBuyerAccessSession(eventKey, {
    channelIds: ["chn_9f1c"],
    includePublic: false,
    allowedOrigin: "https://booking.travel-agency.example",
    expiresInSeconds: 1800,
    maxQuantity: 4,
    buyerRef: buyerId,
    clientRequestId: `agency-login-${buyerId}`,
  });

  // Persist sessionId for audit and revocation. Return only these two.
  return { token: session.token, expiresAt: session.expiresAt };
}
server/access.pypython
session = seatlayer.create_buyer_access_session(
    event_key,
    channel_ids=["chn_9f1c"],
    include_public=False,
    allowed_origin="https://booking.travel-agency.example",
    expires_in_seconds=1800,
    max_quantity=4,
    buyer_ref=buyer_id,
    client_request_id=f"agency-login-{buyer_id}",
)

Checklist

  • Authenticate the buyer yourself before asking SeatLayer to mint. A session is a decision, not a lookup.
  • For ordinary Platform Public sale, use the SDK’s direct publicKey bootstrap instead of this server endpoint.
  • Mint here only when buyer identity or audience scope requires login, presale, partner, or channel access.
  • Send includePublic explicitly, every time.
  • Keep the token in memory in the browser; never in storage, a URL, or a log.
  • Send clientRequestId so a retry rotates instead of replaying.
  • Store sessionId so you can revoke without waiting for expiry.
  • Handle expired and revoked as distinct outcomes, not as a network failure.
  • Keep test and live scopes separate.

Next: sales channels API and the private and partner sales tutorial.

Troubleshooting

Symptom Cause Fix
Public Platform picker shows “The seat map didn’t load”; bootstrap is 404 not_found Missing/wrong-mode publicKey, an unregistered exact origin, or a non-Public event lane. Refusals are deliberately indistinguishable Pass the event account’s matching publishable key and register the page’s exact Embed domain
Scoped private picker shows “The seat map didn’t load”; GET /pub/events/:key/chart is 404 not_found No Authorization: Bearer bse_… on the request. A missing bearer is deliberately indistinguishable from an unknown event Pass buyerAccessTokenProvider; after authenticating the buyer, have your server ask SeatLayer for the scoped session
403 buyer_access_origin_mismatch allowedOrigin differs from the page’s origin (scheme, host, or port) Mint with the exact origin the checkout page is served from; one session per origin
401 buyer_access_expired after the map rendered Token lapsed and the provider did not renew Return a provider function (not a one-shot buyerAccessToken) so the SDK can re-mint
422 include_public_required includePublic omitted from an explicit private-audience mint Send it explicitly. Use true only when that scoped audience should also see Public inventory; ordinary Public sale uses direct publicKey bootstrap instead
422 invalid_channel_scope channelIds: [] with includePublic: false A session must see inventory. Add channels or set includePublic: true
Navigation

Type to search…

↑↓ navigate↵ selectEsc close