# Authentication

How Sandchest API keys work, both header forms the API accepts, who in a workspace can create them, and how to rotate one without downtime.

Every request to the Sandchest API carries one API key in the `Authorization`
header. This page covers where keys come from, the two header forms that work,
who can manage them, and how to rotate one safely.

## API keys

A key looks like this:

```text
sc_live_Xf9m2QvR7pKd3sTn1yBhLc8WgZaE0jUo5NrPiVxD4M
```

- Keys start with `sc_live_`. Anything that does not is rejected without a database
  lookup.
- A key is shown **once**, when you create it. Sandchest stores only an
  HMAC-SHA256 digest of the key, so a lost key cannot be recovered — create a new one
  and revoke the old.
- The dashboard lists a key's **prefix** (its first 16 characters), name, creation
  time and last-used time. That is enough to tell keys apart without exposing them.
- A key belongs to a **workspace**, not to a person. It can read and write every
  transcript in that workspace and spend that workspace's credits.

Create and revoke keys at
[sandchest.com/dashboard/api-keys](https://sandchest.com/dashboard/api-keys).

## The header

Both of these authenticate identically. Use whichever your HTTP client makes easy.

```http
Authorization: sc_live_...
```

```http
Authorization: Bearer sc_live_...
```

The AssemblyAI SDKs send the raw form; most generic HTTP clients default to
`Bearer`. Sandchest strips a case-insensitive `Bearer ` prefix and trims
surrounding whitespace before comparing.

```bash
curl -sS "https://stt-api.sandchest.com/v2/transcript?limit=1" \
  -H "Authorization: $SANDCHEST_API_KEY"
```

A missing, malformed, revoked or expired key returns:

```json title="401 Unauthorized"
{ "error": "A valid Sandchest API key is required." }
```

> [!NOTE]
> There is no separate account id, project id or secret. One header, one key.

## Workspaces and roles

A workspace owns its API keys, audio, transcripts, usage and credits. Users join
workspaces as one of three roles:

| Role | Can transcribe | Can manage API keys | Can manage members | Can manage billing |
| --- | --- | --- | --- | --- |
| `owner` | Yes | Yes | Yes | Yes |
| `admin` | Yes | Yes | Yes | No |
| `member` | Yes | No | No | No |

A member who tries to create or revoke a key gets
`{"error":"Only workspace admins can create keys."}` or
`{"error":"Only workspace admins can revoke keys."}` with HTTP 403. Billing actions
are owner-only.

Keys are scoped to one workspace. A transcript created with a key from workspace A
is invisible to every key in workspace B — a lookup for someone else's transcript id
is indistinguishable from a lookup for an id that never existed.

## Rotating a key

Revocation takes effect immediately, so rotate in this order:

1. Create the new key in the dashboard and copy it.
2. Deploy the new key to every process that talks to Sandchest.
3. Wait until the old key's **last used** timestamp stops moving. Sandchest refreshes
   it at most once every five minutes, so give it a little longer than that.
4. Revoke the old key.

If a key has leaked, skip straight to revoking it. Requests using it start failing
with 401 at once; in-flight transcripts continue and are still billed, because the
work is already queued.

## Keeping keys out of client code

- Call Sandchest from your server, a serverless function, or a background worker.
  Never from a browser, a mobile app, a desktop app, or anything else a user can open.
- Give each deployment environment its own key so one can be revoked without
  affecting the others.
- Never log the `Authorization` header. Sandchest's own error responses never echo it
  back, and neither should yours.
- If you need to expose transcription to end users, put your own endpoint in front of
  Sandchest and authenticate your users there.

## Verify

This should return `200` with a JSON body. Anything else means the key is wrong.

```bash
curl -sS -o /dev/null -w '%{http_code}\n' \
  "https://stt-api.sandchest.com/v2/transcript?limit=1" \
  -H "Authorization: $SANDCHEST_API_KEY"
```

```text title="expected output"
200
```

Then confirm the `Bearer` form works too:

```bash
curl -sS -o /dev/null -w '%{http_code}\n' \
  "https://stt-api.sandchest.com/v2/transcript?limit=1" \
  -H "Authorization: Bearer $SANDCHEST_API_KEY"
```

```text title="expected output"
200
```

## Next

[Using the AssemblyAI SDK](/docs/assemblyai) — if you already have AssemblyAI code,
this is the shortest path.
