Skip to content

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

  1. Open Settings → Integrations → API keys (needs the Scale plan, and the settings permission).
  2. Choose Read only for reporting and syncing out, or Full access to add volunteers and log hours.
  3. 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
curl https://www.sevastack.in/api/v1/volunteers?limit=10 \
  -H "Authorization: Bearer seva_live_YOUR_KEY"

Conventions

FormatJSON in and out, snake_case fields, dates as YYYY-MM-DD, times as ISO 8601 UTC.
PagingLists return { data, next_cursor }. Pass ?cursor= with the last next_cursor until it is null. ?limit= is 1–100 (default 50).
SyncingGET /volunteers?updated_since=2026-10-01T00:00:00Z returns only what changed. Store the time of each sync and pass it next time.
Rate limit60 requests a minute per organisation. Over that you get 429 with a Retry-After header in seconds.
RetriesSend 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 body
{ "error": { "code": "validation_failed", "message": "Check the fields and try again.", "details": { "phone": ["invalid"] } } }
StatuscodeMeaning
401unauthenticated, invalid_keyNo key, or the key is wrong, expired or revoked.
403read_only_keyA read-only key tried to change something.
403plan_required, plan_limitYour plan doesn't include the API, or its volunteer limit is full.
403forbiddenThe key's creator no longer has permission. Create a new key.
404not_found, volunteer_not_foundNo such record in your organisation.
409duplicate, already_registeredAlready exists; details has the existing id.
422validation_failed, idempotency_key_reusedBad input, or an Idempotency-Key reused with a different body.
429rate_limitedWait Retry-After seconds.

Volunteers

List: GET /volunteers

Filters: status (ONBOARDED, ACTIVE, INACTIVE, ALUMNI), employer, search (name, email or phone), updated_since.

Response
{
  "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
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_to opens a portal page, for example /my-volunteer/hours, /my-volunteer/discover or /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
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
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.

Response (group_by=employer)
{
  "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

JavaScript (Node 18+)
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();
PHP (WordPress plugin or any PHP site)
$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 there

What 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.