Summary and recommendation
RingCentral exposes user management through two distinct API surfaces: the REST API (base URL https://platform.ringcentral.com/restapi/v1.0) using OAuth 2.0, and the SCIM 2.0 API (base URL https://platform.ringcentral.com/scim/v2) using a static bearer token generated in the Admin Portal.
These are separate systems - changes made via SCIM may not immediately reflect in REST API responses due to eventual consistency. Integrations that build an identity graph across both surfaces must account for this lag explicitly.
User-management endpoints (extension read/write) are classified as Heavy usage plan, capped at 10 requests/min per app per user. Bulk provisioning at scale must use the async bulk-create endpoint (POST /account/{accountId}/extension/bulk-create) and handle partial failures per-record.
Access tokens expire after 1 hour; JWT grant flows do not issue refresh tokens by default, requiring re-authentication on expiry.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise (RingEX Ultra or Enterprise tier; SSO must be configured as a prerequisite) |
Authentication
Auth method: OAuth 2.0
Setup steps
- Register an application at https://developers.ringcentral.com/my-account.html#/applications
- Select the appropriate auth flow: Authorization Code (user-facing apps), JWT (server-to-server/service accounts), or Client Credentials
- Note the Client ID and Client Secret from the app dashboard
- For JWT flow: create a service account user in RingCentral Admin Portal and generate a JWT credential
- Exchange credentials for an access token at https://platform.ringcentral.com/restapi/oauth/token
- Include the access token as a Bearer token in the Authorization header of all API requests
- Access tokens expire in 1 hour; use the refresh token (Authorization Code flow) or re-authenticate (JWT flow) to obtain a new one
Required scopes
| Scope | Description | Required for |
|---|---|---|
| ReadAccounts | Read account, extension, and user information | GET user, list users, read extension details |
| EditAccounts | Create, update, and delete extensions/users | POST, PUT, PATCH, DELETE user/extension operations |
| ReadPresence | Read user presence status | GET presence/status for users |
| EditPresence | Update user presence status | PUT presence for users |
| Contacts | Read and manage company contacts | Directory/contact operations |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique internal extension/user ID | system-generated | immutable | Used as path parameter in all user-specific requests |
| extensionNumber | string | User's internal extension number | optional (auto-assigned if omitted) | updatable | Must be unique within the account |
| contact.firstName | string | User's first name | required | updatable | Nested under contact object |
| contact.lastName | string | User's last name | required | updatable | Nested under contact object |
| contact.email | string | User's email address (used for login) | required | updatable | Must be unique across the account |
| contact.businessPhone | string | User's direct business phone number | optional | updatable | E.164 format recommended |
| contact.mobilePhone | string | User's mobile phone number | optional | updatable | |
| contact.department | string | Department the user belongs to | optional | updatable | |
| contact.jobTitle | string | User's job title | optional | updatable | |
| status | enum | Account status: Enabled, Disabled, NotActivated, Unassigned | optional (defaults to NotActivated) | updatable | Setting to Disabled suspends the user without deleting |
| type | enum | Extension type: User, Department, Announcement, Voicemail, etc. | required | generally immutable | Use 'User' for standard user accounts |
| name | string | Display name (read-only, derived from contact.firstName + contact.lastName) | system-derived | read-only | |
| password | string | User's password (write-only on create) | optional | updatable via separate endpoint | Not returned in GET responses |
| regionalSettings.timezone | object | User's timezone setting | optional | updatable | Nested under regionalSettings |
| regionalSettings.language | object | User's language preference | optional | updatable | |
| serviceFeatures | array | List of features enabled for the user (e.g., SMS, Voicemail, VideoConferencing) | system-assigned based on license | some features togglable | Read-only for most features; controlled by license tier |
| profileImage | object | Reference to user's profile image | not applicable | updatable via separate image endpoint | Use PUT /restapi/v1.0/account/{accountId}/extension/{extensionId}/profile-image |
Core endpoints
List Users (Extensions)
- Method: GET
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension - Watch out for: Use accountId '~' to reference the authenticated user's account. Filter by type=User to exclude non-user extensions (departments, voicemail boxes, etc.).
Request example
GET /restapi/v1.0/account/~/extension?type=User&status=Enabled&perPage=100&page=1
Authorization: Bearer {access_token}
Response example
{
"uri": "https://platform.ringcentral.com/restapi/v1.0/account/~/extension?page=1&perPage=100",
"records": [{"id": "12345", "extensionNumber": "101", "name": "Jane Doe", "status": "Enabled"}],
"paging": {"page": 1, "perPage": 100, "totalPages": 3, "totalElements": 250}
}
Get User
- Method: GET
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId} - Watch out for: Use extensionId '~' to retrieve the currently authenticated user's own profile.
Request example
GET /restapi/v1.0/account/~/extension/12345
Authorization: Bearer {access_token}
Response example
{
"id": "12345",
"extensionNumber": "101",
"contact": {"firstName": "Jane", "lastName": "Doe", "email": "jane@example.com"},
"status": "Enabled",
"type": "User"
}
Create User (Extension)
- Method: POST
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension - Watch out for: Newly created users have status 'NotActivated' until they complete email verification or an admin activates them. A license must be available in the account to assign to the new user.
Request example
POST /restapi/v1.0/account/~/extension
Authorization: Bearer {access_token}
Content-Type: application/json
{
"type": "User",
"contact": {"firstName": "John", "lastName": "Smith", "email": "john@example.com"},
"extensionNumber": "102"
}
Response example
{
"id": "67890",
"extensionNumber": "102",
"status": "NotActivated",
"contact": {"firstName": "John", "lastName": "Smith", "email": "john@example.com"}
}
Update User
- Method: PUT
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId} - Watch out for: PUT replaces the full resource; omitting optional fields may clear them. Use only the fields you intend to set.
Request example
PUT /restapi/v1.0/account/~/extension/12345
Authorization: Bearer {access_token}
Content-Type: application/json
{
"contact": {"firstName": "Jane", "lastName": "Doe-Updated", "department": "Engineering"}
}
Response example
{
"id": "12345",
"contact": {"firstName": "Jane", "lastName": "Doe-Updated", "department": "Engineering"},
"status": "Enabled"
}
Disable / Suspend User
- Method: PUT
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId} - Watch out for: Setting status to 'Disabled' suspends the user but retains their data and license. There is no dedicated PATCH endpoint; use PUT with the status field.
Request example
PUT /restapi/v1.0/account/~/extension/12345
Authorization: Bearer {access_token}
Content-Type: application/json
{
"status": "Disabled"
}
Response example
{
"id": "12345",
"status": "Disabled"
}
Delete User (Extension)
- Method: DELETE
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId} - Watch out for: Deleting an extension is irreversible and releases the license. Only extensions of type 'User' with status 'Disabled' or 'NotActivated' can typically be deleted via API.
Request example
DELETE /restapi/v1.0/account/~/extension/12345
Authorization: Bearer {access_token}
Response example
HTTP 204 No Content
List User Roles / Permissions
- Method: GET
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension/{extensionId}/assigned-role - Watch out for: Role assignment is separate from user creation. Roles must be pre-configured in the RingCentral Admin Portal before assignment via API.
Request example
GET /restapi/v1.0/account/~/extension/12345/assigned-role
Authorization: Bearer {access_token}
Response example
{
"uri": "...",
"records": [{"id": "1", "displayName": "Standard User", "siteCompatible": true}]
}
Bulk Create/Update Users
- Method: POST
- URL:
https://platform.ringcentral.com/restapi/v1.0/account/{accountId}/extension/bulk-create - Watch out for: Bulk operations are asynchronous; check individual record statuses in the response. Partial failures are possible - some records may succeed while others fail.
Request example
POST /restapi/v1.0/account/~/extension/bulk-create
Authorization: Bearer {access_token}
Content-Type: application/json
{
"records": [
{"type": "User", "contact": {"firstName": "A", "email": "a@x.com"}},
{"type": "User", "contact": {"firstName": "B", "email": "b@x.com"}}
]
}
Response example
{
"response": [
{"id": "111", "status": "NotActivated"},
{"id": "112", "status": "NotActivated"}
]
}
Rate limits, pagination, and events
- Rate limits: RingCentral enforces per-app, per-user rate limits grouped by API usage plan (Light, Medium, Heavy, Auth). Limits are applied per minute. Exceeding limits returns HTTP 429.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: Each endpoint is classified into a usage plan (Light/Medium/Heavy/Auth). Headers X-Rate-Limit-Limit, X-Rate-Limit-Remaining, X-Rate-Limit-Window, and Retry-After are returned. User-management endpoints (read/write extensions) are typically Heavy plan.
- Pagination method: offset
- Default page size: 100
- Max page size: 1000
- Pagination pointer: page and perPage (page is 1-indexed; perPage sets page size)
| Plan | Limit | Concurrent |
|---|---|---|
| Light | 50 requests/min per app per user | 0 |
| Medium | 40 requests/min per app per user | 0 |
| Heavy | 10 requests/min per app per user | 0 |
| Auth | 5 requests/min per app per user | 0 |
- Webhooks available: Yes
- Webhook notes: RingCentral supports webhooks via its Subscription API. Subscriptions are created via POST to /restapi/v1.0/subscription and deliver events to a publicly accessible HTTPS endpoint. Subscriptions expire and must be renewed (max TTL is configurable, typically up to 20160 minutes / 14 days).
- Alternative event strategy: RingCentral also supports WebSocket-based subscriptions (PubNub transport deprecated) for real-time event delivery without requiring a public endpoint.
- Webhook events: /restapi/v1.0/account//extension, /restapi/v1.0/account//extension//presence, /restapi/v1.0/account//extension//message-store, /restapi/v1.0/account//extension//telephony/sessions, /restapi/v1.0/account//extension/~/voicemail
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise (RingEX Ultra or Enterprise tier; SSO must be configured as a prerequisite)
Endpoint: https://platform.ringcentral.com/scim/v2
Supported operations: GET /Users (list users), GET /Users/{id} (get user), POST /Users (create user), PUT /Users/{id} (replace user), PATCH /Users/{id} (update user), DELETE /Users/{id} (delete user), GET /Groups (list groups), GET /ServiceProviderConfig, GET /Schemas
Limitations:
- SSO (SAML 2.0) must be enabled before SCIM provisioning can be activated
- SCIM provisioning requires Enterprise-tier plan (Ultra or above for RingEX)
- Supported IdPs with native connectors: Okta, Microsoft Entra ID (Azure AD), OneLogin
- Group provisioning support is limited; not all IdP group-push features are fully supported
- SCIM token is generated in the RingCentral Admin Portal under Tools > User Management > SCIM
- SCIM token does not use OAuth 2.0; it is a static bearer token generated in the admin UI
Common scenarios
Provisioning a new employee requires a POST to /restapi/v1.0/account/~/extension with type=User and core contact fields.
The returned user will have status NotActivated - a separate PUT to /assigned-role is needed to set the role, and a license must be confirmed available via GET /account/~/service-info before bulk runs, or the create call will fail.
Deprovisioning requires a PUT with status=Disabled before a DELETE can be issued; attempting to DELETE an Enabled extension returns an error, and deletion is irreversible - voicemail and call logs are lost.
SCIM sync with Okta requires the account to be on an Enterprise/Ultra plan with SAML 2.0 SSO already active.
The SCIM bearer token is generated under Tools > User Management > SCIM in the Admin Portal - it is a static credential, not an OAuth token, and does not expire automatically but can be revoked.
Group push support via SCIM is limited; verify supported attributes in the Okta RingCentral OIN app configuration before relying on group-based provisioning.
Webhook subscriptions for real-time user-state events expire at approximately 14 days and require explicit renewal logic to avoid event gaps.
Provision a new employee
- POST /restapi/v1.0/account/~/extension with type=User, contact.firstName, contact.lastName, contact.email to create the extension
- Note the returned extension id; status will be 'NotActivated'
- PUT /restapi/v1.0/account/~/extension/{id}/assigned-role to assign the appropriate role (e.g., Standard User)
- Optionally assign a phone number via PUT /restapi/v1.0/account/~/extension/{id}/phone-number
- Trigger activation email or set status to 'Enabled' if SSO is configured (user won't need password)
Watch out for: A license must be available in the account before creating a new user. Check account license availability via GET /restapi/v1.0/account/~/service-info before bulk provisioning.
Deprovision a departing employee
- GET /restapi/v1.0/account/~/extension?email={email} to locate the user's extensionId
- PUT /restapi/v1.0/account/~/extension/{id} with status=Disabled to immediately suspend access
- Optionally reassign or forward the user's phone number to another extension
- DELETE /restapi/v1.0/account/~/extension/{id} to permanently remove the extension and release the license
Watch out for: DELETE will fail if the extension is still in 'Enabled' status. Always disable first. Deletion is irreversible; voicemail and call logs associated with the extension are lost.
Sync users via SCIM with Okta
- Ensure RingCentral account is on an Enterprise/Ultra plan with SSO (SAML 2.0) configured
- In RingCentral Admin Portal, navigate to Tools > User Management > SCIM and generate a SCIM bearer token
- In Okta, add the RingCentral application from the Okta Integration Network (OIN)
- Configure the SCIM base URL as https://platform.ringcentral.com/scim/v2 and paste the bearer token
- Enable provisioning features: Push New Users, Push Profile Updates, Deactivate Users
- Assign users/groups in Okta to trigger SCIM provisioning to RingCentral
Watch out for: SCIM token is a static credential - store it securely. If SSO is not configured before enabling SCIM, provisioning will fail. Group push support is limited; verify supported attributes in the Okta RingCentral app configuration.
Why building this yourself is a trap
The most common integration failure mode is treating the REST API and SCIM API as a unified identity graph when they are not.
Writes via SCIM are not guaranteed to be immediately visible via REST API reads, which breaks any provisioning pipeline that creates a user via SCIM and then immediately queries the REST API to confirm or enrich the record. Build explicit retry and consistency-check logic between the two surfaces.
The Heavy rate limit (10 req/min per app per user) on extension endpoints is the second trap: sequential per-user provisioning loops will hit this ceiling quickly. Use bulk-create for initial loads and design incremental sync to batch updates.
Finally, the SCIM static token is a single credential with no automatic rotation - treat it as a high-value secret, store it in a secrets manager, and implement revocation procedures as part of your offboarding runbook for the integration itself, not just for end users.
Automate RingCentral 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.