# 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.

- URL canônica: https://docs.growthstation.app/docs/api/conceitos/paginacao-filtros-ordenacao
- Idioma: pt-BR
- Última atualização: 2026-09-20T08:21:20.450Z
- Produto: GS Engage
- Mantido por: Product Education
- Revisão editorial: 2026-07-20



<PageHero emoji="📊" title="Paginação, filtros e ordenação" description="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." gradient="brand" />

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.

<Callout type="tip" title="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.
</Callout>

🧱 Os dois parâmetros da paginação [#-os-dois-parâmetros-da-paginação]

Toda listagem aceita dois controles na URL:

<FieldInfoGroup>
  <FieldInfo title="limit" showOptional>
    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.
  </FieldInfo>

  <FieldInfo title="page" showOptional>
    **Qual página** você quer, começando em `1`. Ex.: `page=2` traz o segundo bloco de resultados.
  </FieldInfo>
</FieldInfoGroup>

Você combina os dois na URL, junto com a sua `apiKey`. Este pedido traz a primeira página com 50 leads:

```bash
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.

📦 Como vem uma resposta paginada [#-como-vem-uma-resposta-paginada]

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.

```json
{
  "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`. |

<Callout type="tip" title="A regra de ouro do laço">
  Continue pedindo enquanto `page < totalPages`. Quando `page` for igual a `totalPages`, você baixou tudo. Simples assim.
</Callout>

⚠️ Uma lista NÃO tem o bloco meta [#️-uma-lista-não-tem-o-bloco-meta]

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.

<Callout type="warning" title="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`.
</Callout>

Veja a diferença lado a lado. À esquerda, o formato paginado (a maioria, incluindo `custom-fields`); à direita, o `webhooks` (sem `meta`):

<Tabs items={['Paginado (data + meta)', 'Sem meta (só webhooks)']}>
  <Tab value="Paginado (data + meta)">
    Vale para `leads`, `routines`, `prospections`, `custom-fields`, `conversations/threads` e as demais listagens.

    ```json
    {
      "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.
  </Tab>

  <Tab value="Sem meta (só webhooks)">
    Vale **só** para `GET /api/v1/webhooks`.

    ```json
    {
      "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.
  </Tab>
</Tabs>

↕️ Ordenar os resultados [#️-ordenar-os-resultados]

Para decidir a **ordem** em que os registros voltam, use dois parâmetros:

<FieldInfoGroup>
  <FieldInfo title="orderBy" showOptional>
    O **campo** pelo qual ordenar (ex.: data de criação, nome). Depende do recurso que você está listando.
  </FieldInfo>

  <FieldInfo title="orderDirection" showOptional>
    A **direção**: `asc` (crescente, do menor/mais antigo para o maior/mais novo) ou `desc` (decrescente, o inverso).
  </FieldInfo>
</FieldInfoGroup>

Este pedido traz os leads do mais recente para o mais antigo:

```bash
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.

<Callout type="info" title="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.
</Callout>

🔍 Filtros comuns por recurso [#-filtros-comuns-por-recurso]

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:

```bash
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`:

```bash
curl "https://api.gsengage.com/api/v1/prospections?routineIds=507f1f77bcf86cd799439011&apiKey=SUA_CHAVE"
```

**Trazer só as prospecções em andamento** — filtre por `status`:

```bash
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       |

<Callout type="tip" title="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.
</Callout>

🔁 Percorrer todas as páginas (o laço pronto) [#-percorrer-todas-as-páginas-o-laço-pronto]

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 é:

<Mermaid
  chart={`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`):

```js
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:

```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.

<Callout type="warning" title="Esse laço não serve para 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.
</Callout>

✅ Boas práticas de extração [#-boas-práticas-de-extração]

<DoDont>
  <DoDontItem type="do">
    Use a 

    `meta.totalPages`

     como condição de parada — nunca "chute" o número de páginas.
  </DoDontItem>

  <DoDontItem type="do">
    Filtre na URL (

    `status`

    , 

    `routineIds`

    , 

    `search`

    ) para baixar só o necessário.
  </DoDontItem>

  <DoDontItem type="do">
    Mantenha o mesmo 

    `orderBy`

    /

    `orderDirection`

     em todas as páginas do laço.
  </DoDontItem>

  <DoDontItem type="do">
    Respeite o 

    `Retry-After`

     quando receber 

    `429`

     — espere e tente de novo.
  </DoDontItem>

  <DoDontItem type="dont">
    Não aplique o laço de paginação a 

    `webhooks`

     — esse endpoint vem sem 

    `meta`

    .
  </DoDontItem>

  <DoDontItem type="dont">
    Não dispare todas as páginas em paralelo — você estoura o limite de 200 leituras/min.
  </DoDontItem>

  <DoDontItem type="dont">
    Não baixe a base inteira só para filtrar depois — deixe o servidor filtrar por você.
  </DoDontItem>
</DoDont>

<Checklist id="extracao-paginada" title="Antes de rodar sua extração">
  <ChecklistItem>
    Escolhi um 

    `limit`

     razoável (ex.: 100) para equilibrar peso e número de pedidos?
  </ChecklistItem>

  <ChecklistItem>
    Meu laço para quando 

    `page`

     chega a 

    `meta.totalPages`

    ?
  </ChecklistItem>

  <ChecklistItem>
    Apliquei os filtros (

    `status`

    , 

    `routineIds`

    , 

    `search`

    ) direto na URL?
  </ChecklistItem>

  <ChecklistItem>
    Tratei o 

    `429`

     esperando o 

    `Retry-After`

    ?
  </ChecklistItem>

  <ChecklistItem>
    Lembrei que apenas 

    `webhooks`

     não tem 

    `meta`

     e não se pagina?
  </ChecklistItem>
</Checklist>

❓ Perguntas frequentes [#-perguntas-frequentes]

<FAQ>
  <FAQItem question="Qual 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.
  </FAQItem>

  <FAQItem question="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`.
  </FAQItem>

  <FAQItem question="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.
  </FAQItem>

  <FAQItem question="Como percorro custom-fields ou webhooks então?">
    Use o mesmo laço paginado para `GET /api/v1/custom-fields`, lendo `data` e `meta.totalPages`. Para `GET /api/v1/webhooks`, faça um único pedido e leia `data`: somente webhooks não oferece `page`, `limit` ou `meta`.
  </FAQItem>

  <FAQItem question="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`.
  </FAQItem>
</FAQ>

Artigos Relacionados [#artigos-relacionados]

<RelatedArticles>
  <RelatedArticle href="/docs/api/conceitos/glossario" title="Glossário do GS Engage" description="Cada termo técnico da API explicado em uma frase, com o rótulo do app." />

  <RelatedArticle href="/docs/api/guias/enviar-leads-do-crm" title="Enviar leads do CRM ou planilha" description="Crie leads em lote respeitando o limite de requisições." />

  <RelatedArticle href="/docs/api/referencia" title="Referência da API" description="Todos os endpoints, campos e filtros disponíveis." />
</RelatedArticles>
