Summary and recommendation
The Veeva Vault REST API (v24.3) exposes full user lifecycle management - create, read, update, and deactivate - under the /objects/users path.
There is no PromoMats-specific API layer;
all calls target the standard Vault REST API, with the vault subdomain and object types differentiating PromoMats vaults from other Vault products.
Authentication supports either session-based tokens (POST /auth) or OAuth 2.0 via a Connected App;
note that session IDs are passed as a raw Authorization header value with no 'Bearer' prefix, unlike standard OAuth bearer tokens - mixing these up returns a 401.
SCIM 2.0 is available at /scim/v2 on Enterprise plans, supporting GET, POST, PUT, PATCH, and DELETE (which maps to deactivation, not hard deletion).
SCIM tokens are long-lived OAuth 2.0 bearer tokens generated separately in Vault Admin and are distinct from session tokens.
For identity graph use cases - syncing PromoMats user state into a downstream directory or IDP - SCIM is the preferred integration surface;
the REST API's VQL query endpoint is the fallback for delta-sync patterns where SCIM is unavailable.
Rate limits are 200 calls per 5-minute window per session and 500,000 calls per vault per day.
The API does not return rate-limit headers or a Retry-After header;
implement exponential backoff on HTTP 429.
API versioning is explicit in the URL path and Veeva increments versions on a roughly twice-yearly release cadence - always pin to a supported version and monitor the deprecation schedule.
API quick reference
| Has user API | Yes |
| Auth method | Session ID (username/password) or OAuth 2.0 / OpenID Connect. Session ID is obtained via POST to /auth; OAuth 2.0 is supported for connected app integrations. |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: Session ID (username/password) or OAuth 2.0 / OpenID Connect. Session ID is obtained via POST to /auth; OAuth 2.0 is supported for connected app integrations.
Setup steps
- Option A – Session Auth: POST credentials to https://{vault_domain}.veevavault.com/api/v24.3/auth with username and password; extract sessionId from response.
- Option B – OAuth 2.0: Register a Connected App in Vault Admin > OAuth 2.0 / OpenID Connect; obtain client_id and configure authorization server; exchange authorization code or client credentials for a bearer token.
- Pass the session ID as the Authorization header value (no 'Bearer' prefix for session tokens) or pass OAuth bearer token as 'Authorization: Bearer {token}'.
- All requests must use HTTPS; HTTP is not supported.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| vault_owner or admin role | Vault-level administrative role required to create, update, deactivate, or retrieve users via API. | All user management operations |
| Connected App OAuth scope (vault-specific) | When using OAuth 2.0 Connected Apps, the app must be granted access to the target vault and appropriate user-management permissions. | OAuth 2.0 authenticated user management |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique Vault user ID | system-generated | read-only | Numeric string assigned by Vault |
| user_name__v | string | Username (typically email address) | required | optional | Must be unique within the vault |
| user_first_name__v | string | User's first name | required | optional | |
| user_last_name__v | string | User's last name | required | optional | |
| user_email__v | string | User's email address | required | optional | Used for notifications and login |
| user_timezone__v | string | User's timezone (e.g., America/New_York) | optional | optional | |
| user_locale__v | string | User's locale setting (e.g., en_US) | optional | optional | |
| user_language__v | string | User's preferred language | optional | optional | |
| license_type__v | string | Vault license type assigned to user (e.g., full__v, read_only__v, external__v) | required | optional | Affects billing and feature access |
| security_profile__v | string | Security profile name controlling permissions | required | optional | Must reference an existing security profile in the vault |
| user_title__v | string | User's job title | optional | optional | |
| department__v | string | User's department | optional | optional | |
| company__v | string | User's company name | optional | optional | |
| active__v | boolean | Whether the user account is active | optional (defaults true) | optional | Set to false to deactivate; Vault does not support hard-delete of users |
| federated_id__v | string | External identity provider subject identifier for SSO | optional | optional | Required when using SAML/OIDC SSO |
| domain_id__v | string | Vault domain ID the user belongs to | optional | read-only | |
| created_date__v | datetime | Timestamp of user creation | system-generated | read-only | ISO 8601 format |
| modified_date__v | datetime | Timestamp of last modification | system-generated | system-generated | ISO 8601 format |
Core endpoints
Authenticate / Create Session
- Method: POST
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/auth - Watch out for: Session tokens expire after inactivity. Re-authenticate programmatically; do not cache indefinitely.
Request example
POST /api/v24.3/auth
Content-Type: application/x-www-form-urlencoded
username=admin%40example.com&password=secret
Response example
{
"responseStatus": "SUCCESS",
"sessionId": "ABC123sessiontoken",
"userId": 12345,
"vaultId": 1001
}
List All Users
- Method: GET
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/objects/users - Watch out for: Returns all users including inactive. Filter active__v client-side or use query endpoint for server-side filtering.
Request example
GET /api/v24.3/objects/users?limit=200&offset=0
Authorization: {sessionId}
Response example
{
"responseStatus": "SUCCESS",
"users": [
{"user": {"id": "12345", "user_name__v": "jane@example.com", "active__v": true}}
],
"responseDetails": {"total": 450, "offset": 0, "limit": 200}
}
Retrieve Single User
- Method: GET
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/objects/users/{user_id} - Watch out for: Use numeric user ID, not username. Retrieve the ID from the list endpoint first.
Request example
GET /api/v24.3/objects/users/12345
Authorization: {sessionId}
Response example
{
"responseStatus": "SUCCESS",
"users": [{
"user": {
"id": "12345",
"user_name__v": "jane@example.com",
"license_type__v": "full__v"
}
}]
}
Create User
- Method: POST
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/objects/users - Watch out for: license_type__v and security_profile__v are required. Invalid values return a PARAMETER_REQUIRED or INVALID_DATA error with no partial creation.
Request example
POST /api/v24.3/objects/users
Authorization: {sessionId}
Content-Type: application/json
{"user_name__v":"new@example.com","user_first_name__v":"Jane","user_last_name__v":"Doe","user_email__v":"new@example.com","license_type__v":"full__v","security_profile__v":"vault_owner__v"}
Response example
{
"responseStatus": "SUCCESS",
"id": 67890
}
Update User
- Method: PUT
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/objects/users/{user_id} - Watch out for: PUT replaces only the fields provided; omitted fields retain existing values. However, required fields must still be valid if included.
Request example
PUT /api/v24.3/objects/users/67890
Authorization: {sessionId}
Content-Type: application/json
{"user_title__v":"Senior Manager","department__v":"Marketing"}
Response example
{
"responseStatus": "SUCCESS",
"id": 67890
}
Deactivate User
- Method: PUT
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/objects/users/{user_id} - Watch out for: Vault does not support hard-deletion of users. Deactivation (active__v=false) is the only supported offboarding action via API.
Request example
PUT /api/v24.3/objects/users/67890
Authorization: {sessionId}
Content-Type: application/json
{"active__v": false}
Response example
{
"responseStatus": "SUCCESS",
"id": 67890
}
Retrieve Current User (Me)
- Method: GET
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/objects/users/me - Watch out for: Useful for validating session credentials and retrieving the authenticated integration user's ID.
Request example
GET /api/v24.3/objects/users/me
Authorization: {sessionId}
Response example
{
"responseStatus": "SUCCESS",
"users": [{
"user": {"id": "12345", "user_name__v": "api_user@example.com"}
}]
}
Query Users via VQL
- Method: GET
- URL:
https://{vault_domain}.veevavault.com/api/v24.3/query - Watch out for: VQL (Vault Query Language) is SQL-like but not SQL. Unsupported SQL clauses (e.g., JOIN across unrelated objects) will return errors. URL-encode the q parameter.
Request example
GET /api/v24.3/query?q=SELECT+id,user_name__v,active__v+FROM+users+WHERE+active__v%3Dtrue
Authorization: {sessionId}
Response example
{
"responseStatus": "SUCCESS",
"responseDetails": {"total": 120},
"data": [
{"id": "12345", "user_name__v": "jane@example.com", "active__v": true}
]
}
Rate limits, pagination, and events
- Rate limits: Veeva Vault enforces API burst and daily limits. The official docs specify a burst limit of 200 API calls per 5-minute window per user session, and a daily limit of 500,000 API calls per vault. Limits apply across all Vault REST API endpoints.
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: The official API docs do not document rate-limit response headers or a Retry-After header. When limits are exceeded, the API returns HTTP 429. Vault recommends implementing exponential backoff.
- Pagination method: offset
- Default page size: 200
- Max page size: 1000
- Pagination pointer: offset and limit query parameters; response includes responseDetails.next_page URL when additional records exist
| Plan | Limit | Concurrent |
|---|---|---|
| All Vault plans (including PromoMats) | 200 calls per 5-minute window per session; 500,000 calls per vault per day | 0 |
- Webhooks available: No
- Webhook notes: Veeva Vault does not offer outbound webhooks for user lifecycle events in the traditional sense. Vault supports 'Vault Actions' and workflow notifications internally, but there is no documented push-webhook mechanism for user create/update/deactivate events to external systems.
- Alternative event strategy: Poll the /objects/users endpoint on a schedule using modified_date__v filtering via VQL to detect changes. Vault also supports Spark Messaging (internal event bus) for vault-to-vault integrations, but this is not a general-purpose external webhook system.
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://{vault_domain}.veevavault.com/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 /ServiceProviderConfig, GET /Schemas
Limitations:
- SCIM DELETE maps to deactivation (active=false), not hard deletion, consistent with Vault's user model.
- SCIM endpoint availability requires Enterprise plan; not available on lower tiers per context data.
- SCIM provisioning typically requires configuration through an IdP (e.g., Okta, Azure AD) using the Vault SCIM base URL and a dedicated SCIM OAuth token.
- Group provisioning support via SCIM /Groups endpoint availability should be verified against current Vault release notes as it may be limited.
- The SCIM token is a long-lived OAuth 2.0 bearer token generated in Vault Admin; it is separate from session-based API tokens.
Common scenarios
Three core automation scenarios are well-supported by the API:
Provisioning: POST to /objects/users with user_name__v, license_type__v, and security_profile__v as required fields. Retrieve valid license type and security profile values first via GET /objects/users/license_types and GET /objects/securityprofiles - submitting an unrecognized value returns INVALID_DATA with no partial creation. Bulk provisioning is supported via multipart/form-data CSV upload; the standard JSON endpoint is single-record only.
Delta sync for identity graph population: Query via VQL (GET /query) with a WHERE modified_date__v > '{last_sync_timestamp}' clause to retrieve only changed user records. Paginate with offset/limit parameters; continue until responseDetails.next_page is absent. VQL timestamps must be ISO 8601 UTC and URL-encoded - the FROM clause uses 'users' (no __v suffix), unlike the object API path.
Offboarding: PUT to /objects/users/{user_id} with active__v=false. Hard deletion is not supported anywhere in the Vault API; deactivation is the only offboarding action. Reactivation via active__v=true is possible and immediately resumes license consumption. Verify the state change with a follow-up GET before closing the offboarding ticket.
Onboard a new PromoMats user via REST API
- POST to /auth with admin credentials to obtain a sessionId.
- POST to /objects/users with required fields: user_name__v, user_first_name__v, user_last_name__v, user_email__v, license_type__v, security_profile__v.
- Capture the returned numeric user id from the response.
- Optionally assign the user to groups via PUT /objects/groups/{group_id} to add the user ID to the group's membership.
Watch out for: If license_type__v or security_profile__v values do not exactly match vault-configured values, the API returns INVALID_DATA with no partial creation. Retrieve valid values first via GET /objects/users/license_types and GET /objects/securityprofiles.
Sync active users from PromoMats to an external directory
- Authenticate and obtain sessionId.
- Issue a VQL query: GET /query?q=SELECT id,user_name__v,user_email__v,active__v,modified_date__v FROM users WHERE active__v=true
- Paginate using offset/limit until responseDetails.next_page is absent.
- Store modified_date__v per user; on subsequent syncs, add WHERE modified_date__v > '{last_sync_timestamp}' to retrieve only changed records.
Watch out for: VQL timestamps must be in ISO 8601 UTC format and URL-encoded. The FROM clause uses 'users' (lowercase, no __v suffix) in VQL, unlike object API paths.
Offboard a departing user
- Authenticate and obtain sessionId.
- If only the username is known, query via VQL: SELECT id FROM users WHERE user_name__v='departing@example.com' to get the numeric user ID.
- PUT to /objects/users/{user_id} with body {"active__v": false} to deactivate the account.
- Verify deactivation by GET /objects/users/{user_id} and confirming active__v is false in the response.
Watch out for: Deactivation is irreversible via standard API in the sense that content ownership and audit history are retained. Reactivation is possible by setting active__v=true, but license consumption resumes immediately.
Why building this yourself is a trap
The most consequential caveat for integrators is that Veeva Vault has no outbound webhook mechanism for user lifecycle events. There is no push notification when a user is created, updated, or deactivated - external systems must poll /objects/users on a schedule.
Vault's internal Spark Messaging bus handles vault-to-vault events but is not a general-purpose external webhook system.
Two additional traps are worth flagging explicitly. First, session tokens expire on inactivity and must be re-authenticated programmatically; do not cache them indefinitely.
Running parallel sessions multiplies effective throughput against the per-session rate limit but accelerates consumption of the vault-level daily cap.
Second, the vault subdomain is instance-specific and must be retrieved from Vault Admin or via API endpoint discovery - there is no single global base URL, which complicates multi-vault identity graph designs where PromoMats is one node among several Vault products.
Automate Veeva PromoMats 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.