The Certificate resource — training credentials and achievements awarded to volunteers, the naming inconsistency in the spec, and the patterns for working with certification data.
A Certificate in the Volunteer API represents a training credential, badge, or achievement that volunteers can 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, CPR certification)
Certificates have an expiration period — partner integrations can use this to detect when a volunteer’s credential is about to expire and trigger renewal outreach.
That’s it. The API exposes only Certificate definitions (the “what certificates exist”) — not who has earned which Certificate, or when they earned it. Earned-certificate data isn’t exposed through a dedicated endpoint.
Information about which Users have earned which Certificates may be embedded in the User detail response (GET /users/{id}) and the Project detail response (which lists required certificates). For workflows that need to know who has which Certificate, fetch User details and inspect the user’s earned certifications.
The Certificate resource has the most pronounced naming inconsistency of any resource in the Volunteer API:
Where it appears
Name used
Endpoint path
/certificates
OpenAPI tag
Certifications
operationId
listCertifications
Schema name
CertificateResource
Schema description
"List of Certifications"
⚠️ Spec gap (audit #14): The resource is referred to as “Certificate”, “Certification”, and “Certifications” in different places. The path is /certificates (the canonical URL); the schema is CertificateResource (the canonical type name).For partner integrations, use the path (/certificates) and the schema name (CertificateResource) as your reference points. The variations will be reconciled in a future spec revision.
For the rest of this page, the resource is referred to as Certificate — matching the path and schema name.
The CertificateResource schema documents these fields:
Field
Type
Description
type
string
"certificate" — the VOMO object type
id
integer
The Certificate’s stable ID
name
string
The Certificate’s display name
requirement
string
Description of what’s required to earn the Certificate
slug
string
A slug for the Certificate (see warning below)
expiration_in_months
integer
How long the Certificate remains valid after issuance
creator_id
integer
The User ID of the Certificate’s creator
organization_id
integer
The Organization the Certificate belongs to
created_at
string
ISO 8601 datetime
updated_at
string
ISO 8601 datetime
⚠️ Spec gap (audit #17): The CertificateResource schema is incorrectly typed as array in the spec — the same issue affecting FormResource and FormFieldResource. The actual response is a single Certificate object per item in the response’s data array. Code generated from the spec may need manual adjustment.
⚠️ Spec gap (audit #15): The slug field has the same enum-vs-example contradiction documented on FormResource. The enum lists form-field-like values (SHORTTEXT, LONGTEXT, etc.) and the example is a UUID. Treat the slug as a UUID string per the example; the misleading enum will be removed in a future spec revision.
⚠️ Spec gap (audit #16): The id field has minimum: 3, maximum: 45 constraints — string-length-style constraints applied to an integer. Treat IDs as unconstrained integers.
The expiration_in_months field captures how long a Certificate remains valid after a User earns it:
Value
Meaning
12
Valid for 12 months from issuance
24
Valid for 24 months
0 or null
Likely “never expires” (confirm against live data)
For partner integrations, this is the dimension that drives renewal workflows — knowing the validity window for each Certificate type lets the integration compute “when does this user’s certification expire?” given their earn date.The expiration is on the Certificate definition, not on the per-user earned record. So the same Certificate has the same validity window for everyone who earns it.
The /certificates endpoint doesn’t document explicit query filters in the spec. Pagination via ?page=N likely works the same as other list endpoints — follow links.next.
JavaScript
async function listAllCertificates() { const certs = []; let url = 'https://api.vomo.org/v1/certificates'; while (url) { const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) throw new Error(`Failed: ${response.status}`); const page = await response.json(); certs.push(...page.data); url = page.links.next; } return certs;}
For most customers, the list of available Certificates is short (typically a handful to a few dozen) — fitting comfortably in memory and changing infrequently. Cache the result aggressively.
For workflows that watch for Certificates approaching expiration:
JavaScript
async function findUsersWithExpiringCertificates(daysUntilExpiration = 30) { // Note: This pattern requires the User detail to expose earned certificates with dates. // The exact format isn't documented in the spec — confirm against live data. const allUsers = await paginate('https://api.vomo.org/v1/users'); const certCache = new CertificateCache({ token }); const expiringPerUser = []; for (const userSummary of allUsers) { const user = await getUserDetail(userSummary.id); const earnedCerts = user.earned_certificates ?? []; // confirm field name from live API for (const earned of earnedCerts) { const cert = await certCache.getById(earned.certificate_id); if (!cert?.expiration_in_months) continue; const earnedDate = new Date(earned.earned_at); const expirationDate = new Date(earnedDate); expirationDate.setMonth(expirationDate.getMonth() + cert.expiration_in_months); const daysUntilExp = (expirationDate - Date.now()) / (24 * 60 * 60 * 1000); if (daysUntilExp <= daysUntilExpiration && daysUntilExp > 0) { expiringPerUser.push({ user, certificate: cert, expirationDate, daysUntilExpiration: Math.round(daysUntilExp), }); } } } return expiringPerUser;}
The exact field name and shape of earned-certificate data on the User Detail response (GET /users/{id}) isn’t documented in the OpenAPI spec — the example UserDetailResource only formally documents participations and profile_field_values. The pattern above assumes a field like earned_certificates with certificate_id and earned_at per entry, but confirm against the live API before relying on it.
The output is a list of “User X’s Certificate Y expires in N days” entries — feed this into the customer’s renewal outreach pipeline.
If a partner integration needs to programmatically award Certificates (e.g., after a User completes external training), coordinate with VOMO’s admin team for an alternative path.See Understand Write Limitations.