> ## Documentation Index
> Fetch the complete documentation index at: https://docs.virtuous.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Understand Write Limitations

> The explicit reference for what the Volunteer API cannot do — the complete write-surface inventory, the workarounds where they exist, and the escalation paths for capabilities that require admin team coordination.

The Volunteer API is heavily read-oriented. Of 24 endpoints, only 4 support writes (one upsert for Users, four endpoints for Groups, and two for Projects). Most operations a partner integration might want to do — recording participations, submitting Form responses, scheduling Project Dates, awarding Certificates — are not exposed in the API.

This page is the canonical reference for those gaps. It enumerates **what the API doesn't support**, organized by resource, with the workarounds where they exist and the escalation paths where they don't. The audience is integration architects setting expectations during onboarding, support engineers triaging "can we do X?" questions, and engineers planning workflows.

If you have a partner integration in design or production, treat this as the explicit "what to coordinate with the customer's admin team on" list.

## The four write endpoints

For reference, the only write surface in the API:

| Endpoint               | Method | What it does                              |
| ---------------------- | ------ | ----------------------------------------- |
| `/users`               | POST   | Create or update a User (upsert by email) |
| `/projects`            | POST   | Create a Project with its schedule        |
| `/projects/{id}`       | PUT    | Replace a Project (with its schedule)     |
| `/groups`              | POST   | Create a Group                            |
| `/groups/{id}`         | PUT    | Update a Group's metadata + members       |
| `/groups/{id}`         | DELETE | Delete a Group                            |
| `/groups/{id}/members` | PUT    | Replace a Group's member list             |

That's it. Everything else — every other resource family and every other write operation — happens through the VOMO admin UI or through a separate admin-team coordination.

***

## Participation: not exposed

The most-requested missing capability. Participations are the central record of "this volunteer attended this Project Date" — and they're entirely managed in the VOMO admin UI.

| What partners often want                                  | Status                  |
| --------------------------------------------------------- | ----------------------- |
| Create a Participation (sign someone up programmatically) | **Not exposed**         |
| Update a Participation's check-in / check-out times       | **Not exposed**         |
| Modify the recorded hours for a Participation             | **Not exposed**         |
| Mark a Participation as verified                          | **Not exposed**         |
| Cancel or delete a Participation                          | **Not exposed**         |
| Bulk-import historical Participations                     | **Not exposed** via API |

### What partners can do instead

| Goal                                                     | Workaround                                                                                                                         |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Sign volunteers up programmatically                      | Coordinate with VOMO admin team; typically a CSV import                                                                            |
| Record completed volunteer hours                         | Same — admin team workflow                                                                                                         |
| Display participation data                               | Read via `GET /users/{id}` (embedded in `UserDetailResource.participations`) and `GET /projects/date/{id}` (embedded participants) |
| Detect new participations for change-detection workflows | Poll the User detail or Project Date endpoints; compare against your last-known snapshot                                           |

### Why this matters for architecture

Many partner integrations imagine a flow like:

```text theme={null}
External event → Partner API → POST /participations (doesn't exist!)
```

The right flow is typically:

```text theme={null}
External event → Partner API → record locally → coordinate with admin team for batch import
```

Or, for workflows where pure VOMO authority isn't required:

```text theme={null}
External event → Partner API → record locally → external system is authoritative; VOMO is sync target only via CSV
```

Set this expectation with customers during integration design. The API's lack of Participation writes is the **single biggest architectural constraint** for most Volunteer integrations.

***

## Project Date: not independently managed

Project Dates exist only as part of a Project. You cannot:

| What partners often want                             | Status                                                             |
| ---------------------------------------------------- | ------------------------------------------------------------------ |
| Create a Project Date independently                  | **Not exposed** — `dates` is required at Project create/update     |
| Modify a Project Date's start/end times in isolation | **Not exposed** — must PUT the full Project                        |
| Cancel a single Project Date                         | **Not exposed** — must remove from Project's `dates` array via PUT |
| List all Project Dates across all Projects           | **Not exposed** — no top-level `/project-dates` endpoint           |
| Get a Project Date by `project_id + date`            | **Not exposed** — only by Project Date `id`                        |

### What partners can do

| Goal                                           | Workaround                                                          |
| ---------------------------------------------- | ------------------------------------------------------------------- |
| Add a new Project Date to a recurring Project  | GET-then-PUT on `/projects/{id}` with extended `dates` array        |
| Remove a Project Date                          | GET-then-PUT on `/projects/{id}` with the Date omitted from `dates` |
| Read today's Project Dates                     | Use `GET /projects/today`                                           |
| Read a specific Project Date with participants | Use `GET /projects/date/{id}`                                       |
| Read a Project's full schedule                 | Use `GET /projects/{id}` — `all_dates` is embedded                  |

See [Create or Update a Project: Adjust a Project's schedule](/volunteer/workflows/create-or-update-a-project#scenario-3-adjust-a-projects-schedule) for the schedule-modification pattern.

### The "can't have zero dates" constraint

Because `dates` is required on `PUT /projects/{id}`, you can't remove all Project Dates from a Project via the API. Implications:

* "Cancel all future shifts" requires either keeping a placeholder date or deleting the Project (which requires admin UI)
* Programmatic cleanup of expired schedules has friction

***

## Form submission: not exposed

Form Completions are read-only via the API. Partners cannot:

| What partners often want                       | Status          |
| ---------------------------------------------- | --------------- |
| Submit a Form on behalf of a user              | **Not exposed** |
| Update an existing Form Completion's responses | **Not exposed** |
| Delete a Form Completion                       | **Not exposed** |
| Modify a Form's fields or options              | **Not exposed** |
| Create a new Form                              | **Not exposed** |
| Archive or delete a Form                       | **Not exposed** |

### What partners can do

| Goal                                                          | Workaround                                                                  |
| ------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Capture form data externally (e.g., on partner's signup page) | Store externally; coordinate with admin team for periodic VOMO sync         |
| Display submitted Form Completions                            | Read via `GET /forms/{id}/completions` and join with Form field definitions |
| Detect new Form submissions                                   | Incremental polling with `created_after` filter                             |
| Aggregate Form responses for reporting                        | Pull all completions, aggregate client-side or push to a BI tool            |

***

## Certificates: read-only

Certificates have a single `GET /certificates` endpoint and nothing else:

| What partners often want                          | Status          |
| ------------------------------------------------- | --------------- |
| Award a Certificate to a user                     | **Not exposed** |
| Revoke a Certificate from a user                  | **Not exposed** |
| Create a new Certificate type                     | **Not exposed** |
| Modify a Certificate's requirements or expiration | **Not exposed** |
| Delete a Certificate type                         | **Not exposed** |

### What partners can do

| Goal                                                    | Workaround                                                                                                       |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Display Certificates required for a Project             | Read via `GET /projects/{id}` — Certificates listed on the Project                                               |
| Display Certificates a user has earned                  | Read via `GET /users/{id}` (the exact field shape for earned certificates is not formally specified in the spec) |
| Track Certificate expiration for renewal workflows      | Calculate from `expiration_in_months` on Certificate definition + earned date from User detail                   |
| Sync Certificate types to an external compliance system | Read via `GET /certificates` periodically                                                                        |

For workflows that need to award certificates programmatically (e.g., after a user completes external training), coordinate with the customer's admin team — typically a CSV import or admin-UI workflow.

***

## Organizations: read-only

Organizations are entirely admin-UI-managed:

| What partners often want                                 | Status          |
| -------------------------------------------------------- | --------------- |
| Create a new Organization                                | **Not exposed** |
| Modify an Organization's metadata                        | **Not exposed** |
| Change parent-child relationships                        | **Not exposed** |
| Delete an Organization                                   | **Not exposed** |
| Update Organization monikers, addresses, or contact info | **Not exposed** |

### What partners can do

| Goal                                            | Workaround                                                   |
| ----------------------------------------------- | ------------------------------------------------------------ |
| Display Organization family structure           | Read via `GET /organizations` and `GET /organizations/{id}`  |
| Route data based on which org within the family | Read the `organization_slug` from Project / Campaign records |
| Display custom monikers in your UI              | Cache from `GET /organizations/{id}`                         |

For workflows that need to provision new child organizations (e.g., expanding a customer's family programmatically), coordinate with VOMO support.

***

## Campaigns: read-only

Campaigns are entirely admin-UI-managed:

| What partners often want         | Status                                                              |
| -------------------------------- | ------------------------------------------------------------------- |
| Create a new Campaign            | **Not exposed**                                                     |
| Modify a Campaign's metadata     | **Not exposed**                                                     |
| Add a Project to a Campaign      | **Not exposed** — Project ↔ Campaign attachment is admin-UI-managed |
| Remove a Project from a Campaign | **Not exposed**                                                     |
| Delete a Campaign                | **Not exposed**                                                     |

### What partners can do

| Goal                                   | Workaround                                               |
| -------------------------------------- | -------------------------------------------------------- |
| Display Campaign list and metadata     | Read via `GET /campaigns` and `GET /campaigns/{id}`      |
| Filter Projects by Campaign membership | Read Projects, filter by the `campaigns` array on each   |
| Report on Campaign performance         | Aggregate Project-level data into Campaign-level metrics |

For partner integrations that want to create programmatic per-customer Campaigns, coordinate with VOMO support.

***

## Users: limited write surface

Users have one write endpoint (`POST /users`) — an upsert by email. Beyond that:

| What partners often want                                 | Status                                                                                                                                         |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Update a User by ID (without their email)                | **Not exposed** — upsert matches on email only                                                                                                 |
| Delete a User                                            | **Not exposed**                                                                                                                                |
| Merge two duplicate User records                         | **Not exposed**                                                                                                                                |
| Change a User's email                                    | **Not exposed** — would create duplicate per [the email-change problem](/volunteer/workflows/create-or-update-a-user#the-email-change-problem) |
| Restore a soft-deleted User                              | **Not exposed**                                                                                                                                |
| Modify a User's `user_status` (e.g., verify, ban)        | **Not exposed** — managed in admin UI                                                                                                          |
| Modify a User's `membership_status` or `membership_role` | **Not exposed**                                                                                                                                |
| Bulk-upsert many Users in one request                    | **Not exposed** — one User per request                                                                                                         |

### What partners can do

| Goal                                       | Workaround                                               |
| ------------------------------------------ | -------------------------------------------------------- |
| Create or update a User from external data | Use `POST /users` upsert                                 |
| Detect create vs. update                   | Inspect 200 (updated) vs 201 (created) response          |
| Find a User by email                       | Use `GET /users?email_like=X` with exact-equality filter |
| Read User detail including participations  | Use `GET /users/{id}`                                    |
| Bulk import                                | Iterate with throttling — one request per User           |

See [Create or Update a User](/volunteer/workflows/create-or-update-a-user) for the upsert details and the email-change problem.

***

## Groups: full CRUD (the exception)

Groups are the one resource family with full write support:

| Capability     | Status                              |
| -------------- | ----------------------------------- |
| Create         | ✓ `POST /groups`                    |
| Read           | ✓ `GET /groups`, `GET /groups/{id}` |
| Update         | ✓ `PUT /groups/{id}`                |
| Delete         | ✓ `DELETE /groups/{id}`             |
| Manage members | ✓ `PUT /groups/{id}/members`        |

If your integration needs programmatic organization of users into stable collections, Groups are where you do it. See [Manage Groups and Members](/volunteer/workflows/manage-groups-and-members).

***

## Other notable missing capabilities

### No webhooks

The Volunteer API has no webhook surface. Partner integrations that need to react to changes in VOMO data must **poll**. See [Polling and Sync](/volunteer/polling-and-sync/overview) for the patterns.

### No bulk operations

Every write is a single-resource operation. There's no:

* Bulk User upsert (one body with many users)
* Bulk Group create
* Bulk member-add across multiple Groups
* Bulk operation for any other resource

For partner integrations operating at scale, this means throttled iteration — see [Rate Limits](/volunteer/rate-limits).

### No PATCH (partial update)

All updates are full-record replacement via PUT. The implications:

| Implication                                       | What it means                                                       |
| ------------------------------------------------- | ------------------------------------------------------------------- |
| **GET-then-PUT for any partial update**           | Cost of one read per update                                         |
| **Easy to accidentally clear fields by omission** | The GET-then-PUT pattern protects against this                      |
| **No optimistic concurrency**                     | No `If-Match` header or ETag mechanism                              |
| **Race conditions possible**                      | Two clients PUT-ing the same Group can clobber each other's changes |

For high-concurrency workflows, consider locking or coordination at the partner integration level.

### No conditional requests

The API doesn't support `If-Modified-Since` or ETag-based conditional reads. Every GET re-fetches the full record.

### No field-level filtering

The API doesn't support `?fields=name,email` style sparse-fieldset queries. You always get the full resource shape.

### No GraphQL or batch query endpoint

Each query is a separate HTTP request. There's no way to ask "give me these 10 users with these 5 fields each" in one request — you make 10 requests (or one large list query and then filter).

***

## Escalation paths

When a partner integration needs something the API doesn't support, the path forward depends on the type of need:

### For one-time data operations

| Operation                                         | Escalation                                   |
| ------------------------------------------------- | -------------------------------------------- |
| Bulk import historical Participations             | Customer's admin team — typically CSV import |
| Bulk award Certificates to past completers        | Same                                         |
| One-time roster migration from another platform   | Same                                         |
| Initial setup of Forms / Campaigns / Certificates | Admin UI by customer                         |

### For ongoing programmatic needs

| Operation                            | Escalation                                                     |
| ------------------------------------ | -------------------------------------------------------------- |
| Programmatic Participation recording | **Coordinate with VOMO product team** about future API support |
| Programmatic Project Date scheduling | Same                                                           |
| Programmatic Form submission         | Same                                                           |
| Programmatic Certificate awarding    | Same                                                           |

These are common partner requests. If your integration's design depends on one of them, raise it with VOMO product/integration leads early — the API surface evolves over time, and feedback from real integration needs influences what gets added.

### For VOMO product / API issues

| Issue                           | Escalation                                           |
| ------------------------------- | ---------------------------------------------------- |
| API returning unexpected errors | VOMO support                                         |
| Documentation gaps              | VOMO docs team (or this documentation's maintainers) |
| Customer-specific data issues   | VOMO support + customer's admin team                 |
| Token / authentication problems | VOMO support                                         |

***

## What this means for integration design

A few principles to bake into integration architecture given these limitations:

### 1. Treat VOMO as a read source for most data

For most partner integrations, VOMO is an information source — your integration reads from it and pushes that data into other systems. Treat external systems (CRMs, BI tools, accounting) as the destinations, with VOMO as the upstream.

### 2. Use Users + Groups as your primary write surface

The two write-capable resource families (Users via upsert, Groups via full CRUD) are where most "push into VOMO" workflows should focus. If your integration concept depends on writing to other resources, redesign or coordinate with admin team.

### 3. Build expectations into customer onboarding

The most common customer disappointment is "I want my external system to automatically sign up volunteers for shifts" — which isn't possible via API. Set this expectation explicitly during onboarding so the customer understands they (or their admin team) handle scheduling in VOMO, while your integration handles the data flow around it.

### 4. Polling is fine — design for it

The lack of webhooks isn't a deal-breaker; many production integrations operate fine on polling. Build polling architecture into your design from the start rather than retrofitting it. See [Polling and Sync](/volunteer/polling-and-sync/overview).

### 5. Cache aggressively

The lack of conditional requests, the small page size, and the read-heavy nature of typical workflows all argue for aggressive caching. Cache:

* Form field definitions (change rarely)
* Certificate definitions (change rarely)
* Organization data (changes rarely)
* Project schedules (between updates)
* User detail (between known changes)

See [API Performance Tips](/volunteer/best-practices/api-performance-tips).

### 6. Document gaps in your own partner docs

If you're building a partner integration that surfaces VOMO data to end customers, document the same limitations in your own product docs. Customers will ask "why can't I do X?" — and the answer is often "VOMO's API doesn't expose that," not a limitation of your product.

***

## A self-check before promising features

Before promising a customer that your integration can do something, walk through this:

* Can the operation be done via one of the four write endpoints?
* If not, can the data be captured externally and synced separately?
* If a sync is needed, does the customer's admin team need to participate?
* What's the freshness expectation, and does polling meet it?
* What's the failure mode if VOMO is unavailable or rate-limited?

This checklist catches most "we can't actually do that" surprises before they reach the customer.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Polling and Sync" icon="arrows-rotate" href="/volunteer/polling-and-sync/overview">
    The change-detection patterns that compensate for the lack of webhooks.
  </Card>

  <Card title="The Volunteer Data Model" icon="diagram-project" href="/volunteer/concepts/data-model">
    The full data model — useful for understanding what's read-only vs. write-capable.
  </Card>

  <Card title="API Performance Tips" icon="gauge" href="/volunteer/best-practices/api-performance-tips">
    The caching patterns that make read-heavy workflows scale.
  </Card>

  <Card title="Sync Architecture Patterns" icon="route" href="/volunteer/best-practices/sync-architecture-patterns">
    The broader architectural patterns for read-source-and-sync designs.
  </Card>
</CardGroup>
