NOT FOR PROFIT

Republic bot API

Civic public good. Not a commercial product. No token. No fees to voters.

An external bot uses a personal API key to call the same JSON API a signed-in person uses. Create the key on your profile. Read OpenAPI or the plain-text guide if you are a language model.

How a bot authenticates

Send one of these headers on every call:

Authorization: Bearer rk_…
X-Api-Key: rk_…

The secret starts with rk_. It is shown once, with a copy button, and cannot be shown again. The Node stores only a SHA-256 hash. The email does not contain the API key.

Identifiers

Path parameters named id, partyId, requestId, methodId, and electionId are UUIDs. A sequential integer is rejected with 400 and ID_NOT_UUID, so the path does not reveal how many people or records exist. The on-chain election id stays inside the election object for the circuit. Short links /v/{code} and /e/{code} redirect to /vote/{uuid} and are not accepted as /api/elections/:id.

Ballot labels (questionId, optionId), guardian slots (:index), mail template names, and page slugs are not record counts. A new jurisdiction is assigned a UUID.

curl -sS \
  -H "Authorization: Bearer $REPUBLIC_API_KEY" \
  -H "Accept: application/json" \
  "$ORIGIN/api/me"

Authorization levels

One account can hold many keys. Each key has its own level. The level is a ceiling. The person’s existing role is still enforced.

Expiration

Choose one expiration when you create the key. The default is 30d.

After expiresAt, the key is rejected. never does not expire. Revoke a key from the profile, or with DELETE /api/me/api-keys/:id using a voter or officer key. Revocation takes effect immediately.

Create a key

Sign in, open /me, and use API keys. The browser session calls:

POST /api/me/api-keys
Content-Type: application/json

{ "name": "assistant", "level": "read", "expiresIn": "30d" }

level is read, voter, officer. An API key cannot create another key.

Creating a key sends the email template api_key.created to the account contact email (or the primary email). The message says an API key was created and does not include the key. If you did not create it, contact support as soon as possible at /feedback, then revoke the key.

Errors

JSON errors use { "error": "…", "nfp": true, "code": "…" }. 401 means the key is missing, unknown, revoked, or expired. 403 with API_KEY_SCOPE means this key’s level cannot make that call. Ballot choice is never a response field.

Route groups

A route that is not listed here rejects a personal API key. Sign-in routes are listed so a bot can complete email or Ethereum login, and they refuse an API key so a key cannot mint a browser session.

Status

Method Path Minimum key Sign-in What a bot does
GET /health read No Process health, writer mode, chain id, and the hourly disclosure window.
GET /api/health read No Same payload as GET /health.
GET /api/config read No Public Node configuration: voting hostname, chain, polls flag, identity caps, ballot types, receipt portal, and outcome portal.

Documentation

Method Path Minimum key Sign-in What a bot does
GET /api read No Human documentation for this bot API. Send Accept: application/json, or ?format=json, for a machine index of the same routes.
GET /api/openapi.json read No OpenAPI 3 description of every route listed here.
GET /api/llms.txt read No Plain-text guide for a language model that will call this API.
GET /llms.txt read No Same plain-text guide as GET /api/llms.txt.

Sign-in

Method Path Minimum key Sign-in What a bot does
POST /api/auth/passkey/register/begin session No Begin WebAuthn registration. An API key is refused here. A bot that already has a key does not need this ceremony. Body: handle, acceptedTerms.
POST /api/auth/passkey/register/finish session No Finish WebAuthn registration and start email MFA when MFA is on. Body: WebAuthn credential, handle, email.
POST /api/auth/passkey/login/begin session No Begin WebAuthn sign-in. Body: handle.
POST /api/auth/passkey/login/finish session No Finish WebAuthn sign-in. Sets the session cookie after MFA.
POST /api/auth/web3/challenge session No Issue a sign-in challenge for a bound Ethereum address. Body: address.
POST /api/auth/web3/login session No Submit the Ethereum signature for that challenge. Body: address, signature.
POST /api/auth/email/start session No Start email sign-in. The one-time code arrives by transactional email. Body: email.
POST /api/auth/smart-card/challenge session No Issue a smart-card sign-in challenge.
POST /api/auth/smart-card/login session No Complete smart-card sign-in.
POST /api/auth/civic-id/login session No Sign in with a hashed driver license or passport already bound to the person.
GET /api/auth/mfa/status session No Whether email MFA is required and whether SES is configured.
POST /api/auth/mfa/email session No Send or confirm the email factor during sign-in.
POST /api/auth/mfa/resend session No Resend the email one-time code.
POST /api/auth/mfa/verify session No Verify the email one-time code and receive the session cookie. Body: code.
POST /api/auth/logout session Yes End the browser session. An API key is refused here.
GET /api/auth/session session Yes Session cookie profile. Prefer GET /api/me with an API key.

Profile

Method Path Minimum key Sign-in What a bot does
GET /api/me read Yes The signed-in person: handle, role, identity summary, jurisdiction grants, and election registration status. Choice is never included.
GET /api/session read Yes Same payload as GET /api/me.
PATCH /api/me/contact-email voter Yes Set or clear the contact email used for operator notices and the API-key alert. Body: contactEmail.
GET /api/notifications read Yes In-app notices for this person, newest first.
POST /api/notifications/:id/read voter Yes Mark one notice read.

API keys

Method Path Minimum key Sign-in What a bot does
GET /api/me/api-keys read Yes List this account’s API keys. The secret is never returned. Revoked and expired keys stay in the list.
POST /api/me/api-keys session Yes Create a key from a browser session on /me. Body fields: name, level (read, voter, or officer), expiresIn (7d, 30d, 90d, 180d, 365d, or never; default 30d). The secret is displayed once and cannot be retrieved again. The account email says a key was created and does not include the key. Body: name, level, expiresIn.
DELETE /api/me/api-keys/:id voter Yes Revoke one of this account’s keys. Later calls with that secret are rejected.

Vote PIN

Method Path Minimum key Sign-in What a bot does
POST /api/me/pin/keypad voter Yes Issue the scrambled phone keypad used to enter the vote PIN.
POST /api/me/pin voter Yes Set the 4–8 digit vote PIN. Body: pin.
POST /api/me/pin/verify voter Yes Verify the vote PIN and receive a short-lived pinToken for POST /api/elections/:id/vote. Body: pin.
POST /api/me/pin/forgot voter Yes Start a PIN reset for the signed-in person. Completes with email MFA.
POST /api/pin/forgot voter No Start a PIN reset from an email address. Sends a one-time code. Body: email.

Identity

Method Path Minimum key Sign-in What a bot does
GET /api/identity/methods read No Identity method types a person may bind: email, passkey, Web3, smart card, driver license, passport.
GET /api/me/identity read Yes Full identity view for the signed-in person: emails, passkeys, Ethereum addresses, civic credentials. No trapdoor and no PIN.
POST /api/identity/email/start voter Yes Start binding another email. One email is primary. Cap is 5. Body: email.
POST /api/identity/web3/challenge voter Yes Challenge used to prove control of an Ethereum address before it is bound. Body: address.
POST /api/identity/web3/verify voter Yes Bind a signature-verified Ethereum address. Only one address is active. Body: address, signature.
POST /api/identity/smart-card/challenge voter Yes Challenge for binding a smart card.
POST /api/identity/smart-card/verify voter Yes Bind a smart card after the reader signs the challenge.
POST /api/identity/civic-id/verify voter Yes Bind a hashed driver license or passport. Images and full document numbers are not stored.
GET /api/me/emails read Yes Email methods on this person.
POST /api/me/emails/primary voter Yes Choose the primary email. Exactly one email is primary. Body: id or email.
DELETE /api/me/emails/:id voter Yes Remove one email method.
GET /api/me/passkeys read Yes Passkeys bound to this person. Cap is 5.
DELETE /api/me/passkeys/:credentialId voter Yes Remove one passkey.
GET /api/me/eth-accounts read Yes Verified Ethereum addresses and which one is active.
POST /api/me/eth-accounts voter Yes Submit the signature that binds an Ethereum address. Body: address, signature.
POST /api/me/eth-accounts/active voter Yes Switch the single active Ethereum address. Body: address.
GET /api/me/credentials read Yes Smart cards and civic documents on this person.
POST /api/persons/:id/person-key officer Yes Registrar or clerk binds the person key for someone on their jurisdiction.
POST /api/persons/:id/methods officer Yes Officer adds an identity method to a person in their jurisdiction.
POST /api/persons/:id/eth-accounts officer Yes Officer records an Ethereum address on a person. Body: address.
POST /api/persons/:id/eth-accounts/active officer Yes Officer switches the person’s active Ethereum address. Body: address.
GET /api/persons/:id/identity read Yes Registrar or clerk reads a person’s identity. A read-only key still needs that officer grant.
GET /api/office/credentials read Yes Credentials waiting for civil-officer review.
POST /api/persons/:id/methods/:methodId/review officer Yes Approve or reject a smart card or civic document. Body: decision.
POST /api/persons/:id/methods/:methodId/revoke officer Yes Revoke an expired card or license. Revocation is an event.
POST /api/integrations/credentials/status session No Credential-provider status callback. Uses the host provider secret header, not a personal API key. An API key is refused.

Elections

Method Path Minimum key Sign-in What a bot does
GET /api/elections read No Published elections. Counts follow the hourly disclosure rule.
POST /api/elections officer Yes Clerk creates an election in a jurisdiction they already serve. Body: title, jurisdictionId, startTime, cutoffTime, questions or ballot.
GET /api/elections/:id read No One election. When the caller is signed in, includes that person’s registration status. Choice is never included.
PATCH /api/elections/:id officer Yes Clerk edits the ballot before the roll is frozen.
PUT /api/elections/:id officer Yes Same edit as PATCH.
GET /api/elections/:id/info read No Voter-facing questions, options, and supporting info.
GET /api/elections/:id/info/:questionId/:optionId read No One option and its external links.
GET /api/elections/:id/results read No Certified plurality or IRV results after tally. Empty of option counts before tally.
GET /api/elections/:id/summary read No Short public status: state, window, disclosed ballot count, links.
GET /api/elections/:id/ballots read No Disclosed ballot proofs for closed hours. Choice is not a field.
GET /api/elections/:id/artifacts read No Auditor pack: roots, counts, and public checks. No Node private database.
GET /api/elections/:id/l1 read No L1 certification status for the tally, when a tally exists.
GET /api/elections/:id/tally-key read No Public tally ceremony view. The Node does not return a tally private key.
GET /api/elections/:id/staff read Yes Election staff list. Caller must already be staff for that election.
POST /api/elections/:id/staff officer Yes Platform admin approves election staff. Body: handle or userId.
DELETE /api/elections/:id/staff officer Yes Platform admin revokes election staff. Body: handle or userId.
GET /api/elections/:id/registrations read Yes Clerk, registrar, or disqualification officer lists the roll and requests.
GET /api/elections/:id/registration-requests read Yes Clerk or registrar lists registration requests. Filter with ?status=.
POST /api/elections/:id/register voter Yes Voter asks to join the roll. A registrar or clerk of that jurisdiction must approve. Body: identityCommitment.
POST /api/elections/:id/registration-requests/:requestId/approve officer Yes Clerk or registrar approves a registration request and inserts the commitment.
POST /api/elections/:id/registration-requests/:requestId/deny officer Yes Clerk or registrar declines a registration request. Body: reason.
POST /api/elections/:id/freeze officer Yes Clerk freezes the eligible and disqualification roots.
POST /api/elections/:id/vote voter No Submit one ballot. A signed-in voter must send pinToken from POST /api/me/pin/verify. The body is a proof plus ciphertext. Choice is not a public field. The same nullifier is accepted once. A read-only key is refused. Body: proof, public inputs, ciphertext, pinToken, identityCommitment when an external party is active.
POST /api/elections/:id/disqualify officer Yes Disqualification officer applies a coded reason and an evidence hash. Body: identityCommitment, reasonCode, evidenceHash.
POST /api/elections/:id/disqualify/revoke officer Yes Disqualification officer records a revocation event.
POST /api/elections/:id/revoke officer Yes Same revocation event as /disqualify/revoke.
GET /api/elections/:id/envelopes read Yes Tally operator reads encrypted envelopes after cutoff. Plaintext choice is not stored.
POST /api/elections/:id/guardians officer Yes Tally operator enrolls a guardian in the opens-ballots ceremony. Non-production cryptography.
POST /api/elections/:id/guardians/deals officer Yes Tally operator posts a guardian deal.
GET /api/elections/:id/guardians/:index/inbox read No Encrypted shares addressed to one enrolled guardian index.
POST /api/elections/:id/guardians/partials officer Yes Tally operator submits partial decryptions after cutoff.
POST /api/elections/:id/tally officer Yes Tally operator posts the tally after cutoff. Invented counts that do not match opened ballots are rejected for opens-ballots.
POST /api/elections/:id/certify-l1 officer Yes Tally operator asks the Node to mirror the tally to L1 when L1 env is set.

External parties

Method Path Minimum key Sign-in What a bot does
GET /api/elections/:id/parties read No External eligibility parties for an election. Endpoint URLs are included only for staff.
POST /api/elections/:id/parties officer Yes Election staff registers an external eligibility party. Body: name, mode, operatorHandle.
POST /api/elections/:id/parties/check officer Yes Staff checks whether a commitment is cleared to cast. Body: identityCommitment.
PATCH /api/elections/:id/parties/:partyId officer Yes Staff changes a party status. Body: status.
GET /api/elections/:id/parties/:partyId/rejections read Yes Staff lists party rejections.
POST /api/elections/:id/parties/:partyId/reject officer Yes Staff or the party operator records a rejection. Body: identityCommitment, reasonCode, evidenceHash.
POST /api/elections/:id/parties/:partyId/clear officer Yes Staff or the party operator clears a rejection. Body: identityCommitment, evidenceHash.
POST /api/elections/:id/parties/:partyId/attest officer Yes Staff or the party operator attests a commitment. Body: identityCommitment, expiresAt, evidenceHash.

Jurisdictions

Method Path Minimum key Sign-in What a bot does
GET /api/jurisdictions read No Jurisdictions on this Node. Officer lists are included only for officers.
POST /api/jurisdictions officer Yes Platform admin creates a jurisdiction. Body: name, metadataURI. The Node assigns a UUID id; a caller cannot choose it..
GET /api/jurisdictions/:id read No One jurisdiction.
GET /api/jurisdictions/:id/roles read Yes Officers granted on a jurisdiction. Caller must already be an officer.
POST /api/jurisdictions/:id/roles officer Yes Platform admin grants clerk, registrar, disqualification officer, tally operator, or election staff. Body: role, handle or userId.
DELETE /api/jurisdictions/:id/roles officer Yes Platform admin revokes a jurisdiction grant. Body: role, handle or userId.
POST /api/admin/roles officer Yes Same grant as POST /api/jurisdictions/:id/roles. Body: jurisdictionId, role, handle or userId.
POST /api/admin/grant officer Yes Alias of POST /api/admin/roles. Body: jurisdictionId, role, handle or userId.
GET /api/users read Yes Platform admin lists accounts and their jurisdiction grants. A read-only key may list. It cannot change roles.
PATCH /api/users/:id officer Yes Platform admin sets a role the account is already allowed to grant: voter, observer, clerk, registrar, disqualification_officer, tally_operator, or election_staff. Body: role.
GET /api/reason-codes read No Public disqualification reason catalog.

Pages

Method Path Minimum key Sign-in What a bot does
GET /api/pages read No Published public pages and the shared-contract site view.
GET /api/pages/:slug read No One published page.
GET /api/site read No Public site record, including pinned contract addresses.
GET /api/office/site read Yes Clerk reads the office site record.
PATCH /api/office/site officer Yes Clerk pins shared contract addresses. Environment pins win over the stored site.
GET /api/office/pages read Yes Clerk lists CMS pages, including drafts they may edit.
POST /api/office/pages officer Yes Clerk creates a page.
GET /api/office/pages/:id read Yes Clerk reads one CMS page.
PATCH /api/office/pages/:id officer Yes Clerk edits a CMS page.
DELETE /api/office/pages/:id officer Yes Clerk deletes a CMS page.

Terms

Method Path Minimum key Sign-in What a bot does
GET /api/terms read No Current Terms of Use metadata and whether the caller has accepted this PDF hash.
GET /api/terms.pdf read No Terms PDF bytes. SHA-256 is in the X-Pdf-SHA256 header.
GET /terms.pdf read No Same Terms PDF as GET /api/terms.pdf.
POST /api/terms/view voter No Record that the signed-in person viewed the Terms.
POST /api/terms/accept voter Yes Accept the current Terms. Acceptance stores the SHA-256 of the PDF bytes. Body: acceptedTerms.

Feedback

Method Path Minimum key Sign-in What a bot does
GET /api/feedback read No Feedback form limits, categories, and whether the operator inbox is configured.
POST /api/feedback voter No Send one feedback message. This is also how a person contacts support. Rate-limited. A read-only key is refused because the call sends mail. Body: category, name, email, message, acceptedTerms.

Receipts

Method Path Minimum key Sign-in What a bot does
GET /api/receipts/metrics read No Public receipt metrics and hourly turnout. No candidate and no option.
POST /api/receipts/verify read No Check a hash-only receipt or its locked PDF. Returns whether the ballot counted. Never returns a choice. Body: proof or receipt text, or PDF bytes.
POST /api/receipts/email voter Yes Email the voter their hash-only receipt after the ballot is accepted.
POST /api/receipts/pdf voter Yes Download the locked receipt PDF for a ballot this caller may access.

Outcomes

Method Path Minimum key Sign-in What a bot does
GET /api/outcomes read No Every election grouped before, during, and after voting. No unofficial option counts during the open window.
GET /api/outcomes/:id read No One election’s public outcome monitor.
GET /api/outcomes/reports read No Issued official outcome reports.
GET /api/outcomes/reports/:id read No One official outcome report record.
GET /api/outcomes/reports/:id/pdf read No Locked official outcome PDF.
POST /api/outcomes/verify read No Check an official outcome PDF was not modified. Body: pdf bytes or id.
GET /api/me/outcome-subscriptions read Yes Elections whose official PDF this person will be emailed. Voting subscribes them by default.
PUT /api/me/outcome-subscriptions/:electionId voter Yes Subscribe or confirm a subscription to an official outcome PDF.
POST /api/me/outcome-subscriptions voter Yes Subscribe the caller to an election’s official outcome PDF. Body: electionId.
GET /api/admin/outcome-report-template read Yes Platform admin reads the official outcome PDF template. QR and hash seals stay required.
PATCH /api/admin/outcome-report-template officer Yes Platform admin edits template copy. QR and hash seals cannot be removed.
POST /api/admin/outcome-report-template/preview officer Yes Platform admin previews the outcome PDF template.

Explorer

Method Path Minimum key Sign-in What a bot does
GET /api/explorer/disclosure read No Hourly disclosure window the explorer uses.
GET /api/explorer/blocks read No Civic L2 blocks.
GET /api/explorer/blocks/:n read No One civic L2 block.
GET /api/explorer/txs/:hash read No One civic L2 transaction. Ballot plaintext is not a field.
GET /api/explorer/nullifiers/:value read No Whether a nullifier was accepted. The nullifier does not reveal the choice or the person.
GET /api/explorer/search read No Search blocks, transactions, and elections.
GET /api/explorer/elections/:id read No Explorer view of one election.
POST /api/explorer/ingest officer No Operator ingest of civic L2 events. Send the host ingest secret in X-Republic-Ingest-Secret. A personal API key does not replace that secret. Minimum key level when a key is also sent: officer.

Polls

Method Path Minimum key Sign-in What a bot does
GET /api/polls read No Token polls when POLLS_ENABLED is on. Polls are signal, not binding elections. Omitted from the Node when the flag is off.
GET /api/polls/:id read No One token poll.
GET /api/polls/:id/weight read No Read-only token weight for an address. The Node does not move tokens.
GET /api/polls/:id/typed-data read No EIP-712 typed data a holder signs. Query: address, choices.
POST /api/polls/:id/ballots voter No Submit a signed token-poll ballot. No Republic account is required. A read-only key is refused.
POST /api/polls/:id/tally voter Yes Any signed-in person may ask the Node to tally a closed token poll. Counts come from accepted ballots.
GET /api/polls/:id/results read No Token poll results after tally.
GET /api/polls/:id/artifacts read No Token poll auditor pack.
GET /api/polls/:id/ballots.json read No Disclosed token-poll ballots.
GET /api/polls/:id/outcome.pdf read No Token poll outcome PDF.

Mail

Method Path Minimum key Sign-in What a bot does
GET /api/mail/templates read Yes Platform admin lists operator mail templates stored on the Node. Mail is never written to civic L2.
GET /api/mail/templates/:id read Yes Platform admin previews one operator mail template, including api_key.created.
GET /api/mail/outbox read Yes Platform admin lists queued operator mail. Bodies stay on the detail route.
GET /api/mail/outbox/:id read Yes Platform admin reads one outbox message, including text and HTML.