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

# Authentication

> How to authenticate API requests with an OptimalDial API key.

Every request to the OptimalDial API is authenticated by a single bearer token: an **API key**. There is no OAuth flow, no client/secret pair, and no JWT exchange — you mint a key in the developer panel, send it on every request, and revoke it when you're done.

## API key format

OptimalDial keys look like this:

```
od_live_FxXkV6bA2YqpW3LhR9zJMTnGoQ8sK4dC
```

* The prefix `od_live_` identifies the environment (currently only `live` exists).
* The remainder is 32 URL-safe characters of cryptographic randomness (256 bits of entropy).
* Total length is around 40 characters.
* Keys are case-sensitive.

The full key is shown to you exactly once — at the moment you create it. We store only a SHA-256 hash, plus the first 12 characters as a non-secret display prefix (e.g. `od_live_FxXk`) so you can identify which key you're holding without revealing the secret.

If you lose a key you must create a new one — there is no "show key" recovery. Treat the secret like a password.

## Creating an API key

1. Sign in at [app.optimaldial.com](https://app.optimaldial.com) as an **organization owner**.
2. Open *Developers* in the sidebar (admin-gated during pre-release).
3. Click **Create API key**, give it a descriptive name (e.g. `production-billing-pipeline`), and optionally set an `expires_at` date.
4. Copy the full key from the one-time reveal panel and store it in your secrets manager **before closing the modal**.

You can have multiple active keys per organization. Create one per integration so you can rotate or revoke them independently.

## Sending the key on requests

Pass the key in the `Authorization` header as a Bearer token. This is the only thing that needs to change between an unauthenticated request and an authenticated one.

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -H "Authorization: Bearer od_live_FxXkV6bA2YqpW3LhR9zJMTnGoQ8sK4dC" \
         https://api.optimaldial.com/api/v1/uploads
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const res = await fetch("https://api.optimaldial.com/api/v1/uploads", {
      headers: {
        Authorization: `Bearer ${process.env.OPTIMALDIAL_API_KEY}`,
      },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import requests

    resp = requests.get(
        "https://api.optimaldial.com/api/v1/uploads",
        headers={"Authorization": f"Bearer {os.environ['OPTIMALDIAL_API_KEY']}"},
    )
    ```
  </Tab>
</Tabs>

You do **not** need to send `X-Organization-Id` on public-API requests — the key already binds the call to the organization that minted it.

## What a key gives access to

Each key is scoped to **one organization** and carries a static set of permissions:

| Scope             | Lets the key…                                              |
| ----------------- | ---------------------------------------------------------- |
| `uploads:write`   | Create uploads                                             |
| `uploads:read`    | List uploads, fetch upload details, generate download URLs |
| `webhooks:manage` | Create, update, delete webhook endpoints; list deliveries  |

Today every key is issued with all three scopes. Per-key scope selection is reserved for a future release; the field exists in responses so your client can read it forward-compatibly.

A key can never be used to manage other API keys — that's intentional, so a leaked key cannot bootstrap fresh keys on its own. Key minting and revocation flow through the in-app *Developers* panel only.

## Rotating keys

Keys never expire automatically unless you set an `expires_at` value at creation. To rotate:

1. **Create** a new key in the developer panel.
2. **Deploy** the new key to your application (env var or secret manager).
3. **Verify** with a single request that the new key works.
4. **Revoke** the old key in the developer panel.

Both keys can be active simultaneously, so this is a zero-downtime swap.

## Auto-revocation when a user leaves an organization

If the user who minted a key is removed from the organization (or removes themselves), every key they created for that organization is auto-revoked at the database level. This is enforced by a trigger, not by application code, so it survives bugs and outages.

Practical consequence: keys should be minted by a long-lived owner account (e.g. a service-owner identity), not by an individual employee who might leave.

## Authentication errors

A failed auth attempt always returns `401 Unauthorized` with `WWW-Authenticate: Bearer` and one of:

| Body                                                          | Reason                                              |
| ------------------------------------------------------------- | --------------------------------------------------- |
| `{"detail":"API key required"}`                               | Missing or empty `Authorization` header             |
| `{"detail":"Invalid API key format"}`                         | Header is present but doesn't start with `od_live_` |
| `{"detail":"Invalid or revoked API key"}`                     | Key was revoked, expired, or doesn't exist          |
| `{"detail":"API key creator no longer exists"}`               | The minting user was deleted                        |
| `{"detail":"Organization for this API key no longer exists"}` | The organization was deleted                        |

`403 Forbidden` is reserved for **subscription** failures, not auth — see [errors and rate limits](/guides/errors-and-rate-limits).
