# Paginação e filtros

URL: https://staging.pagpolar.com/docs/guias/fundamentos/paginacao-e-filtros

> Percorra qualquer listagem da API do começo ao fim e use os filtros que cada uma realmente aplica.

## Parâmetros `page` e `per_page`

Toda listagem aceita dois parâmetros na query string:

| Parâmetro  | O que faz                         | Padrão | Limites              |
| ---------- | --------------------------------- | ------ | -------------------- |
| `page`     | Número da página, começando em 1. | `1`    | Inteiro, mínimo 1.   |
| `per_page` | Quantos itens vêm por página.     | `25`   | Inteiro, de 1 a 100. |

`per_page` acima de 100 responde `400 invalid_request`, com `message` vazia. Uma página depois da última responde `200` com `data` vazio.

## Formato da resposta

Toda listagem responde com `data` e `meta`:

```json
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "identifier": "PPO9876543210",
      "status": "PAID",
      "total_amount": 97
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 58,
    "total_pages": 3
  }
}
```

Resposta resumida. Os campos de cada item estão na [Referência da API](/docs/referencia).

| Campo de `meta` | O que significa                            |
| --------------- | ------------------------------------------ |
| `page`          | Página devolvida.                          |
| `per_page`      | Itens por página usados.                   |
| `total`         | Total de itens que atendem aos filtros.    |
| `total_pages`   | Total de páginas. `0` quando não há itens. |

## Ordem dos resultados

Todas as listagens vêm da **mais nova para a mais antiga**, pela data de criação (`created_at`). Não há parâmetro para mudar a ordem.

## Filtros de cada listagem

Cada listagem aceita só os filtros da tabela, além de `page` e `per_page`. Um filtro que a rota não aplica, ou um valor fora da lista, responde `400 invalid_request`.

| Rota                                                                          | Filtros aceitos                                                                                                                     |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /sales`](/docs/referencia/vendas/list-sales)                            | `status`, `payment_method`, `created_from`, `created_to`, `external_reference`, `product_id`, `customer_email`, `customer_document` |
| [`GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions)       | `status`, `plan_id`, `customer_email`, `customer_document`, `created_from`, `created_to`                                            |
| [`GET /refunds`](/docs/referencia/reembolsos/list-refunds)                    | `status`, `sale_identifier`, `customer_email`, `customer_document`, `created_from`, `created_to`                                    |
| [`GET /customers`](/docs/referencia/clientes/list-customers)                  | `email`, `name`, `document`                                                                                                         |
| [`GET /products`](/docs/referencia/produtos/list-products)                    | `name`, `type`, `is_active`                                                                                                         |
| [`GET /plans`](/docs/referencia/planos/list-plans)                            | `name`, `is_active`                                                                                                                 |
| [`GET /offers/by-product/{id}`](/docs/referencia/ofertas/list-product-offers) | `title`, `is_active`                                                                                                                |
| [`GET /plans/{id}/offers`](/docs/referencia/planos/list-plan-offers)          | `title`, `is_active`                                                                                                                |

Os valores aceitos em `status`, `payment_method` e `type` estão em cada rota da [Referência da API](/docs/referencia). O `status` de `GET /sales` usa as situações da venda, e o de `GET /subscriptions`, as da assinatura: um não vale no outro.

Você pode combinar filtros na mesma chamada. A listagem traz só os itens que atendem a **todos** eles.

### Como cada tipo de filtro compara

| Tipo            | Filtros                            | Como compara                                                                     | Exemplo                                                      |
| --------------- | ---------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| Busca por texto | `name`, `title`                    | Parte do texto, sem diferenciar maiúsculas de minúsculas.                        | `name=curso` encontra "Curso de Excel" e "Minicurso".        |
| E-mail          | `email`, `customer_email`          | E-mail completo, sem diferenciar maiúsculas de minúsculas.                       | `customer_email=Maria@Email.com` encontra `maria@email.com`. |
| Documento       | `document`, `customer_document`    | CPF com 11 dígitos ou CNPJ com 14, com ou sem pontuação. Compara só os dígitos.  | `123.456.789-09` e `12345678909` encontram o mesmo cliente.  |
| Id              | `product_id`, `plan_id`            | O `id` exato, no formato uuid.                                                   | `plan_id=3fa85f64-5717-4562-b3fc-2c963f66afa6`               |
| Situação e tipo | `status`, `payment_method`, `type` | O valor exato, em maiúsculas.                                                    | `status=PAID`                                                |
| Ativo           | `is_active`                        | `true` traz só os ativos; `false`, só os inativos. Sem o filtro, vêm os dois.    | `is_active=true`                                             |
| Data            | `created_from`, `created_to`       | Data e hora completas em ISO 8601, com fuso. As duas pontas entram no resultado. | `created_to=2026-09-15T23:59:59-03:00`                       |

Detalhes que evitam surpresas:

* `GET /sales?product_id=` traz as vendas que têm o produto em **algum** item. A venda vem com **todos** os itens, inclusive os de outros produtos.
* `GET /products` não lista planos. Para planos, use `GET /plans`. Por isso `type` aceita só `DIGITAL`, `PHYSICAL` e `PACKAGE`.
* Documento com outra quantidade de dígitos, ou com letras, responde `400 invalid_request`.
* Na busca por texto, `%` e `_` valem como caracteres comuns, não como curinga.

Exemplo: vendas pagas de um cliente, buscando pelo CPF.

```bash
curl "https://api.pagpolar.com/v1/sales?status=PAID&customer_document=123.456.789-09" \
  -H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>"
```

## Percorra todas as páginas sem perder registros

Novas vendas entram no topo da lista enquanto você pagina. Isso empurra os itens para a página seguinte, e um mesmo item pode aparecer duas vezes.

Para evitar isso:

1. Fixe `created_to` com a data e hora em que você começou a busca.
2. Use `per_page=100` para fazer menos chamadas.
3. Guarde os itens pelo `id`. Se um `id` repetir, ignore.

Os exemplos abaixo usam um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token).

#### cURL

```bash
    curl "https://api.pagpolar.com/v1/sales?page=1&per_page=100&created_from=2026-09-01T00:00:00-03:00&created_to=2026-09-15T23:59:59-03:00" \
      -H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>"
    ```

#### Node.js

```js
    const apiUrl = 'https://api.pagpolar.com/v1';
    const accessToken = '<SEU_TOKEN_DE_ACESSO>';
    const createdTo = new Date().toISOString();
    const salesById = new Map();

    let page = 1;
    let totalPages = 1;

    do {
      const query = new URLSearchParams({
        page: String(page),
        per_page: '100',
        created_from: '2026-09-01T00:00:00-03:00',
        created_to: createdTo,
      });

      const response = await fetch(`${apiUrl}/sales?${query}`, {
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      if (!response.ok) {
        throw new Error(`Erro ${response.status}: ${await response.text()}`);
      }

      const body = await response.json();

      for (const sale of body.data) {
        salesById.set(sale.id, sale);
      }

      totalPages = body.meta.total_pages;
      page += 1;
    } while (page <= totalPages);

    console.log(`${salesById.size} vendas encontradas`);
    ```

## Próximos passos

- [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — Pagine sem estourar o limite por minuto.
- [Valores, datas e identificadores](/docs/guias/fundamentos/valores-datas-e-identificadores) — Leia valores e ids das respostas sem errar.
