Summary and recommendation
Notion's REST API (base URL: `https://api.notion.com/v1`, stable version header `2022-06-28`) supports read operations on workspace users via `GET /v1/users` and `GET /v1/users/{user_id}`. Authentication uses either an internal integration Bearer token (`secret_*`) or OAuth 2.0 for public integrations.
There is no API endpoint to create, update, or delete workspace users - user lifecycle management requires SCIM (Enterprise only) or manual admin action.
The API enforces a rate limit of 3 requests per second per integration token. HTTP 429 responses include a `Retry-After` header; the `X-RateLimit-*` header family is not documented. Pagination is cursor-based via `start_cursor`; max page size is 100.
Always check `has_more` before terminating a pagination loop.
API quick reference
| Has user API | Yes |
| Auth method | Bearer token (internal integration secret) or OAuth 2.0 (public integrations) |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: Bearer token (internal integration secret) or OAuth 2.0 (public integrations)
Setup steps
- Go to https://www.notion.so/my-integrations and create a new integration.
- Copy the Internal Integration Secret (starts with 'secret_') for server-to-server use.
- For OAuth 2.0 public integrations, configure redirect URIs and use the authorization code flow at https://api.notion.com/v1/oauth/authorize.
- Share the target Notion workspace pages/databases with the integration to grant access.
- Pass the token in the Authorization header: 'Authorization: Bearer
' and set 'Notion-Version: 2022-06-28'.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| read_user | Read user objects (name, email, avatar) for workspace members. | List users, Retrieve user |
| read_content | Read pages and databases; indirectly needed to resolve user references in content. | Resolving user mentions in page content |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| object | string | Always 'user'. | system-set | immutable | |
| id | string (UUID) | Unique identifier for the user. | system-set | immutable | |
| type | string | 'person' or 'bot'. | system-set | immutable | Bot users represent integrations. |
| name | string | Display name of the user. | user-set | user-managed | May be null for bots without a name. |
| avatar_url | string (URL) | null | URL of the user's profile photo. | user-set | user-managed | Nullable. |
| person.email | string | Email address of a person-type user. | user-set | user-managed | Only present when the integration has user email capability enabled and the user has granted access. |
| bot.owner | object | Owner info for bot-type users; contains 'type' ('workspace' or 'user') and optionally a user object. | system-set | immutable | Only present on bot-type users. |
| bot.workspace_name | string | null | Workspace name for workspace-owned bots. | system-set | immutable | Only present on workspace-owned bots. |
Core endpoints
List all users
- Method: GET
- URL:
https://api.notion.com/v1/users - Watch out for: Requires the integration to be added to the workspace by an admin. Returns all workspace members visible to the integration.
Request example
GET /v1/users?page_size=100
Authorization: Bearer secret_xxx
Notion-Version: 2022-06-28
Response example
{
"object": "list",
"results": [{"object":"user","id":"uuid","type":"person","name":"Alice"}],
"next_cursor": "cursor_abc",
"has_more": true
}
Retrieve a user
- Method: GET
- URL:
https://api.notion.com/v1/users/{user_id} - Watch out for: Email is only returned if the integration has the 'Read user information including email addresses' capability enabled.
Request example
GET /v1/users/abc123
Authorization: Bearer secret_xxx
Notion-Version: 2022-06-28
Response example
{
"object": "user",
"id": "abc123",
"type": "person",
"name": "Alice",
"avatar_url": "https://...",
"person": {"email": "alice@example.com"}
}
Retrieve the bot (token owner) user
- Method: GET
- URL:
https://api.notion.com/v1/users/me - Watch out for: Returns the bot user associated with the integration token, not a human user.
Request example
GET /v1/users/me
Authorization: Bearer secret_xxx
Notion-Version: 2022-06-28
Response example
{
"object": "user",
"id": "bot-uuid",
"type": "bot",
"name": "My Integration",
"bot": {"owner":{"type":"workspace"},"workspace_name":"Acme"}
}
Search (find pages/databases, resolves user mentions)
- Method: POST
- URL:
https://api.notion.com/v1/search - Watch out for: Not a direct user search endpoint; user objects appear as nested references in page/database results.
Request example
POST /v1/search
Content-Type: application/json
{"query": "Alice", "filter": {"value":"page","property":"object"}}
Response example
{
"object": "list",
"results": [{"object":"page","id":"page-uuid","created_by":{"object":"user","id":"abc123"}}]
}
Rate limits, pagination, and events
- Rate limits: Notion enforces an average rate limit of 3 requests per second per integration token. Bursting above this threshold returns HTTP 429.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: On HTTP 429, Notion returns a 'Retry-After' header indicating seconds to wait. The header 'X-RateLimit-*' family is not documented; rely on 429 + Retry-After.
- Pagination method: cursor
- Default page size: 100
- Max page size: 100
- Pagination pointer: start_cursor
| Plan | Limit | Concurrent |
|---|---|---|
| All plans (per integration) | 3 requests/second average | 0 |
- Webhooks available: No
- Webhook notes: Notion does not offer native webhooks as of the current API version (2022-06-28). There is no event-push mechanism for user creation, deletion, or updates.
- Alternative event strategy: Poll GET /v1/users on a schedule to detect membership changes, or use SCIM provisioning events via your IdP (Okta, Entra ID) for user lifecycle events.
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://www.notion.so/scim/v2
Supported operations: GET /Users – list users, GET /Users/{id} – retrieve user, POST /Users – provision user, PUT /Users/{id} – replace user, PATCH /Users/{id} – update user attributes, DELETE /Users/{id} – deprovision user, GET /Groups – list groups, GET /Groups/{id} – retrieve group, POST /Groups – create group, PUT /Groups/{id} – replace group, PATCH /Groups/{id} – update group members, DELETE /Groups/{id} – delete group
Limitations:
- Requires Enterprise plan; not available on Free, Plus, or Business plans.
- SAML SSO must be configured before enabling SCIM.
- SCIM token is generated in workspace Settings > Identity & Provisioning; only workspace owners can generate it.
- Supported IdPs with documented setup: Okta, Microsoft Entra ID (Azure AD), OneLogin, Google Workspace.
- SCIM Groups map to Notion workspace groups; group membership changes are reflected in workspace permissions.
- Deprovisioning via SCIM removes workspace access but does not delete user-created content.
- Email address is used as the unique identifier for matching existing Notion accounts.
Common scenarios
For identity graph construction, GET /v1/users is the primary enumeration endpoint. Each person-type user object exposes id (UUID), name, and `person.
email- but email is only returned if the integration has the 'Read user information including email addresses' capability explicitly enabled in integration settings. Bot-type users appear in the same endpoint response and must be filtered bytype` field to avoid polluting identity records.
For automated provisioning and deprovisioning, the SCIM 2.0 API (https://www.notion.so/scim/v2) is the correct surface. It supports full user and group lifecycle: POST /Users, PATCH /Users/{id}, DELETE /Users/{id}, and group management via /Groups. SCIM Groups map directly to Notion workspace groups, making group-based access control automatable from an IdP. Supported IdPs with documented setup include Okta, Microsoft Entra ID, OneLogin, and Google Workspace.
For token introspection, GET /v1/users/me returns the bot user associated with the integration token. To resolve the authorizing human for OAuth integrations, inspect bot.owner.user - the top-level response is always the bot, not the human who created the integration.
Sync workspace member list to an external directory
- Authenticate with an internal integration token (Bearer secret_xxx).
- Call GET /v1/users?page_size=100 with Notion-Version: 2022-06-28.
- Iterate through 'results'; if 'has_more' is true, repeat with 'start_cursor' set to 'next_cursor' value.
- For each user of type 'person', store id, name, and person.email (requires email capability enabled).
- Schedule polling (e.g., every 15 minutes) since no webhooks are available.
Watch out for: Email is absent unless the integration's 'Read user information including email addresses' capability is enabled in the Notion integration settings.
Provision and deprovision users via SCIM (Enterprise)
- Ensure the workspace is on the Enterprise plan with SAML SSO configured.
- Navigate to Settings > Identity & Provisioning and generate a SCIM token.
- Configure your IdP (e.g., Okta) with base URL https://www.notion.so/scim/v2 and the SCIM token as Bearer auth.
- Map IdP user attributes to SCIM schema: userName (email), name.givenName, name.familyName, active.
- Assign users/groups in the IdP; provisioning events trigger POST /Users or PATCH /Users/{id} calls to Notion.
- Deprovisioning (removing from IdP) triggers DELETE /Users/{id}, revoking workspace access.
Watch out for: Deprovisioning removes access but does not delete content created by the user. Group membership via SCIM Groups maps to Notion workspace groups.
Identify the bot user associated with an integration token
- Call GET /v1/users/me with the integration's Bearer token.
- Inspect the returned 'bot.owner' field: type 'workspace' means a workspace-level integration; type 'user' means a user-authorized OAuth integration.
- Use the returned bot 'id' to filter out bot activity from audit logs or page-created-by fields.
Watch out for: GET /v1/users/me returns the bot user, not the human who created the integration. To get the authorizing human user for OAuth integrations, inspect 'bot.owner.user'.
Why building this yourself is a trap
The most significant architectural caveat: Notion has no native webhooks as of API version 2022-06-28. There is no event-push mechanism for user creation, deletion, or role changes.
Any identity graph that depends on real-time membership state must poll GET /v1/users on a schedule and diff results - or rely on IdP-side SCIM events for lifecycle signals, which are only available on Enterprise.
Internal integrations cannot self-discover workspace content; they must be manually added to pages or databases by a workspace member. This means an integration scoped for user enumeration still requires an admin to explicitly authorize it within the workspace.
Combined with the email capability gate, it is straightforward to build an integration that silently returns incomplete user records - person.email will be absent with no error, only an empty field, if the capability is not enabled.
Automate Notion workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.