Summary and recommendation
The Zendesk REST API (`https://{subdomain}.zendesk.com/api/v2`) supports full user lifecycle management via Basic Auth (email/token) or OAuth 2.0 Bearer Token. Granular OAuth scopes (`users:read`, `users:write`) are preferred over broad `read`/`write` scopes for production integrations.
Rate limits are enforced per subdomain - not per API key - so all integrations on an account share a single quota (200–700 req/min depending on plan tier), and HTTP 429 responses include a `Retry-After` header.
For identity graph construction, the `external_id` field is the correct anchor for mapping Zendesk user records to upstream HR or IdP identities. It must be unique per account; duplicate values return a 422.
Pair `external_id` with `last_login_at` (returned on `GET /api/v2/users`) to build an accurate, queryable picture of agent activity across your identity graph without relying on the admin UI's limited reporting.
API quick reference
| Has user API | Yes |
| Auth method | Basic Auth (email/token or email/password) or OAuth 2.0 Bearer Token |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise (Suite Enterprise or Support Enterprise). Note: Native SCIM is not available on lower tiers. Professional-tier accounts can use IdP API connector integrations (e.g., Okta, Entra ID) as a workaround. |
Authentication
Auth method: Basic Auth (email/token or email/password) or OAuth 2.0 Bearer Token
Setup steps
- Basic Auth via API token: Enable API token access in Admin Center → Apps and Integrations → APIs → Zendesk API. Generate a token. Encode 'agent@example.com/token:{api_token}' in Base64 and pass as Authorization: Basic {encoded}.
- OAuth 2.0: Register an OAuth client in Admin Center → Apps and Integrations → APIs → OAuth Clients. Implement the authorization code flow to obtain a Bearer token. Pass as Authorization: Bearer {access_token}.
- Password auth (not recommended for production): Use email:password encoded as Basic Auth. Requires password auth to be enabled in Admin Center.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| read | Read access to all resources including users. | GET user endpoints |
| write | Create and update access to resources including users. | POST and PATCH user endpoints |
| users:read | Granular read scope for user resources. | GET user endpoints when using granular OAuth scopes |
| users:write | Granular write scope for user resources. | POST, PATCH, DELETE user endpoints when using granular OAuth scopes |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique Zendesk user ID. | auto-assigned | immutable | Use this ID for all subsequent API calls referencing the user. |
| name | string | Full name of the user. | required | optional | Max 255 characters. |
| string | Primary email address. | required (for non-end-user roles) | optional | Must be unique across the account. | |
| role | string | User role: 'end-user', 'agent', or 'admin'. | optional (defaults to end-user) | optional | Changing to 'agent' or 'admin' consumes a seat license. |
| active | boolean | Whether the user account is active. | auto (true) | optional | Set to false to deactivate (soft delete). |
| verified | boolean | Whether the user's primary identity is verified. | optional | optional | Unverified users cannot log in. |
| suspended | boolean | Whether the user is suspended. | optional | optional | Suspended users cannot log in or submit tickets. |
| organization_id | integer | ID of the user's primary organization. | optional | optional | Use Organization Memberships API for multiple org membership. |
| external_id | string | Unique identifier from an external system. | optional | optional | Useful for syncing with external identity providers. Must be unique if set. |
| alias | string | Agent alias displayed to end-users. | optional | optional | Agents only. |
| phone | string | Primary phone number. | optional | optional | E.164 format recommended. |
| time_zone | string | User's time zone (e.g., 'Pacific Time (US & Canada)'). | optional | optional | Must match a Zendesk-supported time zone string. |
| locale | string | User's locale (e.g., 'en-US'). | optional | optional | |
| tags | array[string] | Tags associated with the user. | optional | optional | Replaces all existing tags on update unless using tag-specific endpoints. |
| custom_role_id | integer | ID of a custom agent role (Enterprise only). | optional | optional | Only applicable when role is 'agent'. |
| user_fields | object | Key-value map of custom user field values. | optional | optional | Keys are custom field keys defined in Admin Center. |
| created_at | string (ISO 8601) | Timestamp of user creation. | auto-assigned | immutable | |
| updated_at | string (ISO 8601) | Timestamp of last update. | auto-assigned | auto-updated | |
| last_login_at | string (ISO 8601) | Timestamp of last login. | null | system-managed | |
| two_factor_auth_enabled | boolean | Whether 2FA is enabled for the user. | read-only | read-only | Cannot be set via API. |
Core endpoints
List Users
- Method: GET
- URL:
/api/v2/users - Watch out for: Default returns all roles. Filter by role=agent|end-user|admin. Cursor pagination is preferred; legacy offset pagination is deprecated for large datasets.
Request example
GET /api/v2/users?role=agent&page[size]=100
Authorization: Basic {encoded}
Response example
{
"users": [{"id": 123, "name": "Jane Doe", "email": "jane@example.com", "role": "agent"}],
"meta": {"has_more": true, "after_cursor": "abc123"},
"links": {"next": "https://example.zendesk.com/api/v2/users?page[after]=abc123"}
}
Show User
- Method: GET
- URL:
/api/v2/users/{user_id} - Watch out for: Returns 404 if user is deleted (hard-deleted). Suspended or deactivated users still return 200.
Request example
GET /api/v2/users/123
Authorization: Basic {encoded}
Response example
{
"user": {
"id": 123,
"name": "Jane Doe",
"email": "jane@example.com",
"role": "agent",
"active": true
}
}
Create User
- Method: POST
- URL:
/api/v2/users - Watch out for: Creating an agent or admin consumes a seat. If the email already exists, Zendesk returns the existing user (not an error) unless skip_verify_email is used carefully.
Request example
POST /api/v2/users
Content-Type: application/json
{"user": {"name": "John Smith", "email": "john@example.com", "role": "agent"}}
Response example
{
"user": {
"id": 456,
"name": "John Smith",
"email": "john@example.com",
"role": "agent",
"active": true,
"created_at": "2024-01-15T10:00:00Z"
}
}
Update User
- Method: PUT
- URL:
/api/v2/users/{user_id} - Watch out for: Uses PUT but behaves as a partial update (only supplied fields are changed). PATCH is also accepted and behaves identically.
Request example
PUT /api/v2/users/456
Content-Type: application/json
{"user": {"name": "John A. Smith", "suspended": false}}
Response example
{
"user": {
"id": 456,
"name": "John A. Smith",
"suspended": false,
"updated_at": "2024-01-16T09:00:00Z"
}
}
Delete (Deactivate) User
- Method: DELETE
- URL:
/api/v2/users/{user_id} - Watch out for: DELETE sets active=false (soft delete). The user record is retained. Hard deletion requires a separate GDPR-compliant deletion request via the Deletion API or Admin Center.
Request example
DELETE /api/v2/users/456
Authorization: Basic {encoded}
Response example
{
"user": {
"id": 456,
"active": false,
"name": "John A. Smith"
}
}
Search Users
- Method: GET
- URL:
/api/v2/users/search - Watch out for: Uses Zendesk search syntax. Results are eventually consistent. Max 1000 results returned. For exact lookups, prefer /api/v2/users?external_id= or /api/v2/users/show_many.
Request example
GET /api/v2/users/search?query=email:jane@example.com
Authorization: Basic {encoded}
Response example
{
"users": [{"id": 123, "name": "Jane Doe", "email": "jane@example.com"}],
"count": 1
}
Create or Update Many Users (Bulk)
- Method: POST
- URL:
/api/v2/users/create_or_update_many - Watch out for: Accepts up to 100 users per request. Returns a job_status object; poll /api/v2/job_statuses/{id} to check completion. Failures for individual records are reported in job results, not as HTTP errors.
Request example
POST /api/v2/users/create_or_update_many
Content-Type: application/json
{"users": [{"name": "A", "email": "a@x.com"}, {"name": "B", "email": "b@x.com"}]}
Response example
{
"job_status": {
"id": "8b726e606741012ffc2d782bcb7848fe",
"status": "queued",
"url": "https://example.zendesk.com/api/v2/job_statuses/8b726e606741012ffc2d782bcb7848fe"
}
}
Show Current User (Me)
- Method: GET
- URL:
/api/v2/users/me - Watch out for: Useful for validating OAuth token identity and confirming the acting user's role and permissions.
Request example
GET /api/v2/users/me
Authorization: Bearer {access_token}
Response example
{
"user": {
"id": 789,
"name": "API Bot",
"role": "admin",
"email": "bot@example.com"
}
}
Rate limits, pagination, and events
- Rate limits: Rate limits are enforced per subdomain and vary by plan. Limits apply to the total number of API requests per minute across all integrations on the account.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: When rate limited, the API returns HTTP 429. The Retry-After header indicates seconds to wait. Headers X-Rate-Limit and X-Rate-Limit-Remaining are returned on responses. Bulk endpoints (e.g., Create Many Users) count as a single request but have their own job-queue limits.
- Pagination method: cursor
- Default page size: 100
- Max page size: 100
- Pagination pointer: page[size] (cursor-based) or per_page + page (offset-based, legacy)
| Plan | Limit | Concurrent |
|---|---|---|
| Suite Team / Support Team | 200 requests/min | 0 |
| Suite Growth / Support Professional | 400 requests/min | 0 |
| Suite Professional | 400 requests/min | 0 |
| Suite Enterprise / Support Enterprise | 700 requests/min | 0 |
- Webhooks available: Yes
- Webhook notes: Zendesk supports webhooks (called 'Webhooks' in Admin Center) that can be triggered by triggers, automations, or directly via the Webhooks API. User-related events can be captured via triggers on ticket events that involve user changes, or via the Zendesk Events API for user-specific activity.
- Alternative event strategy: For real-time user lifecycle events, use the Zendesk Sunshine Events API or poll the Users API with the updated_since filter parameter.
- Webhook events: user.created (via Sunshine Events API), user.updated (via Sunshine Events API), Trigger-based: ticket created/updated when requester or assignee changes, Automation-based: time-based conditions on user-related ticket fields
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise (Suite Enterprise or Support Enterprise). Note: Native SCIM is not available on lower tiers. Professional-tier accounts can use IdP API connector integrations (e.g., Okta, Entra ID) as a workaround.
Endpoint: https://{subdomain}.zendesk.com/api/scim/v2
Supported operations: GET /Users – list users, GET /Users/{id} – retrieve user, POST /Users – create user, PUT /Users/{id} – replace user, PATCH /Users/{id} – update user, DELETE /Users/{id} – deactivate user, GET /Groups – list groups (organizations), POST /Groups – create group, PATCH /Groups/{id} – update group membership
Limitations:
- Requires Enterprise plan; not available on Suite Team, Growth, or Professional natively.
- SCIM provisioning maps to Zendesk organizations for group management, not custom roles.
- Custom agent roles cannot be assigned via SCIM.
- SCIM DELETE sets the user to inactive (soft delete), not hard delete.
- Zendesk QA (formerly Klaus) is a separate product with its own SCIM endpoint.
- SSO is not a prerequisite but is commonly configured alongside SCIM.
Common scenarios
Provisioning a new agent from an HR system: search first with GET /api/v2/users/search? query=email:{email} to avoid the silent duplicate behavior (a POST with an existing email returns HTTP 200 with the existing record, not a 409).
If not found, POST with role='agent' and external_id set to the HR system identifier. Be aware that role assignment to agent or admin immediately consumes a paid seat - a 422 is returned if the account is at seat limit, so check capacity before bulk provisioning.
Bulk deactivation for departed employees: use POST /api/v2/users/update_many with active: false for up to 100 users per request. This is a soft delete only - active is set to false and the record is retained. Poll GET /api/v2/job_statuses/{job_id} for completion; per-user failures surface in the job results array, not as HTTP errors. For GDPR hard deletion, a separate call to DELETE /api/v2/users/{id}/permanently is required after deactivation.
SCIM sync on Enterprise: native SCIM 2.0 is available at https://{subdomain}.zendesk.com/api/scim/v2 on Suite Enterprise only. A critical caveat: SCIM group push maps to Zendesk organizations, not agent groups or custom roles. Custom role assignment must be handled separately via the REST API post-provisioning. On sub-Enterprise plans, IdP API connector integrations (Okta, Entra ID) are the available workaround, requiring Professional tier ($115/agent/mo) and IdP-side configuration.
Provision a new agent from an external HR system
- Check if user exists: GET /api/v2/users/search?query=email:{email} to avoid duplicates.
- If not found, POST /api/v2/users with name, email, role='agent', external_id set to HR system ID.
- Assign to organization if needed: POST /api/v2/organization_memberships with user_id and organization_id.
- Optionally set custom user fields: included in the user POST body under user_fields key.
- Verify the response user.id and store it mapped to the HR system record for future updates.
Watch out for: Creating an agent immediately consumes a seat license. If the account is at seat limit, the API returns a 422 error. Check seat availability before bulk provisioning.
Bulk deactivate departed employees
- Build a list of external_ids or emails for departed users.
- For each user, GET /api/v2/users/search?query=external_id:{id} to resolve Zendesk user IDs.
- Batch update using POST /api/v2/users/update_many with {users: [{id: ..., active: false}, ...]} (max 100 per request).
- Poll /api/v2/job_statuses/{job_id} until status is 'completed'.
- Review job results array for any per-user failures and handle individually.
Watch out for: update_many sets active=false (soft delete). If hard deletion is required for GDPR compliance, use the separate User Deletion API endpoint DELETE /api/v2/users/{id}/permanently after deactivation.
Sync user profile updates from IdP via SCIM (Enterprise)
- Configure SCIM in Admin Center → Security → Team Member Authentication → SCIM.
- Copy the SCIM base URL (https://{subdomain}.zendesk.com/api/scim/v2) and generate a SCIM token.
- Enter the SCIM URL and token in the IdP (Okta, Entra ID, OneLogin) SCIM provisioning settings.
- Map IdP attributes to SCIM attributes: userName→email, displayName→name, active→active.
- Enable provisioning actions in IdP: Create Users, Update User Attributes, Deactivate Users.
- Run an initial import/sync in the IdP to reconcile existing users by email match.
Watch out for: SCIM group push maps to Zendesk organizations, not agent groups or custom roles. Custom role assignment must be done separately via the REST API after SCIM provisioning.
Why building this yourself is a trap
Several API behaviors deviate from standard REST conventions and will cause silent failures if not explicitly handled.
The PUT /api/v2/users/{id} endpoint behaves as a partial update despite its method - only supplied fields change - but the tags field is a full replacement on any PUT or PATCH; use the dedicated /api/v2/users/{id}/tags sub-resource for additive tag operations.
Cursor-based pagination (page[size]) is the current standard; legacy offset pagination (page= and per_page=) is deprecated for large datasets and may be removed. The GET /api/v2/users/search endpoint returns eventually consistent results and caps at 1,000 records - for exact lookups by external identity, prefer GET /api/v2/users?external_id= to ensure accuracy within your identity graph.
Bulk endpoints (create_or_update_many, update_many) are asynchronous and always require polling GET /api/v2/job_statuses/{id}. Treating the initial HTTP 200 response as confirmation of completion will cause missed failures.
Finally, DELETE /api/v2/users/{id} is a soft delete only; teams with GDPR obligations must implement a separate hard-deletion flow and should not assume the standard DELETE endpoint satisfies data removal requirements.
Automate Zendesk 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.