Partner provisioning
Provisioning lets a partner application create a MyCLM organization for each of its customers, then keep its structure and users in sync, using your own identifiers (externalId).
- GET
/v1/provisioning/orgsList the organizations of the application - POST
/v1/provisioning/orgsProvision an organization - GET
/v1/provisioning/orgs/{orgId}/nodesList the nodes of an organization - POST
/v1/provisioning/orgs/{orgId}/nodesProvision an entity or a group - POST
/v1/provisioning/orgs/{orgId}/membersProvision a member - PATCH
/v1/provisioning/memberships/{id}Update a provisioned membership - DELETE
/v1/provisioning/memberships/{id}Remove a provisioned membership - GET
/v1/provisioning/usersFind a user by external id
Onboard a customer
Section titled “Onboard a customer”-
Create the organization with its first entity and its owner.
Terminal window curl -s -X POST "$MLT_API_URL/v1/provisioning/orgs" \-H "Authorization: Bearer $MLT_TOKEN" -H "Content-Type: application/json" \-d '{"name": "Cabinet Bernard Immobilier","externalId": "agency-8841","firstEntity": { "name": "Agence Lyon 6e", "externalId": "agency-8841-lyon6" },"owner": {"email": "claire.bernard@cabinet-bernard.fr", "firstName": "Claire", "lastName": "Bernard","locale": "fr", "externalId": "user-1207", "sendInvitation": false}}'const response = await fetch(`${process.env.MLT_API_URL}/v1/provisioning/orgs`, {method: 'POST',headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },body: JSON.stringify({name: 'Cabinet Bernard Immobilier',externalId: 'agency-8841',firstEntity: { name: 'Agence Lyon 6e', externalId: 'agency-8841-lyon6' },owner: { email: 'claire.bernard@cabinet-bernard.fr', firstName: 'Claire', lastName: 'Bernard', locale: 'fr', externalId: 'user-1207', sendInvitation: false },}),});const { org, nodes, created } = await response.json(); // 201 created, 200 already existedresponse = requests.post(f"{api}/v1/provisioning/orgs", headers=headers, json={"name": "Cabinet Bernard Immobilier","externalId": "agency-8841","firstEntity": {"name": "Agence Lyon 6e", "externalId": "agency-8841-lyon6"},"owner": {"email": "claire.bernard@cabinet-bernard.fr", "firstName": "Claire", "lastName": "Bernard","locale": "fr", "externalId": "user-1207", "sendInvitation": False},})result = response.json() # response.status_code: 201 created, 200 already existed$response = $http->post("$api/v1/provisioning/orgs", ['headers' => $headers,'json' => ['name' => 'Cabinet Bernard Immobilier','externalId' => 'agency-8841','firstEntity' => ['name' => 'Agence Lyon 6e', 'externalId' => 'agency-8841-lyon6'],'owner' => ['email' => 'claire.bernard@cabinet-bernard.fr', 'firstName' => 'Claire', 'lastName' => 'Bernard','locale' => 'fr', 'externalId' => 'user-1207', 'sendInvitation' => false],],]);$result = json_decode((string) $response->getBody(), true); // getStatusCode(): 201 created, 200 already existedThe application automatically gets access to the organizations it creates (a
provisionedgrant with theadminrole, limited to its scopes). WithsendInvitation: false, the owner account is created without invitation email: use it with SSO or with your own onboarding email. -
Add teams (groups) under the entity, referenced by your ids:
POST /v1/provisioning/orgs/{orgId}/nodes{ "parentExternalId": "agency-8841-lyon6", "type": "group", "name": "Gestion locative", "externalId": "team-42" }Entities are created directly under the organization (
"type": "entity", no parent needed); groups underparentIdorparentExternalId. -
Add users to a node with a role (system role key
admin,manager,member,viewer, or a role id):POST /v1/provisioning/orgs/{orgId}/members{"email": "paul.martin@cabinet-bernard.fr", "firstName": "Paul", "lastName": "Martin", "locale": "fr","externalId": "user-1208", "nodeExternalId": "team-42", "role": "member", "sendInvitation": true}The account is created when needed. Applications cannot grant the
ownerrole (403 role_escalation).
Idempotency
Section titled “Idempotency”Every provisioning POST that carries an externalId is idempotent: when a resource with the same externalId already exists, it is returned with 200 and created: false instead of 201. Retry freely after a timeout, and run your sync jobs as “ensure” operations.
An externalId used by another resource of the same kind answers 409 external_id_taken.
Keep users in sync
Section titled “Keep users in sync”| Need | Call |
|---|---|
| Find a user by your id | GET /v1/provisioning/users?externalId=user-1208: the user and their memberships in your organizations. |
| Change role or node | PATCH /v1/provisioning/memberships/{id} with { "role": "manager" } or { "nodeId": "…" }. |
| Remove from a node | DELETE /v1/provisioning/memberships/{id} (the last owner cannot be removed: 409 last_owner). |
| List your organizations | GET /v1/provisioning/orgs, optionally ?externalId=agency-8841. |
| Resolve a node by your id | GET /v1/provisioning/orgs/{orgId}/nodes?externalId=team-42. |
Roles are bounded by the role of your application in the organization: never owner (by key or by id), never above the grant role, and memberships above it cannot be changed nor removed (403 role_escalation).
Then work for these users with delegated OAuth once they authorized your application, and receive the events of their organizations through the webhooks of your application.
Every provisioning write is recorded in the audit log of the organization, attributed to your application.