Start a SCIM connector
Building a connector, step 7 (SCIM track, 1 of 3). Previous: Choose a kind.
A SCIM connector is one file, definition.json, that describes a vendor's SCIM 2.0 endpoint as the
standard's defaults plus that vendor's documented deviations. A server that follows the standard needs
little more than a base URL and an authentication option. Most of the work is finding what the vendor
does differently and writing each difference down, backed by the vendor's documentation.
Owlie derives the connector's full declaration from the definition, and the SCIM engine built into
Owlie performs every operation: it lists and reads users and groups, creates, updates, enables,
disables and deletes users, creates, updates and deletes groups where you allow it, and adds and removes
group members. The connector runs in Owlie's cloud. An install can run it on a
private-network gateway instead when the endpoint is only
reachable inside a network. The gateway's operator then adds the connector's id to the gateway's
allowedCustomConnectors and lists the SCIM host in the connector's egressAllowlist
(Settings custom connectors depend on).
When SCIM is the right kind
Choose SCIM when the vendor's SCIM endpoint covers everything worth governing in the system: users, groups and group membership. SCIM has no vocabulary for roles, workspaces, projects or licences, unless the vendor maps them onto groups. Snowflake does: its SCIM groups are Snowflake roles, so Owlie's Snowflake connector is a SCIM connector.
Choose HTTP instead when:
- access you need to govern lives outside SCIM groups, such as roles scoped to a project, or a licence;
- the vendor creates accounts only by invitation, or only through single sign-on. A SCIM connector always creates accounts directly;
- the SCIM endpoint cannot filter users by
userName. The engine looks accounts up that way; - the vendor's SCIM endpoint is partial and its REST API covers the rest. That is one HTTP connector, which can call the SCIM endpoints from its handlers where they help.
Read the vendor's SCIM documentation first
Many SCIM implementations support only part of the standard. Before you write anything, find the answers to these questions in the vendor's SCIM documentation. Each answer becomes a setting on the next page, or confirms that the default is right.
| Question | Where the answer goes |
|---|---|
| Is the base URL fixed, or does it contain a tenant, region or account id? | baseUrl and config.properties (this page) |
| Which authentication does the SCIM endpoint accept? | auth (this page) |
| Does it support groups at all? Can Owlie create, rename or delete them? | groups.enabled, groups.manage |
| How does a list call page, and what is the largest page size? | pagination |
Which attributes may a filter use (at least userName)? Is there a filter on meta.lastModified? | filtering |
Does it accept PATCH? PATCH with a filtered path such as emails[type eq "work"].value? | patch |
| Does a group read return its members? How does a PATCH add a member? | groups.membership |
Does it accept request bodies as application/scim+json, or only application/json? | http.contentType |
| Which status does it answer for a duplicate, a missing resource and a rate limit? | errors |
| Which user attributes does it store, and which are required on create? | users.fields (Map fields) |
Keep the link to the documentation page behind each answer in your connector's README.md, and list
anything the documentation does not say as an assumption to confirm on a test tenant.
Create the connector
The kind is chosen when the connector is created and cannot change later.
In the Connector Builder
- Go to Admin > Integrations > Connectors, open the Custom tab and select New connector.
- In the New Connector dialog, keep Start from on Blank and choose SCIM as the Kind.
- Enter a Name and a Key (lowercase letters, digits,
-and_, starting with a letter or digit), and optionally a Description. Select Create connector.
The SCIM editor opens with a starter definition: a standards-compliant server at a base URL each install enters, bearer-token authentication, and the default user and group fields written out so you can see every row. Its tabs are Basics, Connection, Users, Groups, Protocol, Errors, Install values, Handlers, Derived manifest, Raw JSON and Files. The builder saves as you type. Derived manifest shows the declaration Owlie derives from your definition as you edit it.
Through the API
Create the connector with createCustomConnector, passing kind: "scim" with the key and name.
Its draft starts from the same starter definition. Write your definition.json into the draft's
files with updateCustomConnectorDraft; Owlie derives the declaration from the saved definition, so
you never write a manifest for a SCIM connector. Build with the API and agents
lists the operations and their inputs.
The definition
This skeleton shows every top-level field. Only engine and baseUrl are required; every other
section has a default that follows the SCIM standard, and you add it only when the vendor deviates.
{
"engine": 1,
"description": "Kestrel Desk agents and teams over SCIM, synced and provisioned.",
"icon": "@kestreldesk.example",
"config": {
"properties": {
"workspaceId": { "type": "string", "minLength": 1, "title": "Workspace ID" }
}
},
"baseUrl": { "$oxl": "'https://api.kestreldesk.example/scim/v2/workspaces/' + encode(config.workspaceId)" },
"auth": { "bearer": {} },
"http": {},
"pagination": {},
"filtering": {},
"patch": {},
"users": {},
"groups": {},
"errors": {}
}
| Field | What it is |
|---|---|
engine | Always 1, the version of the SCIM definition format. |
description, icon | What the catalog shows. icon is @ plus the vendor's domain, or an image URL. |
config.properties | The fields an install fills in, as JSON Schema: the values the base URL reads, and the seeds of install-editable values. |
baseUrl | Required. The SCIM base URL: a literal URL, or an expression over the install's configuration. |
auth | The authentication options an install may choose. |
http, pagination, filtering, patch, errors | Protocol deviations (next page). |
users, groups | The Users and Groups resources: endpoints, ids, membership and field lists (next page and Map fields). |
A key the format does not define is refused, so a misspelled setting fails instead of silently falling back to a default. Every field is in the SCIM definition reference.
The base URL
baseUrl is either a literal https URL or an expression over the install's configuration.
- A fixed URL for a vendor with one endpoint for every customer:
"baseUrl": "https://scim.kestreldesk.example/v2". - An expression for a URL that differs per install:
{ "$oxl": "config.baseUrl" }when the vendor gives each customer a full SCIM URL to paste in, or an expression that builds the URL from parts.
An expression is OXL, Owlie's expression language, and reads only config: the install's
configuration values, never its credentials. It can join strings with + and choose with
condition ? a : b. It has one extra function, encode, which URL-encodes a value placed in the
path. Owlie's own SCIM connectors use expressions like these:
'https://' + config.accountDomain + '/api/2.1/accounts/' + encode(config.accountId) + '/scim/v2'
'https://' + config.apiHost + '/scim/v2/enterprises/' + encode(config.enterpriseSlug)
'https://' + config.accountIdentifier + '.snowflakecomputing.com/scim/v2' + (config.integrationUuid ? '/' + encode(config.integrationUuid) : '')
Put every configuration value that lands in the path through encode. Configuration is entered by
whoever installs the connector, so Owlie checks the URL after evaluating it and refuses it unless:
- it is an absolute
httpsURL; - its host is a DNS name with at least one dot, not an IP address;
- it has no user name or password, no query string (
?) and no fragment (#); - its path has no
.or..segments and no encoded slashes (%2F,%5C) or backslashes.
encode turns a space, ?, # or / in a value into its percent-encoded form, so the URL keeps its
shape, and an encoded / is refused by the check above. A value placed in the path without encode
is not protected: a/b silently adds a path segment, and only a whole . or .. segment is refused.
That is why every path value must go through encode. Owlie removes trailing slashes from the result.
encode does not protect a value placed in the host. A host built from configuration, such as a region
or an account domain, goes wherever the install's value points, including outside the vendor's domain.
Constrain every value that lands in the host in its config schema, with an enum of the vendor's
regions or a pattern, so an install cannot send its credentials to another host.
Owlie evaluates the base URL when an install's configuration is saved, as soon as every value the expression reads has been entered, and refuses a save that gives no valid URL. It evaluates it again for every operation.
You do not list egress domains for a SCIM connector. Owlie allows the connector to reach the host of each install's evaluated base URL, and the host of an OAuth token URL the install enters, so an install whose configuration moves the host moves what the connector may reach with it.
In the Connector Builder
On the Connection tab, under Base URL, choose A URL or An expression over config for Base URL is, then fill in URL or Expression.
Install configuration
config.properties declares the fields an install fills in, one JSON Schema per field. Give every
field a title and a description that says where the admin finds the value in the vendor's console,
and a default where a sensible one exists:
{
"config": {
"properties": {
"accountDomain": {
"type": "string",
"minLength": 1,
"title": "Account domain",
"description": "For example accounts.kestreldesk.example.",
"default": "accounts.kestreldesk.example"
},
"accountId": { "type": "string", "minLength": 1, "title": "Account ID" }
}
}
}
Every declared field is required unless its default is "" or null: an empty default marks an
optional value, such as an identifier only some customers have. The same properties hold the seeds of
install-editable field lists (Map fields).
In the Connector Builder, declare these on the Install values tab, in the Install values table
(Name, Type, Default, Title, Description). Add value adds a string field; set
any other schema keyword, such as minLength or another type, in Raw JSON.
Authentication
auth lists the authentication options an install may choose. Allow only the options the vendor's
SCIM documentation describes. If you leave auth out, all four options are allowed, which offers
admins choices that cannot work; an empty auth object is refused.
| Option | The install enters | Owlie sends |
|---|---|---|
bearer | A bearer token | Authorization: Bearer <token> |
basic | A username and password | HTTP Basic authentication |
oauth2-client-credentials | A token URL, client ID and client secret, and optionally a scope and audience | A token from the token URL, requested with the client credentials, as Authorization: Bearer <access token>; a new token when it expires |
custom-header | A header name and a header value | The value in that header |
Every secret an install enters is stored as a secret, never in the connector.
A definition can fix the values a vendor dictates, and Owlie then leaves them out of the install form:
{
"auth": {
"oauth2-client-credentials": { "tokenUrl": "https://login.kestreldesk.example/oauth/token", "scope": "scim" },
"custom-header": { "headerName": "X-Api-Key" }
}
}
oauth2-client-credentialsacceptstokenUrl(an absolutehttpsURL),scopeandaudience. The install still enters the client ID and secret, and any value you did not fix.custom-headeracceptsheaderName. With it fixed, the install enters only the header value.Authorization,Content-Type,Content-Length,HostandCookiecannot be used, because Owlie sets them.bearerandbasictake no settings:{}.
In the Connector Builder, the Connection tab's Authentication section has a checkbox per option (Bearer token, Basic, OAuth2 client credentials, Custom header). Checking OAuth2 client credentials shows Token URL, Scope and Audience; checking Custom header shows Header name. Leave a field blank to let the install enter it.