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

# SCIM 2.0 Provisioning

> Connect Microsoft Entra ID, Okta or any RFC 7644 client to KnoxCall: base URL, token issuance, supported resources and filters, the attributes we accept but never store, the Enterprise requirement, and the rate limits your connector must honour.

# SCIM 2.0 Provisioning

KnoxCall implements [RFC 7644](https://www.rfc-editor.org/rfc/rfc7644) (SCIM Protocol) and
[RFC 7643](https://www.rfc-editor.org/rfc/rfc7643) (SCIM Schema) so your identity provider can
create, update and deprovision workspace members — and OAuth clients — without anyone logging
into KnoxCall.

This is the page your connector's `documentationUri` points at. If you arrived here from
`GET /scim/v2/ServiceProviderConfig`, you are in the right place.

<Note>
  **SCIM provisioning requires the Enterprise plan.** Discovery
  (`/ServiceProviderConfig`, `/Schemas`, `/ResourceTypes`) is served on every plan so that a
  connector can be configured and can read this URL; the `/Users` and `/OAuthClients`
  endpoints are served to Enterprise workspaces. See
  [Plan requirement](#plan-requirement) for exactly what a workspace sees when the plan does
  not cover it.
</Note>

## Base URL

The SCIM service lives under `/scim/v2` on your workspace's KnoxCall control-plane host:

```
https://<workspace>.knoxcall.com/scim/v2
```

`https://admin.knoxcall.com/scim/v2` works too. Requests that arrive on any other hostname —
including directly to the origin address — are answered `404` before authentication is
attempted, so paste the workspace URL exactly.

Every endpoint is relative to that base:

| Method                       | Path                        | What it does                                 |
| ---------------------------- | --------------------------- | -------------------------------------------- |
| `GET`                        | `/ServiceProviderConfig`    | Capability document (RFC 7643 §5)            |
| `GET`                        | `/Schemas`, `/Schemas/{id}` | Attribute definitions                        |
| `GET`                        | `/ResourceTypes`            | The resources this service exposes           |
| `GET` `POST`                 | `/Users`                    | List / filter, and provision a member        |
| `GET` `PUT` `PATCH` `DELETE` | `/Users/{id}`               | Read, replace, patch, deprovision            |
| `GET` `POST`                 | `/OAuthClients`             | List / filter, and provision an OAuth client |
| `GET` `PUT` `PATCH` `DELETE` | `/OAuthClients/{id}`        | Read, replace, patch, revoke                 |

## Token issuance

Your connector authenticates with a **SCIM credential** — a bearer token that begins with
`scim_`. It is not a user's session and not an API key; it belongs to the workspace, carries
its own tenant binding, and reaches `/scim/v2` and nothing else.

To issue one, an **owner or admin** goes to the KnoxCall dashboard and mints a credential from
the Single sign-on settings (`POST /admin/scim/credentials` if you are scripting it). Minting
requires a recent step-up verification, is written to the audit log, and asks for an explicit
expiry — a credential with no expiry is something you have to choose, never something you get
by omitting a field.

```http theme={"dark"}
GET /scim/v2/Users?filter=userName%20eq%20%22ada@example.com%22 HTTP/1.1
Host: acme.knoxcall.com
Authorization: Bearer scim_3f9c…
Accept: application/scim+json
```

The token is shown **exactly once**, when it is minted. It is stored only as a SHA-256 digest,
so it cannot be recovered — if it is lost, revoke it and mint another.

<Warning>
  Use a **Live** credential for `/Users`. Workspace membership is not partitioned into Live and
  Test data spaces — a workspace has one set of people — so a Test credential is refused on
  `/Users` with a `403`. A Test credential does provision `/OAuthClients`, which *is*
  partitioned.
</Warning>

Revoking a credential takes effect on the connector's next request: the check is a single
indexed lookup evaluated against the database clock, not a cache.

## Content type

RFC 7644 §3.1 requires `application/scim+json`, and KnoxCall accepts it on requests and returns
it on responses. `application/json` is accepted on requests as well, so a connector that sends
the plain type is not broken by it.

## Supported resources

### `/Users`

A `User` is a **workspace membership plus a directory record**. The attributes your IdP asserts
live on the directory record, never on the person's global KnoxCall account — one workspace's
directory must not rename or re-address someone in another workspace.

| Attribute                                             | Notes                                                                                                |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `userName`                                            | Required, unique within the workspace, **case-insensitive**                                          |
| `name.givenName`, `name.familyName`, `name.formatted` | Separate fields, because Entra patches them one operation at a time                                  |
| `displayName`                                         | As this workspace's directory asserts it                                                             |
| `emails[type eq "work"]`                              | Held on the directory record; it never moves the KnoxCall login address                              |
| `active`                                              | **Derived**, not stored — true exactly while the person holds a live membership on an active account |
| `externalId`                                          | Your directory's own identifier; reconciliation matches on it                                        |

`active` being derived is the property that matters most in practice: if an administrator
removes someone from the workspace on the team page, your next `GET` reports `active: false`
without KnoxCall having to write anything.

**Provisioning a new person requires a verified domain.** SCIM grants membership without asking
the subject to accept an invitation, so KnoxCall applies the same admission rule as SAML
just-in-time provisioning: the address must sit within a domain this workspace has proven by
DNS. Verify the domain under Settings → Single sign-on first. Someone who is **already** a
member can be given a directory record without this.

New memberships are always granted at the `member` role. A SCIM connector cannot mint an
administrator.

#### Deprovisioning

Both forms end the membership immediately, and **neither erases personal data** — erasure is a
separate, subject-initiated process:

* `PATCH` with `active: false` — the membership is revoked and the directory record **stays**,
  so a later `GET` returns it with `active: false`. This is the shape Entra sends on a
  soft-delete.
* `DELETE` — the same revocation, plus a tombstone on the directory record, so a later `GET`
  returns `404`.

### `/OAuthClients`

A custom resource, `urn:knoxcall:scim:2.0:OAuthClient`, for provisioning machine credentials
alongside people. `clientName`, `allowedScopes`, `redirectUris`, `requirePkce`, `requireDpop`,
`stepUpScopes` and `active` are writable; `clientId`, `clientType` and `grantTypes` are not.
The `client_secret` of a confidential client is returned exactly once, on create.

Redirect URIs are validated on registration with the same rule the dashboard and the
Management API apply — a non-registrable URI is refused with a `400` rather than silently
dropped from the list.

## Filtering

`filter` implements the RFC 7644 §3.4.2.2 grammar, not a subset of it:

* Comparison operators `eq`, `ne`, `co`, `sw`, `ew`, `gt`, `ge`, `lt`, `le`, and the presence
  operator `pr`
* `and`, `or`, `not`, and parentheses
* Value filters on multi-valued attributes — `emails[type eq "work"]`
* URN-qualified attribute names

Attribute names are matched case-insensitively, as RFC 7643 §2.1 requires, except for the
identifiers `id`, `externalId` and `clientId`, which RFC 7643 §7 defines as case-exact.
`userName` deliberately folds case, so `userName eq "Ada@Example.com"` finds the record stored
as `ada@example.com`.

A filter KnoxCall cannot honour is answered `400` with `scimType: "invalidFilter"` — it is
never ignored, and you will never receive an unfiltered list in response to a filter we did not
understand.

Pagination is `startIndex` (1-based) and `count`, capped at the `filter.maxResults` value
`/ServiceProviderConfig` advertises.

## Attributes accepted but not stored

Entra's default user mapping sends attributes KnoxCall has no home for. Rejecting them would
refuse the **whole** `PatchOp` — including the `active: false` in the same request — so an
offboarding would silently not happen. KnoxCall therefore accepts these, names them in the
audit record, and stores none of them:

| Schema                                                       | Attributes                                                                          |
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `urn:ietf:params:scim:schemas:core:2.0:User`                 | `title`, `preferredLanguage`, `addresses[…]`, `phoneNumbers[…]`                     |
| `urn:ietf:params:scim:schemas:extension:enterprise:2.0:User` | `employeeNumber`, `costCenter`, `organization`, `division`, `department`, `manager` |

Anything outside that set — an unknown core attribute, a custom extension, a foreign schema URN
— is still refused with `scimType: "invalidPath"`, and the schema qualifier on a path is read,
so `urn:…:enterprise:2.0:User:active` does **not** deactivate anyone.

## Plan requirement

SCIM provisioning is an **Enterprise** feature.

**Discovery is never gated.** `/ServiceProviderConfig`, `/Schemas` and `/ResourceTypes` answer
on every plan, so a connector can always be configured and can always read this page's URL out
of the capability document.

**The resource endpoints are.** A workspace whose plan does not include SCIM receives `403` on
`/Users` and `/OAuthClients` with a SCIM Error object:

```json theme={"dark"}
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "403",
  "detail": "SCIM provisioning requires the Enterprise plan. …",
  "error": "scim_plan_required"
}
```

The `error` member is a KnoxCall extension, because RFC 7644 §3.12's `scimType` vocabulary is
closed and has no value meaning "your plan does not include this". A SCIM client ignores the
extra member; `schemas`, `status` and `detail` are exactly what the RFC specifies. There is no
`scimType` on this refusal — giving it a near-miss value would make a connector retry with a
different body forever.

### What happens if the subscription lapses

**Nothing stops.** A workspace whose Enterprise subscription goes `past_due`, enters dunning or
is cancelled **keeps being provisioned**. An identity provider does not read a `403` as "the
invoice is unpaid" — Entra marks the object in error, retries, and eventually quarantines the
whole provisioning job, and restarting a quarantined job re-runs a full reconciliation. Turning
a card decline into that would churn every member of the workspace at the moment the people who
could fix the card are the ones losing access.

What a lapse does stop is **issuing a new SCIM credential**. Minting requires a current
Enterprise plan; connectors that already hold one keep working. A workspace that deliberately
**downgrades** to a lower tier is a different case — that is a choice, not an accident of
billing — and its resource endpoints are refused as above.

Listing and revoking credentials is never gated. A workspace that has stopped paying must still
be able to see which connectors hold a credential and switch them off.

## Rate limits

An identity provider does not send traffic like a person. Entra reconciles in bursts, retries
aggressively, and treats a `429` without a `Retry-After` header as a hard failure rather than
as backpressure. KnoxCall's limits are built around that.

|                                |                                                                                                                                                                                    |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Unit**                       | Per workspace *and per credential type*, not per source address — your connector's budget is its own, and is never spent by administrators using the SCIM endpoints from a browser |
| **Budget**                     | 600 requests per 60 seconds, plus a 150-request burst — 750 in any 60-second window                                                                                                |
| **On exhaustion**              | `429`, always with `Retry-After` in seconds                                                                                                                                        |
| **If metering is unavailable** | `503`, also with `Retry-After` — a KnoxCall-side fault, never reported as `429`                                                                                                    |

The unit is the **workspace** deliberately. Entra's provisioning service egresses from a shared
Azure address pool, so two KnoxCall customers' reconciliations routinely arrive from the same
IP; a purely per-address budget would let one customer's initial sync throttle another's
connector. A separate per-address flood shield still sits in front of the mount, set above the
per-workspace budget so that one workspace can never consume it on its own.

A `429` body is a SCIM Error object like any other refusal:

```http theme={"dark"}
HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/scim+json

{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "429",
  "detail": "Too many SCIM requests for this workspace. …",
  "error": "scim_rate_limited"
}
```

**Honour `Retry-After`.** A provisioning cycle interrupted by a `429` can be resumed without
losing state — nothing was half-applied, because the request was shed before it reached a
handler. At 750 requests per minute a full first-time synchronisation of 10,000 users completes
in about fifteen minutes, and an incremental cycle never approaches the ceiling.

## Error codes

Alongside the RFC 7644 §3.12 members, KnoxCall refusals that the `scimType` vocabulary cannot
express carry an `error` member:

| `error`                         | Status | Meaning                                                                     |
| ------------------------------- | ------ | --------------------------------------------------------------------------- |
| `scim_plan_required`            | `403`  | The workspace's plan does not include SCIM provisioning                     |
| `scim_rate_limited`             | `429`  | The workspace's SCIM budget is spent; retry after `Retry-After`             |
| `scim_rate_limiter_unavailable` | `503`  | KnoxCall could not meter the request and did not serve it                   |
| `scim_users_live_only`          | `403`  | A Test credential was presented to `/Users`                                 |
| `operator_custody_blocked`      | `403`  | A KnoxCall operator is not permitted to act on this workspace's credentials |

A refusal never states whether some other workspace exists. Your credential binds the request
to one workspace, so there is nothing to disambiguate.

## Setting up Microsoft Entra ID

1. Mint a SCIM credential in KnoxCall (Settings → Single sign-on) and copy the `scim_…` token.
2. In Entra, open your enterprise application → **Provisioning** → **Automatic**.
3. **Tenant URL**: `https://<workspace>.knoxcall.com/scim/v2`
4. **Secret Token**: the `scim_…` token.
5. **Test Connection** — Entra fetches `/ServiceProviderConfig` and `/Schemas`.
6. Leave the default user attribute mapping as it is. The attributes KnoxCall does not store
   are accepted rather than refused, precisely so that the default mapping works unmodified.
7. Verify your email domain in KnoxCall before starting the first cycle, or every create for a
   new person is refused.

Okta is configured the same way: **Base URL** `https://<workspace>.knoxcall.com/scim/v2`,
OAuth Bearer Token, and *Import New Users and Profile Updates* + *Push New Users* +
*Push Profile Updates* + *Deactivate Users*.

## What KnoxCall does not implement

`/ServiceProviderConfig` is the authoritative answer, and it says so honestly:

* **Bulk** operations — not supported
* **Sorting** — not supported
* **ETags** — not supported
* **`changePassword`** — not supported, and never will be: KnoxCall does not hold member
  passwords
* **`/Groups`** — not yet exposed
