The eight resource families in the Volunteer API, how they relate, and the central Participation pattern that ties Users to specific Project Dates.
The Volunteer API’s data model is built around volunteer engagement — Users who participate in Projects on specific Dates, organized into Groups, attached to Campaigns, owned by Organizations. This page walks through the eight resource families, how they relate, and the central Participation pattern that’s unique to volunteer management.The audience is partner integration engineers and architects building or designing against the Volunteer API. Understanding this model first prevents a lot of confusion downstream.
The central pattern: User ↔ Participation ↔ Project Date
The most important relationship in the model is the Participation triangle:When a volunteer (User) participates in a specific scheduled occurrence (Project Date) of a recurring opportunity (Project), a Participation record is created. The Participation captures:
Field
What it captures
signed_up_at
When the volunteer registered for the Project Date
checked_in_at
When they actually arrived
checked_out_at
When they left
hours
Total time volunteered (fractional — e.g., 2.5 hours)
verified
Whether the organizer confirmed the participation
role
What role they played (Volunteer, Team Lead, etc.)
guests
How many additional people they brought
project_id
The Project the participation is for
project_date_id
The specific Project Date
Participations are how the API answers questions like:
Question
How Participation answers it
”How many hours has Bruce volunteered this year?”
Sum hours across his participations with signed_up_at in 2025
”Who’s confirmed for the food bank shift on Saturday?”
List participations with that project_date_id
”Has this user shown up for any project they signed up for?”
Compare checked_in_at to signed_up_at
”Which users are repeat volunteers?”
Group participations by user_id, count distinct project_id
There’s no top-level GET /participations endpoint. Participations are always returned embedded in another resource’s response:
Where participations appear
Endpoint
As an array on a user’s detail response
GET /users/{id} returns UserDetailResource.participations
As participant entries on a Project Date
GET /projects/date/{id} includes participants
Indirectly visible in Project responses
Through participant counts and similar aggregates
For partner integrations that need a flat “all participations” view, the typical pattern is:
List all users via GET /users with pagination.
For each user, GET /users/{id} to get their participations.
Flatten across all users.
This is N+1 — one initial query plus one per user. For large customers, it’s expensive. See API Performance Tips for the optimization patterns.
⚠️ Spec gap (audit #40, #41): The ParticipationResource schema types hours as integer and project_id / project_date_id as string. The live API returns fractional hours (e.g., "4.00") and integer IDs. Code should parse hours as a float and IDs as integers regardless of the spec’s type declaration.
A Project is the volunteer opportunity itself — what it is, what’s being asked of volunteers, what skills are needed, what the organization wants to accomplish. It’s a long-lived record that persists across many actual occurrences.
Project: "Saturday Food Bank Volunteer Shift" • Description: Sort donated food, pack boxes for delivery • Address: 123 Charity Lane • Skills needed: Lifting up to 25 lbs • Age limit: 16+ • Privacy: Public • [recurring schedule — runs many Saturdays]
The Project has fields like name, description, organization, address, age_limit, privacy, categories, and so on.
All Project Dates happening today (across all Projects)
⚠️ Spec gap (audit #34): The endpoint GET /projects/date/{id} uses a verb-like singular noun (date) in the path, which is inconsistent with REST conventions. The audit recommends renaming to GET /projects/{projectId}/dates/{dateId}. The current path is what works against the live API today; the alternative path may exist in a future v2.
A common bug: querying /projects/{id} expecting to get the participants for an upcoming shift, then being surprised that it returns the template’s metadata instead. The participants are on the Project Date, not the Project.
Organizations in VOMO can have parent-child relationships. The customer’s primary Organization may have children — sub-organizations like local chapters, regional offices, or affiliated nonprofits — and may itself be a child of a larger umbrella organization.
The family of organizations the token has access to
GET /organizations/{id}
A single organization with its parent/child relationships
⚠️ Spec gap (audit #25, #26, #27): The Organization endpoints have empty schema: {} in the spec — the response shape is documented only through inline examples. There is no OrganizationResource component schema. The “organization family” concept is mentioned in the endpoint summary but isn’t formally documented. Build parsers from the actual response shapes.
Forms in VOMO are data-collection templates — waivers, signups, profile fields, application questions. Volunteers complete Forms; the completion is captured as a Form Completion.The relationships:
Resource
Description
Form
The template — questions, validation rules, field types
Form Field
An individual question within a Form (with options for dropdowns, etc.)
Form Completion
A volunteer’s specific submission of a Form, with their answers
Form Field Response
A volunteer’s answer to a specific field within a Completion
Forms are attached to Projects in the admin UI; the partner integration reads them through the endpoints above. Common use cases include waivers (signed once per volunteer), event-specific signups (additional info beyond standard Project signup), and post-event surveys.
The form_type field on FormResource has documented enum values, though the audit (#54) flags that only "PROJECT" is documented and other types likely exist.
Certificates represent training credentials, badges, or other achievements that volunteers earn. Common uses include:
Required safety training (e.g., food handler certification for food bank volunteers)
Background check completion
Specialized role qualifications (e.g., team leader training)
Completion badges for milestone hours
Endpoint
What it returns
GET /certificates
All Certificates in the customer’s organization
⚠️ Spec gap (audit #14): The same resource is referred to as “certificate”, “certification”, and “certificates” in different places in the spec — the path is /certificates, the tag is Certifications, the operationId is listCertifications, and the schema is CertificateResource. The naming will be reconciled in a future spec revision. For partner integrations today, use the path (/certificates) and the schema name (CertificateResource) as your reference points.
Certificate earning is recorded per-user; the User Detail response (GET /users/{id}) typically includes earned certificates alongside other user data.
Groups in VOMO organize Users into collections — teams, affinity groups, chapter members, etc. Unlike Projects (which represent opportunities), Groups represent stable collections of people that persist independent of specific events.The relationships:
Resource
Description
Group
A named collection of Users
Group Member
The relationship between a User and a Group
Parent Group
A Group can have a parent Group (for hierarchical groupings)
Groups have the most write-heavy surface of any Volunteer resource:
Endpoint
What it does
GET /groups
List Groups (filterable by parent_id, name_like, etc.)
POST /groups
Create a Group
GET /groups/{id}
Get a Group’s details
PUT /groups/{id}
Update a Group (full replacement)
DELETE /groups/{id}
Delete a Group
GET /groups/{id}/members
List the Users in a Group
PUT /groups/{id}/members
Replace the Group’s member list
This makes Groups the canonical “build a volunteer team programmatically” resource. Common patterns include:
Syncing external team rosters into VOMO Groups
Building Groups based on participation criteria (e.g., “active volunteers in 2024”)
Creating event-specific Groups for managing rosters
⚠️ Spec gap (audit #21, #22):POST /groups returns 200 (not 201 per HTTP convention for resource creation). DELETE /groups/{id} returns 200 (not 204 per convention for empty-body deletes). These are spec-confirmed behaviors; integrations should code for what the API actually returns, not what HTTP conventions suggest.
All Campaigns (filterable by org_slug, name_like, etc.)
GET /campaigns/{id}
A single Campaign with embedded Project list
Campaigns are read-only from the API — they’re managed in the VOMO admin UI. Partner integrations use them for reporting and attribution (“how is the Summer Outreach campaign performing?”).
⚠️ Spec gap (audit #4, #13): The Campaign endpoints have empty schema: {} in the spec, and CampaignResource description erroneously reads “A VOMO Project.” Build parsers from actual response shapes.
Several resource types that exist in VOMO but aren’t currently exposed in the API:
Resource
Why partner integrations should know
Participation creation/update
Volunteer hours are recorded in the admin UI; no API path to create or modify participations
Project Date scheduling
Project Dates are managed in the admin UI; no API path to create or modify them
Form submission
Volunteers complete Forms through the VOMO UI; no API path to submit a Form response
Certificate awarding
Certificates are awarded in the admin UI
Volunteer time tracking
Live clock-in/clock-out happens in the admin UI
For partner integrations needing to record participations programmatically, coordinate with VOMO’s admin team for alternative paths — typically through Group management or scheduled imports.See Understand Write Limitations for the complete picture.
Across the API, several patterns govern how IDs work:
ID type
Stability
Notes
user.id
Stable
The primary identifier for a User across all references
project.id
Stable
The primary identifier for a Project (the template)
project_date.id
Stable
The primary identifier for a specific occurrence
group.id
Stable
The primary identifier for a Group
campaign.id
Stable
The primary identifier for a Campaign
organization.id
Stable
The primary identifier for an Organization
organization.slug
Stable but human-readable
Used for filtering (e.g., ?org_slug=wayne-foundation)
form_completion.id
Stable
One per submission
User matching for upsert (POST /users) is done by email — submitting a user with an existing email updates that user; submitting with a new email creates a new one. See Users: upsert behavior.
For partners integrating Volunteer alongside CRM+ or Raise, the conceptual mappings:
Volunteer concept
CRM+ equivalent
Raise equivalent
User
Contact
Donor
Participation
Activity (volunteer activity)
(no equivalent)
Project Date
Activity (event)
(no equivalent)
Campaign
Communication
Campaign
Group
Communication audience / Segment
Segment
Organization
(organization itself)
(organization itself)
Note these are conceptual analogs, not exact matches. A Volunteer User is roughly equivalent to a CRM+ Contact in role (the person record), but the field shapes and write paths differ significantly.For partner integrations syncing across all three APIs, the typical pattern is:
Match by email when joining records across APIs
Treat each API’s records as authoritative for their own domain (User in Volunteer, Contact in CRM+, Donor in Raise)
Use partner-side mapping tables when the same person needs distinct records in each system
See the Volunteer Recipes for examples of cross-API integration patterns.