POST /users distinctive.
The three endpoints
That’s the entire User write surface: one upsert endpoint that handles both create and update. There’s no separate
PUT /users/{id} for updates.
The User resource
List shape (UserResource)
GET /users returns an array of UserResource objects — the abbreviated profile fields suitable for list display and filtering:
Detail shape (UserDetailResource)
GET /users/{id} returns a UserDetailResource — a superset of UserResource plus two additional fields:
The
UserDetailResource is what you get when you have a specific User and want the full picture. It includes the base UserResource fields along with the detail-only arrays. The UserResource is what you get when iterating through many Users for filtering or display.
Membership concepts
Three status fields capture the User’s relationship to the customer’s organization:
The response field can have other values, but the
membership_status query parameter on the read endpoints accepts only ACCEPTED or LOBBY. Handle unfamiliar response values gracefully if new statuses are introduced.
Listing users
cURL
Available filters
All filters use snake_case. The
*_like filters are case-insensitive substring matches. The *_before / *_after filters work on the corresponding timestamp fields.
The list is scoped to the API key’s organization, not its entire organization family. Without membership_status, it includes only accepted members, even for organizations enabled for Lobby access. A search that matches only Lobby users returns 200 with an empty data array unless you explicitly request LOBBY.
Accessing Lobby profiles
Lobby profile reads are available only when Virtuous enables theuse-api-lobby-users entitlement for the organization associated with your API key. The entitlement is not enabled by default; an API key alone does not grant Lobby access, and there is no additional per-key scope to request. Contact Virtuous to have access enabled for your organization. This feature is deployed in production and has been enabled and tested for Uplift Education; that customer-specific activation does not make it generally available.
Use the same endpoints as for accepted members, with an explicit membership_status=LOBBY query parameter:
cURL
200 with a data object, for example:
membership_status: "LOBBY"), but not the detail-only participations or profile_field_values. The detail response includes these arrays when applicable; participation records and profile field values are restricted to the API key organization, so data from another organization is not included. Empty arrays above are illustrative, not guaranteed for every Lobby user.
Send
Accept: application/json for JSON error responses. Other list filters and pagination also apply when listing Lobby users. The membership_status parameter only filters reads; it does not approve a Lobby user or change their status.
Common list patterns
Recently active users:JavaScript
JavaScript
JavaScript
email_like is a substring match, so a search for bruce@wayne.example might match bruce@wayne.example.com if such an address exists. Confirm with strict equality after the API call.
Fetching a single user
cURL
UserDetailResource wrapped in data:
data as a single object, not an array.
Participations
Theparticipations field on UserDetailResource is an array of the User’s participation records:
Profile field values
Theprofile_field_values field captures the User’s responses to custom profile fields configured in the customer’s VOMO account:
field_id (stable identifier), field_label (the human-readable name set in the admin UI), and value (the User’s response).
For partner integrations that need to surface or filter on custom data, this is the access path. Look up by field_label for readability or by field_id for stability.
Creating and updating users (the upsert)
POST /users is unusual — it creates OR updates a User, with the behavior determined by whether the submitted email already exists.
How matching works
The match is on
email. There’s no other way to identify the User for an update — no separate PUT /users/{id} exists.
⚠️ Spec gap (audit #47): The
operationId is createUser but the endpoint is functionally an upsert. The spec correctly documents both 200 (updated) and 201 (created) response codes, but the operation name doesn’t reflect the upsert behavior. Future spec revisions may rename this to upsertUser and/or split into separate create and update endpoints.The request
Detecting create vs. update
The response status code distinguishes the two cases:JavaScript
Request body fields
Required fields for an upsert (typically — confirm against live API for the exact set):
Optional fields:
Validation errors
If the request body fails validation (missing required fields, invalid email format, etc.), the API returns422 Unprocessable Entity with a structured error response. See Error Handling: 422 Validation Error.
JavaScript
Common workflows
Sync from an external system
For partner integrations syncing volunteer records from an external CRM or HR system:JavaScript
Bulk import
For one-time imports of volunteer rosters, throttle to stay within rate limits:JavaScript
Find or create
JavaScript
Calculate volunteer hours
JavaScript
parseFloat(p.hours) rather than treating it as an integer — see the audit-flagged type issue.
What can’t be done via the API
For most of these, the customer’s admin team handles the action through the VOMO UI. See Understand Write Limitations for the broader picture.
ID and matching considerations
A few practical patterns for partner integrations:Email is the matching key
Volunteer matches Users by email for upsert. Implications:- Email changes break matching. If a User changes their email in your external system, a subsequent upsert will create a new VOMO User rather than update the existing one. Track email history in your integration to handle this case.
- Email case is normalized. Searches and matches are case-insensitive. Submit emails in any case; the API handles it.
- Email is treated as the canonical identifier. Two records with the same email are considered the same person — there’s no way to have two Users with the same email.
Map between systems by both ID and email
For partner integrations that sync Users across systems, maintain a mapping table:JavaScript
When IDs are unknown
If your integration needs to find a User but doesn’t have a VOMO ID:
For programmatic flows where ambiguity is unacceptable, fall back to a surfaced-for-human-review path rather than guessing.
A reference user client
A minimal, well-organized User client:JavaScript
Where to go next
Projects and Project Dates
The volunteer opportunities Users participate in.
The Volunteer Data Model
The full data model context — how Users relate to other resources.
List Users with Filters
The workflow walkthrough for filtered User reads.
Create or Update a User
The upsert workflow in workflow-page depth.