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

# API keys

> Project-scoped vs account-wide keys, creating and revoking them, the show-once secret, and how to scope keys for security.

Keys authenticate every request. Each can be tied to one project or span your whole account — and the secret is shown exactly once, at creation, then stored only as a hash you can't read back.

An API key is sent as a Bearer token on every request: `Authorization: Bearer $DRIN_API_KEY`. Manage keys in the dashboard under [API Keys](https://app.drin.run/api-keys), or over the API with the endpoints below.

## Project-scoped vs account-wide

A Drin account can hold several **projects**, each with its own sender identity, domains, and keys. How a key is scoped decides which projects it can act on — and whether you must name the project per request.

<CardGroup cols={2}>
  <Card title="Project-scoped key" icon="lock">
    Created with a `senderId`. Tied to one project; the project is implied, so you send with just the `Authorization` header. The recommended default — least privilege.
  </Card>

  <Card title="Account-wide key" icon="key">
    Created without a `senderId`. Spans every project, so you name the sender per request with the `X-Drin-Product` header (or the SDK's `sender` option).
  </Card>
</CardGroup>

<Info>
  **Naming the project on an account-wide key.** Account-wide keys need `X-Drin-Product: my-project` on each request to say who's sending. [Authentication](/authentication) covers the header and its `X-Drin-Sender` alias in full.
</Info>

## Create a key

**`POST /v1/api-keys`**

`name` is required and is for your own bookkeeping. Pass `senderId` to scope the key to one project; omit it for an account-wide key. An optional `scopes` array narrows what the key may do.

<CodeGroup>
  ```bash cURL · project theme={null}
  # Project-scoped: pass the project's senderId
  curl https://api.drin.run/v1/api-keys \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "name": "production · receipts", "senderId": "snd_8f12" }'
  ```

  ```bash cURL · account theme={null}
  # Account-wide: omit senderId
  curl https://api.drin.run/v1/api-keys \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "name": "ci · all projects" }'
  ```

  ```typescript Node.js theme={null}
  const key = await drin.apiKeys.create({
    name: "production · receipts",
    senderId: "snd_8f12",   // omit for an account-wide key
  });

  console.log(key.secret);  // shown ONCE — store it now
  ```
</CodeGroup>

The response includes the one-time `secret`. Every later read shows only the non-secret metadata — `keyPrefix`, `last4`, scope, and timestamps:

```json 201 · secret shown once theme={null}
{
  "id": "ak_0Q7d…",
  "name": "production · receipts",
  "keyPrefix": "drin_live",
  "last4": "9c2e",
  "senderId": "snd_8f12",
  "senderExternalId": "receipts",
  "scopes": [],
  "secret": "drin_live_3f9a…9c2e",
  "lastUsedAt": null,
  "revokedAt": null,
  "createdAt": "2026-06-02T09:00:00Z"
}
```

<Warning>
  **The secret is shown once.** The full secret is returned **only** in this create response. Drin stores a hash, never the secret itself — there is no endpoint to read it back. Copy it into your secret manager immediately. Lost it? Revoke the key and create a new one.
</Warning>

## List keys

**`GET /v1/api-keys`**

A cursor-paged list of every key on the account. Secrets are never included — you see `keyPrefix` and `last4` to identify a key, plus `lastUsedAt` and `revokedAt`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.drin.run/v1/api-keys \
    -H "Authorization: Bearer $DRIN_API_KEY"
  ```

  ```typescript Node.js theme={null}
  for await (const k of drin.apiKeys.paginate()) {
    console.log(k.id, k.name, k.last4, k.revokedAt ? "(revoked)" : "active");
  }
  ```
</CodeGroup>

## Revoke a key

**`DELETE /v1/api-keys/{id}`**

Revoking takes effect immediately — the next request using that key gets a `401`. Revocation is permanent; there's no un-revoke. Returns `204 No Content`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://api.drin.run/v1/api-keys/ak_0Q7d \
    -H "Authorization: Bearer $DRIN_API_KEY"
  ```

  ```typescript Node.js theme={null}
  await drin.apiKeys.revoke("ak_0Q7d");
  ```
</CodeGroup>

<Warning>
  **Don't revoke the key you're holding.** Mint and deploy the replacement first, then revoke the old key. A revoked key that's still in use anywhere — a worker, a cron job, the dashboard's own service key — turns every one of its requests into a `401` the moment it's gone.
</Warning>

## Scoping & security

* **Server-side only.** A key is a secret. Never embed one in a browser, mobile app, or anything shipped to a user — it grants full send and account access.
* **Prefer project-scoped keys.** Give each project — and ideally each environment and service — its own key. The blast radius of a leak is then one project, and revoking it touches nothing else.
* **One key per environment.** Separate production, staging, and CI so you can rotate or revoke one without disrupting the others.
* **Rotate on suspicion.** If a key might have leaked, revoke it and create a new one. `lastUsedAt` helps you spot a key that's active when it shouldn't be, or stale and safe to retire.

<Tip>
  **Name keys for their job.** A descriptive `name` like `prod · receipts-worker` makes the list self-documenting and makes the right key obvious when it's time to rotate.
</Tip>

## Related

<CardGroup cols={2}>
  <Card title="Authentication" icon="lock" href="/authentication">
    The Bearer header, the `X-Drin-Product` header, and idempotency.
  </Card>

  <Card title="Errors" icon="bolt" href="/errors">
    What a revoked or invalid key returns — `authentication_error`.
  </Card>

  <Card title="API keys API" icon="code" href="/api-reference/api-keys/create">
    Parameters and response schemas for each endpoint.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Get your first key and send in under a minute.
  </Card>
</CardGroup>
