Paginação, filtros e ordenação
Percorra listas grandes do GS Engage página por página, filtre por status ou cadência e ordene os resultados — do jeito certo para relatórios de BI.
Paginação, filtros e ordenação
Traga listas grandes do GS Engage para o seu relatório sem perder nenhum registro — página por página, filtrando e ordenando o que importa.
Você quer puxar todos os leads, prospecções ou cadências para uma planilha, um dashboard ou o seu data warehouse. O problema: a API não devolve 5.000 registros de uma vez. Ela entrega em páginas — pedaços menores, um pedido de cada vez.
Esta página mostra como percorrer a lista inteira sem pular nem repetir registro, como filtrar para trazer só o que interessa (por status, por cadência, por busca) e como ordenar o resultado. Se "endpoint" ainda soa técnico, pense nele como um endereço na internet que responde com dados quando você pede.
Para quem é esta página
Se o seu trabalho é montar relatórios e cruzar dados, este é o seu manual de extração. No fim tem um laço de repetição pronto para copiar, que baixa a lista completa respeitando o limite de requisições.
Toda listagem aceita dois controles na URL:
Quantos itens você quer por página. Ex.: limit=50 traz 50 registros por pedido. Um valor maior significa menos pedidos, mas respostas mais pesadas.
Qual página você quer, começando em 1. Ex.: page=2 traz o segundo bloco de resultados.
Você combina os dois na URL, junto com a sua apiKey. Este pedido traz a primeira página com 50 leads:
curl "https://api.gsengage.com/api/v1/leads?limit=50&page=1&apiKey=SUA_CHAVE"A resposta te diz, no bloco meta, quantos registros existem no total e quantas páginas há — é assim que você sabe até onde ir.
A maioria das listagens responde no formato data + meta: data é a lista de registros daquela página, e meta é o "painel de controle" que informa onde você está e quanto falta.
{
"data": [
{ "id": "507f1f77bcf86cd799439011", "name": "Maria Silva" },
{ "id": "507f1f77bcf86cd799439012", "name": "João Pereira" }
],
"meta": {
"limit": 50,
"count": 2,
"total": 137,
"page": 1,
"totalPages": 3
}
}Cada campo do meta tem um papel prático quando você está extraindo dados:
| Campo | O que significa | Para que serve no seu código |
|---|---|---|
limit | O tamanho de página que você pediu | Confirma quantos itens cabem por página. |
count | Quantos itens vieram nesta página | Na última página costuma ser menor que o limit. |
total | Quantos itens existem no total (após os filtros) | O número real que você espera ter no relatório. |
page | Em qual página você está | Ajuda a saber "onde estou". |
totalPages | Quantas páginas existem ao todo | A condição de parada: pare quando page chegar a totalPages. |
A regra de ouro do laço
Continue pedindo enquanto page < totalPages. Quando page for igual a totalPages, você baixou tudo. Simples assim.
Quase toda listagem vem embrulhada em data + meta. Uma exceção: GET /api/v1/webhooks traz os itens em data, mas sem meta — retorna todos os webhooks do projeto de uma vez, sem paginação.
webhooks vem sem meta (custom-fields NÃO)
GET /api/v1/webhooks responde { "data": [ ... ] }, sem meta — não tente paginá-lo. Já GET /api/v1/custom-fields é paginado normal ({ data, meta }), como a maioria. Em todos os casos, os itens ficam em data.
Veja a diferença lado a lado. À esquerda, o formato paginado (a maioria, incluindo custom-fields); à direita, o webhooks (sem meta):
Vale para leads, routines, prospections, custom-fields, conversations/threads e as demais listagens.
{
"data": [
{ "id": "507f1f77bcf86cd799439011", "name": "Maria Silva" }
],
"meta": {
"limit": 50,
"count": 1,
"total": 137,
"page": 1,
"totalPages": 3
}
}Você lê os registros em data e usa meta.totalPages para saber quando parar.
Vale só para GET /api/v1/webhooks.
{
"data": [
{ "id": "5f0a1b2c3d4e5f6a7b8c9d0e", "name": "Webhook CRM", "url": "https://..." }
]
}Os itens estão em data (igual às outras listagens), mas não há meta nem paginação — a lista já vem completa.
Para decidir a ordem em que os registros voltam, use dois parâmetros:
O campo pelo qual ordenar (ex.: data de criação, nome). Depende do recurso que você está listando.
A direção: asc (crescente, do menor/mais antigo para o maior/mais novo) ou desc (decrescente, o inverso).
Este pedido traz os leads do mais recente para o mais antigo:
curl "https://api.gsengage.com/api/v1/leads?orderBy=createdAt&orderDirection=desc&limit=50&page=1&apiKey=SUA_CHAVE"Ordenar é útil para relatórios incrementais: ordenando por data de criação em desc, os registros novos ficam sempre no topo da primeira página.
Ordenação e paginação andam juntas
Mantenha o mesmo orderBy e orderDirection em todas as páginas de um mesmo laço. Trocar a ordem no meio da varredura embaralha os resultados e faz você pular ou repetir registros.
Filtrar é o que transforma "toda a base" em "só o que eu preciso". Cada listagem aceita filtros próprios, sempre como parâmetros na URL. Aqui estão os principais:
| Recurso | Endpoint | Filtros disponíveis |
|---|---|---|
| Leads | GET /api/v1/leads | search, status, sourceIds, routineIds, createdByIds |
| Cadências | GET /api/v1/routines | status, priorities, acquisitionTypes |
| Prospecções | GET /api/v1/prospections | status, routineIds, leadId |
| Conversas | GET /api/v1/conversations/threads | canais WHATSAPP e EMAIL |
Alguns exemplos concretos:
Buscar um lead por nome, empresa, e-mail ou telefone — o filtro search procura em todos esses campos de uma vez:
curl "https://api.gsengage.com/api/v1/leads?search=maria@empresa.com&apiKey=SUA_CHAVE"Listar só as prospecções de uma cadência específica — passe o identificador dela em routineIds:
curl "https://api.gsengage.com/api/v1/prospections?routineIds=507f1f77bcf86cd799439011&apiKey=SUA_CHAVE"Trazer só as prospecções em andamento — filtre por status:
curl "https://api.gsengage.com/api/v1/prospections?status=IN_PROGRESS&apiKey=SUA_CHAVE"O status usa valores técnicos que têm um rótulo no app. Para prospecções, os valores são:
| Valor técnico | Rótulo no app |
|---|---|
IN_PROGRESS | Em andamento |
FROZEN | Congelada |
WON | Ganha |
LOST | Perdida |
Filtre no servidor, não na planilha
Sempre que der, aplique o filtro na URL em vez de baixar tudo e filtrar depois. Menos dados trafegados, menos páginas para percorrer e menos chance de esbarrar no limite de requisições.
Este é o coração da extração: um laço que começa na página 1 e continua até totalPages, juntando tudo. A lógica em pseudocódigo é:
graph TD
A["página = 1"] --> B["GET /leads?page=página&limit=100"]
B --> C["Acumula resposta.data"]
C --> D{"página < meta.totalPages ?"}
D -->|"Sim"| E["página = página + 1"]
E --> B
D -->|"Não"| F["Fim: lista completa"]Na prática, em JavaScript, com uma pausa entre pedidos e respeito ao limite de requisições (a API permite 200 leituras por minuto; se estourar, responde 429 com o header Retry-After):
const BASE = "https://api.gsengage.com/api/v1";
const apiKey = process.env.GS_ENGAGE_API_KEY; // nunca deixe a chave no código
// Repete o pedido respeitando o Retry-After quando levar 429
async function callWithBackoff(url) {
while (true) {
const res = await fetch(url);
if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") || 5);
await new Promise((r) => setTimeout(r, wait * 1000));
continue; // tenta de novo depois de esperar
}
return res;
}
}
// Baixa TODAS as páginas de uma listagem paginada (data + meta)
async function fetchAll(path, filtros = {}) {
const todos = [];
let page = 1;
let totalPages = 1;
do {
const params = new URLSearchParams({
...filtros,
limit: "100",
page: String(page),
apiKey,
});
const res = await callWithBackoff(`${BASE}${path}?${params}`);
const json = await res.json();
todos.push(...json.data); // acumula os registros desta página
totalPages = json.meta.totalPages; // condição de parada
page++;
} while (page <= totalPages);
return todos;
}
// Exemplo: todas as prospecções em andamento de uma cadência
const prospeccoes = await fetchAll("/prospections", {
status: "IN_PROGRESS",
routineIds: "507f1f77bcf86cd799439011",
});
console.log(`Baixei ${prospeccoes.length} prospecções.`);O mesmo laço em Python:
import os, time, requests
BASE = "https://api.gsengage.com/api/v1"
api_key = os.environ["GS_ENGAGE_API_KEY"] # nunca deixe a chave no código
def call_with_backoff(url, params):
while True:
res = requests.get(url, params=params)
if res.status_code == 429:
wait = int(res.headers.get("Retry-After", "5"))
time.sleep(wait) # respeita o Retry-After e tenta de novo
continue
return res
def fetch_all(path, filtros=None):
filtros = filtros or {}
todos, page, total_pages = [], 1, 1
while page <= total_pages:
params = {**filtros, "limit": 100, "page": page, "apiKey": api_key}
res = call_with_backoff(f"{BASE}{path}", params)
json = res.json()
todos.extend(json["data"]) # acumula os registros da página
total_pages = json["meta"]["totalPages"] # condição de parada
page += 1
return todos
# Exemplo: todas as prospecções em andamento de uma cadência
prospeccoes = fetch_all("/prospections", {
"status": "IN_PROGRESS",
"routineIds": "507f1f77bcf86cd799439011",
})
print(f"Baixei {len(prospeccoes)} prospecções.")Ao final, todos (ou prospeccoes) tem a lista completa, pronta para virar linha de planilha ou registro no seu banco.
Esse laço NÃO serve para custom-fields nem webhooks
O fetchAll lê json.meta.totalPages e json.data — funciona em todos os endpoints paginados, inclusive custom-fields. A única exceção é GET /api/v1/webhooks, que não tem meta: nele, faça um único GET e leia json.data direto, sem laço.
- Use a
meta.totalPagescomo condição de parada — nunca "chute" o número de páginas. - Filtre na URL (
status,routineIds,search) para baixar só o necessário. - Mantenha o mesmo
orderBy/orderDirectionem todas as páginas do laço. - Respeite o
Retry-Afterquando receber429— espere e tente de novo.
- Não aplique o laço de paginação a
custom-fieldsouwebhooks— eles vêm semmeta. - Não dispare todas as páginas em paralelo — você estoura o limite de 200 leituras/min.
- Não baixe a base inteira só para filtrar depois — deixe o servidor filtrar por você.
Antes de rodar sua extração
0/5Qual o melhor valor de limit?
Depende do equilíbrio entre peso e número de pedidos. Valores como 50 ou 100 costumam funcionar bem: poucos pedidos e respostas ainda leves. Se o total for muito grande, um limit maior reduz o número de páginas — só lembre de continuar respeitando o limite de 200 leituras por minuto.
Por que a última página traz menos itens que o limit?
Porque os registros raramente terminam num número redondo. Se há 137 itens e limit=50, as duas primeiras páginas trazem 50 cada e a terceira traz 37. Nesse caso, o count da última página vem 37, menor que o limit.
Filtrei e o total mudou. Está certo?
Sim. O meta.total reflete a contagem depois dos filtros. Ao aplicar status=IN_PROGRESS, o total passa a contar só as prospecções em andamento — é o número que você quer no relatório.
Como percorro custom-fields ou webhooks então?
Você não percorre: faz um único GET /api/v1/custom-fields (ou /webhooks) e usa a resposta como uma lista direta. Não há page, limit nem meta — tudo vem de uma vez.
Posso combinar filtro, ordenação e paginação no mesmo pedido?
Pode, e é o uso normal. Basta juntar todos os parâmetros na URL: ?status=IN_PROGRESS&orderBy=createdAt&orderDirection=desc&limit=100&page=1&apiKey=SUA_CHAVE.
Este artigo foi útil?
Limites de uso (rate limit)
Entenda quantas chamadas a API do GS Engage aceita por minuto e como tratar o erro 429 com backoff para não travar sua integração.
Respostas e erros
Entenda os dois formatos de resposta de sucesso da API e aprenda a tratar cada erro (400, 401, 404, 429) lendo as mensagens em português do backend.