> ## 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.

# Configuration

> Configuration model and required settings for the api service

## Configuration model

Unkey services read configuration from a TOML file passed at startup. Environment variables can be referenced with `${VAR}` and are expanded before parsing. Defaults and validation run after parsing.

The config schema maps to [`svc/api/config.go`](https://github.com/unkeyed/unkey/blob/main/svc/api/config.go).

Minimal config example:

```toml theme={"theme":"kanagawa-wave"}
instance_id = "${POD_NAME}"
platform = "aws"
http_port = 7070
region = "${UNKEY_REGION}"
redis_url = "${UNKEY_REDIS_URL}"

[database]
primary = "${UNKEY_DATABASE_PRIMARY}"
readonly_replica = "${UNKEY_DATABASE_REPLICA}"

[clickhouse]
url = "${UNKEY_CLICKHOUSE_URL}"
analytics_url = "${UNKEY_CLICKHOUSE_ANALYTICS_URL}"

[control]
url = "${UNKEY_CTRL_URL}"
token = "${UNKEY_CTRL_TOKEN}"

[vault]
url = "${UNKEY_VAULT_URL}"
token = "${UNKEY_VAULT_TOKEN}"
```

<ResponseField name="instance_id" type="string">
  Instance identifier for logs and cache invalidation.
  Example: `"api-7d9b8c4f5d-2kq7m"`.
</ResponseField>

<ResponseField name="platform" type="string">
  Platform label for logs and metrics.
  Example: `"aws"`.
</ResponseField>

<ResponseField name="image" type="string">
  Container image identifier logged at startup.
  Example: `"ghcr.io/unkeyed/unkey:v2.0.77"`.
</ResponseField>

<ResponseField name="http_port" type="int" default="7070">
  HTTP server port.
  Example: `7070`.
</ResponseField>

<ResponseField name="region" type="string" default="unknown">
  Region label for logs and analytics.
  Example: `"us-east-1"`.
</ResponseField>

<ResponseField name="redis_url" type="string" required>
  Redis connection string for counters and usage limiting.
  Example: `"redis://redis:6379"`.
</ResponseField>

<ResponseField name="test_mode" type="bool" default="false">
  Enables test-only behaviors. Do not use in production.
</ResponseField>

<ResponseField name="max_request_body_size" type="int" default="10485760">
  Maximum request size in bytes.
</ResponseField>

<ResponseField name="auth" type="object[]" required>
  Ordered authentication resolver configuration. Each entry registers one auth
  mechanism. At least one entry is required: a config without auth entries
  would reject every request, including valid root keys, so startup fails
  instead.

  <Expandable title="Fields">
    <ResponseField name="auth.type" type="string" required>
      Auth mechanism. Supported values are `jwt`, `portal_session`, and
      `root_key`.
    </ResponseField>

    <ResponseField name="auth.issuer" type="string">
      Expected JWT `iss` claim. Required for `type = "jwt"` entries and rejected
      as an unknown field on `portal_session` and `root_key` entries. The issuer
      does not affect permission translation; set `auth.provider` to opt into
      WorkOS slug translation.
    </ResponseField>

    <ResponseField name="auth.provider" type="string">
      Permission dialect carried by this entry's tokens for `type = "jwt"`.
      Leave unset when tokens already carry canonical Unkey permissions. Set
      `provider = "workos"` to translate WorkOS permission slugs into canonical
      Unkey permissions after verification. Translation is selected by this
      field, not inferred from the issuer, so any issuer (including custom auth
      domains) can opt in the same way.
    </ResponseField>

    <ResponseField name="auth.audience" type="string">
      Optional expected JWT `aud` claim for `type = "jwt"`. Verification
      requires the configured value to appear in the token's `aud` list. The
      Unkey WorkOS environments use a JWT template that sets `aud` to
      `["api.unkey.com"]`, so set
      `audience = "api.unkey.com"` on the WorkOS entry. Omit this field only
      for providers that do not emit an audience claim. Setting it rejects
      tokens without a matching `aud`, so when introducing the claim, add it
      to the provider's token template first, wait for tokens minted without
      it to expire, then set this field.
    </ResponseField>

    <ResponseField name="auth.enabled" type="bool" default="true">
      Optional explicit enable flag. `false` is invalid; remove the auth entry
      instead.
    </ResponseField>

    <ResponseField name="auth.secrets" type="string[]">
      Ordered JWT verification secrets for `type = "jwt"`. During
      rotation, put the active signing secret first and keep retired secrets
      later in the list until every token signed with those secrets has expired.
      Configure either `secrets` or `jwks_url`, never both.
    </ResponseField>

    <ResponseField name="auth.jwks_url" type="string">
      JWKS endpoint for `type = "jwt"`. Must be an absolute `https` URL so
      signing keys cannot be substituted over an unauthenticated channel. The
      API fetches the JSON Web Key Set on first use and verifies incoming JWTs
      against usable RSA signing keys from the response. When a token fails
      verification against every cached key, the key set is refetched (rate
      limited to once per minute), so signing-key rotations are picked up
      without a restart. Configure either `jwks_url` or `secrets`, never both.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="database" type="object" required>
  MySQL configuration.

  <Expandable title="Fields">
    <ResponseField name="database.primary" type="string" required>
      Primary MySQL DSN.
    </ResponseField>

    <ResponseField name="database.readonly_replica" type="string">
      Optional read replica DSN.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="clickhouse" type="object">
  ClickHouse configuration.

  <Expandable title="Fields">
    <ResponseField name="clickhouse.url" type="string">
      ClickHouse connection string for shared analytics.
    </ResponseField>

    <ResponseField name="clickhouse.analytics_url" type="string">
      Base URL for workspace-specific analytics connections.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="tls" type="object">
  TLS settings for HTTPS.

  <Expandable title="Fields">
    <ResponseField name="tls.disabled" type="bool">
      Disable TLS when true.
    </ResponseField>

    <ResponseField name="tls.cert_file" type="string">
      Path to TLS certificate.
    </ResponseField>

    <ResponseField name="tls.key_file" type="string">
      Path to TLS key.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="vault" type="object">
  Vault connection.

  <Expandable title="Fields">
    <ResponseField name="vault.url" type="string">
      Vault base URL.
    </ResponseField>

    <ResponseField name="vault.token" type="string">
      Bearer token for Vault.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="control" type="object">
  Control plane connection.

  <Expandable title="Fields">
    <ResponseField name="control.url" type="string" required>
      Control API URL.
    </ResponseField>

    <ResponseField name="control.token" type="string" required>
      Bearer token for control API.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="pprof" type="object">
  pprof endpoint configuration.

  <Expandable title="Fields">
    <ResponseField name="pprof.username" type="string" required>
      Basic auth username.
    </ResponseField>

    <ResponseField name="pprof.password" type="string" required>
      Basic auth password.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="observability" type="object">
  Tracing, logging, and metrics configuration.

  <Expandable title="Fields">
    <ResponseField name="observability.tracing.sample_rate" type="float" default="0.25">
      Trace sampling rate.
    </ResponseField>

    <ResponseField name="observability.logging.sample_rate" type="float" default="1.0">
      Log sampling rate.
    </ResponseField>

    <ResponseField name="observability.logging.slow_threshold" type="duration" default="1s">
      Slow log threshold.
    </ResponseField>

    <ResponseField name="observability.metrics.prometheus_port" type="int" default="0">
      Prometheus port for the `/metrics` listener. To disable metrics, omit the `observability.metrics` section.
    </ResponseField>
  </Expandable>
</ResponseField>

## Environment variables

The Helm chart provides these variables for the default config template:

<ResponseField name="UNKEY_REGION" type="env">
  Region label for logs and traces.
</ResponseField>

<ResponseField name="UNKEY_REDIS_URL" type="env" required>
  Redis URL for counters and usage limiting.
</ResponseField>

<ResponseField name="UNKEY_DATABASE_PRIMARY" type="env" required>
  MySQL primary DSN.
</ResponseField>

<ResponseField name="UNKEY_DATABASE_REPLICA" type="env">
  MySQL read replica DSN.
</ResponseField>

<ResponseField name="UNKEY_CLICKHOUSE_URL" type="env">
  ClickHouse shared URL.
</ResponseField>

<ResponseField name="UNKEY_CLICKHOUSE_ANALYTICS_URL" type="env">
  ClickHouse analytics base URL.
</ResponseField>

<ResponseField name="UNKEY_CTRL_URL" type="env" required>
  Control API URL.
</ResponseField>

<ResponseField name="UNKEY_CTRL_TOKEN" type="env" required>
  Control API token.
</ResponseField>

<ResponseField name="UNKEY_VAULT_URL" type="env">
  Vault URL.
</ResponseField>

<ResponseField name="UNKEY_VAULT_TOKEN" type="env">
  Vault bearer token.
</ResponseField>

<ResponseField name="UNKEY_PPROF_USERNAME" type="env">
  pprof username.
</ResponseField>

<ResponseField name="UNKEY_PPROF_PASSWORD" type="env">
  pprof password.
</ResponseField>

## Dashboard proxy configuration

The dashboard proxy forwards the WorkOS access token when a WorkOS session is
available. The API verifies that token through a `type = "jwt"` auth entry
configured with the WorkOS issuer and JWKS URL. The WorkOS JWT template includes
the organization as `org.id`, and the API reads Unkey RBAC permissions from the
token's `permissions` claim. The template also sets `aud` to
`["api.unkey.com"]`, and the auth entry pins `audience = "api.unkey.com"`.

Each WorkOS environment (production, canary) must configure this JWT template
in the WorkOS dashboard under Authentication settings:

```json theme={"theme":"kanagawa-wave"}
{
  "org": { "id": {{organization.id}} },
  "aud": ["api.unkey.com"]
}
```

The `aud` value must be a JSON array. The API parses `aud` as a string list and
rejects tokens that carry it as a bare string, so a string-valued template
claim fails verification with "Invalid bearer token". Without the template, or
with a missing `aud` claim, every forwarded WorkOS access token fails the
audience check the same way.

Local development still uses a dashboard-minted fallback JWT when no WorkOS
access token exists. For that path, the dashboard needs a signing secret and the
API must include the same secret in a `type = "jwt"` auth entry. The local
fallback JWT includes the dashboard proxy permission set directly, so every
local dashboard user is effectively an API admin.

<ResponseField name="UNKEY_API_URL" type="env" default="https://api.unkey.com">
  API base URL that dashboard proxy requests are forwarded to.
</ResponseField>

<ResponseField name="UNKEY_JWT_SECRET" type="env">
  Local dashboard proxy signing secret. Add the same value to the API JWT auth
  entry's `secrets` list so the API can verify dashboard-minted fallback JWTs.
</ResponseField>

## Example configuration

```toml theme={"theme":"kanagawa-wave"}
instance_id = "${POD_NAME}"
platform = "aws"
http_port = 7070
region = "${UNKEY_REGION}"
redis_url = "${UNKEY_REDIS_URL}"

[[auth]]
type = "jwt"
issuer = "https://api.workos.com"
audience = "api.unkey.com"
jwks_url = "${UNKEY_JWT_JWKS_URL}"
provider = "workos"

[[auth]]
type = "portal_session"

[[auth]]
type = "root_key"
enabled = true

[observability.tracing]
sample_rate = 0.1

[observability.logging]
sample_rate = 0.01
slow_threshold = "1s"

[observability.metrics]
prometheus_port = 2112

[database]
primary = "${UNKEY_DATABASE_PRIMARY}"
readonly_replica = "${UNKEY_DATABASE_REPLICA}"

[clickhouse]
url = "${UNKEY_CLICKHOUSE_URL}"
analytics_url = "${UNKEY_CLICKHOUSE_ANALYTICS_URL}"

[control]
url = "${UNKEY_CTRL_URL}"
token = "${UNKEY_CTRL_TOKEN}"

[vault]
url = "${UNKEY_VAULT_URL}"
token = "${UNKEY_VAULT_TOKEN}"

[pprof]
username = "${UNKEY_PPROF_USERNAME}"
password = "${UNKEY_PPROF_PASSWORD}"
```

## Related docs

* [Overview](/architecture/services/api/overview)
