Ir para o conteúdo

June 18, 2026 · 10 min read

Como colocamos nossa retro semanal no piloto automático com a API do PollsLive

O passo a passo de uma desenvolvedora: criar uma enquete de retro, abrir uma sessão ao vivo e depois puxar resultados e um CSV, tudo a partir de um pequeno script Node num cron. Endpoints reais da /api/v1, payloads reais.

By the PollsLive team·DevelopersHow-toTeams & meetings

Sou engenheira de plataforma e, toda sexta-feira, eu gastava dez minutos montando à mão o mesmo deck de retro. As mesmas cinco perguntas, semana diferente. Clássico caso de algo-que-um-script-deveria-fazer. Então passei tudo para a API do PollsLive e um cron job: agora a enquete se cria sozinha, uma sessão ao vivo abre automaticamente e, depois da daily, um CSV cai no nosso Slack. Aqui vai exatamente como, com os endpoints e payloads reais.

Autenticação: um único token Bearer

Gerei uma chave de API do workspace em Studio → API para desenvolvedores (elas têm o formato `plv_live_…`) e a coloquei no ambiente do script. Toda requisição é só um token Bearer, sem dança de OAuth para uso 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
# Limits and HTTP 429 handling: https://pollslive.com/docs/developers/rate-limits

Passo 1: Criar a enquete de retro

Uma enquete é um deck tipado: `content.questions` é um array de slides, cada um com um discriminador `kind`. Para a retro eu uso uma scale, uma open_ended renderizada como nuvem e uma multiple_choice para votar na correção. Um `POST /polls` com `renderingMode: "LIVE"` me devolve um rascunho.

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" }
          ]
        }
      ]
    }
  }'

A resposta `201` envolve a enquete num envelope `data`. Guardo o `data.id` para o resto do fluxo e o `data.accessPin` / `data.shareUrl` para os detalhes de entrada:

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"
  }
}

Passo 2: Publicar e depois abrir uma sessão ao vivo

Publicar tira um snapshot do conteúdo para que seja seguro apresentá-lo. Um `PATCH /polls/{id}` com `publish: true` faz isso; depois `POST /sessions` cria a sessão do apresentador e devolve o PIN de entrada mais um `hostToken` de uso único (o segredo de autoridade do host: ele só é retornado na criação, então o guardo imediatamente).

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/play/6098",
#       "hostUrl": "https://pollslive.com/host/ses_4dF1",
#       "hostToken": "hst_…", "status": "LOBBY" } }

Meu cron posta a `joinUrl` e o PIN no canal do time um minuto antes da daily. O pessoal escaneia, e os slides que montei em código são o que eles veem. Aqui está o resultado ao vivo do slide `health` depois que todo mundo respondeu:

Live scale
Join at the PIN 6098

Quão saudável esta sprint pareceu? (1 = difícil, 5 = ótima)

5 - Ótima11% · 1
4 - Bem22% · 2
3 - OK22% · 2
2 - Difícil33% · 3
1 - Ruim11% · 1
O slide que defini em JSON, renderizado ao vivo, sem montar deck na mão.

Passo 3: Puxar resultados e um CSV depois da daily

Quando a sessão termina, `GET /polls/{id}/results` devolve as contagens num array `slides[]`, uma entrada por slide, com agregados por tipo (`optionCounts` para slides de escolha, médias de escala, texto aberto etc.). É essa parte que você deve ler depois de uma sessão ao vivo: `totalVotes` e `voteCounts` no nível superior contam só votos assíncronos, então ficam em zero e vazio numa enquete usada apenas ao vivo.

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

Para o arquivo, `GET /polls/{id}/export` faz streaming de um CSV no formato RFC-4180 (número do slide, pergunta, tipo, rótulo da resposta, contagem/valor). Eu salvo e anexo à mensagem do Slack. Todo o passo pós-daily é uma pequena função:

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();

  // 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,
  };
}

É isso tudo. O deck da retro se monta sozinho, a sessão abre na hora marcada e o resumo + CSV são postados automaticamente. Recuperei meus dez minutos de sexta-feira, e o formato é idêntico toda semana, que é exatamente o que se quer de uma retro.

A parte mais bacana: os slides que descrevo em JSON são os mesmos slides que o time vê e vota. Sem divergência entre 'o que o script fez' e 'o que rodamos'.

A referência completa de endpoints e os schemas estão na referência de API interativa, e o quickstart narrativo está no guia para desenvolvedores. Próximo na minha lista: webhooks, para que o resumo do Slack dispare no instante em que a sessão termina, em vez de num timer, que é exatamente o que o artigo sobre webhooks cobre.

Tip

Use um cron job para clonar o deck de retro da semana passada e publicar um link novo automaticamente.

Alternativa sem código: enquetes recorrentes sem reconstruir.

Desenvolva no PollsLive: crie enquetes, conduza sessões ao vivo, obtenha resultados e receba webhooks pelo seu próprio código.

Leia a documentação para desenvolvedores

Os cookies essenciais mantêm a sua sessão iniciada e evitam votos duplicados. Com a sua autorização, também utilizamos o Cloudflare Web Analytics para contar visualizações de páginas e o Whop Pixel para saber que visitas levam a uma compra. Política de cookies