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

# API Keys

> Create, scope, rotate, and revoke API keys — including single-inbox agent keys.

Every request to `https://api.gork.email/v1` authenticates with a Bearer API key. Keys start with `gork_live_` (or `gork_test_`) followed by 48 hex characters. Only a SHA-256 hash is stored — the raw key is shown once, at creation.

## Create a key

<Steps>
  <Step title="Open the console">
    Go to **Dashboard → API Keys** and choose **Create key**, or call the API with a key that already holds `keys:write`.
  </Step>

  <Step title="Name it and pick scopes">
    Give the key a descriptive name (`sdr-agent-prod`) and select the minimum scopes it needs (table below). Omit `scopes` entirely for a full-access key (`*`).
  </Step>

  <Step title="Save the raw key immediately">
    The `apiKey` field is returned exactly once. Store it in a secrets manager — afterwards only the `keyPrefix` (first 16 characters) is visible, for identification.
  </Step>
</Steps>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.gork.email/v1/keys \
    -H "Authorization: Bearer gork_live_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "sdr-agent-prod",
      "scopes": ["inboxes:read", "messages:read", "messages:send", "threads:read"]
    }'
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch("https://api.gork.email/v1/keys", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GORK_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "sdr-agent-prod",
      scopes: ["inboxes:read", "messages:read", "messages:send", "threads:read"],
    }),
  });
  const { data } = await res.json();
  // data.apiKey — save now, it is never shown again
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.post(
      "https://api.gork.email/v1/keys",
      headers={"Authorization": f"Bearer {os.getenv('GORK_API_KEY')}"},
      json={
          "name": "sdr-agent-prod",
          "scopes": ["inboxes:read", "messages:read", "messages:send", "threads:read"],
      },
  )
  print(res.json()["data"]["apiKey"])  # save now, it is never shown again
  ```
</CodeGroup>

```json Response (201) theme={null}
{
  "data": {
    "id": "key_9f2c41ab77d04e1c90ab33de",
    "name": "sdr-agent-prod",
    "keyPrefix": "gork_live_9f2c41",
    "scopes": ["inboxes:read", "messages:read", "messages:send", "threads:read"],
    "inboxId": null,
    "createdAt": "2026-09-22T00:00:00.000Z",
    "apiKey": "gork_live_9f2c41ab...",
    "warning": "Please save this secret key safely. You will not be able to view it again."
  }
}
```

List keys (prefixes only) with `GET /v1/keys` (`keys:read`). Revoke with `DELETE /v1/keys/:id` (`keys:write`) — revocation deactivates the key immediately and returns `{ "data": { "id": "...", "status": "revoked" } }`. A revoked or unknown key fails with `invalid_api_key` (401).

## Scopes

| Scope                                      | Grants                                                                                         |
| :----------------------------------------- | :--------------------------------------------------------------------------------------------- |
| `inboxes:read` / `inboxes:write`           | List and read inboxes / provision and delete inboxes                                           |
| `messages:read` / `messages:send`          | Read messages, threads search, attachment download / send, schedule, delete, and manage drafts |
| `threads:read`                             | Read conversation threads                                                                      |
| `webhooks:read` / `webhooks:write`         | List webhooks and delivery logs / register, rotate, and delete webhooks                        |
| `keys:read` / `keys:write`                 | List keys (prefixes only) / create and revoke keys                                             |
| `domains:read` / `domains:write`           | List domains / register, verify, and delete domains                                            |
| `organization:read`                        | Read workspace details and members                                                             |
| `suppressions:read` / `suppressions:write` | List suppressions / add and remove suppressions                                                |
| `*`                                        | Full access (the default when `scopes` is omitted)                                             |

A key can also hold a resource wildcard such as `messages:*`, which covers every `messages:` scope. Endpoints reject under-scoped keys with `insufficient_scope` (403), naming the required and granted scopes.

<Note>
  Follow least privilege: a sending agent needs `inboxes:read`, `messages:read`, `messages:send`, and `threads:read` — nothing else. It should never hold `keys:write`, which would let it mint new keys.
</Note>

## Inbox-scoped agent keys

Pass `inboxId` when creating a key to bind it to a single inbox. Bound keys default to mail-only scopes (`inboxes:read`, `messages:read`, `messages:send`, `threads:read`); any requested scopes are intersected with that allowlist, and a request with zero mail scopes fails with `invalid_scopes` (400).

```bash cURL theme={null}
curl -X POST https://api.gork.email/v1/keys \
  -H "Authorization: Bearer gork_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "support-agent-3", "inboxId": "inb_728af980b4e"}'
```

A bound key can only operate inside its own inbox — reads are filtered to it, sends from any other inbox fail with `inbox_access_denied` (403), and it is blocked from `POST /v1/inboxes` entirely. Use one bound key per agent so a compromised key cannot reach other inboxes.

## Rotation

<Steps>
  <Step title="Create the replacement">Create a new key with the same name plus a version suffix and the same scopes.</Step>
  <Step title="Deploy it">Update the secret in your agent's environment and confirm traffic succeeds (check `lastUsedAt` on the new key via `GET /v1/keys`).</Step>
  <Step title="Revoke the old key">Delete the old key with `DELETE /v1/keys/:id`. There is no grace period — do this last.</Step>
</Steps>

## If a key leaks

1. Revoke it immediately: `DELETE /v1/keys/:id`. Authentication fails closed on revocation.
2. Create a replacement key and rotate the secret everywhere it was stored.
3. Review `GET /v1/keys` (`lastUsedAt` per key) and your sending logs for traffic you do not recognize.
4. If the leaked key held `keys:write`, audit the key list for keys you did not create and revoke those too.

<Warning>
  Never commit keys to git, bake them into client-side bundles, or log the `Authorization` header. The raw key exists only in the creation response — support cannot recover it.
</Warning>
