> ## Documentation Index
> Fetch the complete documentation index at: https://developers.zouti.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Paginação

> Como percorrer listas com cursor e o que cada ordem garante durante a leitura.

As listas, como [Listar pedidos](/api-reference/orders/list), são paginadas por **cursor**. Não
há limite de profundidade: uma conta com centenas de milhares de pedidos é lida até o fim.

## Percorrer uma lista

1. Faça a primeira chamada sem `cursor`.
2. Enquanto `has_more` for `true`, repita a chamada enviando o `next_cursor` recebido no
   parâmetro `cursor`, **com os mesmos filtros e o mesmo `sort`**.
3. Quando `has_more` for `false`, `next_cursor` vem `null` e a leitura terminou.

```text theme={null}
GET /v1/orders?limit=100&status=PAID                       → has_more: true,  next_cursor: "eyJ2..."
GET /v1/orders?limit=100&status=PAID&cursor=eyJ2...        → has_more: true,  next_cursor: "eyJ3..."
GET /v1/orders?limit=100&status=PAID&cursor=eyJ3...        → has_more: false, next_cursor: null
```

## Parâmetros e campos

| Nome | Onde | Descrição |
| - | - | - |
| `limit` | query | Itens por página, de `1` a `100`. Padrão: `20`. Fora disso, `400` com `param: "limit"`. |
| `cursor` | query | O `next_cursor` da página anterior. Omita na primeira chamada. |
| `has_more` | resposta | `true` quando há mais itens depois desta página. |
| `next_cursor` | resposta | Cursor da próxima página, ou `null` na última. |

A resposta não traz contagem total (`total`, `total_count`). Para saber quantos itens existem,
percorra a lista. Uma lista vazia vem com `data: []`, `has_more: false` e `next_cursor: null`.

## Regras do cursor

* **É opaco.** Não interprete o conteúdo nem monte um cursor: envie exatamente o que recebeu.
* **Vale só para a mesma conta, os mesmos filtros e o mesmo `sort`** da chamada que o gerou.
  Cursor alterado, de outra conta ou enviado com filtros ou `sort` diferentes recebe `400`
  `invalid_request` com `param: "cursor"`. Nesse caso, recomece a listagem sem `cursor`.
* **O `limit` pode mudar** entre páginas; os filtros e o `sort`, não.

## Garantias de cada ordem

O que acontece com pedidos criados ou alterados enquanto você percorre as páginas depende do
`sort`:

| `sort` | Garantia durante a leitura |
| - | - |
| `created_at_desc` (padrão) | Não repete nem pula os pedidos que já existiam na primeira chamada. Pedidos criados depois dela não aparecem nas páginas seguintes. |
| `updated_at_asc` | Nenhum pedido que casava com os filtros na primeira chamada é pulado. Um pedido atualizado durante a leitura ganha `updated_at` novo e **aparece de novo** numa página seguinte, já com os dados novos. Um pedido criado durante a leitura aparece no fim. Pedidos com `updated_at` `null` vêm depois de todos os outros. |

Com `updated_at_asc`, o mesmo pedido pode vir mais de uma vez: grave por `id`, substituindo a
versão anterior. Essa é a base da [sincronização de pedidos](/concepts/sync).

<RequestExample>
  ```bash cURL theme={null}
  # Requer jq. Percorre todas as páginas dos pedidos pagos, 100 por vez.
  cursor=""
  while :; do
    page=$(curl -sS -G "https://api.zouti.com.br/v1/orders" \
      -H "Authorization: Bearer $ZOUTI_API_KEY" \
      --data-urlencode "limit=100" \
      --data-urlencode "status=PAID" \
      ${cursor:+--data-urlencode "cursor=$cursor"})
    echo "$page" | jq -c '.data[] | {id, status, amount_total}'
    [ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
    cursor=$(echo "$page" | jq -r '.next_cursor')
  done
  ```

  ```javascript Node.js theme={null}
  // Node.js 18+ (fetch nativo). Percorre todas as páginas dos pedidos pagos, 100 por vez.
  const API_KEY = process.env.ZOUTI_API_KEY;
  const BASE_URL = 'https://api.zouti.com.br';
  const FILTERS = { limit: '100', status: 'PAID' }; // os mesmos em todas as páginas

  async function listPage(cursor) {
    const query = new URLSearchParams(FILTERS);
    if (cursor) query.set('cursor', cursor);
    const response = await fetch(`${BASE_URL}/v1/orders?${query}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });
    const body = await response.json();
    if (!response.ok) {
      throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);
    }
    return body;
  }

  const orders = [];
  let cursor = null;
  do {
    const page = await listPage(cursor);
    orders.push(...page.data);
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);

  console.log(`${orders.length} pedidos lidos`);
  ```

  ```python Python theme={null}
  # Python 3 com requests. Percorre todas as páginas dos pedidos pagos, 100 por vez.
  import os

  import requests

  API_KEY = os.environ["ZOUTI_API_KEY"]
  BASE_URL = "https://api.zouti.com.br"
  FILTERS = {"limit": 100, "status": "PAID"}  # os mesmos em todas as páginas


  def list_page(cursor=None):
      params = dict(FILTERS)
      if cursor:
          params["cursor"] = cursor
      response = requests.get(
          f"{BASE_URL}/v1/orders",
          params=params,
          headers={"Authorization": f"Bearer {API_KEY}"},
          timeout=30,
      )
      body = response.json()
      if not response.ok:
          error = body["error"]
          raise RuntimeError(f"{error['code']}: {error['message']} ({error['request_id']})")
      return body


  orders = []
  cursor = None
  while True:
      page = list_page(cursor)
      orders.extend(page["data"])
      if not page["has_more"]:
          break
      cursor = page["next_cursor"]

  print(f"{len(orders)} pedidos lidos")
  ```

  ```php PHP theme={null}
  <?php
  // PHP 7.4+ com a extensão curl. Percorre todas as páginas dos pedidos pagos, 100 por vez.
  const BASE_URL = 'https://api.zouti.com.br';
  const FILTERS = ['limit' => 100, 'status' => 'PAID']; // os mesmos em todas as páginas

  function list_page(?string $cursor): array
  {
      $query = FILTERS;
      if ($cursor !== null) {
          $query['cursor'] = $cursor;
      }
      $ch = curl_init(BASE_URL . '/v1/orders?' . http_build_query($query));
      curl_setopt_array($ch, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_TIMEOUT => 30,
          CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('ZOUTI_API_KEY')],
      ]);
      $raw = curl_exec($ch);
      if ($raw === false) {
          throw new RuntimeException(curl_error($ch));
      }
      $body = json_decode($raw, true);
      if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) {
          $error = $body['error'];
          throw new RuntimeException("{$error['code']}: {$error['message']} ({$error['request_id']})");
      }
      return $body;
  }

  $orders = [];
  $cursor = null;
  do {
      $page = list_page($cursor);
      array_push($orders, ...$page['data']);
      $cursor = $page['has_more'] ? $page['next_cursor'] : null;
  } while ($cursor !== null);

  printf("%d pedidos lidos\n", count($orders));
  ```
</RequestExample>

<ResponseExample>
  ```json 200 (uma página) theme={null}
  {
    "object": "list",
    "data": [
      {
        "object": "order",
        "id": "ord_8k2m4q6s1v3x5z7b9d0f2h",
        "role": "OWNER",
        "status": "PAID",
        "currency": "BRL",
        "amount_total": 27730,
        "amount_subtotal": 29700,
        "amount_in_brl": 27730,
        "created_at": "2026-10-14T13:05:22.418Z",
        "paid_at": "2026-10-14T13:05:24.902Z",
        "canceled_at": null,
        "updated_at": "2026-10-14T13:05:25.117Z",
        "customer": {
          "name": "Maria Oliveira",
          "email": "maria.oliveira@example.com",
          "phone": "+5511999998888",
          "document": "12345678909",
          "document_type": "CPF"
        },
        "shipping_address": {
          "line1": "Rua das Flores",
          "line2": "123",
          "line3": "Apto 45",
          "neighborhood": "Centro",
          "city": "São Paulo",
          "state": "SP",
          "country": "BR",
          "postal_code": "01001000",
          "full_name": "Maria Oliveira"
        },
        "payment": {
          "method": "CREDIT_CARD",
          "status": "PAID",
          "installments": 1,
          "amount": 27730,
          "interest_amount": 0,
          "discount_amount": 1970,
          "fee": 1386,
          "amount_refunded": 0,
          "refunded_at": null,
          "decline_code": null,
          "decline_message": null,
          "card": {
            "brand": "MASTERCARD",
            "last4": "4242"
          }
        },
        "items": [
          {
            "product_id": "prod_3n7p1r5t9w2y4a6c8e0g1j",
            "name": "Curso Completo de Fotografia",
            "quantity": 1,
            "unit_price": 19700,
            "sub_total": 19700,
            "currency": "BRL",
            "offer_id": "prod_offer_5d1f3h7k9m2p4r6t8v0x2z",
            "plan_id": null,
            "order_bump_id": null
          },
          {
            "product_id": "prod_6b8d0f2h4k6m8p0r2t4v6x",
            "name": "Pacote de Presets",
            "quantity": 1,
            "unit_price": 10000,
            "sub_total": 10000,
            "currency": "BRL",
            "offer_id": null,
            "plan_id": null,
            "order_bump_id": "ob_2c4e6g8j0l2n4q6s8u0w2y"
          }
        ],
        "offer": {
          "id": "prod_offer_5d1f3h7k9m2p4r6t8v0x2z",
          "name": "Oferta de lançamento"
        },
        "promocode": {
          "code": "BEMVINDO10",
          "description": "10% de desconto no curso",
          "amount": -1970
        },
        "utm": {
          "utm_source": "instagram",
          "utm_medium": "social",
          "utm_campaign": "lancamento-outubro",
          "utm_term": null,
          "utm_content": "stories"
        },
        "tracking": {
          "sck": "parceiro-123",
          "src": "bio"
        },
        "navigator": {
          "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Instagram 350.0.0",
          "referer": "https://www.instagram.com/",
          "device": "Mobile",
          "browser": "Instagram",
          "origin": "instagram.com"
        },
        "metadata": {}
      },
      {
        "object": "order",
        "id": "ord_1q3s5u7w9y0a2c4e6g8i0k",
        "role": "COPRODUCER",
        "status": "PAID",
        "currency": "BRL",
        "amount_total": 9700,
        "amount_subtotal": 9700,
        "amount_in_brl": 9700,
        "created_at": "2026-10-13T18:42:10.036Z",
        "paid_at": "2026-10-13T18:43:51.274Z",
        "canceled_at": null,
        "updated_at": "2026-10-13T18:43:52.009Z",
        "customer": {
          "name": "Maria Oliveira",
          "email": "maria.oliveira@example.com",
          "phone": "+5511999998888",
          "document": "12345678909",
          "document_type": "CPF"
        },
        "shipping_address": null,
        "payment": {
          "method": "PIX",
          "status": "PAID",
          "installments": 1,
          "amount": 9700,
          "interest_amount": 0,
          "discount_amount": 0,
          "fee": 485,
          "amount_refunded": 0,
          "refunded_at": null,
          "decline_code": null,
          "decline_message": null,
          "card": null
        },
        "items": [
          {
            "product_id": "prod_9x1z3b5d7f9h1k3m5p7r9t",
            "name": "Mentoria em Grupo",
            "quantity": 1,
            "unit_price": 9700,
            "sub_total": 9700,
            "currency": "BRL",
            "offer_id": "prod_offer_4w6y8a0c2e4g6j8l0n2q4s",
            "plan_id": null,
            "order_bump_id": null
          }
        ],
        "offer": {
          "id": "prod_offer_4w6y8a0c2e4g6j8l0n2q4s",
          "name": "Mentoria em Grupo"
        },
        "promocode": null,
        "utm": null,
        "tracking": null,
        "navigator": null,
        "metadata": {
          "order_parent_id": "ord_7h9k1m3p5r7t9v1x3z5b7d"
        }
      }
    ],
    "has_more": true,
    "next_cursor": "eyJ2IjoxLCJyIjoib3JkZXJzIiwicyI6ImNyZWF0ZWRfYXRfZGVzYyJ9.k3X9vQ2mT7bR1wL5yN8pZg"
  }
  ```
</ResponseExample>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.