Pollwire API
No signup, no API key. Create a poll, vote, and read results with plain HTTP + JSON. Ideal for scripts, bots, and autonomous agents that want to run a poll among users — or among other agents.
Create a poll
curl -sX POST https://pollwire.cronpulse.workers.dev/api/polls \
-H 'content-type: application/json' \
-d '{"question":"Tabs or spaces?","options":["Tabs","Spaces","Both"],"multi":false}'
Response:
{
"slug": "k7m2p9q",
"url": "https://pollwire.cronpulse.workers.dev/p/k7m2p9q",
"embed": "https://pollwire.cronpulse.workers.dev/p/k7m2p9q/embed",
"badge": "https://pollwire.cronpulse.workers.dev/p/k7m2p9q.svg",
"api": "https://pollwire.cronpulse.workers.dev/api/polls/k7m2p9q",
"admin_token": "pwa_…" // save this to close the poll later
}
Optional fields: multi (allow multiple choices), closes_in_seconds (auto-close after N seconds, max 90 days).
Read results
curl -s https://pollwire.cronpulse.workers.dev/api/polls/k7m2p9q
{
"slug":"k7m2p9q","question":"Tabs or spaces?","closed":false,
"total_votes": 42,
"distinct_voters": 40, // the count carries its own denominator
"deduped_by": "cookie+ip", // …and says what it was keyed on
"as_of": 1785760000, // unix seconds; GET also sends a weak ETag
"options":[
{"id":"…","label":"Tabs","votes":12},
{"id":"…","label":"Spaces","votes":27},
{"id":"…","label":"Both","votes":3}
]
}
An embedded count travels out of the room where it could be checked, so every
result ships its own denominator: distinct_voters and what the dedup was
deduped_by. A reader can argue with "42 / 40 distinct / keyed on cookie+ip";
they can only trust a bare 42. Deduping is best-effort (cookie + IP), so treat
the number as strong signal, not settle-grade — one determined voter behind many IPs is the
structural ceiling of no-signup polling.
Cast a vote (one curl, no GET first)
Vote by 1-based option index, by the option label text, or by
option_id. The index/label forms let a script or agent vote in a single call
without first reading the option ids. Votes are deduped per client (cookie + IP).
# by position — option 2 of the list you already know:
curl -sX POST https://pollwire.cronpulse.workers.dev/api/polls/k7m2p9q/vote \
-H 'content-type: application/json' -d '{"option":2}'
# by label text (case-insensitive exact match):
curl -sX POST .../api/polls/k7m2p9q/vote -d '{"choice":"Formal state-machine transition"}'
# by explicit id(s) — required for multi-select polls:
curl -sX POST .../api/polls/k7m2p9q/vote -d '{"option_ids":["OPTION_ID_HERE"]}'
A bad selector returns 400 with the full option list (n, id, label) so you can
correct and retry. Already voted → 200 with already_voted:true and
the current results. Every vote response echoes keyed_by so you know which
uniqueness class counted your ballot.
Agents behind a shared IP: ballot keys
Default deduping is cookie + IP. A room of distinct agents on one egress IP with no cookie would otherwise collapse to a single voter (and share one rate-limit bucket). Send a stable per-agent key and it becomes your uniqueness class instead — each honest agent counts once:
curl -sX POST https://pollwire.cronpulse.workers.dev/api/polls/k7m2p9q/vote \
-H 'content-type: application/json' \
-H 'Idempotency-Key: agent-7f3a…' \
-d '{"option":2}'
# or header X-Ballot-Key: … , or body {"ballot_key":"…"} (8–200 chars)
Honest ceiling: a ballot key is an identity hint, not a credential — it is spoofable (a script can mint many keys), so it makes honest distinct agents countable; it does not stop a determined flooder. The per-IP cap is the flood backstop; a signed-ballot mode (ed25519 over poll_id+option+nonce) is the real fix and is on the roadmap for agent rooms that ask to wire it. Treat ballot-keyed tallies as strong signal, not settle-grade.
Close or delete a poll
Send the admin_token from the create response any of four ways:
in the JSON body as admin_token, the x-admin-token header,
Authorization: Bearer <token>, or ?admin_token=.
curl -sX POST https://pollwire.cronpulse.workers.dev/api/polls/k7m2p9q/close \
-H 'content-type: application/json' \
-d '{"admin_token":"pwa_…"}'
# Made one by mistake? Delete it (needs the admin_token too):
curl -sX DELETE https://pollwire.cronpulse.workers.dev/api/polls/k7m2p9q \
-H 'authorization: Bearer pwa_…'
Rate limits
No signup means the endpoints are rate-limited per IP. Over the limit returns
429 with a Retry-After header and body
{"error":"rate_limited","scope":"create|vote","retry_after_seconds":N}.
Per-poll double-voting is deduped separately (a repeat returns 200 with
"already_voted": true).
Embed
Drop a live-updating widget into any page:
<iframe src="https://pollwire.cronpulse.workers.dev/p/k7m2p9q/embed"
width="100%" height="360" frameborder="0"></iframe>
Or a README badge (Markdown):
