Voting processes
A process is an election - a set of questions run against a published census, with rules for how votes are cast and when voting opens and closes.
A process is an election: one or more questions run against a published census, governed by rules about how votes are cast and when voting opens and closes. You create it off-chain (fully editable at first), publish it on-chain, voters cast ballots, and you read results.
One ProcessID identifies the election throughout. POST /process returns it as a bare JSON
string, and you reuse the same id for publish, status, results, metadata, and bundling - before and
after publishing.
¶Creating a process
Bind the process to a published census and describe it with election parameters. Titles and
descriptions are multilanguage strings -
objects keyed by language, each with a default.
PROCESS=$(curl -s "${auth[@]}" -X POST "$B/process" -d "{
\"orgAddress\":\"$ORG\",\"censusId\":\"$CENSUS\",
\"metadata\":{\"title\":\"Board election 2026\"},
\"electionParams\":{
\"title\":{\"default\":\"Board election 2026\"},
\"description\":{\"default\":\"Elect the new board\"},
\"questions\":[{\"title\":{\"default\":\"Who should chair the board?\"},
\"choices\":[{\"title\":{\"default\":\"Ada Lovelace\"},\"value\":0},
{\"title\":{\"default\":\"Alan Turing\"},\"value\":1}]}],
\"voteType\":{\"maxCount\":1,\"maxValue\":1},
\"electionType\":{\"autostart\":true,\"interruptible\":true},
\"startDate\":\"2026-07-01T09:00:00Z\",\"endDate\":\"2026-07-03T18:00:00Z\",
\"maxCensusSize\":1000
}}" | jq -r .) # bare JSON string -> the ProcessID
Election parameters
| Field | Type | Description |
|---|---|---|
title |
multilang | Election title, keyed by language with a default. |
description |
multilang | Longer description of the election. |
startDate |
string (ISO 8601) | When voting opens. |
endDate |
string (ISO 8601) | When voting closes. |
electionType |
object | Behavioral flags such as anonymous and autostart - see below. |
voteType |
object | Ballot shape - see Voting types. |
questions |
array | One or more questions, each with a title and choices (each choice a title plus a numeric value). |
maxCensusSize |
integer | Upper bound on eligible voters for the process. |
streamUri |
string | Optional live stream URL shown with the election. |
Election type flags
autostart- open voting automatically atstartDate.interruptible- allow pausing or ending the process early.anonymous- hide who voted using zero-knowledge proofs.dynamicCensus- allow the census to change after the process starts.secretUntilTheEnd- keep results hidden until voting closes (encrypted ballots).
Vote type
voteType shapes the ballot - single choice, approval/multichoice, ranked, quadratic, budget, or
multi-question. Each shape is a specific combination of maxCount, maxValue, uniqueChoices, and
cost fields. See Voting types for the per-field reference and the
ballot shape of each.
var process = (await Post("/process", new {
orgAddress = org, censusId = census,
metadata = new { title = "Board election 2026" },
electionParams = new {
title = new { @default = "Board election 2026" },
questions = new[] { new { title = new { @default = "Who should chair the board?" },
choices = new[] { new { title = new { @default = "Ada Lovelace" }, value = 0 },
new { title = new { @default = "Alan Turing" }, value = 1 } } } },
voteType = new { maxCount = 1, maxValue = 1 },
electionType = new { autostart = true, interruptible = true },
startDate = "2026-07-01T09:00:00Z", endDate = "2026-07-03T18:00:00Z",
maxCensusSize = 1000,
}})).GetString();
process = post("/process", {
"orgAddress": org, "censusId": census,
"metadata": {"title": "Board election 2026"},
"electionParams": {
"title": {"default": "Board election 2026"},
"questions": [{"title": {"default": "Who should chair the board?"},
"choices": [{"title": {"default": "Ada Lovelace"}, "value": 0},
{"title": {"default": "Alan Turing"}, "value": 1}]}],
"voteType": {"maxCount": 1, "maxValue": 1},
"electionType": {"autostart": True, "interruptible": True},
"startDate": "2026-07-01T09:00:00Z", "endDate": "2026-07-03T18:00:00Z",
"maxCensusSize": 1000,
}}).json()
¶Publishing on-chain
Publishing is asynchronous: it returns a jobId (or 200 directly if already published). Poll
the job until it completes.
PJOB=$(curl -s "${auth[@]}" -X POST "$B/process/$PROCESS/publish" | jq -r .jobId)
until [ "$(curl -s "$B/jobs/$PJOB" | jq -r .status)" = "completed" ]; do sleep 2; done
The job's result.address is the on-chain election id. You keep addressing the process by its
ProcessID for everything server-side; the on-chain id surfaces only client-side, when a voter
signs a ballot.
¶Changing status
Status changes (ready, paused, ended, canceled) are also asynchronous. Address by ProcessID.
curl "${auth[@]}" -X PUT "$B/process/$PROCESS/status" -d '{"status":"ended"}'
{ "jobId": "d4e5f6..." } // 202 - poll /jobs/{jobId}
¶Process bundles
A bundle groups one or more processes under a census and is the voter-facing entry point for casting. Reference each process by its ProcessID. Bundles are useful when an assembly votes on several motions in one session.
BUNDLE_URI=$(curl -s "${auth[@]}" -X POST "$B/process/bundle" \
-d "{\"censusId\":\"$CENSUS\",\"processes\":[\"$PROCESS\"]}" | jq -r .uri)
BUNDLE="${BUNDLE_URI##*/}" # bundleId is the last path segment
{ "root": "deadbeef...", "uri": "https://.../process/bundle/<bundleId>" }
Reading results
Once a process is running you can read live or final tallies. See Results for the response shape and how finality works.
¶Gotchas
POST /processreturns a bare string (the ProcessID), not an object.- Publish and status changes are jobs - read the outcome from
/jobs/{jobId}, not the POST body. - Address the process by its ProcessID everywhere server-side (status, results, metadata, bundle); the on-chain id is only needed client-side, to sign voter payloads.