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"]}'
/api/v1/pollsCreate a new poll with a question and list of options.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| question | string | Yes | The poll question |
| options | string[] | Yes | 2–10 answer options |
| settings | object | No | Optional 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"
}/api/v1/polls/:idRetrieve a poll by UUID or slug, including current results.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Poll 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 }
]
}/api/v1/polls/:id/voteCast a vote on a poll. API votes use source="api" and skip duplicate prevention — your app manages its own dedup.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Poll UUID or slug (path parameter) |
| optionId | string | Yes | UUID 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
}/api/v1/polls/:id/resultsGet current vote results for a poll.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Poll 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.
| Plan | Limit | Price |
|---|---|---|
| Free | 100 requests/day | $0 |
| Pro | 10,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 }
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid request body / parameters |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 404 | NOT_FOUND | Poll or resource not found |
| 409 | ALREADY_VOTED | Duplicate vote detected |
| 409 | LIMIT_REACHED | Webhook limit reached (max 1 per user) |
| 410 | POLL_EXPIRED | Poll has closed and no longer accepts votes |
| 429 | RATE_LIMITED | Daily request limit exceeded |
| 500 | INTERNAL_ERROR | Unexpected 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.
/api/v1/webhooksRegister a webhook endpoint. HTTPS URLs only. Max 1 per user.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | HTTPS endpoint URL |
| events | string[] | Yes | Events to subscribe to: "poll.closed", "poll.threshold" |
| threshold | number | No | Vote 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'scloses_attime 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| data-poll-id | string | Yes | Poll slug or UUID |
| data-accent-color | string | No | Hex 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.