Skip to content

Buyer access sessions

Mint, list, and revoke the short-lived origin-bound bse_ token that lets one buyer select a private sales channel's inventory.

Updated View as Markdown

A buyer access session is the credential that lets one buyer see and select one audience’s inventory. Your backend decides who deserves it — a login, an invitation, a package purchase, a partner handshake — and then asks SeatLayer for a session.

Your server mints; the widget consumes. These endpoints hand out the 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.

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. Getting it wrong is expensive in both directions — either a partner quietly drains your public inventory, or a VIP opens the link and sees an empty map — so 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 — a new sessionId and a new bearer — rather than replaying a stored plaintext token back at you. 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
No Authorization header at all Public sale, exactly as before. Nothing changed for existing buyers
Expired, revoked, wrong origin, or wrong event A specific typed error — never a silent downgrade 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

Failing closed rather than degrading to public is the point. Silently widening a private buyer’s access is the exact failure this feature exists to prevent.

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 not be
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 minting. A session is a decision, not a lookup.
  • 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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close