Remote Voting
Let voters cast ballots from your own frontend.
Remote voting uses a voter ID and password to authenticate voters. Once authenticated, CrownVote returns the voter profile, access token, and ballots in a single response.
Remote voting flow
Resolve election
Use the public key to confirm the election context before showing the voting form.
Login voter
Authenticate the voter with voterId and password. The response includes the ballots.
Render ballot
Display categories and candidates from the ballots array. Treat vote values as opaque strings.
Submit selection
Submit one encrypted vote value for every ballot category using the voter bearer token.
Resolve the election context
Use the public key to resolve the election before the voter signs in. This confirms that the election is available, remote, and configured for API voting.
GET /api/v1/election HTTP/1.1
Host: api.crownvote.com
X-PUBLIC-KEY: cv_pub_xxxxx{
"status": true,
"data": {
"election": {
"title": "SRC General Elections 2026",
"institution": "CrownVote Demo Institution",
"description": "Official voting event for the SRC General Elections.",
"instructions": "Login with your voter ID and password to cast your vote.",
"status": "live",
"startsAt": "2026-06-01T08:00:00.000Z",
"endsAt": "2026-06-01T17:00:00.000Z",
"votingMode": "remote",
"accessMode": "api",
"inPersonRequireAdminLogin": null,
"inPersonRequireVoterPassword": null
}
}
}Authenticate the voter
Remote voters authenticate with voterId and password. The response includes an access token and the full ballot payload.
POST /api/v1/voter/login HTTP/1.1
Host: api.crownvote.com
Content-Type: application/json
X-PUBLIC-KEY: cv_pub_xxxxx
{
"voterId": "236165",
"password": "voter-password"
}{
"status": true,
"data": {
"accessToken": {
"type": "Bearer",
"value": "oat_xxxxx",
"expiresAt": null
},
"voter": {
"voterId": "236165",
"firstName": "Voter 1",
"middleName": null,
"lastName": null,
"displayName": "Voter 1",
"group": null,
"status": "active",
"hasVoted": false,
"votedAt": null,
"lastLoginAt": null
},
"ballots": [
{
"categoryId": "70f74ae6-fb73-41e9-87e0-ece419585ed5",
"categoryPosition": 1,
"title": "PRESIDENT",
"description": null,
"instructions": null,
"candidates": [
{
"name": "Kofi Angel",
"image": "https://cdn-f.crownvote.com/elections/...",
"bio": null,
"manifesto": null,
"vote": "encrypted_vote_value"
}
]
}
]
}
}Restore the voter session
Use this endpoint after a page refresh. It returns the voter and ballots again when the bearer token is still valid.
GET /api/v1/voter HTTP/1.1
Host: api.crownvote.com
X-PUBLIC-KEY: cv_pub_xxxxx
Authorization: Bearer oat_xxxxxSubmit encrypted selections
Submit one selection for each ballot category returned during login or session restore. The vote value must be submitted exactly as received.
POST /api/v1/voter/submit-votes HTTP/1.1
Host: api.crownvote.com
Content-Type: application/json
X-PUBLIC-KEY: cv_pub_xxxxx
Authorization: Bearer oat_xxxxx
{
"selections": [
{
"categoryId": "70f74ae6-fb73-41e9-87e0-ece419585ed5",
"vote": "encrypted_vote_value_for_candidate"
},
{
"categoryId": "211c9edf-dfc6-48f5-914c-f3bfc7edeee9",
"vote": "encrypted_vote_value_for_candidate"
}
]
}{
"status": true,
"message": "Vote submitted successfully.",
"data": {
"submission": {
"status": "pending"
}
}
}Check latest submission
Use this endpoint to retrieve the voter’s latest submission state after voting.
GET /api/v1/voter/submission HTTP/1.1
Host: api.crownvote.com
X-PUBLIC-KEY: cv_pub_xxxxx
Authorization: Bearer oat_xxxxx{
"status": true,
"data": {
"submission": {
"status": "processed",
"submittedAt": "2026-06-01T16:13:27.000Z",
"processedAt": "2026-06-01T16:13:28.000Z"
}
}
}End the voter session
Logout clears active voter access tokens. You should also remove the token from local browser storage after a successful vote or logout.
POST /api/v1/voter/logout HTTP/1.1
Host: api.crownvote.com
X-PUBLIC-KEY: cv_pub_xxxxx
Authorization: Bearer oat_xxxxxImplementation rules
{
"status": false,
"message": "Your ballot submission is stale. Please refresh and try again.",
"code": "STALE_BALLOT_SUBMISSION",
"errors": []
}{
"status": false,
"message": "You have already voted in this election",
"code": "ALREADY_VOTED",
"errors": []
}{
"status": false,
"message": "Request validation failed.",
"code": "REQUEST_VALIDATION_FAILED",
"errors": [
{
"field": "selections",
"message": "The selections field must be defined",
"rule": "required"
}
]
}JavaScript example
A minimal browser-side integration for loading the election, logging in, restoring the voter session, and submitting votes.
const API_URL = 'https://api.crownvote.com/api/v1'
const PUBLIC_KEY = 'cv_pub_xxxxx'
async function getElection() {
const response = await fetch(`${API_URL}/election`, {
headers: {
'X-PUBLIC-KEY': PUBLIC_KEY,
},
})
return response.json()
}
async function loginVoter({ voterId, password }) {
const response = await fetch(`${API_URL}/voter/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PUBLIC-KEY': PUBLIC_KEY,
},
body: JSON.stringify({ voterId, password }),
})
const result = await response.json()
if (result.status) {
localStorage.setItem('crownvote_voter_token', result.data.accessToken.value)
}
return result
}
async function restoreVoter() {
const token = localStorage.getItem('crownvote_voter_token')
const response = await fetch(`${API_URL}/voter`, {
headers: {
'X-PUBLIC-KEY': PUBLIC_KEY,
Authorization: `Bearer ${token}`,
},
})
return response.json()
}
async function submitVotes(selections) {
const token = localStorage.getItem('crownvote_voter_token')
const response = await fetch(`${API_URL}/voter/submit-votes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PUBLIC-KEY': PUBLIC_KEY,
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ selections }),
})
return response.json()
}
async function logoutVoter() {
const token = localStorage.getItem('crownvote_voter_token')
const response = await fetch(`${API_URL}/voter/logout`, {
method: 'POST',
headers: {
'X-PUBLIC-KEY': PUBLIC_KEY,
Authorization: `Bearer ${token}`,
},
})
localStorage.removeItem('crownvote_voter_token')
return response.json()
}