# Kilevel auth.md

> How a software agent obtains a credential for Kilevel and presents it to the
> platform API and the MCP server. Two credentials work: an OAuth2
> `client_credentials` token, which is the one to prefer, and a long-lived
> workspace API key.

Last reviewed: 2026-09-22. Questions: support@kilevel.com.

## Who this is for

Agents, MCP clients and unattended integrations that need to work inside a Kilevel
workspace — place and inspect phone calls and chats, read call history, manage the
flows and tools behind them.

An agent here always acts **as an existing workspace**, never as itself. There is no
identity for the agent separate from the credential it holds, and every request is
attributed to, billed to, and limited by that workspace.

## Discovery

The resource server publishes both documents, so a client that supports OAuth
discovery needs nothing from this file:

```text
https://api.kilevel.com/.well-known/oauth-protected-resource
https://api.kilevel.com/.well-known/oauth-authorization-server
```

Protected-resource metadata is also served per resource, for clients that discover
from a concrete URL rather than from the origin:
`https://api.kilevel.com/.well-known/oauth-protected-resource/api/mcp` and
`.../api/external/v1`. An unauthenticated request to the REST API answers `401`
with a `WWW-Authenticate: Bearer resource_metadata="..."` header pointing at the
same document.

Note the host: the metadata lives on the API origin, which is the protected
resource, not on `https://kilevel.com`. This file is on the site because that is where the
Auth.md convention looks for it.

## There is no agent self-registration

Read this before writing a registration flow. Kilevel issues no credential to an
unauthenticated caller. Specifically, there is:

- no dynamic client registration — the authorization server metadata deliberately
  carries no `registration_endpoint`;
- no ID-JAG, verified-email or anonymous assertion flow;
- no `POST /agent/auth`, and no other endpoint that will mint a credential on request.

A credential is created by a human who owns the workspace and who chooses what the
agent may do with it. An agent that has not been given one cannot obtain one, and
probing for a registration endpoint will not find one. Ask your operator.

## Getting a credential

### OAuth2 client (preferred)

The workspace owner creates a client at https://kilevel.com/app/api and selects the API
permissions it may use. A client with no permissions selected cannot reach the API
or the MCP server at all — that is the default, and it is deliberate. The
`client_id` (prefixed `klv_client_`) and `client_secret` are shown once, at creation.

Exchange them for a token the standard way:

```bash
curl -s -X POST https://api.kilevel.com/api/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=$KILEVEL_CLIENT_ID \
  -d client_secret=$KILEVEL_CLIENT_SECRET
```

HTTP Basic works too. The response is an ordinary `access_token` / `token_type` /
`expires_in` triple. Tokens for an API-capable client are capped at 24 hours
regardless of how the client is configured; revoking the client invalidates its
outstanding tokens on their next use rather than at their next expiry.

### Workspace API key

Created at the same place, https://kilevel.com/app/api, and shown once. Keys look like
`klv_live_...` and may be given an expiry. A key is simpler to set up and never
expires unless you say so, which is also the argument against handing one to an
agent: there is no short-lived credential to rotate, only a secret to leak.

## Presenting the credential

### Platform API — `https://api.kilevel.com/api/external/v1`

Either of:

```http
Authorization: Bearer <access_token>
```

```http
X-API-Key: klv_live_...
```

`X-API-Key` takes precedence: the `Authorization` header is only read when no
`X-API-Key` was sent. A request carrying neither answers `401` with
`{"error": "Unauthorized", "message": "Missing API key. Use X-API-Key header."}`.

### MCP server — `https://api.kilevel.com/api/mcp`

Both credentials work, as `X-API-Key` or as a bearer token:

```http
Authorization: Bearer <access_token>
```

An `?apiKey=` query parameter is also accepted, for clients that cannot set headers
on an SSE connection. Prefer a header: query strings are written to access logs and
browser history in a way headers are not.

Every MCP tool call is executed by proxying the REST API with the same credential,
so a client's permissions decide which tools actually work. A token without
`OUTBOUND_CALLS_TRIGGER` can list flows and read history; asking it to place a call
fails the way any unpermitted API call fails.

### Webhook callbacks into Kilevel

Calls that Kilevel *receives* on `/api/webhooks/async-tool-task/response` accept a
token from the same endpoint, as an alternative to that webhook's shared secret.
A client needs no API permissions for this; it is the one thing these tokens could
always do.

## What the credential is scoped to

A credential is bound to the workspace that issued it. No path carries an account or
tenant identifier, so there is no way to address another workspace's data with it.

Within the workspace, permissions are granted individually — call jobs, history,
outbound, flows, agents, tools, structured outputs, phone numbers, data stores,
balance and reports. The scope vocabulary in the OAuth metadata is exactly this list.
The readable version is at https://kilevel.com/docs/workspace/api-keys

Permissions are read from the database on every request, not from the token, so
narrowing what a client may do takes effect on its next call rather than whenever
its current token expires. A request outside them returns `403`.

## Rate limits

The platform API meters per credential — per key, or per OAuth client — at 30
requests per 60 seconds by default, as a token bucket. Every response carries
`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until
refill); an exhausted bucket returns `429`. Read those headers rather than
discovering the limit by hitting it.

## More

- Authentication reference: https://kilevel.com/docs/api/authentication
- API keys and permissions: https://kilevel.com/docs/workspace/api-keys
- Rate limits: https://kilevel.com/docs/api/rate-limits
- Kilevel as an MCP server: https://kilevel.com/docs/mcp-server/kilevel-mcp-server
- Site map for language models: https://kilevel.com/llms.txt

## Machine-readable summary

A restatement of the above, not a substitute for it. The OAuth documents linked
under Discovery are the authoritative machine-readable form.

```yaml
agent_auth:
  skill: kilevel-client-credentials
  register_uri: https://kilevel.com/app/api
  registration_methods:
    - type: human_provisioned
      description: >-
        An owner of the workspace creates an OAuth2 client and selects the API permissions it
        may use. Both halves of the credential are shown once, at creation.
      register_uri: https://kilevel.com/app/api
      api_uri: https://api.kilevel.com/api/webhook-oauth-clients
      api_auth: dashboard session
      credential_types_supported: [client_id, client_secret]
      grant_type: client_credentials
      token_uri: https://api.kilevel.com/api/oauth/token
  self_registration_supported: false
  dynamic_client_registration_supported: false

service: Kilevel
audience: software agents acting on behalf of an existing Kilevel workspace
credentials:
  - type: oauth2_client_credentials
    preferred: true
    client_id_prefix: klv_client_
    token_uri: https://api.kilevel.com/api/oauth/token
    max_token_lifetime_seconds: 86400
    scope_vocabulary: ExternalApiPermission
  - type: api_key
    prefix: klv_live_
    issued_at: https://kilevel.com/app/api
    expiry: optional, chosen at creation
resources:
  - uri: https://api.kilevel.com/api/external/v1
    methods_supported: [header:X-API-Key, header:Authorization-Bearer]
    bearer_methods_supported: [header]
  - uri: https://api.kilevel.com/api/mcp
    methods_supported: [header:X-API-Key, header:Authorization-Bearer, query:apiKey]
    bearer_methods_supported: [header]
oauth:
  issuer: https://api.kilevel.com
  authorization_server_metadata: https://api.kilevel.com/.well-known/oauth-authorization-server
  protected_resource_metadata: https://api.kilevel.com/.well-known/oauth-protected-resource
  grant_types_supported: [client_credentials]
  registration_endpoint: null
scopes_uri: https://kilevel.com/docs/workspace/api-keys
documentation_uri: https://kilevel.com/docs/api/authentication
contact: support@kilevel.com
```
