Símbolo da AmivuSímbolo da AmivuDevelopers

Paginação

Cursor, não página — e por quê.

Toda listagem devolve o mesmo envelope, com um cursor para a próxima página. Não existe page, e não é esquecimento.

A forma

{
  "object": "list",
  "data": [ /* … */ ],
  "pagination": {
    "has_more": true,
    "next_cursor": "cnt_01K7ZFA5X8Q2M4N6P8R0T2V4W6"
  }
}

limit vale 25 por padrão e no máximo 100 . after recebe o next_cursor da página anterior — e nada além disso: um cursor inventado responde 422 invalid_cursor, e não a primeira página em silêncio.

Terminal
curl "https://api.amivu.com.br/v1/conversations?limit=50&after=conv_01K7..." \
  -H "Authorization: Bearer $AMIVU_API_KEY"

Por que não page=2

Dois problemas, e o segundo é o que machuca:

  • Custo. OFFSET cresce com a distância — a página 500 obriga o banco a varrer e descartar 12.475 linhas antes de devolver 25.
  • Ele mente enquanto você pagina. Uma conversa nova entrando no topo empurra tudo para baixo, e a página 2 devolve a última linha da página 1 de novo. Quem sincroniza um CRM assim duplica registro.

O cursor é uma posição, e não uma distância. A consulta vira “depois desta linha” — uma busca por índice — e o que entrou no topo depois simplesmente não aparece, em vez de deslocar tudo.

Percorrer tudo

Pare por `has_more`, não por lista vazia
Uma página cheia com has_more: false é o fim legítimo. Esperar uma página vazia custa uma requisição a mais em toda varredura.
async function* todosOsContatos() {
  let cursor = null;

  while (true) {
    const url = new URL("https://api.amivu.com.br/v1/contacts");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("after", cursor);

    const resposta = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.AMIVU_API_KEY}` },
    });
    const { data, pagination } = await resposta.json();

    yield* data;

    if (!pagination.has_more) return;
    cursor = pagination.next_cursor;
  }
}

Escrevemos sobre operação de atendimento, canais e IA no blog da Amivu.