---
title: "How directory sync works"
description: "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."
url: "https://nextmail.sh/docs/api/directory-sync/overview"
area: "API"
category: "Roles & access"
---

# 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 |
| --- | --- |
| `member` | They joined the workspace. |
| `invited` | An invitation waits for them to accept it. |
| `waiting` | No invitation could be sent yet, such as for a person with no email address. They are invited as soon as one can be. |
| `inactive` | The 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

The same six calls are the `members` methods of the SDKs, from `getDirectorySync` to `setDirectoryGroupRole`, the `members` commands of the CLI, and six MCP tools under Workspace and people, which ask in a chat before they change anything.

- [REST API](https://nextmail.sh/docs/api/reference/directory-sync.md): Every directory sync operation, with what each takes and returns.
- [TypeScript SDK](https://nextmail.sh/docs/sdk/reference/members.md): The methods of `members`, the directory sync ones among them.
- [CLI](https://nextmail.sh/docs/cli/reference/members.md): The `members` commands.
- [MCP tools](https://nextmail.sh/docs/mcp/tools/workspace.md): The workspace and people tools an agent can call.
- [Setting it up in the app](https://nextmail.sh/docs/knowledge/team/invites.md): Connecting Okta or Microsoft Entra, what the directory sends, and what happens when somebody leaves.
