+ New poll

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):

![poll](https://pollwire.cronpulse.workers.dev/p/k7m2p9q.svg)

Make a poll →