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.

By Maya Okafor · Platform engineer·DevelopersHow-toTeams & meetings

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.

auth
# 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.

POST /polls
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:

201 Created
{
  "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).

PATCH /polls/{id} → POST /sessions
# 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:

Live scale
Join at the PIN 6098

¿Qué tan sano se sintió este sprint? (1 = duro, 5 = genial)

5 - Genial11% · 1
4 - Bien22% · 2
3 - Regular22% · 2
2 - Duro33% · 3
1 - Malo11% · 1
La diapositiva que definí en JSON, renderizada en vivo - sin armar plantillas a mano.

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

GET /polls/{id}/results
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:

summarise.mjs (Node 18+)
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.

PollsLive Studio dashboard with poll list, left nav, and Create buttons.
Studio is your home for every poll you have created.
Results panel with stat tiles, breakdown by question, and export buttons.
The Results tab shows every answer and export options.

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