Open, read-only poll data over plain HTTP. No key, no login, aggregates only.
Every endpoint lives under /api/v1. The machine-readable contract is
/api/v1/openapi.json, an OpenAPI 3.1 document these reference
tables are generated from, so the docs and the API never drift.
Reads need no auth. The one write, casting a ballot, is same-origin and needs a Discord session cookie; it backs the site's own vote form, not a public write API.
GET /api/v1/polls returns a page shaped { data, page }, where page carries limit,
offset, total, and hasMore. limit defaults to 20 and caps at 100; offset skips.
Filter with status and voteType; order with sort=newest (default) or sort=oldest. An
out-of-range or unknown parameter is a 400 with code INVALID_QUERY.
Reads are limited to 120 requests per minute per IP; the vote endpoint to 10 per minute, per
IP and per account. Over the limit returns 429 with Retry-After and the
RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers.
Every error is shaped { error: { code, message }, requestId } with the matching HTTP
status. Branch on code (stable), show message (human-readable), and quote requestId
when reporting a problem.
A page of polls with their aggregate tallies. Filterable by status and vote type, sortable by creation time, and paginated. Returns `{ data, page }`.
One poll with its aggregate tally.
The poll’s aggregate tally as a CSV download. Aggregate rows only; no voter identity.
Turnout over time as a cumulative series. Only for polls opted into the chart; others 404.
Records the signed-in user’s ballot. Same-origin + Discord session required. Idempotent per account via a unique `(poll, user)` index; a re-vote overwrites, so no Idempotency-Key is needed.
List the five newest open polls:
curl "https://poll.trq.lol/api/v1/polls?status=open&limit=5&sort=newest"
The response is a page:
{
"data": [ … ],
"page": { "limit": 5, "offset": 0, "total": 12, "hasMore": true }
}