Directory sync

SCIM 2.0 behind a bearer token, so a joiner takes a seat and a leaver gives one back with nobody opening the dashboard.

Where it is

The Directory sync card sits on the Team page. Off, it reads "Connect Entra or Okta so joiners get a seat and leavers lose one automatically." On, it reads "Directory sync is on. Joiners get a seat and leavers lose one automatically."

It is SCIM 2.0 with a long-lived bearer token. One endpoint serves every provider; nothing in it is Entra or Okta specific.

Turning it on

  1. 01

    Generate the token

    Press Generate token. It confirms with "It is shown once and stored hashed. Any token you generated before stops working immediately, so generate a new one only when you are setting sync up or rotating it."

  2. 02

    Copy both fields

    Endpoint and Token appear under the line "Copy the token now. It is stored hashed and cannot be shown again." Only a SHA-256 hash is kept, so a lost token is replaced, never recovered.

  3. 03

    Paste them into your provider

    In Entra these are the provisioning Tenant URL and Secret Token; in Okta, the SCIM connector base URL and the HTTP header bearer token. Test Connection reads /ServiceProviderConfig and passes with no further configuration.

  4. 04

    Assign people

    Assign the users or the group CADABRA covers, and run a cycle. The Members table and the Activity card show what landed.

The endpoint and the token. Copy the endpoint the dashboard prints rather than typing this.

https://accounts.cadabrai.com/scim/v2

Authorization: Bearer <org id>.<43 characters of base64url>
Content-Type: application/scim+json

What is implemented

Users only. There is no /Groups resource at any path.

RequestEffect
GET /ServiceProviderConfigCapabilities. patch true, filter true with maxResults 200, bulk false, sort false, etag false, changePassword false.
GET /ResourceTypesOne resource type, User.
GET /UsersEvery member of the organization. startIndex and count paginate; count defaults to 100 and is capped at 200.
GET /Users?filter=Only userName eq "value" is understood. Anything else is a 400 invalidFilter.
GET /Users/{id}One member. The id is the CADABRA account id, which is what POST returns.
POST /UsersAttaches an existing CADABRA account to the organization and consumes a seat. 201 with the user.
PATCH /Users/{id}Only active is acted on. active: false deprovisions. Every other operation is accepted and ignored.
PUT /Users/{id}Same rule: active: false deprovisions, the rest of the body is ignored.
DELETE /Users/{id}Deprovisions. 204, no body.

The only PATCH that changes anything

PATCH /scim/v2/Users/{id}
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{ "op": "replace", "path": "active", "value": false }]
}

What deprovisioning does

Setting active to false, or sending a DELETE, does exactly what Remove on the Team page does:

  • the account leaves the organization and its seat is freed
  • its plan is set to free, so Drawings Mode and the organization allowance stop
  • any admin flag is cleared
  • the activity log records it with the actor SCIM

The account itself is not deleted, and the person keeps it. Deleting an account is theirs to do, from Your account.

With single sign-on on, an address at a verified domain that has never signed in through your identity provider is joined to the organization again on its first sign-in there, and takes the seat back. An account that has already signed in that way returns without an organization. Remove a leaver at your identity provider as well, not only in CADABRA.

active on a read is not a stored field. It is true when that account is currently a member of the organization asking, and false otherwise.

What does not sync

  • Groups. There is no /Groups endpoint. Assigning a group in your provider works only insofar as the provider expands it into individual users.
  • Roles. Admin and owner are set in the dashboard by the owner. No claim, group or SCIM attribute grants either.
  • Names and email addresses. They are read from the CADABRA account and returned; a write is ignored. Nothing your directory sends renames or re-addresses an account.
  • Passwords. changePassword is false. Credentials stay in your identity provider.
  • Bulk, sort and ETag. All three report false, so a provider that expects them falls back to per-user calls.
  • Everything except active. A PATCH or PUT that changes any other attribute succeeds and changes nothing.

The refusals, and what to do

  • 401 "Invalid token." The token was rotated or revoked. Generate a new one and paste it into your provider.
  • 507 "No seats available. Add seats before provisioning more people." Provisioning stops at the seat cap. Buy seats, then run the cycle again.
  • 409 "User already provisioned." Already a member. Harmless, and providers treat it as a match.
  • 409 "That user belongs to another organization." An account is in one organization at a time.
  • 403 "The organization owner cannot be deprovisioned through SCIM." Deliberate: it stops a directory rule from locking your organization out of itself. Transfer ownership in the dashboard first.
  • 404 "User not found." The id is unknown, or belongs to an account outside your organization.

Every error body is a urn:ietf:params:scim:api:messages:2.0:Error carrying status and detail, and responses are Content-Type: application/scim+json.

Rotating and turning it off

Once sync is on the button reads Rotate token. A new token invalidates the previous one the moment it is issued, so paste the replacement into your provider in the same sitting.

Turn off confirms with "The token stops working immediately, and your identity provider can no longer add or remove members." Members already provisioned keep their seats; the directory stops driving the list.

Both actions are written to the activity log as "changed the SCIM token", with the detail issued or revoked.