Skip to main content
Wildo.ai Coming soon

Accounts and invitations

Keep accounts in step with the company directory

Let a customer’s directory provision people into its organization and manage their membership there. Wildo connects those changes to local accounts and configured organization-unit assignments.

A directory sends additions, updates and deactivation through SCIM to Acme's members.

Keep accounts in step with the company directory

Let a customer’s directory provision people into its organization and manage their membership there. Wildo connects those changes to local accounts and configured organization-unit assignments.

The directory credential determines the organization. You choose the provisioning rules, including whether directory deactivation withdraws access.

Example — Handle a departure through the directory

When an employee leaves, the directory sends the account update. With deactivation enabled, the application withdraws that organization membership and its dependent grants. The person’s global account and memberships in other customers are not disabled.

For engineers

Enable availability, then configure the customer

Wonder Todos declares scimProvisioning: { enabled: true } under organizationTypes.workspace in backend-api/src/saas-config.backend.ts. That makes the route available for the type; it does not mean every organization already has an active directory connection.

The organization has its own provisioning configuration and bearer token. The endpoint path is exposed as scimEndpointUrl, derived from the actual API base rather than stored as tenant data. Combine it with the deployed API host when configuring the directory.

Save the customer’s provisioning policy

Keep scimProvisioning: { enabled: true } alongside the existing organization type’s membership and creation rules. The backend mounts the directory routes when at least one type enables them, and checks the token’s organization type again on incoming requests. Also enable CoreFeature.DIRECTORY_PROVISIONING for the target organization through the application’s feature configuration; the type-level switch does not grant that customer feature.

In an application-owned, organization-admin-authorized backend operation, use the registered configuration service. This helper receives the backend container and the caller’s execution context from that operation; organizationId is its authorized target organization:

import {
  AuthOrganizationConfigurationService,
  SAAS_SERVICE_TYPES,
  type ExecutionContext,
  type InversifyContainer,
} from '@wildo-ai/saas-backend-lib';
import { AuthScopeType, CORE_ORG_ROLES } from '@wildo-ai/saas-models';

async function configureDirectory(
  container: InversifyContainer,
  executionContext: ExecutionContext<any>,
  organizationId: string,
): Promise<void> {
  const configuration = container.get<AuthOrganizationConfigurationService>(SAAS_SERVICE_TYPES.AuthOrganizationConfigurationService);
  await configuration.saveScimProvisioningConfig(
    executionContext,
    AuthScopeType.ORGANIZATION,
    organizationId,
    { autoCreateUsers: true, autoVerifyEmail: true, autoDeactivateUsers: true, defaultRole: CORE_ORG_ROLES.ORG_MEMBER },
  );
}

The service is already registered by the engine. It creates or updates the scope’s configuration through the services registry with the caller’s context, preserving authorization and audit attribution. This is a backend integration helper, not a new HTTP route: the configuration resource’s create/update operations are internal. Keep the administration action in your application’s declared operation and access policy.

Here, directory assertions may create accounts, verify their email and withdraw membership on deactivation. Choose those switches deliberately. The save service rejects the core owner/admin roles as defaultRole; custom roles still need an application-level review of the authority they grant.

Issue the directory its own credential

An organization administrator invokes ScimTokens_Operations.GENERATE_TOKEN on CoreResourceType.ORGANIZATION_SCIM_TOKENS. The registered API operation borrows CREATE, accepts displayName and an optional ISO expiresAt, and returns a persisted record with a one-time token. With BACKEND_URL set to the application’s API base (including its API prefix), invoke the organization-scoped operation with an administrator session. The directory credential itself is not the administrator’s session token:

curl --fail-with-body "$BACKEND_URL/organizations/$ORGANIZATION_ID/organization-scim-tokens/generate-token" \
  -H "Authorization: Bearer $ORG_ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"displayName":"Company directory"}'

Add expiresAt with a future ISO timestamp when the deployment requires an expiry. Transfer the returned token to the directory’s secret configuration and retain the record ID for lifecycle management. Only its hash is stored. Rotation returns newToken and replaces the hash immediately; there is no old-token grace window. Revocation makes the record inactive and keeps it for audit.

Send a directory user and inspect the result

Set SCIM_BASE_URL to the deployed API host plus the configuration’s scimEndpointUrl, and SCIM_TOKEN to the one-time secret above. Use an email accepted by the customer’s verified-domain policy and a user type eligible to join that organization. This example uses a disposable account in your own test organization:

curl --fail-with-body "$SCIM_BASE_URL/Users" \
  -H "Authorization: Bearer $SCIM_TOKEN" \
  -H 'Content-Type: application/scim+json' \
  --data-binary @directory-user.json

The directory-user.json payload supplies both the directory identifier and the profile fields used by the default mapping:

{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "externalId": "directory-person-001",
  "userName": "person@your-verified-domain.example",
  "name": { "givenName": "Alex", "familyName": "Morgan" },
  "emails": [{ "value": "person@your-verified-domain.example", "primary": true }],
  "active": true
}

A successful create returns HTTP 201 with the SCIM user ID and a Location header. Read Users/<returned-id> with the same credential and inspect the organization membership in your application. The token selects the organization; adding another organization ID to this payload does not change the target. Existing accounts can be linked rather than duplicated. A directory push is not an SSO login and does not create a browser session.

Choose what incoming changes may do

Selected current fields from scim-provisioning-config.shared.schemas.ts:

autoCreateUsers: z.boolean().default(true).isSummaryField().isAuditEvidence(),
autoVerifyEmail: z.boolean().default(true),
autoDeactivateUsers: z.boolean().default(true).isAuditEvidence(),
defaultRole: z.string().optional().isAuditEvidence(),
attributeMapping: ScimAttributeMappingSchema.optional(),

defaultRole is an organization-wide role grant, so configuring it is an access decision. Unit assignments use separate organizationUnitMapping and defaultUnitRole fields. When both are configured, directory synchronization becomes authoritative over those assignments: a push replaces the mapped set, including removal of manual assignments. Leave that mapping absent when the application should own them manually.

Let the credential determine the tenant

The middleware hashes the presented bearer token, verifies active/expiry state and takes organizationId from its stored record. The caller cannot choose another organization in the body. Rate limits are per token, keeping one directory’s traffic from using another credential’s budget.

Match the supported provisioning surface

The controller exposes Users create/read/list/replace/patch/delete and bounded bulk operations. Ordinary and bulk writes share lifecycle and unit-update handlers: replacement synchronizes the full mapped unit state; PATCH changes only the supplied fields. Delete performs membership deactivation through provisioning policy.

The active response and filter describe membership state in the addressed organization. Reactivation uses current acquisition and default-role policy rather than restoring historical privileges, and cannot unlock a globally disabled account. Configuration read failures refuse mutation instead of substituting permissive defaults. Groups, sorting and entity tags are not supplied by this surface; configure the directory accordingly. Local first-factor credentials are neutralized for actively directory-managed users so directory removal cannot be bypassed by an old local sign-in method. SSO and provisioning share account creation services but remain distinct connections and responsibilities.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.