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

Important constraint: Both POST /projects and PUT /projects/{id} require a dates array as part of the body. The schedule isn’t a separate concern from the Project — submitting a Project without dates fails validation. See The dates requirement below.

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:
  1. Look up whether the external Project has a known VOMO Project ID (from a mapping table)
  2. If yes → PUT /projects/{vomoId}
  3. If no → POST /projects and record the resulting VOMO ID
See Scenario 1: Sync Projects from an external system.

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.
⚠️ Spec gap (audit #43): address is typed as array in ProjectResource (the read shape), though logically and likely in practice it’s an object. For write bodies, send it as an object matching the read shape you observe from the live API.

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
The response returns the created Project with its assigned 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 Project is created with its full eight-week schedule in one request — no separate Project Date creation needed.

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
The cache is invalidated after each update because local state is stale post-PUT.

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:
Without this table, you can’t reliably know “is this external Project already in VOMO?” — leading to duplicate creates and orphaned VOMO records.

Scenario 2: Bulk Project creation

For one-time setup imports (a new customer onboarding with many existing Projects):
JavaScript
Throttle aggressively for bulk creates — each request includes a full Project with potentially many Project Dates, making the work-per-request substantial.

Scenario 3: Adjust a Project’s schedule

Modifying the schedule is the same as any other update — full PUT with the modified dates array:
JavaScript

The “can’t have zero dates” constraint

Because dates 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 the dates 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.
The exact behavior on omission (whether participations are also deleted, or whether the Project Date is soft-deleted) isn’t documented in the spec. Coordinate with VOMO support before relying on this for production workflows that touch Dates with existing participations.

Handling validation errors

A 422 Unprocessable Entity response means the request body failed validation:
Surface the field-level errors:
JavaScript
Common validation issues: See Error Handling: 422 Validation Error.

A reference Project upsert client

JavaScript
The 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.
Last modified on May 22, 2026