SCIM 2.0 Provisioning

Tetrapus exposes a full SCIM 2.0 server. Point Okta SCIM, Azure AD provisioning, OneLogin, or any RFC 7644 client at it and Users + Groups appear in Tetrapus automatically. Deactivations propagate. Group-based role assignments propagate. No manual user creation, no SAML JIT — the directory is the source of truth.

What gets provisioned

graph LR OKTA["Okta / Azure AD\nGoverning Directory"] -->|SCIM POST /Users| DM["Tetrapus /scim/v2"] OKTA -->|SCIM POST /Groups| DM DM --> USERS["users table"] DM --> GROUPS["groups table"] DM --> MEMB["group_members table"] USERS --> RBAC["RBAC role assignment"] GROUPS --> RBAC MEMB --> RBAC RBAC --> ENFORCE["AuthContext on every request"]

Endpoint surface

All routes live under /api/v1/scim/v2/. Discovery endpoints are unauthenticated; resource endpoints require a per-Org bearer token from the scim_clients table.

MethodPathPurpose
GET/ServiceProviderConfigCapability advertisement
GET/ResourceTypesAvailable resource types
GET/SchemasJSON schemas for User and Group
GET/Users?filter=…&startIndex=1&count=100List with filter + pagination
POST/UsersCreate user
GET/Users/{id}Read single user
PUT/Users/{id}Full replace
PATCH/Users/{id}RFC 7644 patch ops
DELETE/Users/{id}Deactivate (soft)
/Groups, /Groups/{id}Same five verbs as /Users

Filter parser

Compliant with RFC 7644 §3.4.2. Supported operators:

eq
equals
ne
not equal
co
contains
sw
starts with
ew
ends with
gt
greater than
ge
lt
less than
le
pr
present
and
logical AND
or
logical OR
not
negation
Bash
# Find users with email ending @acme.com who are active
curl -G https://tetrapus.example.com/api/v1/scim/v2/Users \
  -H "Authorization: Bearer $SCIM_TOKEN" \
  --data-urlencode 'filter=userName ew "@acme.com" and active eq true'

scim_clients table

Each Org generates one or more SCIM bearer tokens. The token hash is stored; the plaintext is shown once at creation and must be pasted into the IdP's SCIM provisioning configuration.

SQL
CREATE TABLE scim_clients (
    id              TEXT PRIMARY KEY,
    org_id          TEXT NOT NULL REFERENCES orgs(id),
    name            TEXT NOT NULL,            -- "Okta SCIM"
    token_hash      TEXT NOT NULL,            -- argon2id(token)
    token_prefix    TEXT NOT NULL,            -- first 8 chars for display
    enabled         INTEGER NOT NULL DEFAULT 1,
    last_used_at    TEXT,
    created_at      TEXT NOT NULL,
    UNIQUE (org_id, name)
);

Example: create a user

Bash
curl -X POST https://tetrapus.example.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": "ada.lovelace@acme.com",
    "name":     { "givenName": "Ada", "familyName": "Lovelace" },
    "emails":   [{ "value": "ada.lovelace@acme.com", "primary": true }],
    "active":   true
  }'

Group-driven role assignment

Tetrapus maps SCIM Groups to internal Groups one-to-one by displayName. Permissions and roles attached to a Group flow to all members automatically — pushing a user into the tetrapus-operators group in Okta is enough to grant the operator role inside Tetrapus on the next sync. No manual mapping table needed.

Status

Shipped: all 13 filter operators above, full /Users + /Groups CRUD, RFC 7644 PATCH op processing (add / remove / replace), pagination, and ETags. Deferred: SCIM bulk operations (/Bulk), the SCIM /Me endpoint, and complex multi-valued attribute filters with sub-attributes (e.g. emails[type eq "work"].value co "@acme" — the flattened form works).

Related

Questions?

Reach out for help with integration, deployment, or custom domain codecs.