The line's SCIM — the directory's resources
Current for: directory 1.4.0 · Checked: 30.10.2026 · Written by hand (not generated).
The directory is a SCIM 2.0 implementation (RFC 7643/7644) with a fixed set of entities, and the one door to
the line's data for the services, the coordinator, the console and the clients. The doors «under a type»
(/control/policy/*, /control/legs*, /control/gal*, /control/authz*, /v1/service/{directory/feed, people/changes, …}, /v1/directory/*) are gone. A door of its own stays only where the directory itself acts:
sign-in (OIDC/SAML/LDAP, credentials/verify, sessions, keys), the schema (/control/schema*), replication, the
audit, /healthz, /control/about|health|maintenance, and /control/accounts|groups|objects|units for the console.
The root is https://dir.<domain>/scim/v2. The services' client library is shared/scim/client (Go).
Who may enter
| Who | With | May |
|---|---|---|
| the coordinator, an identity administrator, a SCIM token from the console, a replica | its own token | everything: reads, writes, the feed |
a service of the platform (svc:*) | its service token, aud identity | read every collection and the feed; …/{id}/secrets of its objects; a write is 403 |
| a server or a service as an object | its object token | GET …/{id}/secrets of itself alone |
| a person (platform token) | their own token | the reader's mode: GET /Users, /Users/{id}, /Users/{id}/photo, /Schemas, /ResourceTypes, /ServiceProviderConfig |
| an administrator by their grants | platform token | as the coordinator (admintoken.go) |
Writes everywhere: POST, PUT under If-Match (the version is meta.version, W/"<rev>"; a mismatch is 412),
PATCH (Users and Groups only), DELETE. An error is the urn:ietf:params:scim:api:messages:2.0:Error document
(status, scimType, detail).
Collections
| Collection | Object class | Schema | Types |
|---|---|---|---|
/Users | account | core User + enterprise + urn:ietf:params:scim:schemas:extension:simpletwo:2.0:User | — |
/Groups | group | core Group + urn:ietf:params:scim:schemas:extension:simpletwo:2.0:Group | — |
/Units | the organisation's tree (feed ?since= only) | urn:ietf:params:scim:schemas:simple:2.0:Unit | — |
/Servers | server | …simple:2.0:Server | open |
/Services | service | …simple:2.0:Service | open (secure_access.gateway, …) |
/Sites | site | …simple:2.0:Site | dc, office, geo, gateway_group |
/Policies | policy | …simple:2.0:Policy | the installation's policies, one per kind: credentials, sessions, guests, leavers, reviews, terms, app_links, app_passwords, username_retention, telephony, dialplan, legs, photos, branding; quota templates; secure_access.* profiles |
/AddressLists | address_list | …simple:2.0:AddressList | — |
/Authz | authz | …simple:2.0:Authz | grants (a server role's catalogue, catalogue:<role>), role, assignment (user:<uid>, group:<gid>) |
/Legs | leg | …simple:2.0:Leg | sip-endpoint, panel |
An object's attributes are at the top level of the document (name, type, unit, disabled, links, then
the class's attributes from the extension's schema). Links are links: [{rel, value, $ref}]: applies_to on a
policy (target root, unit:<id>, group:<id>), visible_to on an address list.
An object's subresources: GET|PUT|DELETE …/{id}/secrets/{key} (the values to the coordinator and the object
itself, the facts to an administrator), …/{id}/files/{name}, GET …/{id}/revisions.
The User extension (…:simpletwo:2.0:User)
Computed at the answer, read-only: kind (person | guest | bot | room | system), entity, guest, system,
assistant, source, sponsor/owner ({value, display}), expiresAt, extension, site, phone,
telPrimary, dial, formerUserNames, hideFromAddressLists, features (what the account is provisioned
for: messaging, calendar, mail, drive, telephony, secure_access), projected, addressLists (the lists it is in),
addressBook (the lists it reads), groupNames, roles, leaver (state, legalHold, responsible,
deletedAt), photoHash, addresses (the address index's keys: tel:…, email:…, sip:…). The people
catalogue's attributes (hire_date, …) ride there too, by their SCIM paths.
«A sees B» = addressBook(A) ∩ addressLists(B) ≠ ∅. «Whose is this address» = GET /Users?filter=…:addresses eq "<key>" (the keys are dirschema.AddressLookupKeys).
The Group extension (…:simpletwo:2.0:Group)
kind, source, handle, address, owner, owners, writers, defaultConvs; members[].display is the login.
The feed by cursor
Every collection answers ?since=<seq>[&count=<n>][&filter=…]: the rows written after that position of the one
counter, tombstones included (…:Replication: {seq, deleted}), in the order written. The answer carries
…simple:2.0:Replication:head (the last position) and :floor (the oldest tombstone still served; a cursor below
it reads from 0). /Users?since= also carries …:simpletwo:2.0:User:provisioning (on|off): a change of mode
is a read from 0. A service reads a deleted account by id too (GET /Users/{id}: the tombstone with leaver).
The answer also carries …:Replication:next: the store position the page was read up to, past the rows the feed did
not show (another class's on the objects' feed, an expired tombstone's) — a reader continues from it, not from the
last row shown. A feed is read in the words of the IETF SCIM drafts: ?deltaQuery=true the first time, ?deltaToken=<token>
after, the pages by ?cursor=<nextCursor> (RFC 9865); the last page carries nextDeltaToken, which the reader
keeps and sends back as it is — the token is opaque. A pass with nothing new is 304; a position the feed no
longer remembers (below its floor, or past its head after a restore) is 400 with scimType: expiredDeltaToken
and the reader starts again with deltaQuery=true; an unreadable token is 400 invalidValue; a deleted row
carries meta.isDeleted: true. /ServiceProviderConfig advertises pagination (cursor, index, the page
sizes, cursorTimeout) and deltaQuery (supported, deltaTokenExpiry). The old ?since=<seq> with
…:Replication:head, :floor and :next stays for the replicas and for one version of transition. A feed page carries an ETag (the head, the floor and the page's extras). A reader at the head sends it back as
If-None-Match and is answered 304 without a body: «nothing new» costs the directory the head, not a page. Feed
pages run two at a time on one instance: a third waits a second and is answered 503 with Retry-After: 2; any
request that has not answered in 8 s is answered 503 with Retry-After: 5 — before the reader gives up on its own
timeout. A reader is expected to ask once a minute, with jitter, and to double its pause after a refusal.
The computed resources (read-only)
| Resource | Filter | Answer |
|---|---|---|
/ResultantPolicies | account eq "…" [and device eq "…"] [and class eq "…"], or class eq "…" alone | the resultant set of policy per class: content, from, layers; for one class, the root's set (root/<class>) and every account's that differs from it |
/FreeNumbers | `kind eq "person | room |
The reader's mode (/Users under a person's token)
| Request | Answer |
|---|---|
GET /Users | the roster: everybody the reader may hold, with …simple:2.0:Reader on every row (the client's row: username = uid, login, display_name, groups, attrs, name_only, inactive, …) and on the list (cursor, coversOrg, phones) |
GET /Users?since=N&count= | the delta: the rows after the cursor; gone on those who left the reader's sight; tooLong — read the roster again |
GET /Users?filter=displayName co "text" / userName eq "…" / emails.value eq "…" | the people search over the reader's book; truncated |
GET /Users/{id} | one row, 404 outside the book |
GET /Users/{id}/photo | the photo (ETag = SHA-256) |
The change stream is the directory's GET /v1/events (hello {groups, contacts, directory}, directory.changed {cursor}).
Registering a platform's extension
PUT /control/schema/extensions/{platform} with {name, classes: [{Class, Types}], attrs: [ObjectDef…]} — the
coordinator registers the extension whole when it switches the platform on
(shared/dirschema.ExtensionByPlatform); the services register no schema.
Where what lives
| Data | Resource | Writer |
|---|---|---|
| the installation's policies (sign-in, sessions, guests, leavers, telephony, dial plan, app passwords, photos, branding…) | /Policies by type | the console, the coordinator |
| the numbering plan, the main number | /Policies type eq "telephony" (numbering, main_number) | the coordinator |
| the dial plan | /Policies type eq "dialplan" (plan, ETag) | the coordinator; messaging reads |
| the quotas | /Policies type eq "quota" (templates, shared_mailboxes), /ResultantPolicies?filter=class eq "quota" | the console; mail, messaging and the drive read |
| the roles and assignments | /Authz | the console, the coordinator; everybody reads through shared/auth.RBACPuller |
| the address lists | /AddressLists (visible_to) | the console |
| the rooms' devices | /Legs (credentials reconciled by the directory: a new entry is issued, one marked removed is removed) | the coordinator; the MCU reads /v1/legs/feed |
| the tunnel sessions | the coordinator's (left the directory) | — |