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

# OAuth 2.0

> OAuth 2.0 client credentials flow for machine-to-machine API access.

KaireonAI supports OAuth 2.0 client credentials grant (RFC 6749 section 4.4) for machine-to-machine authentication. Create OAuth clients with specific scopes, then exchange credentials for access tokens. Tokens are signed HS256 JWTs with a 1-hour lifetime.

## POST /api/v1/oauth/clients

Create a new OAuth 2.0 client. **Admin only.**

### Request Body

| Field    | Type      | Required | Description                                                      |
| -------- | --------- | -------- | ---------------------------------------------------------------- |
| `name`   | string    | No       | Client name (defaults to `oauth-client-{timestamp}`)             |
| `scopes` | string\[] | No       | Permitted scopes: `read`, `write`, `admin` (default: `["read"]`) |

Only `read`, `write`, and `admin` are accepted. Unknown scopes are silently dropped; if every scope is dropped, the request falls back to `["read"]`.

### Example

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/oauth/clients \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \ \
  -d '{
    "name": "Data Pipeline Client",
    "scopes": ["read", "write"]
  }'
```

### Response (201)

```json theme={null}
{
  "id": "clx...",
  "name": "Data Pipeline Client",
  "clientId": "kci_a1b2c3d4...",
  "clientSecret": "kcs_e5f6g7h8...",
  "scopes": ["read", "write"],
  "createdAt": "2026-03-18T12:00:00.000Z",
  "warning": "Store the client secret securely. It will not be shown again."
}
```

`clientId` is `kci_` followed by 16 random bytes (hex); `clientSecret` is `kcs_` followed by 32 random bytes (hex). Only the HMAC-SHA256 hash of the secret is persisted — the cleartext secret is never stored.

<Warning>
  The `clientSecret` is only returned on creation. Store it securely.
</Warning>

***

## GET /api/v1/oauth/clients

List active (non-revoked) OAuth clients for the tenant. **Admin only.** Returns at most 1000 rows ordered by `createdAt` descending.

### Response

```json theme={null}
{
  "data": [
    {
      "id": "clx...",
      "name": "Data Pipeline Client",
      "clientIdPrefix": "kci_a1b2c3d4...",
      "clientId": "kci_a1b2c3d4...",
      "scopes": ["read", "write"],
      "createdAt": "2026-03-18T12:00:00.000Z"
    }
  ],
  "total": 1
}
```

***

## DELETE /api/v1/oauth/clients?id={id}

Revoke an OAuth client (soft delete — sets `revokedAt`). **Admin only.**

### Query Parameters

| Parameter | Type   | Required | Description            |
| --------- | ------ | -------- | ---------------------- |
| `id`      | string | Yes      | OAuth client record ID |

### Response

`204 No Content` on success. `404 Not Found` when the id does not belong to the caller's tenant.

***

## POST /api/v1/oauth/token

Exchange client credentials for an access token. Follows the OAuth 2.0 client credentials grant. Rate limited to 20 requests per 60 seconds per `client_id` (or per IP when `client_id` is absent). The rate limiter is **fail-closed** — if Redis is unreachable the request is denied to prevent brute-force attacks.

Accepts both `application/x-www-form-urlencoded` and `application/json` request bodies.

### Request Body

| Field           | Type   | Required | Description                     |
| --------------- | ------ | -------- | ------------------------------- |
| `grant_type`    | string | Yes      | Must be `client_credentials`    |
| `client_id`     | string | Yes      | OAuth client ID (`kci_...`)     |
| `client_secret` | string | Yes      | OAuth client secret (`kcs_...`) |

### Example — Form Encoded

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=kci_abc&client_secret=kcs_xyz"
```

### Example — JSON

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "kci_abc",
    "client_secret": "kcs_xyz"
  }'
```

### Response

```json theme={null}
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read write"
}
```

The JWT carries `{ sub: clientId, tid: tenantId, role, scopes, iss: "kaireon", iat, exp }` and is signed HS256 with the `JWT_SIGNING_SECRET`. `role` is derived from scopes — `admin` scope grants `admin` role, `write` grants `editor`, otherwise the role is `viewer`.

### Error Responses

| Status | Error                    | Description                                       |
| ------ | ------------------------ | ------------------------------------------------- |
| 400    | `unsupported_grant_type` | Only `client_credentials` is supported            |
| 400    | `invalid_request`        | Missing `client_id` or `client_secret`            |
| 401    | `invalid_client`         | Invalid or revoked client credentials             |
| 429    | `rate_limit_exceeded`    | Too many token requests; `Retry-After` header set |

### Using the Token

Include the access token in the `Authorization` header for subsequent API calls:

```bash theme={null}
curl https://playground.kaireonai.com/api/v1/offers \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

***

## Configuration

The OAuth subsystem reads three environment variables. The token route hard-fails on startup when the required ones are missing — there is no silent dev-only fallback.

| Variable             | Required                                   | Effect when missing                                                                                                                                              |
| -------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `JWT_SIGNING_SECRET` | Yes (or `NEXTAUTH_SECRET` as fallback)     | Token issuance and verification are impossible — the route throws an explicit "JWT\_SIGNING\_SECRET or NEXTAUTH\_SECRET must be set for OAuth operations" error. |
| `API_KEY_PEPPER`     | Yes                                        | Client create and credential verify both fail — the route throws "API\_KEY\_PEPPER environment variable is required for OAuth client secret hashing".            |
| `NEXTAUTH_SECRET`    | Optional fallback for `JWT_SIGNING_SECRET` | Used only if `JWT_SIGNING_SECRET` is unset.                                                                                                                      |

Rotating either secret invalidates all in-flight access tokens (HS256 signature verify fails) and breaks `clientSecret` verification for every existing client. Plan rotations during a maintenance window and re-issue client secrets after the rotation.

See also: [Authentication](/api-reference/auth) | [Environment variables](/self-host/configure/env-vars)
