June 18, 2026 · 10 min read
Cómo pusimos nuestra retro semanal en piloto automático con la API de PollsLive
El recorrido de una desarrolladora: crea una encuesta de retro, abre una sesión en vivo y luego descarga resultados y un CSV - todo desde un pequeño script de Node en un cron. Endpoints /api/v1 reales, payloads reales.
Soy ingeniera de plataforma y, cada viernes, solía pasar diez minutos armando a mano la misma plantilla de retro. Las mismas cinco preguntas, distinta semana. El clásico caso de algo-que-debería-hacer-un-script. Así que lo trasladé a la API de PollsLive y a un cron - la encuesta ahora se crea sola, se abre automáticamente una sesión en vivo y, después del standup, un CSV aterriza en nuestro Slack. Aquí va exactamente cómo, con los endpoints y payloads reales.
Tip
You do not need to be technical to follow this guide. Every step uses plain buttons in PollsLive - no coding, no app install for your audience.
Autenticación: un único token Bearer
Generé una API key del workspace en Studio → Developers (tienen pinta de `plv_live_…`) y la metí en el entorno del script. Cada petición es simplemente un token Bearer - sin baile de OAuth para usos servidor a servidor.
# 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
# Rate limit: 120 requests/min per key (HTTP 429 if you exceed it).Paso 1 - Crear la encuesta de retro
Una encuesta es una plantilla tipada: `content.questions` es un array de diapositivas, cada una con un discriminador `kind`. Para la retro uso una scale, una open_ended renderizada como nube y una multiple_choice para votar el arreglo. `POST /polls` con `renderingMode: "LIVE"` me devuelve un borrador.
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" }
]
}
]
}
}'La respuesta `201` envuelve la encuesta en un sobre `data` - me quedo con `data.id` para el resto del flujo, y con `data.accessPin` / `data.shareUrl` para los datos de unión:
{
"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"
}
}Paso 2 - Publicar y luego abrir una sesión en vivo
Publicar toma una instantánea del contenido para que sea seguro presentarlo. Un `PATCH /polls/{id}` con `publish: true` lo hace; luego `POST /sessions` levanta la sesión de presentador y devuelve el PIN de unión más un `hostToken` de un solo uso (el secreto de autoridad del host - solo se devuelve al crear, así que lo guardo de inmediato).
# 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/join/6098",
# "hostUrl": "https://pollslive.com/host/ses_4dF1",
# "hostToken": "hst_…", "status": "LOBBY" } }Mi cron publica la `joinUrl` y el PIN en el canal del equipo un minuto antes del standup. El equipo escanea, y las diapositivas que armé en código son lo que ven - aquí está el resultado en vivo de la diapositiva `health` una vez que todos respondieron:
¿Qué tan sano se sintió este sprint? (1 = duro, 5 = genial)
Paso 3 - Descargar resultados y un CSV tras el standup
Cuando la sesión termina, `GET /polls/{id}/results` devuelve los recuentos - `totalVotes`, un mapa `voteCounts` para las diapositivas de elección, y un array `slides[]` con agregados por tipo (promedios de escala, texto abierto agrupado, etc.).
curl -sS "$POLLSLIVE_API/polls/poll_9aZ2kP/results" \
-H "Authorization: Bearer $POLLSLIVE_KEY"
{
"data": {
"pollId": "poll_9aZ2kP",
"title": "Weekly retro - week of Jun 16",
"closed": true,
"totalVotes": 9,
"voteCounts": { "ci": 4, "tickets": 3, "wip": 2, "meetings": 0 },
"slides": [
{ "slideId": "health", "kind": "scale", "average": 2.9 },
{ "slideId": "blockers", "kind": "open_ended", "responseCount": 14 },
{ "slideId": "fix", "kind": "multiple_choice",
"optionCounts": { "ci": 4, "tickets": 3, "wip": 2, "meetings": 0 } }
]
}
}Para el archivo, `GET /polls/{id}/export` transmite un CSV RFC-4180 (número de diapositiva, pregunta, tipo, etiqueta de respuesta, recuento/valor). Lo guardo y lo adjunto al mensaje de Slack. Todo el paso post-standup es una pequeña función:
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();
const winner = Object.entries(data.voteCounts)
.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: data.slides.find((s) => s.slideId === "health")?.average,
topFix: winner?.[0],
voters: data.totalVotes,
csv,
};
}Eso es todo. La plantilla de retro se construye sola, la sesión se abre a su hora, y el resumen + CSV se publican automáticamente. Recuperé mis diez minutos del viernes, y el formato es idéntico cada semana - que es exactamente lo que quieres de una retro.
Lo mejor: las diapositivas que describo en JSON son las mismas que el equipo ve y vota. Sin desvíos entre 'lo que armó el script' y 'lo que ejecutamos'.
La referencia completa de endpoints y los esquemas están en la referencia de la API interactiva, y el quickstart narrativo está en la guía para desarrolladores. Lo siguiente en mi lista: webhooks, para que el resumen de Slack se dispare en el instante en que termina la sesión en lugar de por temporizador - que es exactamente lo que cubre el artículo sobre webhooks.
Tip
Use a cron job to clone last week's retro deck and publish a fresh link automatically.


No-code alternative: recurring polls without rebuilding.
Construye sobre PollsLive - crea encuestas, dirige sesiones en vivo, descarga resultados y recibe webhooks desde tu propio código.
Lee la documentación para desarrolladores