Skip to main content
This quickstart walks through the smallest possible Volunteer API integration: obtain a Bearer token, list the customer’s first page of users, and parse the response. The goal is to confirm the integration’s plumbing works end-to-end before building anything substantial. By the end you’ll have a working API call, a parsed response with users in hand, and a clear picture of what each piece of the response means.

Prerequisites

RequirementHow to obtain
A VOMO customer accountThe customer signs up at vomo.org or coordinates with their VOMO concierge
API access enabled for the accountCoordinate with the VOMO concierge to enable API access (this is not on by default)
A Bearer tokenThe customer’s VOMO administrator generates the token in the admin portal
A tool to make HTTP requestscurl, Postman, or any language with an HTTP client
If you’re new to a Volunteer integration, the first three steps are typically done by the customer or their VOMO concierge — the partner integration team coordinates to receive the token. See Authentication for the full token issuance flow.

Step 1: store the token securely

Once you receive the Bearer token from the customer, store it in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.). For local development, an environment variable is acceptable:
export VOMO_API_TOKEN="eyJhbGci.....yu5CSpyHI"
Don’t paste the token directly into source code or commit it to a repository. The token grants full access to the customer’s VOMO data; treat it like a password.

Step 2: make the first request

The smallest useful call: list the first page of users in the customer’s account.
curl https://api.vomo.org/v1/users \
  -H "Authorization: Bearer $VOMO_API_TOKEN" \
  -H "Accept: application/json"
Three things to notice about the request:
ElementDescription
Authorization: Bearer ...The token in the Authorization header. Required on every request.
Accept: application/jsonOptional but explicit — confirms you want JSON. The API returns JSON regardless.
No query parametersReturns the first page with default page size.

Step 3: read the response

A successful response looks like this (truncated for readability):
{
  "data": [
    {
      "type": "user",
      "id": 1234,
      "first_name": "Bruce",
      "last_name": "Wayne",
      "full_name": "Bruce Wayne",
      "email": "bruce@wayne.example",
      "address": "1007 Mountain Drive, Gotham",
      "phone": "+15551234567",
      "birthday": "1972-02-19",
      "gender": "M",
      "updated_at": "2025-03-15T14:22:10Z",
      "created_at": "2024-08-01T09:00:00Z",
      "user_status": "VERIFIED",
      "membership_status": "ACCEPTED",
      "membership_role": "VOLUNTEER"
    }
    /* ... more users ... */
  ],
  "links": {
    "first": "https://api.vomo.org/v1/users?page=1",
    "last": "https://api.vomo.org/v1/users?page=8",
    "prev": null,
    "next": "https://api.vomo.org/v1/users?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "to": 15,
    "per_page": 15,
    "last_page": 8,
    "total": 120
  }
}
Three top-level fields, each with a specific role:
FieldPurpose
dataThe array of resources for this page. Each element is a User.
linksURLs for navigating between pages — first, last, prev, next. The prev and next are null at the edges.
metaNumeric metadata about the pagination state — current page, page size, total records, etc.
For the first call, the most important field is meta.total — it tells you how many users the customer has in total. Paired with meta.per_page, you know how many pages you’ll need to read to capture everything.

Step 4: parse and iterate

A minimal pattern for processing the response:
async function listAllUsers() {
  const allUsers = [];
  let url = 'https://api.vomo.org/v1/users';

  while (url) {
    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${process.env.VOMO_API_TOKEN}`,
        Accept: 'application/json',
      },
    });

    if (!response.ok) {
      throw new Error(`Failed: ${response.status}`);
    }

    const page = await response.json();
    allUsers.push(...page.data);

    // Follow the next link, or stop if there isn't one
    url = page.links.next;
  }

  return allUsers;
}

const users = await listAllUsers();
console.log(`Loaded ${users.length} users`);
Two things this pattern gets right:
  • Follow links.next rather than constructing page URLs manually. The platform provides the link; use it.
  • Stop when links.next is null. That’s the signal there are no more pages.
For more on pagination patterns, see Pagination.

Step 5: handle errors

The spec is sparse on documented error responses, but partner code should still handle common failure modes:
JavaScript
const response = await fetch('https://api.vomo.org/v1/users', {
  headers: { Authorization: `Bearer ${token}` },
});

if (response.status === 401) {
  // Token is invalid, expired, or revoked
  throw new Error('Authentication failed — check the token');
}

if (response.status === 403) {
  // Token doesn't have permission for this resource
  throw new Error('Access forbidden — token lacks permission');
}

if (response.status === 404) {
  // Resource not found
  throw new Error('Resource not found');
}

if (response.status === 429) {
  // Rate limited (not explicitly documented in spec but possible)
  const retryAfter = response.headers.get('Retry-After');
  throw new Error(`Rate limited — retry after ${retryAfter} seconds`);
}

if (response.status >= 500) {
  // Server error — likely transient, retry with backoff
  throw new Error('Server error — retry with backoff');
}

if (!response.ok) {
  throw new Error(`Unexpected error: ${response.status}`);
}
⚠️ Spec gap: The Volunteer OpenAPI spec doesn’t formally document 401, 403, 404, or 500 responses on most endpoints. The handling above is defensive against what the live API likely returns. Confirm against actual responses for production-critical paths. See Error Handling for the full pattern.

Step 6: try a filter

The list endpoint accepts several filter parameters. Try filtering users by name:
curl "https://api.vomo.org/v1/users?name_like=wayne" \
  -H "Authorization: Bearer $VOMO_API_TOKEN"
The name_like parameter does a substring match against first and last name. Other available filters on GET /users:
ParameterWhat it filters by
name_likeFirst or last name substring match
email_likeEmail substring match
created_beforeUsers created on or before a date
created_afterUsers created on or after a date
updated_beforeUsers updated on or before a date
updated_afterUsers updated on or after a date
pagePage number (default 1)
All parameters use snake_case and are documented in the spec for each endpoint.

What you’ve accomplished

After this quickstart, you have:
  • A working API call that lists users from a customer’s VOMO account
  • Confirmed the Bearer token is valid
  • A pattern for iterating through paginated results
  • Defensive error handling for common failure modes
  • A working filter to narrow results
This is the foundation. Every other integration pattern builds on top of these basics.

Common gotchas

A few things that trip up new Volunteer integrations:

Forgetting the /v1/ segment

Volunteer’s base URL is https://api.vomo.org/v1 — the /v1/ is part of the path. Requests to https://api.vomo.org/users (without /v1) will fail. This differs from Raise, which has no version segment.

Assuming camelCase

createdAfter and nameLike don’t work — the parameters are created_after and name_like with underscores. The same applies to response properties: firstName doesn’t appear in the response, but first_name does.

Treating meta.total as a moving target

If new users are added between page reads, meta.total can change mid-iteration. For strict consistency, capture meta.total from the first page and use it as the iteration bound, accepting that the snapshot may not be perfectly current. For most analytics, this is acceptable.

Treating null strings as null

The spec example for links.prev and links.next shows the value as the literal string "null". The live API returns actual JSON null (no quotes). Code that checks for the string "null" will incorrectly treat all pages as having a previous and next page.
JavaScript
// ❌ Incorrect — treats string "null" as truthy
if (page.links.next) { /* ... */ }
// Works correctly because actual API returns real null

// ❌ Incorrect — looking for the string
if (page.links.next !== 'null') { /* ... */ }
// Wrong — this checks for the literal four-character string
The live API returns null, not "null". Check with === null or simple truthiness.

Tokens are per-organization

The Bearer token is scoped to a single VOMO organization. A token issued for “Wayne Foundation” can only read Wayne Foundation’s data. For multi-customer partner integrations, store one token per customer.

Where to go next

Authentication

The full token issuance, storage, and rotation flow.

First API Call

A deeper walkthrough of the request/response patterns introduced here.

Pagination

The full pagination pattern with edge cases and performance considerations.

The Volunteer Data Model

What’s available beyond users — the full resource model.
Last modified on May 22, 2026