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

# SCIM v2 (User Provisioning)

> SCIM 2.0 compliant user provisioning for enterprise identity providers.

KaireonAI implements the SCIM 2.0 protocol (RFC 7644) for automated user provisioning from enterprise identity providers like Okta, Azure AD, and OneLogin. SCIM endpoints require bearer-token authentication configured in your IdP — the bearer token is a Kaireon `krn_` API key scoped to `"scim"`.

<Note>
  **Base URL.** The working SCIM base path is `/scim/v2/*` (e.g. `https://playground.kaireonai.com/scim/v2/Users`). The `/api/v1/scim/*` alias exists in the codebase but Bearer-authenticated calls to it are blocked at middleware. Always use `/scim/v2/` when configuring your IdP.
</Note>

## Authentication

Every SCIM request runs through Kaireon's SCIM auth gate, which accepts two modes:

1. **Bearer token (production IdP integration).** Send `Authorization: Bearer krn_<your_api_key>`. The token is verified against your tenant's API keys and the bound `tenantId` is used for scoping. The API key **must** carry the `"scim"` scope — a key with an empty `scopes` array (a key minted before scopes existed) is also accepted for backwards compatibility, but a non-empty `scopes` array that does not include `"scim"` returns a SCIM-formatted `403 { detail: "API key lacks the 'scim' scope" }`. Invalid or expired tokens return a SCIM-formatted `401`.
2. **Session fallback (admin UI testing only).** When no `Authorization: Bearer` header is present, the route falls back to the standard `requireRole("admin") + requireTenant` chain. This path is for debugging from a logged-in admin browser session and is not used by IdPs.

### Creating a SCIM-scoped API key

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/api-keys \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: <tenant-id>" \ \
  -d '{"name": "Okta SCIM Provisioner", "scopes": ["scim"]}'
```

Pass the returned `key` value as `Authorization: Bearer <key>` when configuring your IdP.

## GET /scim/v2/Users

List users in SCIM ListResponse format.

### Query Parameters

| Parameter    | Type   | Required | Description                           |
| ------------ | ------ | -------- | ------------------------------------- |
| `startIndex` | number | No       | 1-based pagination index (default: 1) |
| `count`      | number | No       | Page size (default: 100, max: 200)    |

`startIndex` is clamped to a minimum of `1` and `count` to the range `[1, 200]`.

### Response

```json theme={null}
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
  "totalResults": 25,
  "startIndex": 1,
  "itemsPerPage": 25,
  "Resources": [
    {
      "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
      "id": "clx...",
      "userName": "john@example.com",
      "name": { "formatted": "John Smith" },
      "emails": [
        { "value": "john@example.com", "primary": true }
      ],
      "active": true,
      "meta": {
        "resourceType": "User",
        "created": "2026-03-01T00:00:00.000Z",
        "lastModified": "2026-03-18T00:00:00.000Z"
      }
    }
  ]
}
```

`userName` is the user's stored email (falling back to the user id when no email is set). `name` is emitted as `{ "formatted": "<stored full name>" }`, and is omitted entirely when the user has no stored name. `active` is always `true` in this representation — the User resource does not carry a per-user disabled flag in this version. The default page size is `count=50` (max 200) when no `count` query parameter is supplied.

***

## POST /scim/v2/Users

Create a user from a SCIM resource. `userName` is required. New users are provisioned with role `viewer`.

### Request Body

```json theme={null}
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "jane@example.com",
  "name": {
    "givenName": "Jane",
    "familyName": "Doe"
  },
  "emails": [
    { "value": "jane@example.com", "primary": true }
  ]
}
```

The email is resolved in this order: the `emails` entry marked `primary: true`, then `emails[0].value`, then `userName`. The stored name is composed from `name.givenName` + `name.familyName` (space-joined, dropping any blank parts); when both are absent the name is stored as `null`.

### Response (201)

Returns the created SCIM User resource (the same shape as the list `Resources[]` entries — `id`, `userName`, `name.formatted`, `emails`, `active: true`, `meta`).

### Error — Invalid body (400)

When `userName` is missing or the body fails validation, the route returns the Zod validation message:

```json theme={null}
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "400",
  "scimType": "invalidValue",
  "detail": "<zod validation message>"
}
```

### Error — User Exists (409)

The user-uniqueness check is by email. The error detail embeds the conflicting email.

```json theme={null}
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "409",
  "scimType": "uniqueness",
  "detail": "User jane@example.com already exists"
}
```

***

## GET /scim/v2/Users/{id}

Get a single user by ID. Tenant-scoped — returns `404 { detail: "User not found" }` with a SCIM error envelope when the id does not belong to the bearer token's tenant.

***

## PUT /scim/v2/Users/{id}

Replace a user resource. The accepted body fields are `userName`, `name.formatted`, and `emails` — the route updates the user's `email` (from `emails[0].value`, falling back to `userName`, then the existing email) and `name` (from `name.formatted`, falling back to the existing name).

<Note>
  This version's PUT does **not** accept or act on an `active` field — the PutBody schema has no `active` property, so any `active` value in the request is ignored. There is no enable/disable-via-PUT behavior. The returned resource always reports `active: true`.
</Note>

Returns the updated SCIM User resource on success, or `404 { detail: "User not found" }` when the id is not in the caller's tenant, or `400` with the Zod validation message for a malformed body.

***

## DELETE /scim/v2/Users/{id}

Deletes the user record (`prisma.user.delete`). Tenant-scoped — the user must belong to the bearer token's tenant.

### Response

`204 No Content` on success. `404 { detail: "User not found" }` with a SCIM error envelope when the id is not in the caller's tenant.

***

## Related

* [Authentication](/api-reference/auth) — TOTP MFA, API keys, SSO
* [OAuth 2.0](/api-reference/oauth) — machine-to-machine token grants
