---
title: "Authentication and environments"
description: "Keep secret keys on your server, choose public or scoped Buyer SDK access, and separate test, live, staging, and production traffic."
---

SeatLayer has two authentication contexts because the buyer and your backend have different authority.

| Surface | Credential | Capability |
|---|---|---|
| Managed public/unlisted Buyer SDK | Event key | Anonymous Public-sale render, select, hold, release |
| Platform/SDK Public sale | Event key + `pk_test_…` or `pk_live_…` | Direct, origin-bound Public-sale bootstrap |
| Private/login/presale/channel Buyer SDK | Event key + short-lived `bse_…` | Only the audience scope your backend granted |
| Server API | `sk_test_…` or `sk_live_…` | Inspect holds, book, manage inventory, provision resources, report |

## Secret keys belong on your server

Create keys in the dashboard and store the secret immediately—the complete value is shown once.

```js title="server/seatlayer-request.js"
export async function seatlayerRequest(path, init = {}) {
  return fetch(`https://api.seatlayer.io${path}`, {
    ...init,
    headers: {
      authorization: `Bearer ${process.env.SEATLAYER_SECRET_KEY}`,
      "content-type": "application/json",
      ...init.headers,
    },
  });
}
```

<Aside type="danger" title="Never ship a secret key to a client">
  Do not put `sk_…` in browser bundles, mobile applications, public environment variables, page source, analytics, logs, or screenshots. Anyone who obtains it may perform server-authorized operations for your account.
</Aside>

Use your deployment platform’s encrypted server-secret mechanism. Fail startup or the server operation clearly when the variable is missing; do not silently fall back to a client credential.

## Test and live mode

Every secret key has one immutable mode:

<Tabs>
  <TabItem label="Test">
    `sk_test_…` creates and manages sandbox events. Sandbox holds, conflicts, realtime updates, and webhooks behave like production, but bookings consume no credits and payloads carry `livemode: false`.
  </TabItem>
  <TabItem label="Live">
    `sk_live_…` creates and manages live inventory. Real bookings consume credits and payloads carry `livemode: true`.
  </TabItem>
</Tabs>

An event inherits the mode of the key that creates it. Mode cannot be changed later.

```json title="403 response"
{
  "error": "mode_mismatch"
}
```

A test key cannot act on a live event, and a live key cannot act on a sandbox event. Secret-key event lists are filtered to the key's mode; dashboard sessions list both modes. A list with no matching events returns `200` and an empty `events` array. Direct same-organization access to an opposite-mode event returns the `403 mode_mismatch` response above. Build the complete journey with a test key, then create real events using a live key.

## Environment routing

A key may also carry an environment tag such as `dev`, `staging`, or `prod`.

- Events inherit the creating key’s environment.
- Webhook subscriptions can filter delivery by environment.
- Environment is routing metadata, not an event, tenant, or workspace authorization boundary.
- On webhook management routes, an environment-tagged key can manage only subscriptions in that routing scope.
- An unset subscription environment behaves as a wildcard.

Recommended setup:

| Application environment | SeatLayer key | Event mode | Webhook route |
|---|---|---|---|
| Local/CI | Dedicated test key | Test | Test receiver or fixture |
| Staging | Staging test key | Test | Staging endpoint |
| Production | Production live key | Live | Production endpoint |

Do not use an environment tag as proof of tenant or workspace ownership.

## Buyer SDK authentication

The Buyer SDK never receives an account secret. For an ordinary Public-sale
Platform event, give it the event key and the publishable half of your API key:

```js title="browser/public-picker.js"
const picker = new seatlayer.SeatPicker({
  container: "#picker",
  event: "ev_9f3a",
  publicKey: "pk_test_…",
});

await picker.render();
```

On first render, the SDK sends the public key directly to SeatLayer and repeats
that bootstrap only when its in-memory session needs renewal. SeatLayer verifies
that the key belongs to the event, its test/live mode matches, and the browser's
exact origin is registered for the account. A successful bootstrap returns a
Public-sale-only `bse_…` bearer together with the chart and compact inventory
status. The SDK keeps the bearer in memory and reuses it for holds, assets, and
live updates. A public key is publishable, but it is not buyer identity and can
never reveal private channel inventory or authorize booking.

For a login, presale, partner, or other private audience, use an explicit buyer
access token provider backed by your authenticated server:

```js title="browser/picker.js"
const picker = new seatlayer.SeatPicker({
  container: "#picker",
  event: "ev_9f3a",
  buyerAccessTokenProvider: async ({ reason }) => {
    const response = await fetch("/api/seatlayer/buyer-access", {
      method: "POST",
      credentials: "same-origin",
      cache: "no-store",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ reason }),
    });
    if (!response.ok) throw new Error("Unable to open seat selection");
    return response.json();
  },
});

await picker.render();
```

`buyerAccessTokenProvider` and `buyerAccessToken` always take precedence over
`publicKey`. This lets one integration keep the public key in its normal embed
while switching known buyers onto deliberately scoped inventory without any
anonymous fallback.

Browser buyer routes are rate-limited and intentionally limited to buyer-safe
operations. The audience token can read/select only its event scope and cannot
book; permanent booking remains unavailable without a server secret.

## Browser-scoped session tokens

Three credentials exist that are neither a secret key nor an event key. They are
bound to one origin, short lived, revocable, and stored only as hashes —
SeatLayer cannot read them back out of its own database. Your trusted backend
requests `dse_…`, `mse_…`, and private-audience `bse_…` sessions with `sk_…`.
The Public-sale SDK bootstrap is the narrow exception: SeatLayer creates a
Public-only `bse_…` after validating the publishable key and registered origin.

| Prefix | What it authorizes | Issued through |
|---|---|---|
| `mse_` | An **operator** in the embedded control room, limited to an explicit capability list | [`POST /v1/events/:key/manage-sessions`](/platform/embed-sessions) |
| `dse_` | A **designer** in the embedded chart editor | [`POST /v1/designer/sessions`](/platform/embedded-designer) |
| `bse_` | A **buyer** on one event; either Public-only bootstrap or an explicit [sales-channel](/platform/sales-channels) scope | Direct SDK bootstrap for Public sale, or [`POST /v1/events/:key/buyer-access-sessions`](/server-api/buyer-access-sessions) for scoped audiences |

`mse_` and `bse_` are not interchangeable, and neither is a weaker secret key.
An operator token grants organizer capabilities on an event; a buyer token grants
nothing but the right to *see and select* a particular audience's inventory. A
buyer token can never book — booking stays with your server.

Send a buyer token as `Authorization: Bearer bse_…` on buyer routes after
bootstrap. The SDK handles that header. A Platform/SDK Public-sale embed obtains
its token directly with `publicKey`; private/login/presale/channel inventory
still requires a token/provider backed by your server. Omitting both from a
Platform event returns the same `404` as an unknown event. Managed public/unlisted
events retain their anonymous Public-sale flow. Sending a bad token is always an
error, never a downgrade: an expired, revoked, wrong-origin, or wrong-event token
fails with a specific code rather than quietly becoming an anonymous shopper.

## Rotation checklist

- [ ] Create the replacement key in the same mode and environment.
- [ ] Store it in the server secret manager.
- [ ] Deploy the backend using the replacement.
- [ ] Exercise an authenticated read or sandbox booking.
- [ ] Confirm relevant webhook routing.
- [ ] Revoke the old key after traffic has moved.
- [ ] Investigate logs if rotation followed suspected exposure.

## Common mistakes

- Using a `PUBLIC_`, `NEXT_PUBLIC_`, or `VITE_` environment variable for the secret.
- Creating a live event during staging tests.
- Assuming the visible key prefix is the enforcement mechanism.
- Treating an environment or `externalRef` as tenant authorization.
- Logging the complete `Authorization` header during request debugging.

Continue to [Install the Buyer SDK](/buyer-sdk/install) or start the [Quickstart](/start/quickstart).