> ## Documentation Index
> Fetch the complete documentation index at: https://docs.decimal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> API key authentication, scopes, permissions, rotation, and revocation.

All requests to the DecimalAI API authenticate with an API key. Pass it as a standard `Authorization: Bearer <key>` header.

```bash theme={null}
curl https://api.decimal.ai/api/v1/traces \
  -H "Authorization: Bearer dai_sk_..."
```

<Note>
  Every key starts with the prefix `dai_sk_`. The dashboard's session cookie auth (`/api/v1/auth/me`, `/api/v1/auth/verify`) is for the web UI only — programmatic access always uses API keys.
</Note>

## Key scopes

Scope controls **what data** a key can see. It's set at creation time and is immutable.

| Scope       | What it sees                             | When to use                                                                                             |
| ----------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `global`    | Everything in the org                    | SDK init for an app that spans multiple workspaces; the only scope that can create or revoke other keys |
| `workspace` | One workspace (specified at create time) | CI key for one team's repo                                                                              |
| `project`   | One project under a workspace            | Per-environment keys (dev / staging / prod)                                                             |

A key cannot escalate to a wider scope. To widen access, create a new key.

## Key permissions

Permissions control **what actions** a key can take, orthogonal to scope. Default is `admin` for backwards compatibility.

| Permission | Allowed methods                                             |
| ---------- | ----------------------------------------------------------- |
| `read`     | `GET` only                                                  |
| `write`    | `GET` + `POST` / `PUT` / `PATCH` (ingest, register, attach) |
| `admin`    | All of the above + `DELETE` / destructive ops               |

A CI key that only ingests traces should be `write`. An SRE read-only on-call key should be `read`. Use `admin` only for keys that need to delete data.

## Expiration

Keys can optionally expire. Pass an ISO-8601 `expires_at` when creating; omit for a non-expiring key (the historical default).

Once expired, requests return `403` with the response body `{"detail": "API key has expired"}` — a distinct detail string from the revoked-key case (also `403`, `{"detail": "API key has been revoked"}`), so callers can prompt regeneration without a generic "auth failed" branch. Both keys were valid when issued and are simply no longer authorized, so both return `403`; `401` is reserved for a missing, malformed, or unrecognized key. The `detail` string is what distinguishes expiry from revocation.

## Creating a key

### From the dashboard

<Note>
  You may not need to. Signing up mints a default key and shows it once in a
  banner right after signup. These steps are for *additional* keys — or a
  replacement if you no longer have that one.
</Note>

1. Sign in to the [DecimalAI Dashboard](https://app.decimal.ai)
2. Go to [**Settings → API keys**](https://app.decimal.ai/settings/api-keys)
3. Click **Create new secret key**
4. Give it a **Name** — required, and only a label for you; it plays no part in authentication
5. Optionally set **Permissions** (Admin, Write, or Read — defaults to Admin) and **Expiration** (Never, 30 days, 60 days, 90 days, or 1 year — defaults to Never). Dashboard keys are always `global` scope
6. Click **Create secret key**
7. A **Save your secret key** dialog shows the full key. Hit **Copy**, store it, then **Done** — this dialog is the only time the key is displayed, and only its SHA-256 hash is kept on the server. Escape and clicking the backdrop are deliberately disabled here so you can't lose the key by accident

### Programmatically

Requires an existing `global`-scoped key with `admin` role (only global keys can mint other keys):

```bash theme={null}
curl -X POST https://api.decimal.ai/api/v1/api-keys \
  -H "Authorization: Bearer dai_sk_<your_global_admin_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "ci-staging",
    "scope": "workspace",
    "workspace_id": "ws_abc123",
    "permissions": "write",
    "expires_at": "2027-01-01T00:00:00Z"
  }'
```

Response (the raw key is returned **once**; the server keeps only the hash):

```json theme={null}
{
  "id": "key_abc123",
  "raw_key": "dai_sk_...",
  "key_prefix": "dai_sk_xxxxxxxx...",
  "label": "ci-staging",
  "scope": "workspace",
  "permissions": "write",
  "expires_at": "2027-01-01T00:00:00Z",
  "created_at": "2026-05-28T18:00:00Z"
}
```

<Note>
  A security-alert email goes to the org's billing contact when a new key is created. If you didn't expect it, revoke immediately.
</Note>

## Listing keys

```bash theme={null}
curl https://api.decimal.ai/api/v1/api-keys \
  -H "Authorization: Bearer dai_sk_..."
```

Returns prefix, label, scope, permissions, `created_at`, and `last_used_at` for each key. Raw keys are never returned. Only global-scoped keys can list.

`last_used_at` is refreshed asynchronously and at most once every few minutes, so treat it as a cleanup hint rather than an audit record — a stale value is not proof a key is unused.

## Rotating a key

There's no in-place rotation. To rotate:

<Steps>
  <Step title="Create a new key">
    Mint a new key with the same scope and permissions as the one you're replacing.
  </Step>

  <Step title="Roll it out">
    Deploy the new key to your callers (env vars, secret manager) and let them pick it up.
  </Step>

  <Step title="Revoke the old key">
    Revoke the old key once `last_used_at` on it stops advancing.
  </Step>
</Steps>

If `last_used_at` on the old key is still advancing, something still has it. Because the field is refreshed on a delay, wait past your longest caller interval before revoking.

## Revoking a key

```bash theme={null}
curl -X DELETE https://api.decimal.ai/api/v1/api-keys/{key_id} \
  -H "Authorization: Bearer dai_sk_<your_global_admin_key>"
```

In the dashboard, the same thing is the **Revoke** button on the key's row in
[Settings → API keys](https://app.decimal.ai/settings/api-keys); that table also
shows each key's **Last used** column, which is the `last_used_at` value the
rotation steps above tell you to watch.

Revocation is immediate — in-flight requests authenticated with the revoked key will fail on the next handler invocation. Only global-scoped keys can revoke. A revoked key is soft-deleted (the row stays for audit); re-creation requires a new `POST`.

## Security checklist

* **Never commit keys to source control.** Use environment variables or a secret manager.
* **Use the narrowest scope and permissions that work.** A CI key that only sends traces should be `workspace`/`write`, not `global`/`admin`. The dashboard only mints `global`-scoped keys, so create narrower ones with the programmatic `POST /api/v1/api-keys` above.
* **Set `expires_at` on keys handed to humans.** Long-lived keys without expiry should belong to automated systems with rotation procedures.
* **Audit `last_used_at` quarterly.** Revoke anything that hasn't been used in 90 days.
* **Lost a key?** Revoke first, regenerate second. The new key won't have the old key's `id` — update any infra that pins to it.
