Pular para o conteúdo principal

Paginação na API da HubSpot: o guia para não perder (nem duplicar) registros

paginação API HubSpot

Deixa eu adivinhar o seu problema. A sua integração com a HubSpot funciona, roda sem estourar erro, mas no fim traz menos registros do que deveria. Ou traz alguns repetidos que bagunçam o destino. Ou para sempre num número redondo e suspeito, tipo 9.900, como se a base inteira coubesse magicamente naquele limite. Se você se reconheceu em algum desses sintomas, pode relaxar, porque o culpado quase sempre é o mesmo, e tem conserto: a paginação.

A API da HubSpot devolve dados em páginas, e o seu código precisa percorrer todas elas, na ordem certa, sem pular nenhuma e sem repetir nenhuma. Parece uma tarefa boba de laço, mas é justamente onde mora a maior parte dos bugs de extração que vemos no dia a dia. E são bugs traiçoeiros, porque a integração não quebra com um erro vermelho gritando na sua cara. Ela simplesmente entrega um número errado, em silêncio, e você só descobre semanas depois, quando o relatório de pipeline não bate com o que a equipe está vendo na tela.

A boa notícia é que existe um padrão único de paginação que funciona para quase todos os endpoints da HubSpot. Depois que você entende esse padrão e os poucos casos especiais, para de errar. Neste guia, vou te mostrar o padrão de cursor, as três formas de ler que paginam de jeitos diferentes, o detalhe quase sempre esquecido das associações, e como provar, no fim da extração, que você realmente cobriu a base inteira sem deixar nada para trás.

O padrão de cursor, que resolve quase tudo

A maioria dos endpoints v3 e v4 da HubSpot pagina por cursor, e o mecanismo é sempre o mesmo. A resposta da API traz um cursor dentro de paging.next.after. Você pega esse valor, repete exatamente a mesma requisição passando ele no parâmetro after, e segue assim, página após página, até que uma resposta venha sem o paging.next. Esse desaparecimento do cursor é o único sinal confiável de que a extração terminou. Qualquer outra forma de decidir o fim, como um contador fixo, é uma aposta que mais cedo ou mais tarde te trai.

GET /crm/v3/objects/contacts?limit=100&properties=email,firstname

// a resposta traz paging.next.after = "cursor123"

GET /crm/v3/objects/contacts?limit=100&properties=email,firstname&after=cursor123

// repita, atualizando o after a cada volta, até paging.next sumir

O laço é fácil de descrever e surpreendentemente fácil de errar. Você faz a primeira chamada sem cursor, lê o paging.next.after da resposta, monta a próxima chamada com aquele valor e repete. O erro mais comum que vemos é o desenvolvedor guardar o corpo ou a URL da primeira requisição e reenviá-lo sem atualizar o cursor a cada volta. O resultado é uma extração que fica presa na mesma página para sempre, ou que processa só a primeira e acha que acabou. Sempre atualize o after com o valor mais recente antes de cada nova chamada.

Dica de quem já apanhou: na primeira chamada, não envie o parâmetro after de jeito nenhum. Alguns endpoints recusam um after vazio ou malformado e devolvem erro. A regra segura, que vale para todos os casos, é omitir o parâmetro na primeira página e só começar a enviá-lo quando a resposta realmente trouxer um cursor.

Por que isso importa para a sua operação

Paginação errada não é um detalhe técnico que fica escondido no código sem consequência. É um problema de confiança que vaza para toda a operação. Quando a extração perde registros, o BI mostra menos clientes do que existem de fato, a régua de relacionamento deixa contatos importantes de fora, a conciliação com outro sistema acusa diferenças que ninguém consegue explicar de onde vêm. O time então gasta horas caçando um fantasma, desconfiando da rede, do banco de destino, da ferramenta de BI, quando a raiz de tudo é um laço de paginação mal feito. Acertar a paginação é acertar a fundação invisível sobre a qual todo relatório, alerta e automação vão se apoiar depois.

As três formas de ler paginam diferente

Existem três maneiras de ler dados na HubSpot, e cada uma pagina de um jeito. Tratar todas igual é a origem de boa parte dos bugs. A tabela abaixo resume as diferenças, e logo em seguida eu explico cada caso.

Forma

Como pagina

Limite por página

Listagem (GET .../objects/{type})

Cursor em after, sem teto

100

Busca (POST .../search)

Cursor, mas com teto de 10.000 por filtro

100

Batch read (POST .../batch/read)

Sem cursor, você divide os ids

100 por lote

A listagem é a mais direta de todas: você pagina por cursor até o fim e não existe teto de quantos registros pode trazer no total. É a forma certa para uma extração completa de um objeto. A busca pagina exatamente igual, com cursor, mas carrega o teto de 10.000 registros por conjunto de filtros, que você contorna quebrando a consulta em faixas de data que não se sobrepõem. Já o batch read não tem cursor nenhum, porque você não está descobrindo registros, está pedindo registros cujos ids você já conhece. A paginação ali é a sua própria responsabilidade: dividir a lista de ids em blocos de até 100 e cuidar para não mandar id repetido no mesmo lote.

O detalhe quase sempre esquecido: associações também paginam

Aqui está um ponto que pega quase todo mundo na primeira integração séria. Quando você lê as associações de um registro, por exemplo todas as empresas ligadas a um contato, ou todos os contatos ligados a um negócio, essa lista de associados também vem paginada. Ela também pode passar de uma página. Muita gente esquece disso e perde justamente os vínculos dos registros mais conectados, que costumam ser os mais importantes da base, como aquela empresa âncora que tem dezenas de contatos pendurados nela. A regra é simples: trate a leitura de associações com o mesmo laço de cursor que você usa nas leituras normais, sem exceção.

Por que registros pulam de página

Existe um efeito sutil que confunde até quem já tem experiência. Quando registros são criados ou alterados durante a própria extração, eles podem mudar de posição no meio do caminho e, com isso, pular uma página inteira ou aparecer duas vezes. A ordenação explícita é um recurso da Search API, e não da listagem simples. Quando você extrai via Search, ordene por um campo estável e único, como hs_object_id ascendente para ancorar a varredura, porque esse id nunca muda depois que o registro passa a existir. Na listagem simples, onde não há como ordenar, apoie-se na ordem padrão que a HubSpot devolve e nas duas defesas abaixo. Esse cuidado não faz diferença numa base pequena e parada, mas é decisivo numa base grande e ativa, onde dezenas de registros mudam por minuto enquanto você extrai.

Erros comuns que custam horas de depuração

  • Reenviar o corpo sem atualizar o cursor: a extração entra em loop infinito na mesma página, ou processa só a primeira.
  • Enviar `after` vazio na primeira chamada: alguns endpoints recusam e devolvem erro logo de cara.
  • Parar num contador fixo: o total de registros pode mudar durante a extração e você corta a varredura cedo demais.
  • Esquecer de paginar associações: vínculos de registros muito conectados somem sem aviso.
  • Usar uma chave de ordenação instável na Search: em bases ativas, registros pulam de página e geram perda ou duplicidade; use uma chave estável e única, como hs_object_id.
  • Não validar o total no fim: o furo passa despercebido por semanas, até alguém notar que o número não bate.

Como garantir que você não perdeu nem duplicou

Integrate HubSpot with n8n

Tanto a perda quanto a duplicidade aparecem quando registros se movem entre páginas no meio da extração. Três cuidados, aplicados juntos, resolvem a esmagadora maioria dos casos e te dão tranquilidade para confiar no resultado.

  1. Em extrações via Search, ordene por uma chave estável e única, como hs_object_id, para os registros não pularem de página durante a varredura, mesmo com a base sendo alterada. A listagem simples não permite ordenar, então ali você se apoia na conferência do total e na deduplicação por id abaixo.
  2. Valide o total no fim contra a contagem do objeto. Se o número bateu, você cobriu tudo. Se não bateu, a extração tem um furo que precisa ser investigado antes de confiar no dado.
  3. Deduplique por id ao gravar no destino. Isso é especialmente importante em sincronizações incrementais com janela de sobreposição, onde trazer alguns repetidos é esperado e até desejado, e o id é o que garante que o repetido vire atualização, não duplicata.

Na prática: a extração que parava em 9.900

Uma operação extraía contatos para um sistema interno e o número nunca fechava com o que a HubSpot mostrava. A causa eram duas falhas somadas, o que tornava o diagnóstico ainda mais confuso. A busca usada batia no teto de 10.000 por conjunto de filtros, e ao mesmo tempo o laço não estava repassando o cursor corretamente, o que cortava a extração ainda mais cedo, perto de 9.900 registros. O time desconfiou do CRM, da rede, do banco de destino, de tudo, menos da paginação, que parecia simples demais para ser o problema.

A correção teve dois passos. Primeiro, arrumar o laço de cursor para realmente atualizar o after a cada volta. Segundo, quebrar a busca em faixas de data de criação para nenhuma delas bater no teto. Depois disso, o total passou a fechar exatamente com a contagem do objeto. E, mais importante, a validação do total virou parte permanente da rotina, então qualquer furo futuro aparece na mesma hora, e não semanas depois numa reunião de forecast. A confiança no número voltou, e com ela a confiança em tudo o que dependia daquela base.

Checklist de uma paginação à prova de falhas

  1. Você omite o after na primeira chamada e só passa a enviá-lo quando há cursor?
  2. O laço lê o after da resposta e injeta o valor atualizado na próxima requisição?
  3. O laço para quando paging.next some, e não num contador fixo?
  4. As buscas grandes estão quebradas em faixas para não bater no teto de 10.000?
  5. As leituras de associação usam o mesmo laço de cursor das leituras normais?
  6. Nas varreduras via Search, a chave de ordenação é estável e única, como hs_object_id, durante a varredura?
  7. Você valida o total extraído contra a contagem do objeto no fim de cada execução?

Perguntas frequentes

Por que recebo erro ao enviar after na primeira chamada?

Alguns endpoints recusam um after vazio ou malformado. Sempre omita o parâmetro na primeira página e só adicione quando a resposta trouxer um cursor de verdade.

Qual o limite de registros por página?

100 na maioria dos endpoints de listagem e batch. Pedir mais que isso é recusado pela API, então o caminho para grandes volumes é paginar, não aumentar o limite.

Como sei que a paginação terminou?

Quando a resposta não traz mais paging.next.after. Esse é o sinal de fim do laço. Nunca pare num contador fixo, porque o total pode mudar durante a extração.

Preciso paginar a leitura de associações?

Sim. A lista de registros associados a um registro também pode passar de uma página. Use o mesmo laço de cursor, ou vai perder vínculos de registros muito conectados.

Como evito trazer o mesmo registro duas vezes?

Numa varredura via Search, ordene por uma chave estável e única como hs_object_id, valide o total no fim e deduplique por id ao gravar, especialmente em sincronizações com janela de sobreposição.

Listagem ou busca para extrair tudo?

Para extração completa de um objeto, prefira a listagem, que não tem o teto de 10.000. Reserve a busca para recortes filtrados, e quebre-a em faixas quando o recorte passar do teto.

Quer rotinas de extração que rodam do começo ao fim sem furos nem repetidos? Na Insight Sales montamos coleções e fluxos de dados na HubSpot prontos para operar em volume. Fale com a gente e descubra como podemos ajudar.

Pronto para levar sua operação ao próximo nível.

Fale com um especialista e descubra como podemos ajudar.

paper-plane