Search API da HubSpot: como filtrar, ordenar e paginar sem dor de cabeça
Tem um momento que todo mundo que mexe com a API da HubSpot vive. Você não quer todos os contatos, quer só os que se encaixam em uma condição. Os negócios em negociação. As empresas de um setor. Os contatos que mudaram depois de ontem. E aí bate a tentação de puxar a base inteira e filtrar no seu próprio código, do seu lado.
Não faça isso. Existe uma forma certa, e ela se chama Search API. Em vez de baixar dezenas de milhares de registros para descartar a maioria, você manda o filtro para a HubSpot e recebe de volta só o que importa. Menos dados na rede, menos memória consumida, menos tempo de processamento e, principalmente, menos risco de bater nos limites de taxa do portal.
Mas a Search API tem regras próprias, e quem não as conhece esbarra no famoso "a busca para em 10.000 e ninguém sabe por quê". Neste guia, vou te mostrar tudo: como montar o filtro, quais operadores existem, como ordenar, como paginar e, o mais importante, o truque para extrair bases maiores que o teto. No fim, você vai escrever buscas que cobrem a base inteira sem perder um registro.
O que é a Search API e quando usar
A Search API é o endpoint que devolve um subconjunto de registros conforme filtros, ordenação e seleção de propriedades que você define. Ela é um POST, não um GET, porque o filtro vai no corpo da requisição. Use-a sempre que precisar de um recorte, e deixe a listagem simples para quando precisar de absolutamente tudo.
Essa escolha entre listar e buscar parece detalhe, mas define a saúde da sua integração. Buscar quando deveria listar te prende ao teto de 10.000. Listar quando deveria buscar te faz trafegar a base inteira só para usar 2% dela. A tabela abaixo resume a decisão.
|
Você precisa de... |
Use |
Por quê |
|---|---|---|
|
Toda a base de um objeto |
Listagem (GET .../objects/{type}) |
Mais simples e sem o teto de 10.000 |
|
Um recorte por data, estágio ou valor |
Busca (POST .../search) |
Filtra no servidor, traz só o necessário |
|
Registros cujos ids você já tem |
Batch read |
Não precisa filtrar, você já sabe quem quer |
Por que isso importa para a sua operação
Antes do código, o porquê. Buscas bem montadas são a base de quase todo processo de receita que depende de dados frescos. Um alerta de negócio parado, um relatório de pipeline por estágio, uma rotina que sincroniza só os contatos que mudaram: tudo isso é uma Search API por trás. Quando a busca está errada, o sintoma não aparece no código, aparece no número. O painel mostra um pipeline menor do que o real, o alerta não dispara, o relatório "some" com parte da base. E aí a equipe perde a confiança no dado, que é o ativo mais caro de uma operação de vendas.
Como montar uma busca
Uma busca é estruturada em filterGroups. Dentro de um mesmo grupo, os filtros se somam com lógica E. Grupos diferentes se combinam com lógica OU. Essa é a chave para montar condições complexas. "Negócios em negociação E acima de mil reais" é um grupo com dois filtros. "Em negociação OU já ganhos" são dois grupos, cada um com um filtro.
|
POST /crm/v3/objects/deals/search { "filterGroups": [ { "filters": [ { "propertyName": "dealstage", "operator": "EQ", "value": "appointmentscheduled" }, { "propertyName": "amount", "operator": "GTE", "value": "1000" } ] } ], "properties": ["dealname", "amount", "dealstage"], "limit": 100 } |
Repare no properties. Peça só os campos que vai usar de fato. Cada propriedade a mais engorda a resposta e te aproxima dos limites de taxa. Quem pede tudo "por garantia" paga caro em performance e ganha pouco em troca, porque na hora de usar quase nunca precisa de tudo aquilo.
Os operadores que resolvem 90% dos casos
- `EQ` e `NEQ`: igual e diferente. O feijão com arroz dos filtros.
- `GT`, `GTE`, `LT`, `LTE`: maior, maior ou igual, menor, menor ou igual. Servem para números e para datas.
- `BETWEEN`: um intervalo fechado, ótimo para janelas de data de criação ou modificação.
- `IN` e `NOT_IN`: dentro ou fora de uma lista de valores, como selecionar vários estágios de uma vez.
- `HAS_PROPERTY` e `NOT_HAS_PROPERTY`: o campo tem ou não tem valor, sem se importar com qual valor é.
Datas usam epoch em milissegundos ou o formato ISO. Um erro silencioso e muito comum é mandar a data em segundos: a HubSpot trabalha em milissegundos, então multiplique por mil se a sua origem é em segundos. Quando o filtro de data "não traz nada" ou "traz tudo", desconfie da unidade antes de qualquer outra coisa.
Selecionando só as propriedades certas
Vale repetir porque é o ajuste de maior retorno: liste em properties apenas o que a tarefa consome. Se você sincroniza nome e email para um BI, não traga vinte campos. A resposta menor viaja mais rápido, ocupa menos memória e te dá mais folga no limite de taxa, o que importa muito quando a base é grande.
Ordenação: a base de uma varredura previsível
O campo sorts ordena o resultado. Para varrer registros de forma previsível, ordene por hs_lastmodifieddate ou createdate em ordem ascendente. Isso importa mais do que parece. Uma ordem estável é o que evita que registros pulem de página enquanto você pagina, o que é uma das causas mais sutis de registros perdidos ou duplicados em extrações longas.
|
"sorts": [{ "propertyName": "hs_lastmodifieddate", "direction": "ASCENDING" }] |
Paginação na busca, passo a passo
A busca pagina por cursor, igual às leituras simples. A resposta traz paging.next.after, e você repete a chamada com esse valor em after até o cursor sumir. A regra de ouro: na primeira chamada, não envie after. Alguns endpoints recusam um after vazio, então omitir na primeira página é o caminho seguro em qualquer cenário.
O laço de paginação é simples de descrever e fácil de errar. Você faz a primeira chamada sem cursor, lê o paging.next.after da resposta, faz a próxima chamada com aquele valor, e segue assim até a resposta não trazer mais paging.next. Esse é o sinal de fim. Nunca pare num contador fixo, porque o total de registros pode mudar entre o início e o fim da extração.
|
Dica de quem já apanhou: se a sua busca volta sempre os mesmos registros em loop, quase sempre o cursor não está sendo repassado entre as chamadas. Confira se você está lendo o after da resposta anterior e injetando na próxima requisição, em vez de reenviar o primeiro corpo o tempo todo. |
O teto de 10.000 e como passar dele
Aqui está a regra que mais pega gente desprevenida. A busca devolve no máximo 10.000 registros por conjunto de filtros. Se a sua condição casa com mais que isso, a paginação simplesmente para no limite, sem um erro claro gritando com você. Você acha que extraiu tudo, mas faltou metade da base, e ninguém percebe até o número divergir lá na frente.
A solução não é insistir na mesma busca, é dividir. Quebre a consulta em faixas que não se sobrepõem, por exemplo por janelas de data de criação, e pagine cada faixa separadamente. Janeiro, fevereiro, março, cada mês como uma busca independente. Cada faixa fica abaixo do teto, e a soma das faixas cobre a base inteira. Para bases muito densas, use janelas menores, por semana ou por dia.
O segundo limite, o de taxa
A Search API tem um limite de taxa próprio e mais restrito que as leituras simples. Se a sua rotina faz muitas buscas em sequência, é nela que o erro 429 aparece primeiro. Trate com recuo progressivo, respeitando o cabeçalho que indica quando tentar de novo, e considere espaçar as chamadas. Buscar em faixas ajuda também aqui, porque dá ritmo à rotina em vez de socar tudo de uma vez.
Erros comuns que custam horas
- Data em segundos em vez de milissegundos: o filtro parece quebrado, mas é só a unidade errada.
- Não repassar o cursor: a extração entra em loop ou para cedo.
- Ignorar o teto de 10.000: a HubSpot retorna erro 400 ao tentar passar de 10.000, e um código que não trata esse erro para na metade sem ninguém perceber.
- Pedir propriedades demais: resposta mais pesada e lenta, e mais dado para processar.
- Buscar quando deveria listar: você se prende a um teto que não precisava enfrentar.
Na prática: o relatório que parecia incompleto
Uma operação extraía negócios para um painel de pipeline e jurava que faltavam dados. A busca usava um único filtro amplo e parava sempre perto de 10.000 registros, batendo no teto sem ninguém perceber. O painel mostrava um pipeline consistentemente menor do que a realidade, e a liderança comercial desconfiava do número a cada reunião de forecast.
Ao quebrar a extração em janelas de data de criação e paginar cada uma, o painel passou a refletir a base inteira. O forecast voltou a bater com a operação. O problema nunca foi o painel nem a ferramenta de BI, era a forma de buscar. É o tipo de correção invisível que devolve a confiança no dado.
Checklist antes de rodar uma busca em volume
- Você pediu só as propriedades necessárias em properties?
- Os filtros estão agrupados certo, E dentro do grupo e OU entre grupos?
- As datas estão em milissegundos, não em segundos?
- Há um sorts por um campo estável, para a varredura ser previsível?
- A busca cabe abaixo de 10.000, ou está quebrada em faixas que não se sobrepõem?
- Existe tratamento de 429 com recuo progressivo?
- Você valida o total extraído contra o esperado no fim?
Perguntas frequentes
Qual a diferença entre a Search API e a listagem simples?
A listagem (GET /crm/v3/objects/{type}) traz todos os registros em ordem fixa e não tem o teto de 10.000. A busca (POST .../search) aceita filtros e ordenação e devolve só o subconjunto que casa. Use a listagem para extração total e a busca para recortes.
Por que minha busca para em 10.000 registros?
É o teto da Search API por conjunto de filtros. Não insista na mesma busca: divida a consulta em faixas que não se sobrepõem, por exemplo por janelas de data de criação, e pagine cada faixa separadamente.
Como combino condições E e OU na busca?
Filtros dentro de um mesmo filterGroup se somam com lógica E. Grupos diferentes se combinam com lógica OU. Para "A e B", um grupo com dois filtros. Para "A ou B", dois grupos com um filtro cada.
A busca conta para o mesmo limite de taxa das leituras?
Não. A Search API tem um limite próprio e mais baixo. Rotinas com muitas buscas atingem o 429 antes das leituras simples, então trate com backoff e espace as chamadas.
Por que meu filtro de data não traz nada?
Quase sempre a unidade. A HubSpot espera epoch em milissegundos. Se a sua origem está em segundos, multiplique por mil antes de enviar o valor no filtro.
Posso ordenar por qualquer propriedade?
Sim, mas para varreduras o mais seguro é ordenar por um campo estável como createdate ou hs_lastmodifieddate, em ordem ascendente, para os registros não pularem de página durante a paginação.
Precisa extrair recortes específicos do seu CRM sem montar tudo na mão nem parar em 10.000? Na Insight Sales, como parceiros HubSpot, construímos rotinas de dados que respeitam os limites e rodam com segurança. 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.