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

# KeyAuth

> API key authentication policy

KeyAuth authenticates requests using Unkey API keys. It is the only authentication policy the engine executes today; JWTAuth is defined in the schema but not yet active.

## Fields

<ResponseField name="key_space_ids" type="string[]">
  List of keyspace IDs the key must belong to. If the key belongs to a keyspace not in this list, authentication fails with `Frontline.Auth.InvalidKey`.
</ResponseField>

<ResponseField name="locations" type="KeyLocation[]">
  Ordered list of locations to extract the API key from. Frontline tries each location in order and uses the first non-empty key. If omitted, defaults to extracting a Bearer token from the `Authorization` header.
</ResponseField>

<ResponseField name="permission_query" type="string">
  Optional RBAC query evaluated against the key's permissions. If the key does not satisfy the query, authentication fails with `Frontline.Auth.InsufficientPermissions`.
</ResponseField>

<ResponseField name="ratelimits" type="KeyRatelimit[]">
  Optional list of rate limits to enforce on the verified key, mirroring the `ratelimits` field of the verifyKey API. Each entry references a rate limit by `name`. This is in addition to any auto-applied limits on the key or its identity, which are always enforced. Each entry may optionally override the `limit`, `duration` (milliseconds), and `cost`. Supplying both `limit` and `duration` defines an inline limit that does not need to exist on the key. If a named limit does not exist and no inline `limit`/`duration` is provided, the request is rejected.
</ResponseField>

## Examples

<Tabs>
  <Tab title="Bearer token (default)">
    ```json theme={"theme":"kanagawa-wave"}
    {
      "policies": [
        {
          "id": "api-auth",
          "name": "Authenticate API keys",
          "enabled": true,
          "match": [],
          "keyauth": {
            "key_space_ids": ["ks_abc123"],
            "locations": [
              { "bearer": {} }
            ]
          }
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Custom header">
    ```json theme={"theme":"kanagawa-wave"}
    {
      "policies": [
        {
          "id": "api-auth",
          "name": "Authenticate via X-API-Key header",
          "enabled": true,
          "match": [],
          "keyauth": {
            "key_space_ids": ["ks_abc123"],
            "locations": [
              { "header": { "name": "X-API-Key" } }
            ]
          }
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Header with prefix stripping">
    ```json theme={"theme":"kanagawa-wave"}
    {
      "policies": [
        {
          "id": "api-auth",
          "name": "Authenticate with prefix stripping",
          "enabled": true,
          "match": [],
          "keyauth": {
            "key_space_ids": ["ks_abc123"],
            "locations": [
              { "header": { "name": "Authorization", "strip_prefix": "ApiKey " } }
            ]
          }
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Query parameter">
    <Warning>
      Placing API keys in query parameters is dangerous and should only be used as a last resort for trusted-internal services. Query strings are logged by proxies, CDNs, and web servers, cached by browsers and intermediaries, saved in browser history, and leaked to third parties via the `Referer` header. Prefer the `Authorization` header (`bearer` or `header` location) whenever possible.
    </Warning>

    ```json theme={"theme":"kanagawa-wave"}
    {
      "policies": [
        {
          "id": "api-auth",
          "name": "Authenticate via query param",
          "enabled": true,
          "match": [],
          "keyauth": {
            "key_space_ids": ["ks_abc123"],
            "locations": [
              { "query_param": { "name": "api_key" } }
            ]
          }
        }
      ]
    }
    ```
  </Tab>

  <Tab title="With permissions">
    ```json theme={"theme":"kanagawa-wave"}
    {
      "policies": [
        {
          "id": "api-auth",
          "name": "Authenticate with RBAC",
          "enabled": true,
          "match": [],
          "keyauth": {
            "key_space_ids": ["ks_abc123"],
            "locations": [{ "bearer": {} }],
            "permission_query": "(api.keys.read OR api.keys.list) AND billing.read"
          }
        }
      ]
    }
    ```
  </Tab>

  <Tab title="With key rate limits">
    ```json theme={"theme":"kanagawa-wave"}
    {
      "policies": [
        {
          "id": "api-auth",
          "name": "Authenticate and enforce key rate limits",
          "enabled": true,
          "match": [{ "path": { "exact": "/api/v2/getAll" } }],
          "keyauth": {
            "key_space_ids": ["ks_abc123"],
            "locations": [{ "bearer": {} }],
            "ratelimits": [
              { "name": "expensive" },
              { "name": "burst", "limit": 10, "duration": 1000 }
            ]
          }
        }
      ]
    }
    ```
  </Tab>
</Tabs>

## Key extraction

Frontline supports three key extraction locations:

| Location        | Behavior                                                                                                                                                                                                                                                  |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bearer          | Reads the `Authorization` header and strips the `Bearer ` prefix (case-insensitive)                                                                                                                                                                       |
| Header          | Reads a named header. Optionally strips a prefix (case-insensitive)                                                                                                                                                                                       |
| Query parameter | Reads a named query parameter. **Security risk:** query strings are recorded in server logs, proxy logs, CDN logs, browser history, and `Referer` headers. Use only as a last resort for trusted-internal traffic; prefer `bearer` or `header` locations. |

When multiple locations are configured, Frontline tries each in order and uses the first non-empty result.

## Verification flow

1. Extract the key from the request using configured locations.
2. Hash the key using SHA-256.
3. Look up the hash in the key cache (fresh: 10s, stale: 10min, max: 100k entries).
4. Validate key status. Keys that are not found, disabled, expired, or belong to a disabled workspace are rejected.
5. Verify the key belongs to one of the configured `key_space_ids`.
6. Parse the permission query (if configured) and build verify options, including any configured key `ratelimits`.
7. Call `verifier.Verify()` with 1 credit deduction per request. Auto-applied key/identity limits and any policy-configured `ratelimits` are enforced here, using the same path as the verifyKey API.
8. Write rate limit headers (regardless of success or failure).
9. Check post-verification status (rate limit, usage exceeded, permissions).
10. Build and return the principal on success.

## Response headers

KeyAuth writes rate limit headers on every response, including rejected requests:

| Header                  | Value                                                         |
| ----------------------- | ------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Rate limit ceiling                                            |
| `X-RateLimit-Remaining` | Remaining requests in the window                              |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets                         |
| `Retry-After`           | Seconds until retry (only on 429 responses, minimum 1 second) |

## Error responses

| Scenario                                    | Status | Code                                      |
| ------------------------------------------- | ------ | ----------------------------------------- |
| No key found in request                     | 401    | `Frontline.Auth.MissingCredentials`       |
| Key not found in database                   | 401    | `Frontline.Auth.InvalidKey`               |
| Key disabled                                | 401    | `Frontline.Auth.InvalidKey`               |
| Key expired                                 | 401    | `Frontline.Auth.InvalidKey`               |
| Key not in allowed keyspace                 | 401    | `Frontline.Auth.InvalidKey`               |
| Workspace disabled                          | 401    | `Frontline.Auth.InvalidKey`               |
| Permission query not satisfied              | 403    | `Frontline.Auth.InsufficientPermissions`  |
| Rate limit exceeded                         | 429    | `Frontline.Auth.RateLimited`              |
| Usage limit exceeded                        | 429    | `Frontline.Auth.RateLimited`              |
| Configured `ratelimits` name missing on key | 500    | `Frontline.Internal.InternalServerError`  |
| Invalid permission query syntax             | 500    | `Frontline.Internal.InvalidConfiguration` |
