REST API
Connect your own app, your CSR partner's HR system or a script to your volunteers, activities and hours. All data stays scoped to your organisation.
Machine-readable spec (import into Postman, Insomnia or a code generator): /api/v1/openapi.json
1. Get an API key
- Open Settings → Integrations → API keys (needs the Scale plan, and the settings permission).
- Choose Read only for reporting and syncing out, or Full access to add volunteers and log hours.
- Copy the key. It starts with
seva_live_and is shown only once.
A key acts as the team member who created it. It can only do what they can do (volunteer endpoints need the manage volunteers permission), and it stops working if they leave the organisation. Keys expire; create a new one before then. Keep keys on your server: never put one in website code or a mobile app, where anyone can read it. For your website, use the website widgets instead.
2. Make a request
curl https://www.sevastack.in/api/v1/volunteers?limit=10 \
-H "Authorization: Bearer seva_live_YOUR_KEY"Conventions
| Format | JSON in and out, snake_case fields, dates as YYYY-MM-DD, times as ISO 8601 UTC. |
| Paging | Lists return { data, next_cursor }. Pass ?cursor= with the last next_cursor until it is null. ?limit= is 1–100 (default 50). |
| Syncing | GET /volunteers?updated_since=2026-10-01T00:00:00Z returns only what changed. Store the time of each sync and pass it next time. |
| Rate limit | 60 requests a minute per organisation. Over that you get 429 with a Retry-After header in seconds. |
| Retries | Send an Idempotency-Key header (any unique string, up to 200 characters) on every write. Retrying with the same key and body returns the first response (header Idempotent-Replayed: true) instead of doing it twice. Keys are remembered for 24 hours. |
Errors
{ "error": { "code": "validation_failed", "message": "Check the fields and try again.", "details": { "phone": ["invalid"] } } }| Status | code | Meaning |
|---|---|---|
| 401 | unauthenticated, invalid_key | No key, or the key is wrong, expired or revoked. |
| 403 | read_only_key | A read-only key tried to change something. |
| 403 | plan_required, plan_limit | Your plan doesn't include the API, or its volunteer limit is full. |
| 403 | forbidden | The key's creator no longer has permission. Create a new key. |
| 404 | not_found, volunteer_not_found | No such record in your organisation. |
| 409 | duplicate, already_registered | Already exists; details has the existing id. |
| 422 | validation_failed, idempotency_key_reused | Bad input, or an Idempotency-Key reused with a different body. |
| 429 | rate_limited | Wait Retry-After seconds. |
Volunteers
List: GET /volunteers
Filters: status (ONBOARDED, ACTIVE, INACTIVE, ALUMNI), employer, search (name, email or phone), updated_since.
{
"data": [{
"id": "cmv3k8…", "name": "Priya Sharma", "email": "priya@example.com", "phone": "9876543210",
"status": "ACTIVE", "skills": ["Teaching"], "languages": ["Hindi", "English"],
"city": "Pune", "state": "Maharashtra", "employer": "Infosys",
"total_hours": 42.5, "joined_on": "2026-04-12", "source": "api",
"created_at": "2026-04-12T06:30:00.000Z", "updated_at": "2026-10-11T08:00:00.000Z"
}],
"next_cursor": "cmv3k8…"
}Get one: GET /volunteers/{id}
Add: POST /volunteers (full-access key)
name is required; give an email or a 10-digit Indian mobile phone so they can sign in to the volunteer portal. If either already belongs to a volunteer you get 409 duplicate with details.existing_id, so you can link to the existing record instead of creating a copy. Counts toward your plan's volunteer limit.
curl -X POST https://www.sevastack.in/api/v1/volunteers \
-H "Authorization: Bearer seva_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hr-sync-emp-10234" \
-d '{"name":"Priya Sharma","phone":"9876543210","email":"priya@example.com","employer":"Infosys","skills":["Teaching"]}'Update: PATCH /volunteers/{id} (full-access key)
Send only the fields to change: name, email, phone, skills, languages, city, state, employer, status. null clears a field. Unknown fields are rejected.
Sign-in link: POST /volunteers/{id}/portal-link (full-access key)
For your own app or website when it already knows who the volunteer is. Put a “My volunteer hours” button there that opens the Sevastack volunteer portal already signed in, with no second login. Ask your server for a link when the volunteer taps the button and send them to it straight away.
- The link works once, for 10 minutes, and only on your organisation's portal address (your custom domain if you have one).
- Optional
return_toopens a portal page, for example/my-volunteer/hours,/my-volunteer/discoveror/my-volunteer/profile. It must start with/my-volunteer. - Up to 20 links an hour per volunteer. Don't email or text these links: make a fresh one each time someone taps your button.
- An expired or used link shows a friendly page where the volunteer can sign in with their mobile number or email instead.
curl -X POST https://www.sevastack.in/api/v1/volunteers/VOLUNTEER_ID/portal-link \
-H "Authorization: Bearer seva_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"return_to":"/my-volunteer/hours"}'
# 201
{ "data": { "url": "https://www.sevastack.in/my-volunteer/link?org=YOURCODE&token=…", "expires_at": "2026-10-11T09:51:38.404Z" } }Activities
List: GET /activities
Filters: from and to (by start date), status (DRAFT, PUBLISHED, CANCELLED, COMPLETED). Each activity includes capacity, confirmed_count and spots_left.
Registrations: GET /activities/{id}/registrations
Register a volunteer: POST /activities/{id}/registrations (full-access key)
Body: { "volunteer_id": "…" }. The activity must be published. While there is room the status is CONFIRMED; when it is full it is WAITLISTED, and the volunteer is confirmed and emailed automatically when someone cancels.
Attendance: POST /activities/{id}/attendance (full-access key)
Body: { "volunteer_id": "…", "attended": true }. Marking present logs the activity's hours once and sends the volunteer the thank-you email; attended: false removes those hours. Calling it twice changes nothing (changed: false). A volunteer who wasn't registered is added as a walk-in.
Hours
Log hours: POST /hours (full-access key)
One entry, or up to 200 in entries. All or nothing: if any entry is invalid, nothing is saved. hours is 0.1–24,date can't be in the future. Hours logged through the API count as verified by the key's creator.
curl -X POST https://www.sevastack.in/api/v1/hours \
-H "Authorization: Bearer seva_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: csr-day-2026-10-11" \
-d '{"entries":[
{"volunteer_id":"cmv3k8…","date":"2026-10-11","hours":4,"description":"Tree plantation drive"},
{"volunteer_id":"cmv3k9…","date":"2026-10-11","hours":3.5}
]}'Report: GET /reports/hours?from=2026-04-01&to=2027-03-31&group_by=employer
Hours in a period (both dates included, at most 366 days). group_by=volunteer (default) lists each volunteer; group_by=employer totals hours, sessions and volunteers per company. That's the figure a CSR partner asks for.
{
"from": "2026-04-01", "to": "2027-03-31",
"total": { "hours": 1840.5, "volunteers": 212, "sessions": 640 },
"data": [{ "employer": "Infosys", "hours": 612, "sessions": 210, "volunteers": 64 }]
}Examples
const res = await fetch("https://www.sevastack.in/api/v1/volunteers?updated_since=" + lastSync, {
headers: { Authorization: "Bearer " + process.env.SEVASTACK_KEY },
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { data, next_cursor } = await res.json();$res = wp_remote_post("https://www.sevastack.in/api/v1/volunteers", [
"headers" => [
"Authorization" => "Bearer " . SEVASTACK_KEY,
"Content-Type" => "application/json",
"Idempotency-Key" => "wp-form-" . $entry_id,
],
"body" => wp_json_encode(["name" => $name, "phone" => $phone, "email" => $email]),
]);
$code = wp_remote_retrieve_response_code($res); // 201 created, 409 already thereWhat the API does not do
- It never returns volunteers' ID documents, police verification references or other organisations' data.
- It doesn't send volunteers a portal invite. They sign in themselves with the mobile number or email you saved.
- Deleting volunteers is not available through the API. Archive them in the dashboard.