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

# Authentication

> Sign up, sign in, and manage user access on the KaireonAI platform.

## Overview

KaireonAI supports two authentication methods:

* **Google OAuth** — one-click sign-in with your Google account
* **Email & Password** — register with email, verify, then sign in

Both methods create a **personal workspace** where you have full admin access. Each user gets their own isolated tenant — your data is completely separate from other users.

## Playground Registration

The hosted playground at [playground.kaireonai.com](https://playground.kaireonai.com) offers self-service registration.

### Sign Up with Google

1. Visit [playground.kaireonai.com/register](https://playground.kaireonai.com/register)
2. Click **Sign up with Google**
3. Select your Google account
4. You're signed in — no email verification needed

Google OAuth users are automatically verified and receive a welcome email.

### Sign Up with Email

1. Visit [playground.kaireonai.com/register](https://playground.kaireonai.com/register)
2. Fill in your name, email, and password
3. Optionally provide a **Tenant name** — if set, the new tenant is created with this display name. Leave it blank to default to `"{Your Name}'s Workspace"`.
4. Click **Create Account**
5. Check your inbox for a verification email from `support@kaireonai.com`
6. Click the **Verify Email** link
7. Sign in at [playground.kaireonai.com/login](https://playground.kaireonai.com/login)

<Info>
  Password requirements: minimum 8 characters, at least 1 uppercase letter, at least 1 number.
</Info>

The tenant name only controls the display label in the studio header and approval audit log — it does not affect tenant isolation, billing, or API routing.

### After Registration

New users land on the platform as an **admin** of their personal workspace. An onboarding banner guides you to load sample data — go to **Settings → Sample Data** and load a dataset pack (e.g., Retail Rewards) to explore the full platform.

You have full access to create Offers, schemas, Decision Flows, pipelines, and everything else.

## Playground Limits

### Decision Quota

Each playground workspace has a **5,000 lifetime decision limit**. Decisions are counted when:

* The **Recommend API** generates impressions
* **Batch pipeline runs** produce decisions
* **Journey triggers** fire automated decisions

Read operations and outcome recording (Respond API) do not count toward the limit. When the limit is reached, you'll see a banner with options to continue.

### Workspace Reset

If you've used up your decisions or want to start fresh, go to **Settings → Reset Workspace**. This deletes all data in your workspace (offers, flows, schemas, interaction history, etc.) and resets your decision counter to 0. Your account and workspace remain intact.

### No Entity Limits

There are no limits on creating entities (Offers, schemas, Decision Flows, pipelines, etc.) in the playground. The only constraint is the 5,000 decision cap.

<Warning>
  The playground is a shared environment. Do not use production data or real customer information. For production use, [self-host the platform](/quickstart) or email [support@kaireonai.com](mailto:support@kaireonai.com) for a managed SaaS plan.
</Warning>

## Data Isolation

Every user's data is completely isolated in their own tenant:

* **Separate data storage** — offers, schemas, pipelines, interaction history, and all other data is scoped to your tenant
* **No cross-tenant access** — API routes enforce tenant boundaries on every request
* **Independent models** — scoring models and experiments are trained only on your tenant's data
* **Isolated Decision Flows** — your flows, rules, and configurations are private to your workspace

## Self-Hosted Authentication

When self-hosting KaireonAI, authentication is configured via environment variables:

| Variable               | Description                                                     | Required               |
| ---------------------- | --------------------------------------------------------------- | ---------------------- |
| `NEXTAUTH_SECRET`      | Random secret for signing JWT sessions                          | Yes                    |
| `NEXTAUTH_URL`         | Public URL of your deployment (e.g., `https://your-domain.com`) | Yes                    |
| `GOOGLE_CLIENT_ID`     | Google OAuth client ID                                          | For Google sign-in     |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret                                      | For Google sign-in     |
| `SES_FROM_EMAIL`       | Sender email for transactional emails                           | For email registration |
| `AWS_REGION`           | AWS region for SES                                              | For email registration |

<Info>
  Self-hosted instances have **no decision limits** — the 5,000 cap only applies to the hosted playground.
</Info>

### Google OAuth Setup

To enable Google sign-in on your self-hosted instance:

<Steps>
  <Step title="Create OAuth credentials">
    Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials), create an OAuth 2.0 Client ID for a Web application.
  </Step>

  <Step title="Configure redirect URIs">
    Add your deployment URL as an authorized redirect URI:

    ```
    https://your-domain.com/api/auth/callback/google
    ```

    For local development, also add:

    ```
    http://localhost:3000/api/auth/callback/google
    ```
  </Step>

  <Step title="Set environment variables">
    ```env theme={null}
    GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
    GOOGLE_CLIENT_SECRET=GOCSPX-your-secret
    ```
  </Step>
</Steps>

### Email Verification Setup

Email verification requires an email sending service. The platform uses AWS SES by default:

<Steps>
  <Step title="Verify your domain in SES">
    Add DKIM records and verify your sending domain in the AWS SES console.
  </Step>

  <Step title="Set environment variables">
    ```env theme={null}
    SES_FROM_EMAIL=support@your-domain.com
    AWS_REGION=us-east-1
    ```
  </Step>
</Steps>

<Tip>
  If running on AWS (App Runner, ECS, EC2), the platform uses the IAM instance role for SES — no credentials needed. For other environments, configure the standard AWS env vars **AWS\_ACCESS\_KEY\_ID** and **AWS\_SECRET\_ACCESS\_KEY**.
</Tip>

### Seed Admin User

For self-hosted deployments, create the initial admin user with the seed script:

```bash theme={null}
npx tsx prisma/seed.ts
```

This creates a default tenant and an admin user (`admin@kaireonai.com` / `admin123`). Change the password immediately after first login.

## User Roles

| Role       | Permissions                                       |
| ---------- | ------------------------------------------------- |
| **Admin**  | Full access — manage users, settings, all modules |
| **Editor** | Create and modify offers, rules, flows, pipelines |
| **Viewer** | Read-only access to all modules                   |

New playground registrations receive the **admin** role on their personal workspace. Self-hosted admins can manage roles via the database.

## Session Invalidation

KaireonAI sessions are stateless JWTs (\~30-minute lifetime, auto-refreshed).
Deleting a user, disabling a user, or changing a user's role invalidates
their **live** sessions within roughly 30 seconds — not the full JWT
lifetime.

Each user row carries a `tokenVersion` (bumped on role change, account
disable/enable, or MFA enrollment) and a `disabled` flag. Every protected
API route re-validates these against the database at the authorization
choke points (`requireTenant()` and `requireRole()`), not just at sign-in —
a request carrying a JWT whose embedded `tokenVersion` no longer matches
the live row, or whose user is now `disabled`, is rejected even though the
JWT signature itself is still valid and unexpired:

```json theme={null}
{
  "title": "Unauthorized",
  "detail": "Your session is no longer valid (account changed, disabled, or removed). Please sign in again."
}
```

A short (30-second) in-memory cache bounds this to roughly one database
read per user per window rather than a database round trip on every
request, and the check **fails closed** — if the liveness lookup itself
errors, the session is rejected rather than trusted. Previously, none of
this existed: a deleted, disabled, or role-downgraded user's session kept
working for up to the full \~30-minute JWT lifetime.

## API Key Management

API keys provide programmatic access to the KaireonAI REST API without user sessions. They are ideal for server-to-server integrations, CI/CD pipelines, and MCP connections.

### Key scopes: data plane vs control plane

The REST API is split into two planes, and a key's `scopes` array (set at creation, immutable afterwards) determines which it can reach:

* **Data plane** — exactly `POST /api/v1/recommend`, `POST /api/v1/respond`, `POST /api/v1/respond/bulk`, and `POST /api/v1/capture` (a legacy alias of `/respond`). A key minted with no scopes (`[]`, the default) is **data-plane-only**.
* **Control plane** — everything else (all management CRUD: schemas, offers, categories, channels, creatives, decision flows, decisioning gates, connectors, pipelines, segments, models, reports, settings, API keys, …). Requires a first-party session, an MCP connection, or a key minted with `scopes: ["control-plane"]` (admin-only to mint).
* `scopes: ["scim"]` mints a SCIM provisioning key for `/api/v1/scim/v2/*` (used with `Authorization: Bearer <key>`).

A key without the required access gets `403` with `{ "title": "Forbidden", "detail": "This API key is scoped to the data plane (recommend / respond). Management endpoints require a first-party session, an MCP connection, or an API key minted with the \"control-plane\" scope." }`.

<Warning>
  **Migration note:** as of the control-plane / data-plane split, existing API
  keys with no explicit scopes are now **data-plane-only** (recommend/respond).
  To manage resources programmatically, mint a new key with the
  `control-plane` scope — and keep it internal; never embed it in client apps.
</Warning>

The master/internal server key (`API_KEY` env var) and cron/webhook secrets are internal infrastructure, not customer keys, and are unaffected.

### Creating an API Key

**Endpoint:** `POST /api/v1/api-keys`
**Required role:** Admin

```bash theme={null}
curl -X POST https://your-domain.com/api/v1/api-keys \
  -H "Authorization: Bearer <session-token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "CI Pipeline", "expiresAt": "2027-01-01T00:00:00Z" }'
```

Response:

```json theme={null}
{
  "id": "clx...",
  "name": "CI Pipeline",
  "key": "krn_a1b2c3d4e5f6...",
  "prefix": "krn_a1b2c3d4",
  "expiresAt": "2027-01-01T00:00:00.000Z",
  "createdAt": "2026-03-16T12:00:00.000Z",
  "warning": "Store this key securely. It will not be shown again."
}
```

<Warning>
  The raw API key is returned **only at creation time**. Store it securely -- it cannot be retrieved later. Only the prefix is shown in subsequent listings.
</Warning>

### Listing API Keys

**Endpoint:** `GET /api/v1/api-keys`
**Required role:** Admin

Returns all active (non-revoked) keys for the tenant, showing prefix, name, last used date, and expiration.

### Revoking an API Key

**Endpoint:** `DELETE /api/v1/api-keys?id=<key-id>`
**Required role:** Admin

Soft-revokes the key immediately. Revoked keys cannot authenticate. Optionally include a `reason` in the request body.

### Using an API Key

Include the key in the `X-API-Key` header (there is no `Authorization: Bearer` scheme for API keys — a Bearer token is rejected with `401`; the only exception is SCIM keys on `/scim/v2/*`):

```bash theme={null}
curl -X POST https://your-domain.com/api/v1/recommend \
  -H "X-API-Key: krn_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{ "customerId": "cust-123" }'
```

<Info>
  API keys are hashed with HMAC using the `API_KEY_PEPPER` environment variable before storage. Set this variable in production for security.
</Info>

***

## OAuth 2.0 Client Credentials

KaireonAI supports the OAuth 2.0 client credentials grant (RFC 6749 Section 4.4) for machine-to-machine authentication. This is the recommended approach for production integrations.

### Registering an OAuth Client

**Endpoint:** `POST /api/v1/oauth/clients`
**Required role:** Admin

```bash theme={null}
curl -X POST https://your-domain.com/api/v1/oauth/clients \
  -H "Authorization: Bearer <session-token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Data Pipeline Service", "scopes": ["read", "write"] }'
```

Response:

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

**Available scopes:** `read`, `write`, `admin`

### Obtaining an Access Token

**Endpoint:** `POST /api/v1/oauth/token`

Accepts form-encoded or JSON body:

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

Response:

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

Access tokens expire after 1 hour. Request a new token when the current one expires.

### Listing and Revoking Clients

* **List:** `GET /api/v1/oauth/clients` (admin only)
* **Revoke:** `DELETE /api/v1/oauth/clients?id=<client-id>` (admin only)

Revoking a client immediately invalidates all future token requests for that client. Existing tokens remain valid until they expire.

### Rate Limiting

The token endpoint is rate-limited to 20 requests per minute per client ID (or per IP when the client ID is absent). When Redis is unavailable, the rate limiter fails closed to prevent brute-force attacks.

***

## SCIM User Provisioning

KaireonAI implements SCIM 2.0 (System for Cross-domain Identity Management) for automated user provisioning from identity providers like Okta, Azure AD, and OneLogin.

### Base URL

```
https://your-domain.com/api/v1/scim/v2/
```

### Supported Operations

| Endpoint     | Method | Description                                                 |
| ------------ | ------ | ----------------------------------------------------------- |
| `/Users`     | GET    | List users (paginated with `startIndex` and `count`)        |
| `/Users`     | POST   | Create a user from a SCIM resource                          |
| `/Users/:id` | GET    | Get a single user                                           |
| `/Users/:id` | PUT    | Replace a user resource (update name, email, active status) |
| `/Users/:id` | DELETE | Deactivate a user (soft delete)                             |

### Authentication

SCIM endpoints require a bearer token validated by `requireSCIMAuth`. Configure this in your identity provider with the SCIM bearer token from your KaireonAI tenant settings.

### User Lifecycle

* **Create** (`POST /Users`) -- Creates a new user in the tenant with `member` role. Requires `userName` (email).
* **Update** (`PUT /Users/:id`) -- Updates name and email. Set `active: false` to deactivate.
* **Delete** (`DELETE /Users/:id`) -- Soft-deactivates the user (sets `lockedUntil` to a far-future date). Does not hard-delete.

### Example: Create a User

```bash theme={null}
curl -X POST https://your-domain.com/api/v1/scim/v2/Users \
  -H "Authorization: Bearer <scim-token>" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "jane.doe@example.com",
    "name": { "givenName": "Jane", "familyName": "Doe" },
    "displayName": "Jane Doe",
    "active": true
  }'
```

<Info>
  All SCIM operations are audit-logged with `userId: "scim"` for traceability. SCIM users are scoped to the authenticated tenant -- no cross-tenant access is possible.
</Info>

***

## Security

* **JWT sessions** -- 30-minute expiry, auto-refreshed
* **Account lockout** -- 5 failed login attempts locks the account for 15 minutes
* **Email verification** -- required for email/password registration
* **Rate limiting** -- registration endpoint: 5 attempts per IP per hour; OAuth token endpoint: 20 per minute per client
* **CSRF protection** -- all state-changing API requests require `X-Requested-With` header
* **Tenant isolation** -- all data access is scoped to the authenticated user's tenant
* **API key hashing** -- keys are HMAC-hashed with a pepper before storage
* **OAuth secret hashing** -- client secrets are hashed before storage; timing-safe comparison prevents timing attacks
* **Audit logging** -- all auth events (login, token grants, key creation/revocation, SCIM operations) are logged
