June 18, 2026 · 10 min read
हमने अपने weekly retro को PollsLive API से ऑटोपायलट पर कैसे डाला
एक डेवलपर की पूरी कहानी: एक retro poll बनाएँ, एक live session खोलें, फिर results और एक CSV खींचें, सब कुछ cron पर चल रहे एक छोटे से Node स्क्रिप्ट से। असली /api/v1 endpoints, असली payloads।
मैं एक प्लेटफ़ॉर्म इंजीनियर हूँ, और पहले हर शुक्रवार मैं वही retro deck हाथ से बनाने में दस मिनट खर्च करता था। वही पाँच सवाल, बस हफ़्ता अलग। ठेठ वो-काम जो किसी स्क्रिप्ट को करना चाहिए। तो मैंने इसे PollsLive API और एक cron job पर डाल दिया: अब poll खुद बन जाती है, एक live session अपने-आप खुल जाता है, और standup के बाद एक CSV हमारे Slack में आ जाता है। यहाँ ठीक-ठीक कैसे, असली endpoints और payloads के साथ।
Auth: एक ही Bearer token
मैंने Studio → डेवलपर API में एक workspace API key बनाई (ये `plv_live_…` जैसी दिखती हैं) और उसे स्क्रिप्ट के environment में डाल दिया। हर request बस एक Bearer token है: server-to-server इस्तेमाल के लिए कोई OAuth झंझट नहीं।
# Base URL for all calls
export POLLSLIVE_API=https://pollslive.com/api/v1
export POLLSLIVE_KEY=plv_live_xxxxxxxxxxxxxxxxxxxx
# Every request carries the key as a Bearer token:
# Authorization: Bearer $POLLSLIVE_KEY
# Limits and HTTP 429 handling: https://pollslive.com/docs/developers/rate-limitsStep 1: retro poll बनाएँ
एक poll एक typed deck है: `content.questions` slides की एक array है, हर एक में एक `kind` discriminator। retro के लिए मैं एक scale, एक open_ended जो cloud के रूप में दिखती है, और fix पर वोट करने के लिए एक multiple_choice इस्तेमाल करता हूँ। `renderingMode: "LIVE"` के साथ `POST /polls` मुझे एक draft वापस देता है।
curl -sS -X POST "$POLLSLIVE_API/polls" \
-H "Authorization: Bearer $POLLSLIVE_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Weekly retro - week of Jun 16",
"renderingMode": "LIVE",
"content": {
"questions": [
{
"id": "health",
"kind": "scale",
"text": "How healthy did this sprint feel?",
"min": 1, "max": 5,
"minLabel": "Rough", "maxLabel": "Great"
},
{
"id": "blockers",
"kind": "open_ended",
"text": "What slowed us down this sprint?",
"render": "cloud",
"maxLength": 120
},
{
"id": "fix",
"kind": "multiple_choice",
"type": "single",
"text": "What should we fix first next sprint?",
"options": [
{ "id": "ci", "label": "Stabilise the CI pipeline" },
{ "id": "tickets", "label": "Tighter ticket acceptance criteria" },
{ "id": "wip", "label": "Cap WIP and reviews SLA" },
{ "id": "meetings", "label": "Trim the meeting load" }
]
}
]
}
}'`201` response poll को एक `data` envelope में लपेट देता है। बाक़ी flow के लिए मैं `data.id` रखता हूँ, और join विवरण के लिए `data.accessPin` / `data.shareUrl`:
{
"data": {
"id": "poll_9aZ2kP",
"slug": "weekly-retro-jun-16",
"title": "Weekly retro - week of Jun 16",
"renderingMode": "LIVE",
"isPublished": false,
"accessPin": "6098",
"shareUrl": "https://pollslive.com/p/weekly-retro-jun-16",
"createdAt": "2026-06-16T08:00:11.204Z"
}
}Step 2: Publish करें, फिर एक live session खोलें
Publish करने से content का snapshot बन जाता है ताकि उसे present करना सुरक्षित रहे। `publish: true` के साथ एक `PATCH /polls/{id}` यह कर देता है; फिर `POST /sessions` presenter session शुरू करता है और join PIN के साथ एक one-time `hostToken` लौटाता है (यह host-authority सीक्रेट है: यह सिर्फ़ create पर ही लौटता है, इसलिए मैं इसे तुरंत संभाल लेता हूँ)।
# Publish the draft
curl -sS -X PATCH "$POLLSLIVE_API/polls/poll_9aZ2kP" \
-H "Authorization: Bearer $POLLSLIVE_KEY" \
-H "Content-Type: application/json" \
-d '{ "publish": true }'
# Open a presenter-paced live session
curl -sS -X POST "$POLLSLIVE_API/sessions" \
-H "Authorization: Bearer $POLLSLIVE_KEY" \
-H "Content-Type: application/json" \
-d '{ "pollId": "poll_9aZ2kP", "pace": "PRESENTER" }'
# → { "data": { "id": "ses_4dF1", "pin": "6098",
# "joinUrl": "https://pollslive.com/play/6098",
# "hostUrl": "https://pollslive.com/host/ses_4dF1",
# "hostToken": "hst_…", "status": "LOBBY" } }मेरा cron standup से एक मिनट पहले `joinUrl` और PIN टीम चैनल में पोस्ट कर देता है। टीम scan करती है, और कोड में मैंने जो slides बनाए वही वे देखते हैं। सबके जवाब देने के बाद यहाँ `health` slide का live नतीजा है:
इस sprint की सेहत कैसी लगी? (1 = खराब, 5 = बढ़िया)
Step 3: standup के बाद results और एक CSV खींचें
जब session खत्म होता है, `GET /polls/{id}/results` tallies को एक `slides[]` array में लौटाता है, हर slide की एक entry, per-kind aggregates के साथ (choice slides के लिए `optionCounts`, scale averages, open text, वगैरह)। live session के बाद यही हिस्सा पढ़ें: ऊपरी स्तर के `totalVotes` और `voteCounts` सिर्फ़ async votes गिनते हैं, इसलिए जो poll सिर्फ़ live चला हो उसके लिए वे शून्य और खाली रहते हैं।
curl -sS "$POLLSLIVE_API/polls/poll_9aZ2kP/results" \
-H "Authorization: Bearer $POLLSLIVE_KEY"
{
"data": {
"pollId": "poll_9aZ2kP",
"slug": "weekly-retro-jun-16",
"title": "Weekly retro - week of Jun 16",
"isPublished": true,
"closed": false,
"totalVotes": 0,
"voteCounts": {},
"slides": [
{ "slideId": "health", "kind": "scale", "totalResponses": 9,
"average": 2.888888888888889,
"distribution": { "1": 1, "2": 3, "3": 2, "4": 2, "5": 1 } },
{ "slideId": "blockers", "kind": "open_ended", "totalResponses": 2,
"responses": [
{ "text": "flaky CI", "displayName": null,
"createdAt": "2026-06-19T09:04:12.000Z" },
{ "text": "late reviews", "displayName": null,
"createdAt": "2026-06-19T09:04:31.000Z" }
] },
{ "slideId": "fix", "kind": "multiple_choice", "totalResponses": 9,
"optionCounts": { "ci": 4, "tickets": 3, "wip": 2, "meetings": 0 } }
]
}
}आर्काइव के लिए, `GET /polls/{id}/export` एक RFC-4180 CSV स्ट्रीम करता है (slide नंबर, सवाल, type, answer label, count/value)। मैं इसे सेव करके Slack मैसेज में अटैच कर देता हूँ। पूरा post-standup कदम एक छोटा सा function है:
const API = process.env.POLLSLIVE_API;
const KEY = process.env.POLLSLIVE_KEY;
const auth = { Authorization: `Bearer ${KEY}` };
export async function summariseRetro(pollId) {
const res = await fetch(`${API}/polls/${pollId}/results`, { headers: auth });
if (res.status === 429) throw new Error("rate_limited"); // back off + retry
if (!res.ok) throw new Error(`results ${res.status}`);
const { data } = await res.json();
// Live answers are in slides[], not in the top-level voteCounts
const slide = (id) => data.slides.find((s) => s.slideId === id);
const fix = slide("fix");
const winner = Object.entries(fix?.optionCounts ?? {})
.filter(([, count]) => count > 0)
.sort((a, b) => b[1] - a[1])[0];
// Grab the CSV for the archive
const csv = await fetch(`${API}/polls/${pollId}/export`, { headers: auth })
.then((r) => r.text());
return {
health: slide("health")?.average,
topFix: winner?.[0],
voters: fix?.totalResponses ?? 0,
csv,
};
}बस यही पूरी बात है। retro deck खुद बनता है, session समय पर खुलता है, और summary + CSV अपने-आप पोस्ट हो जाते हैं। मुझे मेरे शुक्रवार के दस मिनट वापस मिल गए, और फ़ॉर्मैट हर हफ़्ते बिल्कुल एक जैसा है, जो कि retro से आप यही चाहते हैं।
सबसे अच्छी बात: जो slides मैं JSON में बताता हूँ वही slides टीम देखती है और उन पर वोट करती है। 'स्क्रिप्ट ने क्या बनाया' और 'हमने क्या चलाया' के बीच कोई फ़र्क नहीं।
पूरा endpoint reference और schemas इंटरैक्टिव API reference में हैं, और कहानी-वाला quickstart developer guide में है। मेरी अगली योजना: webhooks, ताकि Slack summary timer पर नहीं, बल्कि session खत्म होते ही फायर हो जाए, जो ठीक वही है जो webhooks वाला लेख कवर करता है।
Tip
Cron job se pichhle hafte ka retro deck clone karein aur automatically fresh link publish karein.
No-code alternative: recurring polls without rebuilding.
PollsLive पर बनाएँ: अपने ही कोड से polls बनाएँ, live sessions चलाएँ, results खींचें, और webhooks पाएँ।
developer docs पढ़ें