POST /projects and PUT /projects/{id} are the two write endpoints for Projects in the Volunteer API. Unlike Users (which have a single upsert endpoint), Projects have explicit create and update endpoints — but both share the same required field set, and updates are full-record replacement rather than partial PATCH.
This workflow page covers the practical mechanics: the required field set (including the often-overlooked dates requirement), the GET-then-PUT pattern for safe partial updates, syncing Projects from external systems, and the audit-flagged quirks partner integrations should know about.
If you haven’t yet, skim the Projects and Project Dates concept page for the field reference and the Project vs. Project Date distinction.
When to use this workflow
The two endpoints
Unlike Users, there’s no auto-upsert behavior — you must know whether you’re creating or updating and call the right endpoint. For partner integrations syncing Projects from external systems, the pattern is typically:
- Look up whether the external Project has a known VOMO Project ID (from a mapping table)
- If yes →
PUT /projects/{vomoId} - If no →
POST /projectsand record the resulting VOMO ID
The request body
POST /projects and PUT /projects/{id} share the same body shape. Required fields:
Optional fields:
⚠️ Spec gap (audit #33): Some field descriptions in the spec for
PUT /projects/{id} contain placeholder text ("Update Project" rather than meaningful descriptions). The field shapes themselves match POST /projects, but the descriptive content is incomplete. The field set on this page comes from the POST endpoint (where descriptions are correct) and is the working contract for both.The dates requirement
The most distinctive part of the Project body: dates is a required field. You can’t create a Project without at least one scheduled Project Date.
This shapes the integration flow significantly:
The exact field shape inside the
dates array isn’t fully specified by the spec. The fields typically expected per Project Date: starts_at (ISO 8601 datetime), ends_at (ISO 8601 datetime), and possibly fields like address if the date overrides the Project’s. Confirm against live API behavior or coordinate with VOMO support for the canonical shape.A minimal create
cURL
id — capture this for subsequent updates.
A create with recurring dates
For a Project running on a recurring schedule (e.g., every Saturday for a month):JavaScript
The GET-then-PUT pattern for updates
PUT /projects/{id} is full-record replacement — submit the entire Project’s data, not just the fields you want to change. Fields omitted from the request are subject to being set to defaults or null.
For partial updates, fetch the current record first, merge your changes, then PUT the full record back:
JavaScript
What rebuildDateForPut is doing
The all_dates field on ProjectDetailResource (the GET response) includes per-Date metadata that may not belong in the PUT body — things like participant_count, id, etc. The PUT expects a clean array of date objects; the helper strips read-only fields to produce that.
Without this transformation, your PUT might either:
- Include unexpected fields the API rejects
- Modify Project Date IDs in ways that confuse downstream consumers
- Persist read-only fields back, causing silent data drift
When GET-then-PUT is necessary
Always. Without it, omitting fields effectively clears them. A “quick update of the age limit” without the GET-then-PUT pattern would wipe the description, settings, and schedule. The cost: one extra read per update. For partner integrations with high update frequency, cache the GET result:JavaScript
Scenario 1: Sync Projects from an external system
For partner integrations whose customer manages Projects in an external scheduling system:JavaScript
The external-ID-to-VOMO-ID mapping
Unlike Users (where email is the natural matching key), Projects have no equivalent matching key in VOMO. The mapping table is essential:Scenario 2: Bulk Project creation
For one-time setup imports (a new customer onboarding with many existing Projects):JavaScript
Scenario 3: Adjust a Project’s schedule
Modifying the schedule is the same as any other update — full PUT with the modifieddates array:
JavaScript
The “can’t have zero dates” constraint
Becausedates is required (and the array must be non-empty), you can’t reduce a Project’s schedule to nothing via the API. Practical implications:
- Removing all Project Dates effectively requires admin-UI involvement (or deleting the Project entirely, which also requires admin UI).
- Schedule cleanup workflows should keep at least one placeholder Date until the Project is intentionally ended.
Preserving Project Date IDs
Existing Project Dates have stable IDs that are referenced from Participation records. When updating thedates array:
- Existing Dates kept in the PUT preserve their IDs and participations.
- New Dates added to the PUT get newly-assigned IDs.
- Existing Dates omitted from the PUT are removed; their participations may be affected.
Handling validation errors
A422 Unprocessable Entity response means the request body failed validation:
JavaScript
See Error Handling: 422 Validation Error.
A reference Project upsert client
JavaScript
updateField helper does the GET-then-PUT in one call — convenient for single-field updates without the boilerplate.
What can’t be done via the API
See Understand Write Limitations for the full picture.
Where to go next
Read a Project's Schedule
The companion read workflow for the Project Dates you’ve scheduled.
Manage Groups and Members
The other write-heavy workflow in the Volunteer API.
Projects and Project Dates
The reference page for Project resource fields and relationships.
Understand Write Limitations
The explicit list of what the API can and can’t do for Projects.