Skip to the documentation
API

How directory sync works

Directory sync lets a company directory, such as Okta or Microsoft Entra, add people to the workspace when they join the company and take them out when they leave. The directory talks to Nextmail over SCIM 2.0. The API holds the settings the owner sees on the Privacy tab: it turns the connection on and off, chooses the roles it gives, and lists the people it synced.

The SCIM connection

The directory calls the SCIM endpoints under https://api.nextmail.sh/scim/v2 itself, with a bearer token that belongs to the workspace. POST /directory-sync turns directory sync on and returns that token in token, with status, where directory sync stands now. Give the directory status.baseUrl and the token.

POST /directory-sync
curl -X POST "$API/directory-sync" -H "$AUTH" {  "object": "directory_sync_token",  "token": "nm_scim_…",  "status": {    "object": "directory_sync",    "connected": true,    "available": true,    "baseUrl": "https://api.nextmail.sh/scim/v2",    "tokenLast4": "q7Xe",    "tokenRotatedAt": "2026-10-25T09:00:00.000Z",    "lastSeenAt": null,    "connectedAt": "2026-10-25T09:00:00.000Z",    "defaultRoleId": null,    "users": { "total": 0, "members": 0, "invited": 0, "waiting": 0, "inactive": 0 },    "groups": []  }}
  • The token is in that answer only and can never be read again, so store it in the directory straight away. Every read after that shows its last four characters in tokenLast4.
  • Calling POST /directory-sync again while directory sync is on makes a new token, and the old one stops working at once.
  • GET /directory-sync reads where it stands: whether it is on, whether the plan includes it in available, when the token was made, when the directory last called with it in lastSeenAt, to the minute, how many people it synced in users, and the groups it synced with the role each gives.
  • DELETE /directory-sync turns it off. Its token stops working at once and the people and groups it synced are forgotten, while everyone who already joined stays a member. disconnected is false when it was already off.
  • Directory sync comes with the Business and Enterprise plans. Turning it on with any other plan is a 403 plan_required, and while the workspace is on another plan the directory itself is refused, so nobody is added or removed.

Default and group roles

Each person the directory sends is matched on their email address. Somebody who is already a member is linked to their entry. Anybody else is invited by email with a role and no addresses, and joins once they accept. The role comes from their groups, or else from the default role.

  • PUT /directory-sync/default-role with { "roleId": "…" } chooses the default role, the one a person gets when none of their groups gives one. null goes back to the built-in Member role.
  • PUT /directory-sync/groups/{groupId}/role with a roleId makes a group give that role, and moves everyone in it to that role now. null stops the group giving a role, so its people fall back to the default role. groupId comes from groups on the read.
  • Somebody in several groups that give a role gets the one with the most permissions. A directory never gives the owner role.
  • Both answer with where directory sync stands now, in the shape of the read.
  • The owner role cannot be given this way (422 invalid_parameter), a role that is not in the workspace is a 422 role_not_found, and anybody but the owner can give only a role that holds nothing they lack themselves (403 insufficient_authority). A group that is not in the workspace is a 404, and both answer 409 directory_sync_not_connected while directory sync is off.

The people list

GET /directory-sync/people lists the people the directory synced, in the order it sent them. Each has id, userName, email, name, the names of their groups, updatedAt, when the directory last changed them, and state, where they stand.

`state`What it means
memberThey joined the workspace.
invitedAn invitation waits for them to accept it.
waitingNo invitation could be sent yet, such as for a person with no email address. They are invited as soon as one can be.
inactiveThe directory switched them off, and they are out of the workspace.
  • Pages are counted from 1, as SCIM pages are. count takes up to 100 and defaults to 100, and the answer carries total and startIndex. For the next page, pass startIndex plus the number of people returned, while that is at most total.
  • users on the read counts the same people by state, so the totals need no paging.

Who can read and change it

  • Reading needs members:read, and every change needs members:write.
  • Only the workspace owner, or a member whose role holds workspace:manage, may call these: an access token acting for anybody else is refused with 403 insufficient_authority. That permission stays in the console and is never given to an app.
  • A key limited to particular addresses or domains is refused every change with 422 capability_unsupported, because the people it changes reach beyond them.
  • An OAuth access token needs a verification code to turn directory sync on or off and to choose a role. Until the app has verified one in the last 60 minutes, the call answers 403 step_up_required and changes nothing. An API key is never asked. The Authentication page shows how to ask for a code and verify it.
  • Turning it on or off, a new token and every role change are written to the workspace security log with who made them, and what the directory itself does is logged as By directory sync.

From code, the terminal and agents