VolunteerBadge has a simple REST API for managing volunteers, sending applications, ordering background checks, and receiving real-time events. If you'd rather not write code, see Import volunteers for the spreadsheet importer, or Connect to Claude to drive everything in plain English.
Base URL
https://www.volunteerbadge.com/api/v1. Requests and responses are JSON.Authentication
Create an API key in Settings → API (you'll accept the CRA End-User Agreement the first time). Keys look like vb_live_… — treat them like a password and never expose one in client-side code. Send it as a bearer token on every request:
Authorization: Bearer vb_live_xxxxxxxxxxxxxxxxxxxxIdentity verification required first (live keys)
403 identity_verification_required. Sandbox test keys are exempt — see below — so you can start building right away.Fair use & abuse monitoring
403 api_access_suspended. Questions? support@screenforgelabs.com.Sandbox / test mode
Build and test your whole integration — including webhooks — before going live, using a sandbox key. In Settings → API, click “Create sandbox test key”. Sandbox keys start with vb_test_ (live keys start with vb_live_) and can be created with no agreement and no identity verification.
A test key hits the same endpoints with the same request shape. In sandbox:
- No credit is charged and no real check runs — nothing reaches the screening vendor.
- You get an instant simulated response, and your registered webhooks fire for real (
check.complete/check.error), so you can validate your handler end-to-end. - Nothing is persisted —
POST /api/v1/volunteersandPOST /api/v1/applicationsreturn simulated IDs without writing a roster row or sending an invite.
The outcome is deterministic from the subject you send (like a test card number):
| Send this | Simulated result |
|---|---|
lastName: "Records" | Records found — result "consider" (fires check.complete) |
SSN ending 0000 | Same — records found |
lastName: "Error" | The check fails (fires check.error) |
| anything else | Clear result (fires check.complete) |
Sandbox check IDs are prefixed chk_test_, and every sandbox response includes "mode": "test". When you're ready for production, generate a vb_live_ key (that one requires the CRA agreement + identity verification, since it furnishes real consumer reports) and run the exact same code.
Endpoints
| Method & path | What it does |
|---|---|
GET /api/v1/volunteers | List volunteers (paginated, filter by status). |
POST /api/v1/volunteers | Create a volunteer (idempotent on email). |
POST /api/v1/applications | Send a volunteer application by email, SMS, or shareable link. |
POST /api/v1/checks | Order a background check for a subject (name + DOB + SSN + address). |
POST /api/v1/checks/instant | Certified no-SSN instant check (name + DOB + state) — requires authorizationOnFile + permissiblePurpose. |
GET /api/v1/checks/{id} | Get a check’s status and result. |
GET /api/v1/credits | Get your remaining check-credit balance. |
Create a volunteer
Push a person into VolunteerBadge — e.g. migrating a roster or syncing from another system. Posting the same email twice returns the existing record instead of duplicating it.
curl -X POST https://www.volunteerbadge.com/api/v1/volunteers \
-H "Authorization: Bearer vb_live_..." \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jamie",
"last_name": "Rivera",
"email": "jamie@example.com",
"phone": "443-790-8081",
"dob": "1981-05-29",
"state": "FL",
"last_check_date": "2025-06-01",
"fcra_authorization_on_file": true,
"status": "approved"
}'
# → { "volunteerId": "f883...", "created": true }fcra_authorization_on_file is required
trueonly for people whose signed FCRA authorization you actually hold — it's what allows checks (and auto-rescreen) to run for them.List volunteers
curl "https://www.volunteerbadge.com/api/v1/volunteers?status=approved&page=1&limit=25" \
-H "Authorization: Bearer vb_live_..."
# → { "volunteers": [ { "id": "...", "first_name": "...", "status": "approved", ... } ],
# "pagination": { "page": 1, "limit": 25, "total": 42, "totalPages": 2 } }Order a background check
Requires firstName, middleName, lastName, dob, ssn, address, city, state, zip. middleName is required — instant checks match on name and date of birth without an SSN address trace, so a middle name is needed to confirm identity and reduce false matches on common names. This consumes 1 credit.
curl -X POST https://www.volunteerbadge.com/api/v1/checks \
-H "Authorization: Bearer vb_live_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jamie", "middleName": "Alex", "lastName": "Rivera",
"dob": "1981-05-29", "ssn": "123456789",
"address": "12112 Blue Hill Trail", "city": "Lakewood Ranch",
"state": "FL", "zip": "34211"
}'
# → { "checkId": "...", "status": "processing", "result": null, "message": "..." }Poll GET /api/v1/checks/{id} for the result, or subscribe to the check.complete webhook (below) so you're notified instead of polling.
Certified instant check (no SSN)
Run an immediate check on firstName, middleName, lastName, dob, state with no SSN — for when you already hold the subject's signed FCRA disclosure and written authorization. Certify it per request with authorizationOnFile: true and a permissiblePurpose (one of volunteer_screening, employment_screening, youth_serving_organization). A middle name is required, and SSNs are rejected on this endpoint.
curl -X POST https://www.volunteerbadge.com/api/v1/checks/instant \
-H "Authorization: Bearer vb_live_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jamie", "middleName": "Lee", "lastName": "Rivera",
"dob": "1981-05-29", "state": "FL",
"permissiblePurpose": "volunteer_screening",
"authorizationOnFile": true
}'
# → { "checkId": "...", "status": "...", "result": "...", "certified": { ... } }Identity verification required (newer accounts)
POST /api/v1/checks and POST /api/v1/checks/instant — require your organization's owner to have completed a free, one-time identity verification first. Until then these calls return 403 with code: ID_VERIFICATION_REQUIRED. The owner verifies once in Settings → Account (“Verify my identity”). Existing accounts are grandfathered, and consent-first POST /api/v1/applications invites are never affected.Errors
| Status | Meaning |
|---|---|
| 400 | Missing or invalid field — see the error message. |
| 401 | Missing or invalid API key. |
| 402 | Insufficient credits — buy more in Billing. |
| 403 | Forbidden — the CRA End-User Agreement must be re-accepted (Settings → API), or (code ID_VERIFICATION_REQUIRED) the org owner must verify their identity before instant checks. |
| 404 | Resource not found. |
| 429 | Rate limited — slow down and retry. |
Webhooks
Instead of polling, register a URL in Settings → Webhooks and VolunteerBadge will POST to it when things happen. Available events:
| Event | Fires when |
|---|---|
check.complete | A background check finishes (clear or consider). |
check.error | A check fails to process. |
application.submitted | A volunteer submits their application. |
volunteer.created | A new volunteer record is created. |
adverse_action.case_opened | A report with records is released and VolunteerBadge opens an adverse-action case (or a case is first created for that check). |
adverse_action.notice_sent | A pre-adverse or final adverse notice is delivered by email (<code>data.stage</code> is <code>pre</code> or <code>final</code>). |
adverse_action.dispute_opened | The consumer submits a dispute through the ScreenForge Labs portal link from the pre-adverse notice. |
adverse_action.dispute_resolved | ScreenForge Labs CRA staff finish reviewing a consumer dispute. |
adverse_action.completed | The final adverse notice is sent and the case is complete. |
Adverse-action webhooks include caseId, checkId, and volunteerId (null for direct checks). Notice and dispute events also include identifiers like noticeId, stage, or disputeId when available.
Each delivery is a JSON POST with two headers — X-VolunteerBadge-Event (the event name) and X-VolunteerBadge-Signature (an HMAC-SHA256 of the raw body, signed with your endpoint's secret):
POST (your endpoint)
X-VolunteerBadge-Event: adverse_action.notice_sent
X-VolunteerBadge-Signature: 9f86d081...
{
"event": "adverse_action.notice_sent",
"data": {
"caseId": "...",
"checkId": "...",
"volunteerId": "...",
"stage": "pre",
"noticeId": "...",
"deliveryStatus": "delivered"
},
"timestamp": "2026-07-18T18:00:00.000Z"
}Always verify the signature before trusting a payload. In Node:
import crypto from 'crypto';
function verify(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}Prefer no-code?

