API Reference
Overview
RosaVote is API-first: the admin console is a thin client over these
endpoints, so anything it does can be scripted. Base URL is this deployment's
origin. All responses are JSON unless noted (ballot/console pages return HTML;
BLT and ZIP exports return files).
Authentication
Admin endpoints take an admin token in the X-Admin-Token
header. Tokens are stored only as SHA-256 hashes. National tokens can do
everything; chapter tokens are scoped to their own polls and cannot
build elections, import rolls, set weights, or run lookups. Voter endpoints
take a one-time voting code in the request body, never a header. Every
administrative action is written to an append-only audit log under the token's
name.
Conventions
<poll> is a poll id ([a-z0-9_]{3,64}).
Errors are {error, message} with a 4xx/5xx status. Rate note:
the service scales on Cloud Run behind an instance cap — floods become
slowness, not failures, and never affect an in-progress vote.
Public — voter
| Method | Path | Auth | What it does |
|---|
| GET | /p/<poll>/ | None | The branded ballot page for a poll. |
| POST | /p/<poll>/resend | None | Self-serve 'lost my code'. Body {contact}. Enumeration-safe (always {status:ok}), rate-limited 15 min/contact; re-sends the member's link to their on-record contacts only. |
| GET | /prefs | None | Self-serve notification preferences page (opt a contact out / back in). |
| POST | /prefs/optout | None | Suppress election contact to an email/phone. Body {contact}. Enumeration-safe. |
| POST | /prefs/optin | None | Re-enable election contact to an email/phone. Body {contact}. |
| GET | /p/<poll>/v/<code> | None | Ballot page with a one-tap voting code embedded. |
| GET | /p/<poll>/voted?code=… | None | Has this code already voted? → {voted: bool}. Malformed/unknown codes are rejected before any DB read. |
| POST | /p/<poll>/vote | Voting code (in body) | Cast a ballot: {code, answers:{questionKey:value}}. One code = one vote, enforced atomically. → {status:'recorded', receipt}. |
| POST | /p/<poll>/provisional | None | Cast a sealed provisional ballot with identifying info for later adjudication. |
| GET | /p/<poll>/verify?receipt=… | None | Confirm a ballot was stored → {found, status}. No identity, no answers — safe to share. |
| GET | /p/<poll>/results | None | Public results page (only after finalize + publish). Aggregate only; served from a frozen cache. |
Admin — auth
| Method | Path | Auth | What it does |
|---|
| GET | /admin/api/whoami | X-Admin-Token | Resolve the caller's identity → {name, role, polls}. |
| POST | /admin/api/admins | National | Mint a scoped admin token (plaintext returned once). Body: {name, role, polls, token?}. |
Admin — elections
| Method | Path | Auth | What it does |
|---|
| GET | /admin/api/polls | Any admin | List polls visible to this token (chapter tokens see only their own). |
| POST | /admin/api/polls/<poll> | National | Create or update a poll config (the whole ballot schema). unfinalize:true to reopen a certified poll (audited). |
| POST | /admin/api/polls/<poll>/open | Poll admin | Open voting now (with or without a schedule). |
| POST | /admin/api/polls/<poll>/close | Poll admin | Close voting now. |
| POST | /admin/api/polls/<poll>/archive | National | Archive a poll: hide it from voting/public and the normal list (ballots kept, reversible). |
| POST | /admin/api/polls/<poll>/unarchive | National | Restore an archived poll to the active set. |
| POST | /admin/api/polls/<poll>/publish | Poll admin | Publish/unpublish the public results page (finalized polls). Body: {publish:bool}. |
| POST | /admin/api/cron/closeout | National | Finalize every poll past its window. Called by Cloud Scheduler every 15 min; idempotent. |
Admin — results & tabulation
| Method | Path | Auth | What it does |
|---|
| GET | /admin/api/polls/<poll>/results | Poll admin | Full tallies (finalized, or national + ?live=1, audited). ?fresh=1 recomputes past the cache. |
| POST | /admin/api/polls/<poll>/recount_preview | Poll admin | Preview a ranked contest under another method (plurality|approval|borda|irv|mntv|meek), or a score contest as score|star|star_pr. Body: {question, method}. |
| GET | /admin/api/polls/<poll>/blt/<qkey> | Poll admin | Download a contest as a standard .blt. ?recount=1 alternates; ?withdraw=IDS dropout recount. |
| GET | /admin/api/polls/<poll>/export.zip | Poll admin | Results package: .txt/.csv/.json reports, printable .html, every .blt, ballots.csv, verify readme. ?anonymize=1. |
| POST | /admin/api/count_blt | Any admin | Run Scottish/Meek STV on a posted .blt (with optional constraints). Touches no stored data. |
Admin — voters
| Method | Path | Auth | What it does |
|---|
| GET | /admin/api/polls/<poll>/voters | Poll admin | Per-voter turnout + received-verification + integrity counter. Never how anyone voted. ?member_id=… to search. |
| POST | /admin/api/polls/<poll>/voters/import | National | Import a roll (JSON members[]). Mints hashed codes, returns the plaintext manifest once. |
| POST | /admin/api/polls/<poll>/voters/import_gcs | National | Import a roll CSV from gs://bucket/file.csv (header member_id; optional chapter, weight). |
| POST | /admin/api/polls/<poll>/voters/import_bigquery | National | Import eligible members by roll chapter straight from the warehouse. |
| POST | /admin/api/polls/<poll>/voters/weight | National | Set a voter's ballot weight (1–1000). Resolved at tally time. |
Admin — adjudication & remedy
| Method | Path | Auth | What it does |
|---|
| GET | /admin/api/polls/<poll>/provisionals | Poll admin | Pending provisionals — identity fields only; answers stay sealed. |
| POST | /admin/api/polls/<poll>/provisionals/<receipt> | Poll admin | Adjudicate: {action:'verify', member_id} promotes + burns codes; {action:'reject', note}. |
| GET | /admin/api/polls/<poll>/lookup?member_id|receipt=… | National | Troubleshooting: identity-linked answers; secret questions show recorded-or-not, never content. Audited. |
| POST | /p/<poll>/admin/void | Poll admin | Void a cast ballot (flag, never delete) and reissue a fresh code. Open-window only, audited. |
Example
curl -s -H "X-Admin-Token: $TOKEN" \
https://<origin>/admin/api/polls/my_poll/results?live=1
← Back to RosaVote
RosaVote is free/libre software under
AGPL-3.0 ·
source code · © 2026 Walker Green