Summary and recommendation
Vitally's REST API is available at https://rest.vitally.io/resources and uses HTTP Basic Auth - the API secret key is passed as the username with an empty password, Base64-encoded into the Authorization header.
Note the subdomain carefully: requests to api.vitally.io will return 404s.
The API is the only programmatic path for user lifecycle management, as Vitally exposes no SCIM 2.0 endpoint and no outbound webhooks for user-management events.
Within an identity graph, Vitally user records must always be anchored to a parent account via accountExternalId - users cannot exist as free-floating entities.
API quick reference
| Has user API | Yes |
| Auth method | HTTP Basic Auth (API secret key as username, empty password) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | N/A |
Authentication
Auth method: HTTP Basic Auth (API secret key as username, empty password)
Setup steps
- Log in to Vitally and navigate to Settings > Integrations > API.
- Generate or copy your API Secret Key.
- Base64-encode the string '
:' (colon with empty password). - Pass the encoded value in the Authorization header: 'Authorization: Basic
'.
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| externalId | string | Your system's unique identifier for the user | required | required (used as lookup key) | Must be unique per account |
| accountExternalId | string | External ID of the account this user belongs to | required | required | Account must exist or be created first |
| name | string | Full display name of the user | optional | optional | |
| string | User's email address | optional | optional | Used for deduplication and notifications | |
| createdAt | ISO 8601 datetime string | Timestamp when the user was created in your system | optional | optional | Defaults to ingest time if omitted |
| traits | object | Key-value map of custom user attributes | optional | optional | Trait keys must be pre-defined in Vitally settings or will be auto-created |
| mrr | number | Monthly recurring revenue attributed to the user | optional | optional | |
| npsScore | integer | NPS score for the user (0–10) | optional | optional |
Core endpoints
Upsert (create or update) a user
- Method: POST
- URL:
https://rest.vitally.io/resources/users - Watch out for: This endpoint upserts by externalId. If the user already exists it will be updated; no separate PUT endpoint is needed.
Request example
POST /resources/users
Authorization: Basic <token>
Content-Type: application/json
{
"externalId": "usr_123",
"accountExternalId": "acct_456",
"name": "Jane Doe",
"email": "jane@example.com"
}
Response example
{
"id": "vit_usr_abc",
"externalId": "usr_123",
"name": "Jane Doe",
"email": "jane@example.com",
"createdAt": "2024-01-15T10:00:00Z"
}
List users
- Method: GET
- URL:
https://rest.vitally.io/resources/users - Watch out for: Pagination uses a 'next' cursor token. Pass it as a query param on subsequent requests to retrieve the next page.
Request example
GET /resources/users?limit=100
Authorization: Basic <token>
Response example
{
"results": [{"id": "vit_usr_abc", "externalId": "usr_123", ...}],
"next": "cursor_token_xyz"
}
Get a single user by externalId
- Method: GET
- URL:
https://rest.vitally.io/resources/users/:externalId - Watch out for: The path parameter is your externalId, not Vitally's internal id.
Request example
GET /resources/users/usr_123
Authorization: Basic <token>
Response example
{
"id": "vit_usr_abc",
"externalId": "usr_123",
"email": "jane@example.com",
"traits": {"plan": "pro"}
}
Delete a user
- Method: DELETE
- URL:
https://rest.vitally.io/resources/users/:externalId - Watch out for: Deletion is permanent. Associated events and traits are also removed.
Request example
DELETE /resources/users/usr_123
Authorization: Basic <token>
Response example
HTTP 204 No Content
Upsert an account (required before creating users)
- Method: POST
- URL:
https://rest.vitally.io/resources/accounts - Watch out for: Users must reference a valid accountExternalId. Create the account first or the user upsert will fail.
Request example
POST /resources/accounts
Authorization: Basic <token>
Content-Type: application/json
{
"externalId": "acct_456",
"name": "Acme Corp"
}
Response example
{
"id": "vit_acct_def",
"externalId": "acct_456",
"name": "Acme Corp"
}
Track a user event
- Method: POST
- URL:
https://rest.vitally.io/resources/events - Watch out for: Events are tied to both user and account. Missing either externalId will cause the event to be dropped.
Request example
POST /resources/events
Authorization: Basic <token>
Content-Type: application/json
{
"userExternalId": "usr_123",
"accountExternalId": "acct_456",
"event": "Feature Used",
"timestamp": "2024-06-01T12:00:00Z"
}
Response example
HTTP 200 OK
{"status": "ok"}
List users for a specific account
- Method: GET
- URL:
https://rest.vitally.io/resources/accounts/:accountExternalId/users - Watch out for: Returns only users scoped to the given account. Use the top-level /users endpoint to list across all accounts.
Request example
GET /resources/accounts/acct_456/users
Authorization: Basic <token>
Response example
{
"results": [{"externalId": "usr_123", "email": "jane@example.com"}],
"next": null
}
Rate limits, pagination, and events
- Rate limits: Vitally enforces rate limits on REST API requests. Official docs note a limit of 60 requests per minute per API key.
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: Official docs mention the 60 req/min limit but do not document specific rate-limit response headers or Retry-After behavior.
- Pagination method: cursor
- Default page size: 100
- Max page size: 100
- Pagination pointer: next
| Plan | Limit | Concurrent |
|---|---|---|
| All plans | 60 requests per minute | 0 |
- Webhooks available: No
- Webhook notes: Vitally does not expose outbound webhooks from its REST API for user-management events. Data flows are primarily inbound (push to Vitally). Notifications and alerts are configured within the Vitally UI.
- Alternative event strategy: Use Vitally's native integrations (e.g., Segment, Zapier) or poll the REST API to detect changes.
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: N/A
- Endpoint: Not documented
Limitations:
- Vitally does not offer a native SCIM 2.0 endpoint.
- No IdP-native SCIM provisioning (Okta, Entra, Google Workspace, OneLogin) is documented.
Common scenarios
Three integration patterns cover the majority of use cases.
First, new-user provisioning: upsert the account via POST /resources/accounts, then upsert the user via POST /resources/users with externalId, accountExternalId, email, and traits account must exist first or the user upsert will fail.
Second, profile sync on plan or attribute change: re-POST to /resources/users with the same externalId;
traits are merged, not replaced, so set unwanted keys explicitly to null.
Third, bulk historical load: there is no batch endpoint, so each user must be upserted individually;
throttle to 60 requests per minute and implement exponential backoff on 429 responses, as no Retry-After header is documented.
Pagination across all users uses an opaque cursor token passed as the next query parameter - numeric offsets are not supported.
Provision a new user when they sign up in your product
- Upsert the account: POST /resources/accounts with externalId and name.
- Upsert the user: POST /resources/users with externalId, accountExternalId, name, email, and any relevant traits.
- Optionally track a 'User Signed Up' event: POST /resources/events with userExternalId, accountExternalId, event name, and timestamp.
Watch out for: Always create or upsert the account before the user. A missing accountExternalId will cause the user upsert to fail.
Sync updated user profile attributes (e.g., plan change)
- Construct the user payload with the updated traits object (e.g., {"plan": "enterprise"}).
- POST /resources/users with the same externalId and accountExternalId - the upsert will merge trait changes.
- Verify the response returns the updated user object.
Watch out for: Traits are merged, not replaced wholesale. To remove a trait value, explicitly set it to null in the traits object.
Bulk-load historical users from your database
- Export users from your database grouped by account.
- For each account, POST /resources/accounts to upsert it.
- For each user in the account, POST /resources/users with externalId, accountExternalId, email, createdAt, and traits.
- Throttle requests to stay under 60 req/min; implement exponential backoff on 429 responses.
Watch out for: There is no bulk/batch endpoint. Each user must be upserted individually. For large datasets, parallelize carefully while respecting the rate limit.
Why building this yourself is a trap
Several non-obvious behaviors will cause silent failures or data quality issues at scale. Custom trait keys sent via the API are auto-created in Vitally if they do not already exist, which can produce schema sprawl across the identity graph over time - establish a trait naming convention before bulk-loading.
Deleting a user via DELETE /resources/users/:externalId is permanent and removes associated event history; there is no soft-delete or deactivation state exposed through the API. Rate-limit headers are not documented, so client-side throttling logic must be implemented proactively rather than reactively.
Finally, the POST /users endpoint is a pure upsert keyed on externalId - there is no separate PATCH, and no way to distinguish a net-new create from an update in the response without querying first.
Automate Vitally 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.