# Authentication

Every server-to-server request to the TicketConnect White-Label API is
authenticated with a **secret API key**, sent as a bearer token. Keys are
**scoped** so each one can do only what you allow — and every endpoint
documents the scope it requires. A second, narrower kind of key — the
**publishable key** — exists solely for the browser-facing hosted checkout
surface (see below).

```text
https://api.ticketconnect.example/v1
```

## Secret keys

Your secret key authenticates server-to-server calls. Keys come in two flavours,
distinguished by their prefix:

| Prefix | Mode | Use it for |
| --- | --- | --- |
| `sk_test_…` | **Test** (sandbox) | Building and testing your integration. Fully isolated from live data. |
| `sk_live_…` | **Live** (production) | Real events, real customers, real payments. |

Test and live keys hit the same code paths, but their data never mixes. See
[Test & live modes](#test-and-live-modes) for the full picture.

> **Warning:** A secret key can create events, take payments, and issue tickets. **Treat it like
> a password.** Use it only from your backend — never embed it in a mobile app,
> single-page app, or anything that runs on a user's device.


### Keys are shown once

When you create a key, the full value is returned **exactly once**, at creation.
After that, TicketConnect stores only a hashed version and can never show you the
plaintext again. Copy it immediately into a secrets manager or environment
variable.

If you lose a key, you can't recover it — create a new one and revoke the old.

### Self-serve key management

You can mint, list, and revoke keys **through the API itself** — no dashboard
round-trip needed for rotation:

* `POST /v1/account/keys` mints a new key (the plaintext is in that response,
  once). A minted key's scopes must be a **subset of the calling key's**, and a
  test-mode key can never mint a live-mode key — no privilege escalation.
  Minting a live-mode key additionally requires live mode to be activated on the
  account; until then the request is rejected with `mode_unavailable`.
* `GET /v1/account/keys` lists your keys (prefix, label, scopes, timestamps);
  revoked keys stay listed as an audit trail. Secrets are never returned.
* `DELETE /v1/account/keys/{prefix}` revokes a key immediately. The calling
  key can never revoke itself, so a rotation always overlaps.

All three require the **`keys:manage`** scope, and active keys are capped at
**25 per account**. See the [Account reference](#api-account) for the full
endpoint details.

### Publishable keys

Alongside secret keys, you can be issued **publishable keys** with a `pk_`
prefix (`pk_test_…` / `pk_live_…`). They exist for one purpose: the
browser-facing **hosted checkout** surface. A publishable key:

* is safe to expose in a browser — it is always confined to the
  `checkout:read` and `checkout:write` scopes, regardless of what is stored
  on the key;
* only works on the `/v1/checkout/…` endpoints — every secret endpoint
  rejects a publishable key with a `401`;
* carries the same test/live mode split in its prefix as a secret key.

The reverse also holds: secret (`sk_`) keys are rejected on the publishable
checkout surface, so a leaked page can never be escalated. See the
[Payments reference](#api-payments) for the checkout endpoints
themselves.

## The Authorization header

Send your key as a bearer token in the `Authorization` header on every
request:

```text
Authorization: Bearer sk_test_your_key_here
```

**cURL**

```bash
curl https://api.ticketconnect.example/v1/account \
  -H "Authorization: Bearer sk_test_your_key_here"
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.ticketconnect.example/v1/account", {
  headers: { Authorization: "Bearer sk_test_your_key_here" },
});
const account = await res.json();
console.log(account.mode); // "test" or "live"
```

A good first call is `GET /v1/account` — it confirms the key is valid and tells
you which `mode` it operates in.

If your HTTP client makes the `Authorization` header awkward to set, the API
also accepts the same key in an `x-api-key` header. Prefer the `Bearer`
transport — it is what every example in these docs uses.

> **Note:** A few endpoints are **public** and need no key at all: `GET /v1/health`
> (uptime checks), `GET /v1/openapi.json` (the machine-readable spec, useful for
> codegen before you have a key), and `GET /v1/docs` (the interactive Swagger
> UI). Everything else requires authentication.


## Key safety & rotation

* **Store keys as secrets.** Use environment variables or a secrets manager —
  never source control.
* **Use one key per environment.** Keep test and live keys separate, and ideally
  scope keys per integration so you can revoke one without breaking others.
* **Rotate regularly, and immediately if a key may have leaked.** Mint a new
  key with `POST /v1/account/keys`, deploy it, then revoke the old one with
  `DELETE /v1/account/keys/{prefix}`. Because keys are stored hashed,
  revocation is the only remediation for a compromised key.
* **Grant least privilege.** Give each key only the scopes it needs (see below).
  A scanning app at the door, for example, needs `scanning:write` and nothing
  more.

## Scopes

Each key carries a set of **scopes** that define exactly which operations it can
perform. If a key is missing a scope an operation needs, the request is rejected
with the standard error envelope and an HTTP `403`:

```json
{
  "error": {
    "type": "authorization_error",
    "code": "forbidden",
    "message": "Missing required scope: events:write",
    "request_id": "8f2c1a4e-…"
  }
}
```

The full set of scopes and what each unlocks:

| Scope | Meaning | Unlocks |
| --- | --- | --- |
| `events:read` | Read events, organizations, and the catalog | `GET /v1/events`, `GET /v1/events/:id`, `GET /v1/events/:id/seatmap`, `GET /v1/events/:id/similar`, `GET /v1/organizations`, `GET /v1/organizations/:id` |
| `events:write` | Create and update events, tiers, and discounts | `POST /v1/events`, `PATCH /v1/events/:id`, `POST /v1/events/:id/tiers`, all `/v1/discounts` operations (including discount triggers under `/v1/discounts/:id/triggers…`) |
| `tickets:read` | Read issued tickets and guest lists, mint delivery links | `GET /v1/tickets`, `GET /v1/tickets/:id`, `POST /v1/tickets/:id/delivery_link`, `GET /v1/events/:id/comps` |
| `tickets:issue` | Issue tickets (paid, comp, and seated) and manage the on-sale waiting room | `POST /v1/events/:id/tickets`, `POST /v1/events/:id/comps`, `POST`/`DELETE /v1/events/:id/seats/hold…`, `GET /v1/events/:id/queue`, `POST /v1/events/:id/queue/join`, `POST /v1/events/:id/queue/leave` |
| `tickets:manage` | Manage a ticket's lifecycle | `POST /v1/tickets/:id/transfer`, `POST /v1/tickets/:id/refund`, `POST /v1/tickets/:id/revoke`, `POST /v1/tickets/:id/upgrade`, `GET /v1/tickets/:id/upgrade-options` |
| `customers:write` | Create and read customers, compute and save segments | `POST /v1/customers`, `GET /v1/customers/:id`, `GET /v1/customers/segments/:kind`, all `/v1/segments` saved-segment operations |
| `payments:write` | Create payment intents (also grants read-back) | `POST /v1/payment_intents`, `POST /v1/payment_intents/:id/confirm`, `GET /v1/payments/:id` |
| `marketplace:read` | Read secondary-market resale listings | `GET /v1/marketplace/listings`, `GET /v1/marketplace/listings/:id` |
| `reports:read` | Read sales and attendance reports | `GET /v1/reports/sales`, `GET /v1/reports/attendance` |
| `scanning:write` | Validate and check in tickets | `POST /v1/scan/validate`, `POST /v1/scan/batch`, `POST /v1/tickets/:id/attendance` |
| `staff:manage` | Manage door staff: roster, permissions, event assignments, scan stats | `POST`/`GET /v1/staff`, `GET`/`PATCH`/`DELETE /v1/staff/:id`, `POST /v1/staff/:id/assignments`, `DELETE /v1/staff/:id/assignments/:eventId`, `GET /v1/staff/:id/stats` |
| `webhooks:manage` | Manage webhook endpoints and deliveries | all `/v1/webhook_endpoints` operations (including `POST …/:id/ping`), `GET /v1/events/deliveries`, `POST /v1/events/deliveries/:id/replay` |
| `keys:manage` | Mint, list, and revoke API keys | `GET`/`POST /v1/account/keys`, `DELETE /v1/account/keys/:prefix` |
| `payouts:read` | Read balance and payout history | `GET /v1/balance`, `GET /v1/payouts` |
| `payouts:claim` | Set up payouts and request a payout | `GET`/`POST /v1/connect/account`, `POST /v1/payouts` (and the read operations above) |

> **Read access is implied by write where it makes sense.** A `payments:write` key
> can read back the payments it created, and `payouts:claim` includes everything
> `payouts:read` can do. When in doubt, the per-endpoint documentation in the
> [API reference](#api-overview) states the exact scope required.


## Next steps

* [Test & live modes](#test-and-live-modes) — integrate in the sandbox first, then go live.
* [Quickstart](#quickstart) — sell and scan a ticket end-to-end.
* [API reference](#api-overview) — every endpoint and its required scope.
