Skip to main content
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 eight resource families

The eight resource families:
FamilyWhat it representsRead endpointWrite endpoints
UserA volunteer (or anyone with a VOMO account)GET /users, GET /users/{id}POST /users (upsert)
ProjectA volunteer opportunity template (the recurring “what”)GET /projects, GET /projects/{id}POST /projects, PUT /projects/{id}
Project DateA specific scheduled occurrence of a Project (the “when”)GET /projects/date/{id}, GET /projects/todayNone
ParticipationA record of a User attending a Project DateEmbedded in UserDetailResource and Project Date responsesNone
GroupA grouping of Users (team, affinity, etc.)GET /groups, GET /groups/{id}, GET /groups/{id}/membersPOST /groups, PUT /groups/{id}, DELETE /groups/{id}, PUT /groups/{id}/members
CampaignAn umbrella initiative spanning multiple ProjectsGET /campaigns, GET /campaigns/{id}None
OrganizationThe customer’s organization (with parent-child hierarchy)GET /organizations, GET /organizations/{id}None
FormA data-collection form attached to a ProjectGET /forms, GET /forms/{id}/completions, GET /forms/{id}/completions/{completion}None
CertificateA training credential or achievementGET /certificatesNone
Two patterns to notice from this table:
PatternImplication
Read-heavy20 of 24 endpoints are reads. The API is designed primarily for data extraction.
No Participation writesParticipations are recorded through the VOMO admin UI (not via API). Partner integrations can read them but cannot create them programmatically.

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:
FieldWhat it captures
signed_up_atWhen the volunteer registered for the Project Date
checked_in_atWhen they actually arrived
checked_out_atWhen they left
hoursTotal time volunteered (fractional — e.g., 2.5 hours)
verifiedWhether the organizer confirmed the participation
roleWhat role they played (Volunteer, Team Lead, etc.)
guestsHow many additional people they brought
project_idThe Project the participation is for
project_date_idThe specific Project Date
Participations are how the API answers questions like:
QuestionHow 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

Reading participations

There’s no top-level GET /participations endpoint. Participations are always returned embedded in another resource’s response:
Where participations appearEndpoint
As an array on a user’s detail responseGET /users/{id} returns UserDetailResource.participations
As participant entries on a Project DateGET /projects/date/{id} includes participants
Indirectly visible in Project responsesThrough participant counts and similar aggregates
For partner integrations that need a flat “all participations” view, the typical pattern is:
  1. List all users via GET /users with pagination.
  2. For each user, GET /users/{id} to get their participations.
  3. 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.

Project vs. Project Date

A common source of confusion: the difference between a Project and a Project Date.

Project = the template (the “what”)

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.

Project Date = the specific occurrence (the “when”)

A Project Date is one specific scheduled occurrence of a Project. The same Project can have dozens or hundreds of Project Dates over its lifetime.
Project Date #1011: occurrence of "Saturday Food Bank Volunteer Shift"
  • Starts: 2025-03-15 09:00
  • Ends: 2025-03-15 13:00
  • Participant count: 15
  • [participations attached]
The Project Date has fields like id, starts_at, ends_at, participant_count, and the embedded participants for that date.

In the API

EndpointWhat it returns
GET /projects/{id}The Project (template) with all metadata
GET /projects/date/{id}A specific Project Date with its participants
GET /projects/todayAll 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.

Why this matters for integrations

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.
GoalEndpoint
Get the opportunity’s description and policiesGET /projects/{id}
Get who’s signed up for a specific shiftGET /projects/date/{dateId}
Get all shifts happening todayGET /projects/today

Organization hierarchy: the “organization family”

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.

What this enables

  • A single API token can be scoped to access the parent organization and its children.
  • Reports and aggregations span the full organization family by default.
  • Projects, Campaigns, and Groups are owned by specific organizations within the family.

What the API exposes

EndpointWhat it returns
GET /organizationsThe 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.

Per-resource organization attribution

Most resources track which organization in the family they belong to:
ResourceOrg-attribution fields
Projectorganization, organization_id, organization_slug
Groupparent_id (the parent group, not the parent organization)
CampaignFilterable by org_slug query parameter
Usermembership_status, membership_role (within the current org)
When the partner integration is reading data, the attribution lets them route or filter records to the right organization within the family.

Forms: data-collection attached to Projects

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:
ResourceDescription
FormThe template — questions, validation rules, field types
Form FieldAn individual question within a Form (with options for dropdowns, etc.)
Form CompletionA volunteer’s specific submission of a Form, with their answers
Form Field ResponseA volunteer’s answer to a specific field within a Completion

Where Forms live

EndpointWhat it returns
GET /formsAll Forms in the customer’s organization
GET /forms/{id}/completionsAll Completions of a specific Form
GET /forms/{id}/completions/{completionId}A single Completion with all field responses
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.

Form types

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: training and achievements

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
EndpointWhat it returns
GET /certificatesAll 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: cross-cutting User collections

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:
ResourceDescription
GroupA named collection of Users
Group MemberThe relationship between a User and a Group
Parent GroupA Group can have a parent Group (for hierarchical groupings)
Groups have the most write-heavy surface of any Volunteer resource:
EndpointWhat it does
GET /groupsList Groups (filterable by parent_id, name_like, etc.)
POST /groupsCreate 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}/membersList the Users in a Group
PUT /groups/{id}/membersReplace 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.

Campaigns: longer-running initiatives

Campaigns group multiple related Projects under a single umbrella initiative. They represent the customer’s longer-term goals or themes:
Campaign: "Summer Outreach 2025"
  ├── Project: "June Food Drive"
  ├── Project: "July Park Cleanup"
  └── Project: "August Volunteer Fair"
EndpointWhat it returns
GET /campaignsAll 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.

What’s NOT in the API

Several resource types that exist in VOMO but aren’t currently exposed in the API:
ResourceWhy partner integrations should know
Participation creation/updateVolunteer hours are recorded in the admin UI; no API path to create or modify participations
Project Date schedulingProject Dates are managed in the admin UI; no API path to create or modify them
Form submissionVolunteers complete Forms through the VOMO UI; no API path to submit a Form response
Certificate awardingCertificates are awarded in the admin UI
Volunteer time trackingLive 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.

ID stability and matching

Across the API, several patterns govern how IDs work:
ID typeStabilityNotes
user.idStableThe primary identifier for a User across all references
project.idStableThe primary identifier for a Project (the template)
project_date.idStableThe primary identifier for a specific occurrence
group.idStableThe primary identifier for a Group
campaign.idStableThe primary identifier for a Campaign
organization.idStableThe primary identifier for an Organization
organization.slugStable but human-readableUsed for filtering (e.g., ?org_slug=wayne-foundation)
form_completion.idStableOne 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.

Cross-API mapping

For partners integrating Volunteer alongside CRM+ or Raise, the conceptual mappings:
Volunteer conceptCRM+ equivalentRaise equivalent
UserContactDonor
ParticipationActivity (volunteer activity)(no equivalent)
Project DateActivity (event)(no equivalent)
CampaignCommunicationCampaign
GroupCommunication audience / SegmentSegment
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.

Where to go next

Users

The User resource reference in depth — fields, filters, upsert behavior.

Projects and Project Dates

The Project (template) vs. Project Date (occurrence) distinction in full detail.

Groups

The most write-heavy resource — Group management and members.

Understand Write Limitations

The explicit list of what the API can and can’t do.
Last modified on May 21, 2026