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

# Authentication

> Bearer-token auth using the same key issued from your dashboard.

Every request to the Verseodin API must carry an `Authorization` header
with a Bearer token issued from the dashboard. There's no OAuth flow,
no per-request signing — just one header, one key.

```http theme={null}
Authorization: Bearer vso_<48 hex chars>
```

## Where to get a key

[verseodin.com/dashboard/settings/api-keys](https://verseodin.com/dashboard/settings/api-keys)
→ **Generate key**.

The plaintext key is shown **exactly once** in the modal. Copy it,
store it somewhere your app can read it (env var, secrets manager),
then dismiss the modal. If you lose it, revoke and generate a new one
— there's no way to recover the original.

## One key, two surfaces

The same `vso_…` key authorises:

* The REST API documented here (`https://verseodin.com/api/v1/*`)
* The [Verseodin Claude / MCP connector](https://verseodin.com/dashboard/settings/api-keys)

There's no separate "MCP key" or "API key" — they're the same row in the same table. Revoking from the dashboard kills both surfaces simultaneously.

## What the key authorises

A key is bound to the user who generated it. It can read:

* Every universe owned by that user
* Every history row, prompt, metric belonging to those universes

It **cannot**:

* Modify any data (the API is GET-only)
* Read other users' universes (each request goes through ownership verification)
* Issue new keys, change account settings, or do anything outside the data-read scope

## Key rotation playbook

If you suspect a key is leaked (committed to git, leaked in logs, copy-pasted into a chat):

<Steps>
  <Step title="Generate the new key first">
    Open the dashboard, click **Generate key**, copy the new `vso_…` value.
  </Step>

  <Step title="Update your apps to use the new key">
    Push the new key to your secrets manager / env vars, redeploy.
  </Step>

  <Step title="Revoke the leaked key">
    In the dashboard, click **Revoke** on the old key. The next request that uses it gets `401 authentication_error` immediately.
  </Step>
</Steps>

The order matters — revoke the old key only **after** every consumer is on the new one, otherwise you'll see traffic fail in between.

## Failure modes

| Scenario                                                           | Response                                                                                |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| Missing `Authorization` header                                     | `401 authentication_error` — "Missing or malformed Authorization header."               |
| Header present but doesn't start with `Bearer `                    | `401 authentication_error`                                                              |
| Key not in our table (typo, fake key)                              | `401 authentication_error` — "invalid or missing api key"                               |
| Key was revoked                                                    | `401 authentication_error`                                                              |
| Key exists and is valid → owns the universe                        | `200` (assuming filters are valid)                                                      |
| Key exists and is valid but **doesn't own** the requested universe | `403 permission_error` — "access denied: this universe does not belong to your account" |

The 401 vs 403 split is intentional: 401 means "we don't know who you are or your key is dead"; 403 means "we know who you are, you just can't touch this resource."
