{"nfp":true,"notForProfit":true,"product":"Republic","app":"zk-voting","documentation":"/api","openapi":"/api/openapi.json","llms":"/api/llms.txt","profile":"/me","support":"/feedback","authentication":{"type":"apiKey","headers":["Authorization: Bearer rk_…","X-Api-Key: rk_…"],"secretPrefix":"rk_","create":"Browser session on /me, or POST /api/me/api-keys with the session cookie. An API key cannot mint another key.","revoke":"DELETE /api/me/api-keys/:id","secretShownOnce":true,"emailTemplate":"api_key.created"},"levels":[{"id":"read","label":"Read-only","summary":"GET and HEAD, plus receipt and outcome verification. Cannot create, vote, register, email, or change a profile."},{"id":"voter","label":"Voter","summary":"Read-only plus this person’s own actions: contact email, vote PIN, registration request, ballot submit, identity methods, notifications, feedback, and outcome subscriptions."},{"id":"officer","label":"Officer","summary":"Voter plus the clerk, registrar, disqualification officer, tally operator, election staff, and platform admin routes this account is already allowed to call. A key does not add a role the person does not hold."}],"expirations":["7d","30d","90d","180d","365d","never"],"defaultExpiration":"30d","defaultLevel":"read","operations":[{"method":"GET","path":"/health","minLevel":"read","auth":false,"apiKey":true,"tag":"Status","summary":"Process health, writer mode, chain id, and the hourly disclosure window."},{"method":"GET","path":"/api/health","minLevel":"read","auth":false,"apiKey":true,"tag":"Status","summary":"Same payload as GET /health."},{"method":"GET","path":"/api/config","minLevel":"read","auth":false,"apiKey":true,"tag":"Status","summary":"Public Node configuration: voting hostname, chain, polls flag, identity caps, ballot types, receipt portal, and outcome portal."},{"method":"GET","path":"/api","minLevel":"read","auth":false,"apiKey":true,"tag":"Documentation","summary":"Human documentation for this bot API. Send Accept: application/json, or ?format=json, for a machine index of the same routes."},{"method":"GET","path":"/api/openapi.json","minLevel":"read","auth":false,"apiKey":true,"tag":"Documentation","summary":"OpenAPI 3 description of every route listed here."},{"method":"GET","path":"/api/llms.txt","minLevel":"read","auth":false,"apiKey":true,"tag":"Documentation","summary":"Plain-text guide for a language model that will call this API."},{"method":"GET","path":"/llms.txt","minLevel":"read","auth":false,"apiKey":true,"tag":"Documentation","summary":"Same plain-text guide as GET /api/llms.txt."},{"method":"POST","path":"/api/auth/passkey/register/begin","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Begin WebAuthn registration. An API key is refused here. A bot that already has a key does not need this ceremony.","body":"handle, acceptedTerms"},{"method":"POST","path":"/api/auth/passkey/register/finish","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Finish WebAuthn registration and start email MFA when MFA is on.","body":"WebAuthn credential, handle, email"},{"method":"POST","path":"/api/auth/passkey/login/begin","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Begin WebAuthn sign-in.","body":"handle"},{"method":"POST","path":"/api/auth/passkey/login/finish","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Finish WebAuthn sign-in. Sets the session cookie after MFA."},{"method":"POST","path":"/api/auth/web3/challenge","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Issue a sign-in challenge for a bound Ethereum address.","body":"address"},{"method":"POST","path":"/api/auth/web3/login","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Submit the Ethereum signature for that challenge.","body":"address, signature"},{"method":"POST","path":"/api/auth/email/start","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Start email sign-in. The one-time code arrives by transactional email.","body":"email"},{"method":"POST","path":"/api/auth/smart-card/challenge","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Issue a smart-card sign-in challenge."},{"method":"POST","path":"/api/auth/smart-card/login","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Complete smart-card sign-in."},{"method":"POST","path":"/api/auth/civic-id/login","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Sign in with a hashed driver license or passport already bound to the person."},{"method":"GET","path":"/api/auth/mfa/status","minLevel":"read","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Whether email MFA is required and whether SES is configured."},{"method":"POST","path":"/api/auth/mfa/email","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Send or confirm the email factor during sign-in."},{"method":"POST","path":"/api/auth/mfa/resend","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Resend the email one-time code."},{"method":"POST","path":"/api/auth/mfa/verify","minLevel":"voter","auth":false,"apiKey":false,"tag":"Sign-in","summary":"Verify the email one-time code and receive the session cookie.","body":"code"},{"method":"POST","path":"/api/auth/logout","minLevel":"voter","auth":true,"apiKey":false,"tag":"Sign-in","summary":"End the browser session. An API key is refused here."},{"method":"GET","path":"/api/auth/session","minLevel":"read","auth":true,"apiKey":false,"tag":"Sign-in","summary":"Session cookie profile. Prefer GET /api/me with an API key."},{"method":"GET","path":"/api/me","minLevel":"read","auth":true,"apiKey":true,"tag":"Profile","summary":"The signed-in person: handle, role, identity summary, jurisdiction grants, and election registration status. Choice is never included."},{"method":"GET","path":"/api/session","minLevel":"read","auth":true,"apiKey":true,"tag":"Profile","summary":"Same payload as GET /api/me."},{"method":"PATCH","path":"/api/me/contact-email","minLevel":"voter","auth":true,"apiKey":true,"tag":"Profile","summary":"Set or clear the contact email used for operator notices and the API-key alert.","body":"contactEmail"},{"method":"GET","path":"/api/notifications","minLevel":"read","auth":true,"apiKey":true,"tag":"Profile","summary":"In-app notices for this person, newest first."},{"method":"POST","path":"/api/notifications/:id/read","minLevel":"voter","auth":true,"apiKey":true,"tag":"Profile","summary":"Mark one notice read."},{"method":"GET","path":"/api/me/api-keys","minLevel":"read","auth":true,"apiKey":true,"tag":"API keys","summary":"List this account’s API keys. The secret is never returned. Revoked and expired keys stay in the list."},{"method":"POST","path":"/api/me/api-keys","minLevel":"voter","auth":true,"apiKey":false,"tag":"API keys","summary":"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"},{"method":"DELETE","path":"/api/me/api-keys/:id","minLevel":"voter","auth":true,"apiKey":true,"tag":"API keys","summary":"Revoke one of this account’s keys. Later calls with that secret are rejected."},{"method":"POST","path":"/api/me/pin/keypad","minLevel":"voter","auth":true,"apiKey":true,"tag":"Vote PIN","summary":"Issue the scrambled phone keypad used to enter the vote PIN."},{"method":"POST","path":"/api/me/pin","minLevel":"voter","auth":true,"apiKey":true,"tag":"Vote PIN","summary":"Set the 4–8 digit vote PIN.","body":"pin"},{"method":"POST","path":"/api/me/pin/verify","minLevel":"voter","auth":true,"apiKey":true,"tag":"Vote PIN","summary":"Verify the vote PIN and receive a short-lived pinToken for POST /api/elections/:id/vote.","body":"pin"},{"method":"POST","path":"/api/me/pin/forgot","minLevel":"voter","auth":true,"apiKey":true,"tag":"Vote PIN","summary":"Start a PIN reset for the signed-in person. Completes with email MFA."},{"method":"POST","path":"/api/pin/forgot","minLevel":"voter","auth":false,"apiKey":true,"tag":"Vote PIN","summary":"Start a PIN reset from an email address. Sends a one-time code.","body":"email"},{"method":"GET","path":"/api/identity/methods","minLevel":"read","auth":false,"apiKey":true,"tag":"Identity","summary":"Identity method types a person may bind: email, passkey, Web3, smart card, driver license, passport."},{"method":"GET","path":"/api/me/identity","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Full identity view for the signed-in person: emails, passkeys, Ethereum addresses, civic credentials. No trapdoor and no PIN."},{"method":"POST","path":"/api/identity/email/start","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Start binding another email. One email is primary. Cap is 5.","body":"email"},{"method":"POST","path":"/api/identity/web3/challenge","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Challenge used to prove control of an Ethereum address before it is bound.","body":"address"},{"method":"POST","path":"/api/identity/web3/verify","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Bind a signature-verified Ethereum address. Only one address is active.","body":"address, signature"},{"method":"POST","path":"/api/identity/smart-card/challenge","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Challenge for binding a smart card."},{"method":"POST","path":"/api/identity/smart-card/verify","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Bind a smart card after the reader signs the challenge."},{"method":"POST","path":"/api/identity/civic-id/verify","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Bind a hashed driver license or passport. Images and full document numbers are not stored."},{"method":"GET","path":"/api/me/emails","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Email methods on this person."},{"method":"POST","path":"/api/me/emails/primary","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Choose the primary email. Exactly one email is primary.","body":"id or email"},{"method":"DELETE","path":"/api/me/emails/:id","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Remove one email method."},{"method":"GET","path":"/api/me/passkeys","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Passkeys bound to this person. Cap is 5."},{"method":"DELETE","path":"/api/me/passkeys/:credentialId","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Remove one passkey."},{"method":"GET","path":"/api/me/eth-accounts","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Verified Ethereum addresses and which one is active."},{"method":"POST","path":"/api/me/eth-accounts","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Submit the signature that binds an Ethereum address.","body":"address, signature"},{"method":"POST","path":"/api/me/eth-accounts/active","minLevel":"voter","auth":true,"apiKey":true,"tag":"Identity","summary":"Switch the single active Ethereum address.","body":"address"},{"method":"GET","path":"/api/me/credentials","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Smart cards and civic documents on this person."},{"method":"POST","path":"/api/persons/:id/person-key","minLevel":"officer","auth":true,"apiKey":true,"tag":"Identity","summary":"Registrar or clerk binds the person key for someone on their jurisdiction."},{"method":"POST","path":"/api/persons/:id/methods","minLevel":"officer","auth":true,"apiKey":true,"tag":"Identity","summary":"Officer adds an identity method to a person in their jurisdiction."},{"method":"POST","path":"/api/persons/:id/eth-accounts","minLevel":"officer","auth":true,"apiKey":true,"tag":"Identity","summary":"Officer records an Ethereum address on a person.","body":"address"},{"method":"POST","path":"/api/persons/:id/eth-accounts/active","minLevel":"officer","auth":true,"apiKey":true,"tag":"Identity","summary":"Officer switches the person’s active Ethereum address.","body":"address"},{"method":"GET","path":"/api/persons/:id/identity","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Registrar or clerk reads a person’s identity. A read-only key still needs that officer grant."},{"method":"GET","path":"/api/office/credentials","minLevel":"read","auth":true,"apiKey":true,"tag":"Identity","summary":"Credentials waiting for civil-officer review."},{"method":"POST","path":"/api/persons/:id/methods/:methodId/review","minLevel":"officer","auth":true,"apiKey":true,"tag":"Identity","summary":"Approve or reject a smart card or civic document.","body":"decision"},{"method":"POST","path":"/api/persons/:id/methods/:methodId/revoke","minLevel":"officer","auth":true,"apiKey":true,"tag":"Identity","summary":"Revoke an expired card or license. Revocation is an event."},{"method":"POST","path":"/api/integrations/credentials/status","minLevel":"voter","auth":false,"apiKey":false,"tag":"Identity","summary":"Credential-provider status callback. Uses the host provider secret header, not a personal API key. An API key is refused."},{"method":"GET","path":"/api/elections","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Published elections. Counts follow the hourly disclosure rule."},{"method":"POST","path":"/api/elections","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk creates an election in a jurisdiction they already serve.","body":"title, jurisdictionId, startTime, cutoffTime, questions or ballot"},{"method":"GET","path":"/api/elections/:id","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"One election. When the caller is signed in, includes that person’s registration status. Choice is never included."},{"method":"PATCH","path":"/api/elections/:id","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk edits the ballot before the roll is frozen."},{"method":"PUT","path":"/api/elections/:id","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Same edit as PATCH."},{"method":"GET","path":"/api/elections/:id/info","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Voter-facing questions, options, and supporting info."},{"method":"GET","path":"/api/elections/:id/info/:questionId/:optionId","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"One option and its external links."},{"method":"GET","path":"/api/elections/:id/results","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Certified plurality or IRV results after tally. Empty of option counts before tally."},{"method":"GET","path":"/api/elections/:id/summary","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Short public status: state, window, disclosed ballot count, links."},{"method":"GET","path":"/api/elections/:id/ballots","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Disclosed ballot proofs for closed hours. Choice is not a field."},{"method":"GET","path":"/api/elections/:id/artifacts","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Auditor pack: roots, counts, and public checks. No Node private database."},{"method":"GET","path":"/api/elections/:id/l1","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"L1 certification status for the tally, when a tally exists."},{"method":"GET","path":"/api/elections/:id/tally-key","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Public tally ceremony view. The Node does not return a tally private key."},{"method":"GET","path":"/api/elections/:id/staff","minLevel":"read","auth":true,"apiKey":true,"tag":"Elections","summary":"Election staff list. Caller must already be staff for that election."},{"method":"POST","path":"/api/elections/:id/staff","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Platform admin approves election staff.","body":"handle or userId"},{"method":"DELETE","path":"/api/elections/:id/staff","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Platform admin revokes election staff.","body":"handle or userId"},{"method":"GET","path":"/api/elections/:id/registrations","minLevel":"read","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk, registrar, or disqualification officer lists the roll and requests."},{"method":"GET","path":"/api/elections/:id/registration-requests","minLevel":"read","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk or registrar lists registration requests. Filter with ?status=."},{"method":"POST","path":"/api/elections/:id/register","minLevel":"voter","auth":true,"apiKey":true,"tag":"Elections","summary":"Voter asks to join the roll. A registrar or clerk of that jurisdiction must approve.","body":"identityCommitment"},{"method":"POST","path":"/api/elections/:id/registration-requests/:requestId/approve","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk or registrar approves a registration request and inserts the commitment."},{"method":"POST","path":"/api/elections/:id/registration-requests/:requestId/deny","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk or registrar declines a registration request.","body":"reason"},{"method":"POST","path":"/api/elections/:id/freeze","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Clerk freezes the eligible and disqualification roots."},{"method":"POST","path":"/api/elections/:id/vote","minLevel":"voter","auth":false,"apiKey":true,"tag":"Elections","summary":"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"},{"method":"POST","path":"/api/elections/:id/disqualify","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Disqualification officer applies a coded reason and an evidence hash.","body":"identityCommitment, reasonCode, evidenceHash"},{"method":"POST","path":"/api/elections/:id/disqualify/revoke","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Disqualification officer records a revocation event."},{"method":"POST","path":"/api/elections/:id/revoke","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Same revocation event as /disqualify/revoke."},{"method":"GET","path":"/api/elections/:id/envelopes","minLevel":"read","auth":true,"apiKey":true,"tag":"Elections","summary":"Tally operator reads encrypted envelopes after cutoff. Plaintext choice is not stored."},{"method":"POST","path":"/api/elections/:id/guardians","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Tally operator enrolls a guardian in the opens-ballots ceremony. Non-production cryptography."},{"method":"POST","path":"/api/elections/:id/guardians/deals","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Tally operator posts a guardian deal."},{"method":"GET","path":"/api/elections/:id/guardians/:index/inbox","minLevel":"read","auth":false,"apiKey":true,"tag":"Elections","summary":"Encrypted shares addressed to one enrolled guardian index."},{"method":"POST","path":"/api/elections/:id/guardians/partials","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Tally operator submits partial decryptions after cutoff."},{"method":"POST","path":"/api/elections/:id/tally","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Tally operator posts the tally after cutoff. Invented counts that do not match opened ballots are rejected for opens-ballots."},{"method":"POST","path":"/api/elections/:id/certify-l1","minLevel":"officer","auth":true,"apiKey":true,"tag":"Elections","summary":"Tally operator asks the Node to mirror the tally to L1 when L1 env is set."},{"method":"GET","path":"/api/elections/:id/parties","minLevel":"read","auth":false,"apiKey":true,"tag":"External parties","summary":"External eligibility parties for an election. Endpoint URLs are included only for staff."},{"method":"POST","path":"/api/elections/:id/parties","minLevel":"officer","auth":true,"apiKey":true,"tag":"External parties","summary":"Election staff registers an external eligibility party.","body":"name, mode, operatorHandle"},{"method":"POST","path":"/api/elections/:id/parties/check","minLevel":"officer","auth":true,"apiKey":true,"tag":"External parties","summary":"Staff checks whether a commitment is cleared to cast.","body":"identityCommitment"},{"method":"PATCH","path":"/api/elections/:id/parties/:partyId","minLevel":"officer","auth":true,"apiKey":true,"tag":"External parties","summary":"Staff changes a party status.","body":"status"},{"method":"GET","path":"/api/elections/:id/parties/:partyId/rejections","minLevel":"read","auth":true,"apiKey":true,"tag":"External parties","summary":"Staff lists party rejections."},{"method":"POST","path":"/api/elections/:id/parties/:partyId/reject","minLevel":"officer","auth":true,"apiKey":true,"tag":"External parties","summary":"Staff or the party operator records a rejection.","body":"identityCommitment, reasonCode, evidenceHash"},{"method":"POST","path":"/api/elections/:id/parties/:partyId/clear","minLevel":"officer","auth":true,"apiKey":true,"tag":"External parties","summary":"Staff or the party operator clears a rejection.","body":"identityCommitment, evidenceHash"},{"method":"POST","path":"/api/elections/:id/parties/:partyId/attest","minLevel":"officer","auth":true,"apiKey":true,"tag":"External parties","summary":"Staff or the party operator attests a commitment.","body":"identityCommitment, expiresAt, evidenceHash"},{"method":"GET","path":"/api/jurisdictions","minLevel":"read","auth":false,"apiKey":true,"tag":"Jurisdictions","summary":"Jurisdictions on this Node. Officer lists are included only for officers."},{"method":"POST","path":"/api/jurisdictions","minLevel":"officer","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Platform admin creates a jurisdiction.","body":"name, metadataURI. The Node assigns a UUID id; a caller cannot choose it."},{"method":"GET","path":"/api/jurisdictions/:id","minLevel":"read","auth":false,"apiKey":true,"tag":"Jurisdictions","summary":"One jurisdiction."},{"method":"GET","path":"/api/jurisdictions/:id/roles","minLevel":"read","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Officers granted on a jurisdiction. Caller must already be an officer."},{"method":"POST","path":"/api/jurisdictions/:id/roles","minLevel":"officer","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Platform admin grants clerk, registrar, disqualification officer, tally operator, or election staff.","body":"role, handle or userId"},{"method":"DELETE","path":"/api/jurisdictions/:id/roles","minLevel":"officer","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Platform admin revokes a jurisdiction grant.","body":"role, handle or userId"},{"method":"POST","path":"/api/admin/roles","minLevel":"officer","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Same grant as POST /api/jurisdictions/:id/roles.","body":"jurisdictionId, role, handle or userId"},{"method":"POST","path":"/api/admin/grant","minLevel":"officer","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Alias of POST /api/admin/roles.","body":"jurisdictionId, role, handle or userId"},{"method":"GET","path":"/api/users","minLevel":"read","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"Platform admin lists accounts and their jurisdiction grants. A read-only key may list. It cannot change roles."},{"method":"PATCH","path":"/api/users/:id","minLevel":"officer","auth":true,"apiKey":true,"tag":"Jurisdictions","summary":"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"},{"method":"GET","path":"/api/reason-codes","minLevel":"read","auth":false,"apiKey":true,"tag":"Jurisdictions","summary":"Public disqualification reason catalog."},{"method":"GET","path":"/api/pages","minLevel":"read","auth":false,"apiKey":true,"tag":"Pages","summary":"Published public pages and the shared-contract site view."},{"method":"GET","path":"/api/pages/:slug","minLevel":"read","auth":false,"apiKey":true,"tag":"Pages","summary":"One published page."},{"method":"GET","path":"/api/site","minLevel":"read","auth":false,"apiKey":true,"tag":"Pages","summary":"Public site record, including pinned contract addresses."},{"method":"GET","path":"/api/office/site","minLevel":"read","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk reads the office site record."},{"method":"PATCH","path":"/api/office/site","minLevel":"officer","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk pins shared contract addresses. Environment pins win over the stored site."},{"method":"GET","path":"/api/office/pages","minLevel":"read","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk lists CMS pages, including drafts they may edit."},{"method":"POST","path":"/api/office/pages","minLevel":"officer","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk creates a page."},{"method":"GET","path":"/api/office/pages/:id","minLevel":"read","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk reads one CMS page."},{"method":"PATCH","path":"/api/office/pages/:id","minLevel":"officer","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk edits a CMS page."},{"method":"DELETE","path":"/api/office/pages/:id","minLevel":"officer","auth":true,"apiKey":true,"tag":"Pages","summary":"Clerk deletes a CMS page."},{"method":"GET","path":"/api/terms","minLevel":"read","auth":false,"apiKey":true,"tag":"Terms","summary":"Current Terms of Use metadata and whether the caller has accepted this PDF hash."},{"method":"GET","path":"/api/terms.pdf","minLevel":"read","auth":false,"apiKey":true,"tag":"Terms","summary":"Terms PDF bytes. SHA-256 is in the X-Pdf-SHA256 header."},{"method":"GET","path":"/terms.pdf","minLevel":"read","auth":false,"apiKey":true,"tag":"Terms","summary":"Same Terms PDF as GET /api/terms.pdf."},{"method":"POST","path":"/api/terms/view","minLevel":"voter","auth":false,"apiKey":true,"tag":"Terms","summary":"Record that the signed-in person viewed the Terms."},{"method":"POST","path":"/api/terms/accept","minLevel":"voter","auth":true,"apiKey":true,"tag":"Terms","summary":"Accept the current Terms. Acceptance stores the SHA-256 of the PDF bytes.","body":"acceptedTerms"},{"method":"GET","path":"/api/feedback","minLevel":"read","auth":false,"apiKey":true,"tag":"Feedback","summary":"Feedback form limits, categories, and whether the operator inbox is configured."},{"method":"POST","path":"/api/feedback","minLevel":"voter","auth":false,"apiKey":true,"tag":"Feedback","summary":"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"},{"method":"GET","path":"/api/receipts/metrics","minLevel":"read","auth":false,"apiKey":true,"tag":"Receipts","summary":"Public receipt metrics and hourly turnout. No candidate and no option."},{"method":"POST","path":"/api/receipts/verify","minLevel":"read","auth":false,"apiKey":true,"tag":"Receipts","summary":"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"},{"method":"POST","path":"/api/receipts/email","minLevel":"voter","auth":true,"apiKey":true,"tag":"Receipts","summary":"Email the voter their hash-only receipt after the ballot is accepted."},{"method":"POST","path":"/api/receipts/pdf","minLevel":"voter","auth":true,"apiKey":true,"tag":"Receipts","summary":"Download the locked receipt PDF for a ballot this caller may access."},{"method":"GET","path":"/api/outcomes","minLevel":"read","auth":false,"apiKey":true,"tag":"Outcomes","summary":"Every election grouped before, during, and after voting. No unofficial option counts during the open window."},{"method":"GET","path":"/api/outcomes/:id","minLevel":"read","auth":false,"apiKey":true,"tag":"Outcomes","summary":"One election’s public outcome monitor."},{"method":"GET","path":"/api/outcomes/reports","minLevel":"read","auth":false,"apiKey":true,"tag":"Outcomes","summary":"Issued official outcome reports."},{"method":"GET","path":"/api/outcomes/reports/:id","minLevel":"read","auth":false,"apiKey":true,"tag":"Outcomes","summary":"One official outcome report record."},{"method":"GET","path":"/api/outcomes/reports/:id/pdf","minLevel":"read","auth":false,"apiKey":true,"tag":"Outcomes","summary":"Locked official outcome PDF."},{"method":"POST","path":"/api/outcomes/verify","minLevel":"read","auth":false,"apiKey":true,"tag":"Outcomes","summary":"Check an official outcome PDF was not modified.","body":"pdf bytes or id"},{"method":"GET","path":"/api/me/outcome-subscriptions","minLevel":"read","auth":true,"apiKey":true,"tag":"Outcomes","summary":"Elections whose official PDF this person will be emailed. Voting subscribes them by default."},{"method":"PUT","path":"/api/me/outcome-subscriptions/:electionId","minLevel":"voter","auth":true,"apiKey":true,"tag":"Outcomes","summary":"Subscribe or confirm a subscription to an official outcome PDF."},{"method":"POST","path":"/api/me/outcome-subscriptions","minLevel":"voter","auth":true,"apiKey":true,"tag":"Outcomes","summary":"Subscribe the caller to an election’s official outcome PDF.","body":"electionId"},{"method":"GET","path":"/api/admin/outcome-report-template","minLevel":"read","auth":true,"apiKey":true,"tag":"Outcomes","summary":"Platform admin reads the official outcome PDF template. QR and hash seals stay required."},{"method":"PATCH","path":"/api/admin/outcome-report-template","minLevel":"officer","auth":true,"apiKey":true,"tag":"Outcomes","summary":"Platform admin edits template copy. QR and hash seals cannot be removed."},{"method":"POST","path":"/api/admin/outcome-report-template/preview","minLevel":"officer","auth":true,"apiKey":true,"tag":"Outcomes","summary":"Platform admin previews the outcome PDF template."},{"method":"GET","path":"/api/explorer/disclosure","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"Hourly disclosure window the explorer uses."},{"method":"GET","path":"/api/explorer/blocks","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"Civic L2 blocks."},{"method":"GET","path":"/api/explorer/blocks/:n","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"One civic L2 block."},{"method":"GET","path":"/api/explorer/txs/:hash","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"One civic L2 transaction. Ballot plaintext is not a field."},{"method":"GET","path":"/api/explorer/nullifiers/:value","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"Whether a nullifier was accepted. The nullifier does not reveal the choice or the person."},{"method":"GET","path":"/api/explorer/search","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"Search blocks, transactions, and elections."},{"method":"GET","path":"/api/explorer/elections/:id","minLevel":"read","auth":false,"apiKey":true,"tag":"Explorer","summary":"Explorer view of one election."},{"method":"POST","path":"/api/explorer/ingest","minLevel":"officer","auth":false,"apiKey":true,"tag":"Explorer","summary":"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."},{"method":"GET","path":"/api/polls","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"Token polls when POLLS_ENABLED is on. Polls are signal, not binding elections. Omitted from the Node when the flag is off."},{"method":"GET","path":"/api/polls/:id","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"One token poll."},{"method":"GET","path":"/api/polls/:id/weight","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"Read-only token weight for an address. The Node does not move tokens."},{"method":"GET","path":"/api/polls/:id/typed-data","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"EIP-712 typed data a holder signs. Query: address, choices."},{"method":"POST","path":"/api/polls/:id/ballots","minLevel":"voter","auth":false,"apiKey":true,"tag":"Polls","summary":"Submit a signed token-poll ballot. No Republic account is required. A read-only key is refused."},{"method":"POST","path":"/api/polls/:id/tally","minLevel":"voter","auth":true,"apiKey":true,"tag":"Polls","summary":"Any signed-in person may ask the Node to tally a closed token poll. Counts come from accepted ballots."},{"method":"GET","path":"/api/polls/:id/results","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"Token poll results after tally."},{"method":"GET","path":"/api/polls/:id/artifacts","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"Token poll auditor pack."},{"method":"GET","path":"/api/polls/:id/ballots.json","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"Disclosed token-poll ballots."},{"method":"GET","path":"/api/polls/:id/outcome.pdf","minLevel":"read","auth":false,"apiKey":true,"tag":"Polls","summary":"Token poll outcome PDF."},{"method":"GET","path":"/api/mail/templates","minLevel":"read","auth":true,"apiKey":true,"tag":"Mail","summary":"Platform admin lists operator mail templates stored on the Node. Mail is never written to civic L2."},{"method":"GET","path":"/api/mail/templates/:id","minLevel":"read","auth":true,"apiKey":true,"tag":"Mail","summary":"Platform admin previews one operator mail template, including api_key.created."},{"method":"GET","path":"/api/mail/outbox","minLevel":"read","auth":true,"apiKey":true,"tag":"Mail","summary":"Platform admin lists queued operator mail. Bodies stay on the detail route."},{"method":"GET","path":"/api/mail/outbox/:id","minLevel":"read","auth":true,"apiKey":true,"tag":"Mail","summary":"Platform admin reads one outbox message, including text and HTML."}]}