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

# Authentication

> Organization API keys, scopes, and the Bearer header every Samsa API request needs.

Every request to the Samsa API is authenticated with an **organization API key**.
Keys are scoped, shown once, and act for the organization that owns them — credits
are drawn from that organization's pool and generated assets appear in the app under
the account of the admin who created the key.

<Note>
  Connecting Samsa to an MCP client (Claude, ChatGPT, Claude Code, Cursor…)? The
  [MCP server](/mcp) uses these same API keys for headless clients and OAuth 2.1
  sign-in for interactive ones — the scopes below apply to MCP tools identically.
</Note>

## How keys work

* **Organization-owned.** A key belongs to an organization, not a person. Anyone
  holding the key acts for that organization.
* **Admin-created.** Only an organization **admin** (`OWNER` or `ADMIN`) can create
  or revoke keys, in the app's **Settings → API Keys** tab.
* **Scoped.** Each key carries a set of [scopes](#scopes) that determine which
  endpoints it can call. Keys are created with all scopes by default; narrow them to
  match what the integration needs.
* **Shown once.** The full secret is displayed exactly once, at creation.

<Warning>
  Samsa stores only a **SHA-256 hash** of each key, never the plaintext. That is why
  a key can never be shown again or recovered — there is nothing to recover from. If
  you lose a key, [revoke it and create a new one](#rotating-a-key).
</Warning>

## The `Authorization` header

Send your key as a **Bearer token** on every request:

```
Authorization: Bearer samsa_sk_your_key_here
```

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.samsa.ai/public/v1/me \
    -H "Authorization: Bearer $SAMSA_API_KEY"
  ```

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

  BASE_URL = "https://api.samsa.ai/public/v1"
  headers = {"Authorization": f"Bearer {os.environ['SAMSA_API_KEY']}"}

  resp = requests.get(f"{BASE_URL}/me", headers=headers)
  resp.raise_for_status()
  print(resp.json())
  ```

  ```typescript TypeScript theme={null}
  const BASE_URL = "https://api.samsa.ai/public/v1";
  const headers = { Authorization: `Bearer ${process.env.SAMSA_API_KEY}` };

  const resp = await fetch(`${BASE_URL}/me`, { headers });
  if (!resp.ok) throw new Error(`GET /me failed: ${resp.status}`);
  console.log(await resp.json());
  ```
</CodeGroup>

Use [`GET /me`](/quickstart) to confirm a key works — it
returns the key's organization, its safe metadata (prefix, scopes, expiry), and the
organization's available credit balance, but never the secret.

## Scopes

Scopes follow a `<resource>.<verb>` shape. A request to an endpoint whose scope the
key lacks fails with [`403 missing_scope`](/guides/errors#missing_scope).

| Scope             | Endpoints unlocked                                                                                                |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- |
| `images.generate` | `POST /images/generations`, `GET /images/generations/{id}`                                                        |
| `images.edit`     | `POST /images/edits`, `GET /images/edits/{id}`                                                                    |
| `videos.generate` | `POST /videos/generations`, `GET /videos/generations/{id}`, `GET /videos/models`                                  |
| `models.read`     | `GET /models`, `GET /models/{id}`, `GET /models/{id}/status`                                                      |
| `models.write`    | `POST /models`, `POST /models/prepare`, `POST /models/{id}/complete`, `PATCH /models/{id}`, `DELETE /models/{id}` |
| `usage.read`      | `GET /credits`, `GET /usage`                                                                                      |

<Note>
  `GET /me` needs **any** valid key — it requires no specific scope. New capabilities
  add new scope strings; existing keys never inherit them automatically, so an admin
  edits the key's scopes or issues a new key to grant access.
</Note>

## When authentication fails

Authentication and authorization failures return the standard
[error envelope](/guides/errors). A missing, malformed, unknown, expired, or revoked
key — or a key whose creator is no longer a member of the organization — returns
**`401 invalid_api_key`**:

```json 401 Unauthorized theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "The provided API key is invalid, expired, or revoked.",
    "request_id": "req_8f14e45fceea167a"
  }
}
```

A valid key that lacks the endpoint's scope returns **`403 missing_scope`**, naming
the required scope:

```json 403 Forbidden theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "missing_scope",
    "message": "The API key is missing the required scope: images.generate.",
    "request_id": "req_1c9a3b7d2e5f4a80",
    "param": "scope"
  }
}
```

<Note>
  Expired and unknown keys both return `401 invalid_api_key` — deliberately
  indistinguishable, so an outsider cannot probe which keys once existed.
</Note>

## Security best practices

<AccordionGroup>
  <Accordion title="Store keys in environment variables" icon="lock">
    Keep keys out of source control. Load them from an environment variable or a
    secrets manager — never hard-code them.

    ```bash theme={null}
    export SAMSA_API_KEY="samsa_sk_..."
    ```
  </Accordion>

  <Accordion title="Never expose keys client-side" icon="browser">
    The Samsa API is **server-side first** — CORS is deliberately restrictive and
    browser calls from arbitrary origins are unsupported. A key in front-end code or
    a mobile app is a leaked key. Always call the API from your backend.
  </Accordion>

  <Accordion title="Rotate by revoke + create" icon="rotate">
    There is no in-place rotation. To rotate, [create a new key](#rotating-a-key),
    deploy it, then revoke the old one. Revocation is terminal and takes effect on
    the very next request.
  </Accordion>

  <Accordion title="Set an expiry for short-lived integrations" icon="clock">
    Keys can be created with an optional **expiry**. An expired key fails exactly
    like an unknown one (`401 invalid_api_key`). Use expiry for temporary
    integrations, trials, and contractors.
  </Accordion>
</AccordionGroup>

### Rotating a key

1. Create a new key in **Settings → API Keys** and copy it.
2. Deploy the new key to your integration.
3. Revoke the old key. Revocation is immediate and cannot be undone.

## Key format

A key looks like:

```
samsa_sk_gK3n8vQ1xY7bT2mW9cR4jL6hF0dS5pZaU8eN1oI3rAb
└───┬───┘└──────────────────┬─────────────────────┘
 prefix           43 random base62 characters
```

The `samsa_sk_` body is 43 base62 characters encoding 256 bits of randomness. In the
app, keys are displayed as `samsa_sk_gK3n…3rAb` (a stable `prefix` plus the last four
characters) so you can identify a key without exposing it.

<Tip>
  The constant `samsa_sk_` prefix lets secret scanners (GitHub secret scanning,
  pre-commit hooks, CI checks) detect an accidentally committed Samsa key. Enable
  secret scanning on your repositories so a leaked key is caught before it ships.
</Tip>
