Summary and recommendation
Salesforce exposes user management through its REST API (sobjects/User), SOQL query interface, SCIM 2.0 (Enterprise+), and Change Data Capture for real-time events. Auth is OAuth 2.0; JWT Bearer Flow with an X.509 certificate is the recommended approach for server-to-server integrations.
The User object has 10 required fields on create - missing any one returns a generic `REQUIRED_FIELD_MISSING` error. User listing requires SOQL queries, not a standard REST collection endpoint. Rate limits are org-wide and shared across all API consumers, which means a poorly behaved integration can starve others.
Users cannot be deleted via API; deactivation is a PATCH to `IsActive: false`, subject to the same dependency blockers that apply in the UI.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise or Unlimited (API access required; SSO must be configured as a prerequisite) |
Authentication
Auth method: OAuth 2.0
Setup steps
- In Salesforce Setup, navigate to App Manager (or External Client Apps Manager in newer orgs) and create a Connected App.
- Under API (Enable OAuth Settings), check 'Enable OAuth Settings' and set a Callback URL.
- Select required OAuth scopes (e.g., 'api', 'refresh_token').
- Save and retrieve the Consumer Key (client_id) and Consumer Secret (client_secret).
- Obtain an access token by POSTing to https://{instance}.my.salesforce.com/services/oauth2/token with grant_type, client_id, client_secret, and (for auth-code flow) the authorization code.
- Include the returned access_token as a Bearer token in the Authorization header of all API requests.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| api | Allows access to the current user's account via REST, SOAP, and other APIs. | All REST API calls including User CRUD operations |
| refresh_token | Allows the app to obtain a new access token using a refresh token without re-prompting the user. | Long-lived integrations and server-to-server flows |
| full | Full access to all data accessible by the logged-in user; equivalent to combining api + web + chatter_api. | Broad integrations requiring unrestricted data access (use least-privilege instead when possible) |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| Id | ID | Unique 18-character Salesforce record ID for the user. | auto-generated | read-only | System field; cannot be set by the caller. |
| Username | string (80) | The user's login name. Must be in email format and globally unique across all Salesforce orgs. | required | optional | Must be unique org-wide and across all Salesforce instances, not just within your org. |
| LastName | string (80) | User's last name. | required | optional | |
| FirstName | string (40) | User's first name. | optional | optional | |
| string (128) | User's email address. | required | optional | Used for system notifications and password resets. | |
| Alias | string (8) | Short name to identify the user on list pages, reports, and other pages where the entire name doesn't fit. | required | optional | Max 8 characters. |
| ProfileId | reference (Profile) | ID of the user's profile, which controls permissions and access. | required | optional | Every user must have exactly one profile. |
| TimeZoneSidKey | picklist | User's time zone (e.g., 'America/New_York'). | required | optional | Must be a valid IANA time zone key. |
| LocaleSidKey | picklist | User's locale for number and date formatting (e.g., 'en_US'). | required | optional | |
| EmailEncodingKey | picklist | Email encoding for outbound emails (e.g., 'UTF-8'). | required | optional | |
| LanguageLocaleKey | picklist | User's language for the Salesforce UI (e.g., 'en_US'). | required | optional | |
| IsActive | boolean | Indicates whether the user can log in. Set to false to deactivate. | optional (defaults to true) | optional | Salesforce does not support hard-deleting users; deactivation via IsActive=false is the standard offboarding method. |
| UserRoleId | reference (UserRole) | ID of the user's role in the role hierarchy. | optional | optional | Required for orgs using role-based record sharing. |
| Title | string (80) | User's job title. | optional | optional | |
| Department | string (80) | User's department. | optional | optional | |
| Phone | phone | User's primary phone number. | optional | optional | |
| MobilePhone | phone | User's mobile phone number. | optional | optional | |
| FederationIdentifier | string (512) | The SAML Federation ID used for SSO. Maps the Salesforce user to an external identity provider identity. | optional | optional | Required when using SAML SSO with a federated identity. Must be unique within the org. |
| UserType | picklist | Type of user license (e.g., 'Standard', 'PowerPartner', 'CsnOnly'). | auto-set by ProfileId | read-only | Determined by the assigned profile's license type. |
| CreatedDate | datetime | Timestamp when the user record was created. | auto-generated | read-only | System field. |
Core endpoints
Create user
- Method: POST
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/sobjects/User - Watch out for: Username must be globally unique across all Salesforce orgs (not just yours) and must be in email format. Creating a user consumes a license seat immediately.
Request example
POST /services/data/v60.0/sobjects/User
Authorization: Bearer {access_token}
Content-Type: application/json
{
"Username": "jdoe@example.com",
"LastName": "Doe",
"FirstName": "Jane",
"Email": "jdoe@example.com",
"Alias": "jdoe",
"ProfileId": "00e000000000001",
"TimeZoneSidKey": "America/New_York",
"LocaleSidKey": "en_US",
"EmailEncodingKey": "UTF-8",
"LanguageLocaleKey": "en_US"
}
Response example
HTTP 201 Created
{
"id": "005xx000001Sv6AAAS",
"success": true,
"errors": []
}
Get single user by ID
- Method: GET
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/sobjects/User/{userId} - Watch out for: By default returns all fields. Use ?fields=Id,Username,Email to limit payload size.
Request example
GET /services/data/v60.0/sobjects/User/005xx000001Sv6AAAS
Authorization: Bearer {access_token}
Response example
HTTP 200 OK
{
"Id": "005xx000001Sv6AAAS",
"Username": "jdoe@example.com",
"FirstName": "Jane",
"LastName": "Doe",
"IsActive": true,
"ProfileId": "00e000000000001"
}
List / search users (SOQL query)
- Method: GET
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/query?q=SELECT+Id,Username,Email,IsActive+FROM+User+WHERE+IsActive=true+LIMIT+200 - Watch out for: SOQL OFFSET is capped at 2000. For datasets >2000 records, use nextRecordsUrl cursor-based pagination returned in the response when done=false.
Request example
GET /services/data/v60.0/query?q=SELECT+Id,Username,Email,IsActive+FROM+User+LIMIT+200
Authorization: Bearer {access_token}
Response example
HTTP 200 OK
{
"totalSize": 42,
"done": true,
"records": [
{"Id": "005xx...", "Username": "jdoe@example.com", "IsActive": true}
]
}
Update user
- Method: PATCH
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/sobjects/User/{userId} - Watch out for: PATCH returns 204 with no body on success. Only include fields you want to change; omitted fields are left unchanged.
Request example
PATCH /services/data/v60.0/sobjects/User/005xx000001Sv6AAAS
Authorization: Bearer {access_token}
Content-Type: application/json
{
"Title": "Senior Engineer",
"Department": "Engineering"
}
Response example
HTTP 204 No Content
Deactivate user
- Method: PATCH
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/sobjects/User/{userId} - Watch out for: Salesforce does not support deleting users via the API. Deactivation (IsActive=false) is the only supported offboarding method. The user's license is freed upon deactivation.
Request example
PATCH /services/data/v60.0/sobjects/User/005xx000001Sv6AAAS
Authorization: Bearer {access_token}
Content-Type: application/json
{"IsActive": false}
Response example
HTTP 204 No Content
Upsert user by external ID
- Method: PATCH
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/sobjects/User/{externalIdField}/{externalIdValue} - Watch out for: The external ID field (e.g., FederationIdentifier) must be marked as an External ID in the org's field settings. Returns 201 on insert, 204 on update.
Request example
PATCH /services/data/v60.0/sobjects/User/FederationIdentifier/ext-user-123
Authorization: Bearer {access_token}
Content-Type: application/json
{
"LastName": "Doe",
"Email": "jdoe@example.com"
}
Response example
HTTP 201 Created (insert) or 204 No Content (update)
Get current API limits
- Method: GET
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/limits - Watch out for: Limit values are accurate within approximately 5 minutes of resource consumption. Avoid rapid polling of this endpoint.
Request example
GET /services/data/v60.0/limits
Authorization: Bearer {access_token}
Response example
HTTP 200 OK
{
"DailyApiRequests": {
"Max": 115000,
"Remaining": 114823
}
}
Query users via SOQL (paginated follow-up)
- Method: GET
- URL:
https://{instance}.my.salesforce.com/services/data/v60.0/query/{queryLocatorId} - Watch out for: Use the nextRecordsUrl from the previous response to fetch the next batch of up to 2000 records. Continue until done=true.
Request example
GET /services/data/v60.0/query/01gxx000000001SAAQ-2000
Authorization: Bearer {access_token}
Response example
HTTP 200 OK
{
"totalSize": 5000,
"done": false,
"nextRecordsUrl": "/services/data/v60.0/query/01gxx000000001SAAQ-4000",
"records": [...]
}
Rate limits, pagination, and events
- Rate limits: Salesforce enforces a rolling 24-hour total API request limit shared across REST, SOAP, Bulk, and Connect APIs. Enterprise Edition orgs start at 100,000 requests per 24-hour period, plus 1,000 additional requests per user license. Concurrent long-running requests (>20 seconds) are capped at 25 for production orgs. Each API call has a 10-minute timeout. The daily limit is a soft cap with a hard ceiling enforced to protect platform health.
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: No standard rate-limit response headers are returned on REST API calls. Remaining API usage can be queried via GET /services/data/v60.0/limits. When the concurrent long-running request cap is exceeded, Salesforce returns a REQUEST_LIMIT_EXCEEDED error. The daily limit operates on a rolling 24-hour window, not a calendar day. Additional API calls can be purchased in increments of 200–10,000 per 24-hour period.
- Pagination method: cursor
- Default page size: 2000
- Max page size: 2000
- Pagination pointer: nextRecordsUrl
| Plan | Limit | Concurrent |
|---|---|---|
| Starter Suite | No API access | 0 |
| Pro Suite | No API access | 0 |
| Enterprise | 100,000 requests/24h base + 1,000 per user license | 25 |
| Unlimited | 5,000 API calls per license per 24h (higher effective total) | 25 |
| Developer Edition | 15,000 requests/24h | 5 |
- Webhooks available: No
- Webhook notes: Salesforce REST API does not offer traditional outbound webhooks for user lifecycle events (create, update, deactivate). Salesforce uses an event-driven model instead.
- Alternative event strategy: Use Salesforce Streaming API (PushTopic or Change Data Capture) to subscribe to real-time User object change events via CometD long-polling. Platform Events can also be used for custom event-driven workflows. Outbound Messages (via Workflow Rules or Flow) can POST XML payloads to an external HTTPS endpoint on user record changes.
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise or Unlimited (API access required; SSO must be configured as a prerequisite)
Endpoint: https://{instance}.my.salesforce.com/services/scim/v2
Supported operations: GET /Users - list users, GET /Users/{id} - get single user, POST /Users - create user, PATCH /Users/{id} - update user attributes, PUT /Users/{id} - replace user, GET /Groups - list groups (Permission Sets/Profiles exposed as groups), GET /ServiceProviderConfig - retrieve SCIM capabilities, GET /Schemas - retrieve supported SCIM schemas
Limitations:
- SSO must be configured before SCIM provisioning can be enabled.
- SCIM is only available on Enterprise and Unlimited editions; Starter and Pro Suite plans do not support it.
- Salesforce does not support hard-deleting users via SCIM; deprovisioning sets IsActive=false.
- Profiles are surfaced as SCIM Groups/roles; permission sets are not directly manageable via standard SCIM group operations.
- The SCIM endpoint uses OAuth 2.0 bearer tokens (not a static bearer token); the Connected App must have SCIM write permissions enabled.
- Salesforce's SCIM PATCH format must match its specific attribute structure; generic SCIM 2.0 payloads from some IdPs (e.g., Entra custom enterprise apps) may cause timeouts if attribute mappings are misaligned.
- Trial orgs cannot use automated SCIM provisioning.
Common scenarios
Offboard a user: Query by email to get the user ID, transfer owned records per object type before deactivating (Mass Transfer Records only covers standard objects - custom objects need individual API calls), then PATCH IsActive: false. Deactivation blockers (Default Lead Owner, approval process assignee, etc.) return API errors; you must resolve each one before the deactivation call succeeds.
Audit inactive users: Query SELECT Id, Username, Email, LastLoginDate, IsActive FROM User WHERE IsActive = true and filter for LastLoginDate older than 90 days or null. Note: LastLoginDate is null for recently provisioned users who have never logged in - separate these from genuinely inactive accounts before acting.
Sync users from an IdP: Match on FederationIdentifier (preferred) or Email. If FederationIdentifier is not set during initial provisioning, email matching is unreliable due to format differences between Salesforce and IdP records. Create missing users with all 10 required fields, PATCH changed attributes, and PATCH IsActive: false for removed users.
Provision a new employee from an IdP (Okta/Entra) via SCIM
- Ensure the Salesforce org is on Enterprise or Unlimited edition with SSO configured.
- In Salesforce Setup, create a Connected App (or External Client App) with OAuth enabled and SCIM write permissions.
- In the IdP (Okta/Entra), configure the Salesforce SCIM app with the tenant URL (https://{instance}.my.salesforce.com/services/scim/v2) and an OAuth bearer token.
- Assign the new employee to the Salesforce app in the IdP.
- The IdP sends a POST /services/scim/v2/Users request with the user's attributes (userName, name, email, profileId via entitlement mapping).
- Salesforce creates the User record and returns the new user's Salesforce ID.
- Verify the user appears in Salesforce with the correct profile and IsActive=true.
Watch out for: SSO must be configured before SCIM is enabled. Profile assignment via SCIM requires mapping IdP roles/groups to Salesforce Profile IDs, which must be pre-configured in the IdP attribute mapping. Entra's pre-integrated Salesforce gallery app uses legacy username/password auth; use a custom enterprise app with OAuth 2.0 Client Credentials for new setups.
Deactivate a departing employee via REST API
- Query the user's Salesforce ID: GET /services/data/v60.0/query?q=SELECT+Id+FROM+User+WHERE+Username='jdoe@example.com'
- Extract the Id from the response.
- Send PATCH /services/data/v60.0/sobjects/User/{Id} with body {"IsActive": false}.
- Confirm the response is HTTP 204 No Content.
- Optionally, reassign the user's open records (Opportunities, Cases) to another user before or after deactivation.
Watch out for: Deactivation does not revoke active OAuth tokens immediately. If the user has active sessions, those may persist until token expiry. Salesforce does not support hard-deleting users; the record remains in the org permanently.
Bulk-list all active users with pagination
- Send GET /services/data/v60.0/query?q=SELECT+Id,Username,Email,IsActive,ProfileId+FROM+User+WHERE+IsActive=true+ORDER+BY+Id
- Parse the response: if done=true, all records are returned. If done=false, a nextRecordsUrl is present.
- Follow the nextRecordsUrl (GET {nextRecordsUrl}) to retrieve the next batch of up to 2000 records.
- Repeat until done=true.
- Aggregate all records across pages for a complete active user list.
Watch out for: Do not use SOQL OFFSET for pagination beyond 2000 records - it will return a NUMBER_OUTSIDE_VALID_RANGE error. Always use the nextRecordsUrl cursor. For very large orgs (tens of thousands of users), consider Bulk API 2.0 query jobs to avoid consuming daily REST API quota.
Why building this yourself is a trap
Salesforce's API surface looks straightforward until you hit the edge cases that dominate real usage. User listing requires SOQL, not a REST collection - you need to learn and maintain query strings. The 10-field create requirement produces opaque errors.
ProfileId is a required 18-character record ID, not a human-readable name, so every provisioning flow needs a profile lookup step first.
Rate limits are org-wide and shared: on Enterprise, the formula is 100,000 + (1,000 × license count) calls per 24 hours across every integration in the org - one runaway sync job affects everything else.
Deactivation blockers apply via API exactly as they do in the UI, meaning your offboarding automation needs to handle an open-ended list of dependency errors gracefully. OAuth tokens expire and JWT Bearer setup requires X.509 certificate management. SCIM (Enterprise+) reduces provisioning complexity but does not cover record ownership transfer or automation reassignment on offboarding.
Stitchflow maintains ~100 deep integrations at this level of specificity so your team does not have to.
Automate Salesforce 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.