> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nyra-labs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nyra-labs.com/_mcp/server.

# Authentication

Every request carries an API key in the `Authorization` header:

```
Authorization: Bearer nl_live_...
```

Keys are issued per organization from the [dashboard](https://platform.nyra-labs.com/api-keys).

## Key format

A key is a prefix followed by 32 bytes of randomness encoded in base62 — 51
characters in total:

```
nl_live_YxQFoF6p2xKvStMzWhYhwPMNSlEnWTwzYL83OJCbyTo
└──────┘└─────────────────────────────────────────┘
 prefix    43 base62 characters (32 random bytes)
```

Every request made with a key is billed against the organization's wallet.

Only the SHA-256 hash of a key is stored, so the plaintext is shown **once** at
creation and cannot be recovered afterwards. Lose it and you mint a new one.
The dashboard and the API only ever show the first 12 characters
(`nl_live_YxQF`), which is enough to tell your keys apart and useless on its
own.

A key carries its organization, its scopes, an optional expiry, its own rate
limit, and a last-used timestamp.

## Errors

Authentication failures return the standard error envelope with
`type: "authentication_error"` and HTTP `401`.

No key at all:

```json
{
  "error": {
    "message": "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer nl_live_...). You can find your API key at https://platform.nyra-labs.com/api-keys.",
    "type": "authentication_error",
    "param": null,
    "code": "missing_api_key"
  }
}
```

A key that is unknown, malformed, revoked or expired. The key is echoed back
redacted to its first 8 and last 4 characters, so you can tell *which* key
failed without the error log becoming a place credentials leak:

```json
{
  "error": {
    "message": "Incorrect API key provided: nl_live_…byTo. You can find your API key at https://platform.nyra-labs.com/api-keys.",
    "type": "authentication_error",
    "param": null,
    "code": "invalid_api_key"
  }
}
```

All four causes return the same message. Distinguishing "unknown" from
"revoked" would confirm to whoever holds a leaked key that it used to be real.

## Scopes

A key can be narrowed to a subset of the API. The available scopes are:

| Scope                  | Grants                                     |
| ---------------------- | ------------------------------------------ |
| `transcriptions:write` | `POST /v1/audio/transcriptions`            |
| `models:read`          | `GET /v1/models`, `GET /v1/models/{model}` |

A key created **without** scopes is unrestricted — that is the default.
Narrowing is opt-in, and it is worth doing for any key that leaves your own
infrastructure: a key that only transcribes cannot enumerate the catalogue,
and a key that only reads the catalogue cannot spend money.

Calling an endpoint your key lacks the scope for returns HTTP `403`:

```json
{
  "error": {
    "message": "You have insufficient permissions for this operation. Your API key is missing the 'transcriptions:write' scope. Grant it at https://platform.nyra-labs.com/api-keys, or use a key with no scope restrictions.",
    "type": "permission_error",
    "param": null,
    "code": "insufficient_scope"
  }
}
```

## Rate limits

Limits are per key and reset on a rolling one-minute window. The default is
600 requests per minute; a lower or higher limit can be set on each key in
the dashboard. Every `/v1` response carries:

| Header                           | Meaning                                  |
| -------------------------------- | ---------------------------------------- |
| `x-ratelimit-limit-requests`     | Requests allowed in the window           |
| `x-ratelimit-remaining-requests` | Requests left in the current window      |
| `x-ratelimit-reset-requests`     | Time until the window resets, e.g. `43s` |
| `retry-after`                    | Seconds to wait. Sent on `429` only      |

Counters are shared across every API instance, so a limit is a limit no matter
which server answers.

Exceeding the limit returns HTTP `429`:

```json
{
  "error": {
    "message": "Rate limit reached for requests. Please slow down or contact support to raise your limit.",
    "type": "rate_limit_error",
    "param": null,
    "code": "rate_limit_exceeded"
  }
}
```

A request that arrives with no usable key is limited by client IP instead,
at a deliberately low rate — it is on its way to a `401` regardless.