Developer Docs

Create polls, collect votes, and receive real-time results through a simple REST API. Add live polls to any website with our embeddable widget.

https://livepolls.com/api/v1Get API Key →

Authentication#

All API requests require a Bearer token. Generate your API key from the Dashboard. Keys use the format lp_live_*.

Authorization: Bearer lp_live_abc123...

Quick Start#

Create a poll with a single request.

curl -X POST https://livepolls.com/api/v1/polls \
  -H "Authorization: Bearer lp_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"question":"Tabs or spaces?","options":["Tabs","Spaces"]}'
POST/api/v1/polls

Create a new poll with a question and list of options.

Parameters

ParameterTypeRequiredDescription
questionstringYesThe poll question
optionsstring[]Yes2–10 answer options
settingsobjectNoOptional settings (e.g. lead_capture)

Example

curl -X POST https://livepolls.com/api/v1/polls \
  -H "Authorization: Bearer lp_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"question":"Tabs or spaces?","options":["Tabs","Spaces"]}'

Response 201

{
  "id": "a1b2c3d4-...",
  "slug": "tabs-or-spaces",
  "question": "Tabs or spaces?",
  "options": [
    { "id": "opt1-...", "text": "Tabs" },
    { "id": "opt2-...", "text": "Spaces" }
  ],
  "createdAt": "2026-03-22T12:00:00.000Z"
}
GET/api/v1/polls/:id

Retrieve a poll by UUID or slug, including current results.

Parameters

ParameterTypeRequiredDescription
idstringYesPoll UUID or slug (path parameter)

Example

curl https://livepolls.com/api/v1/polls/tabs-or-spaces \
  -H "Authorization: Bearer lp_live_abc123..."

Response 200

{
  "id": "a1b2c3d4-...",
  "slug": "tabs-or-spaces",
  "question": "Tabs or spaces?",
  "type": "single",
  "options": [{ "id": "opt1-...", "text": "Tabs" }, ...],
  "closesAt": null,
  "createdAt": "2026-03-22T12:00:00.000Z",
  "totalVotes": 142,
  "results": [
    { "optionId": "opt1-...", "text": "Tabs", "votes": 89, "percentage": 62.68 },
    { "optionId": "opt2-...", "text": "Spaces", "votes": 53, "percentage": 37.32 }
  ]
}
POST/api/v1/polls/:id/vote

Cast a vote on a poll. API votes use source="api" and skip duplicate prevention — your app manages its own dedup.

Parameters

ParameterTypeRequiredDescription
idstringYesPoll UUID or slug (path parameter)
optionIdstringYesUUID of the option to vote for

Example

curl -X POST https://livepolls.com/api/v1/polls/tabs-or-spaces/vote \
  -H "Authorization: Bearer lp_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"optionId":"opt1-..."}'

Response 200

{
  "voteId": "v1d2e3f4-...",
  "results": [
    { "optionId": "opt1-...", "text": "Tabs", "votes": 90, "percentage": 62.94 },
    { "optionId": "opt2-...", "text": "Spaces", "votes": 53, "percentage": 37.06 }
  ],
  "totalVotes": 143
}
GET/api/v1/polls/:id/results

Get current vote results for a poll.

Parameters

ParameterTypeRequiredDescription
idstringYesPoll UUID or slug (path parameter)

Example

curl https://livepolls.com/api/v1/polls/tabs-or-spaces/results \
  -H "Authorization: Bearer lp_live_abc123..."

Response 200

{
  "results": [
    { "optionId": "opt1-...", "text": "Tabs", "votes": 89, "percentage": 62.68 },
    { "optionId": "opt2-...", "text": "Spaces", "votes": 53, "percentage": 37.32 }
  ],
  "totalVotes": 142
}

Rate Limits#

Requests are rate-limited per API key on a rolling daily window.

PlanLimitPrice
Free100 requests/day$0
Pro10,000 requests/day$9/mo

Rate limit headers are included on every response:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1711152000 // Unix timestamp

Error Codes#

Errors return a consistent JSON format: { error, code }

StatusCodeDescription
400VALIDATION_ERRORMissing or invalid request body / parameters
401UNAUTHORIZEDMissing or invalid API key
404NOT_FOUNDPoll or resource not found
409ALREADY_VOTEDDuplicate vote detected
409LIMIT_REACHEDWebhook limit reached (max 1 per user)
410POLL_EXPIREDPoll has closed and no longer accepts votes
429RATE_LIMITEDDaily request limit exceeded
500INTERNAL_ERRORUnexpected server error
{
  "error": "Poll not found",
  "code": "NOT_FOUND"
}

Webhook Setup#

Webhooks are managed entirely via the API. You can register one webhook endpoint per account. When events fire, LivePolls sends a POST request to your URL with an HMAC-signed payload.

POST/api/v1/webhooks

Register a webhook endpoint. HTTPS URLs only. Max 1 per user.

Parameters

ParameterTypeRequiredDescription
urlstringYesHTTPS endpoint URL
eventsstring[]YesEvents to subscribe to: "poll.closed", "poll.threshold"
thresholdnumberNoVote count threshold (required if subscribing to poll.threshold)

Example

curl -X POST https://livepolls.com/api/v1/webhooks \
  -H "Authorization: Bearer lp_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/webhook","events":["poll.closed","poll.threshold"],"threshold":100}'

Response 201

{
  "id": "wh1-...",
  "url": "https://example.com/webhook",
  "events": ["poll.closed", "poll.threshold"],
  "threshold": 100,
  "secret": "whsec_a1b2c3d4...", // shown once
  "active": true,
  "createdAt": "2026-03-22T12:00:00.000Z"
}

Use GET /api/v1/webhooks to list your webhooks, or DELETE /api/v1/webhooks/:id to remove one.

Events#

Two event types are supported:

  • poll.closed — Fires when a poll's closes_at time is reached. Checked every minute via cron.
  • poll.threshold — Fires when total votes reach your configured threshold. Triggered in real time on vote.

Example payload:

{
  "event": "poll.closed",
  "timestamp": "2026-03-22T12:00:00.000Z",
  "data": {
    "id": "a1b2c3d4-...",
    "slug": "tabs-or-spaces",
    "question": "Tabs or spaces?",
    "totalVotes": 142,
    "closedAt": "2026-03-22T12:00:00.000Z",
    "results": [
      { "optionId": "opt1-...", "text": "Tabs", "votes": 89, "percentage": 62.68 },
      { "optionId": "opt2-...", "text": "Spaces", "votes": 53, "percentage": 37.32 }
    ]
  }
}

Signature Verification#

Every webhook delivery includes an X-LivePolls-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this before processing the payload.

async function verifyWebhook(body, signature, secret) {
  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const sig = await crypto.subtle.sign(
    "HMAC", key, new TextEncoder().encode(body)
  );
  const expected = "sha256=" + Array.from(new Uint8Array(sig))
    .map(b => b.toString(16).padStart(2, "0"))
    .join("");
  return signature === expected;
}

// Usage in your webhook handler:
const body = await request.text();
const sig = request.headers.get("X-LivePolls-Signature");
if (!await verifyWebhook(body, sig, "whsec_...")) {
  return new Response("Unauthorized", { status: 401 });
}

Embed Installation#

Add a live poll to any webpage with a single script tag. The widget creates a responsive iframe, handles automatic resizing, and tracks votes with source="embed".

<script
  src="https://livepolls.com/embed/v1/widget.js"
  data-poll-id="your-poll-slug"
  data-accent-color="#10B981"
  async></script>

Embed Customization#

Configure the widget using data attributes on the script tag.

ParameterTypeRequiredDescription
data-poll-idstringYesPoll slug or UUID
data-accent-colorstringNoHex color for brand accent (default: #10B981)

Free accounts display a "Powered by LivePolls" badge in the embed. Upgrade to Pro ($9/mo) to remove it.