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

# Limites de requisição

> As três camadas de limite, os headers RateLimit-* e como ficar dentro deles.

A API aplica três limites, em camadas. Quase toda integração só encontra o primeiro.

| Camada | Limite | Acima do limite |
| - | - | - |
| Conta | Rajada de **20** requisições e reposição de **10 por segundo**, somando todas as chaves e recursos. | `429` `rate_limited` com corpo JSON e `Retry-After`. |
| Falhas de autenticação por IP | **30** respostas `401` para o mesmo IP em **60 segundos**. | `429` `rate_limited` com `Retry-After`, até o fim da janela. |
| Volume por IP | **15.000** requisições do mesmo IP em **5 minutos**. | `429` **sem corpo**, com `Retry-After: 300`. |

## Limite da conta

Pense num balde com 20 fichas. Cada requisição gasta uma ficha, e o balde ganha 10 fichas por
segundo, até voltar a 20. Na prática:

* Em ritmo constante, a conta faz até **10 requisições por segundo**.
* Uma rajada de até 20 requisições de uma vez passa, se o balde estiver cheio.
* O limite é **da conta**: soma todas as chaves e todos os recursos. Criar mais chaves não aumenta
  o limite; duas integrações da mesma conta dividem o mesmo balde.

Toda resposta autenticada traz o estado do balde:

| Header | Descrição |
| - | - |
| `RateLimit-Limit` | Capacidade da rajada: `20`. |
| `RateLimit-Remaining` | Requisições ainda disponíveis agora. |
| `RateLimit-Reset` | Segundos até o balde encher de novo. |
| `Retry-After` | Só no `429`: segundos inteiros, no mínimo `1`, até poder repetir. |

Acima do limite, a resposta é `429` `rate_limited`. Espere os segundos de `Retry-After` antes de
repetir. Repetir antes só gasta tempo: o balde ainda está vazio.

## Falhas de autenticação por IP

Depois de 30 respostas `401` para o mesmo IP em 60 segundos, as requisições seguintes desse IP
recebem `429` `rate_limited` com `Retry-After` até o fim da janela. Uma integração que repete com
a chave errada se bloqueia: corrija a chave em vez de repetir.

O bloqueio poupa a chave válida que **autenticou com sucesso nas últimas 24 horas**: ela segue
funcionando mesmo com o IP bloqueado. Isso protege quem divide o IP de saída com outras contas,
como no n8n cloud, no Make e no Zapier. Uma chave válida sem sucesso nesse período (por exemplo,
recém-criada e ainda não usada) recebe `429` até o fim da janela, como as demais.

## Volume por IP

Acima de 15.000 requisições em 5 minutos vindas do mesmo IP, a borda da Zouti responde `429`
**sem corpo JSON** e com `Retry-After: 300`. Esse limite vale antes da autenticação, para qualquer
chave.

<Warning>
  O `429` do limite por IP não tem `error.code`. Trate o `429` pelo **status HTTP** e pelo header
  `Retry-After`, nunca pelo corpo.
</Warning>

## Como ficar dentro do limite

1. **Leia `RateLimit-Remaining`.** Quando chegar a `0`, espere os segundos de `RateLimit-Reset`
   antes da próxima chamada.
2. **No `429`, espere `Retry-After`** e repita a mesma requisição.
3. **Prefira poucas chamadas grandes.** Use `limit=100` ao percorrer listas: são 100 pedidos por
   requisição em vez de 20.
4. **Não leia tudo de novo a cada rodada.** Leia só o que mudou com a
   [sincronização incremental](/concepts/sync).
5. **Evite muitas requisições em paralelo** com a mesma conta: todas gastam o mesmo balde.

O código ao lado percorre todos os pedidos seguindo as regras 1 a 3. Para um cliente que também
repete `500` e `503` com espera crescente, veja [Erros](/concepts/errors#quando-repetir).

<RequestExample>
  ```bash cURL theme={null}
  # Mostra os headers de limite de uma chamada.
  curl -sS -D - -o /dev/null "https://api.zouti.com.br/v1/orders?limit=100" \
    -H "Authorization: Bearer $ZOUTI_API_KEY" | grep -i -E '^(ratelimit-|retry-after)'
  ```

  ```javascript Node.js theme={null}
  // Node.js 18+ (fetch nativo). Percorre todos os pedidos respeitando o limite da conta.
  const API_KEY = process.env.ZOUTI_API_KEY;
  const BASE_URL = 'https://api.zouti.com.br';
  const MAX_429 = 5;

  const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));

  async function zoutiGet(path) {
    for (let attempt = 1; ; attempt++) {
      const response = await fetch(`${BASE_URL}${path}`, {
        headers: { Authorization: `Bearer ${API_KEY}` },
      });

      // 429: o da conta tem corpo JSON, o do limite por IP não. Trate pelo status.
      if (response.status === 429 && attempt < MAX_429) {
        const retryAfter = Number(response.headers.get('Retry-After')) || 1;
        console.log(`429: esperando ${retryAfter} s`);
        await sleep(retryAfter);
        continue;
      }
      if (!response.ok) {
        throw new Error(`HTTP ${response.status} (Request-Id ${response.headers.get('Request-Id')})`);
      }

      // Balde vazio: espera encher antes da próxima chamada.
      if (response.headers.get('RateLimit-Remaining') === '0') {
        const reset = Number(response.headers.get('RateLimit-Reset')) || 1;
        console.log(`RateLimit-Remaining 0: esperando ${reset} s`);
        await sleep(reset);
      }
      return response.json();
    }
  }

  let total = 0;
  let cursor = null;
  do {
    const query = new URLSearchParams({ limit: '100' });
    if (cursor) query.set('cursor', cursor);
    const page = await zoutiGet(`/v1/orders?${query}`);
    total += page.data.length;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);

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

  ```python Python theme={null}
  # Python 3 com requests. Percorre todos os pedidos respeitando o limite da conta.
  import os
  import time

  import requests

  API_KEY = os.environ["ZOUTI_API_KEY"]
  BASE_URL = "https://api.zouti.com.br"
  MAX_429 = 5


  def zouti_get(path, params):
      for attempt in range(1, MAX_429 + 1):
          response = requests.get(
              f"{BASE_URL}{path}",
              params=params,
              headers={"Authorization": f"Bearer {API_KEY}"},
              timeout=30,
          )

          # 429: o da conta tem corpo JSON, o do limite por IP não. Trate pelo status.
          if response.status_code == 429 and attempt < MAX_429:
              retry_after = int(response.headers.get("Retry-After") or 1)
              print(f"429: esperando {retry_after} s")
              time.sleep(retry_after)
              continue
          if not response.ok:
              raise RuntimeError(
                  f"HTTP {response.status_code} (Request-Id {response.headers.get('Request-Id')})"
              )

          # Balde vazio: espera encher antes da próxima chamada.
          if response.headers.get("RateLimit-Remaining") == "0":
              reset = int(response.headers.get("RateLimit-Reset") or 1)
              print(f"RateLimit-Remaining 0: esperando {reset} s")
              time.sleep(reset)
          return response.json()


  total = 0
  cursor = None
  while True:
      params = {"limit": 100}
      if cursor:
          params["cursor"] = cursor
      page = zouti_get("/v1/orders", params)
      total += len(page["data"])
      if not page["has_more"]:
          break
      cursor = page["next_cursor"]

  print(f"{total} pedidos lidos")
  ```

  ```php PHP theme={null}
  <?php
  // PHP 7.4+ com a extensão curl. Percorre todos os pedidos respeitando o limite da conta.
  const BASE_URL = 'https://api.zouti.com.br';
  const MAX_429 = 5;

  function zouti_get(string $path, array $query): array
  {
      $apiKey = getenv('ZOUTI_API_KEY');
      for ($attempt = 1; ; $attempt++) {
          $headers = [];
          $ch = curl_init(BASE_URL . $path . '?' . http_build_query($query));
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_TIMEOUT => 30,
              CURLOPT_HTTPHEADER => ["Authorization: Bearer {$apiKey}"],
              CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$headers) {
                  $parts = explode(':', $line, 2);
                  if (count($parts) === 2) {
                      $headers[strtolower(trim($parts[0]))] = trim($parts[1]);
                  }
                  return strlen($line);
              },
          ]);
          $body = curl_exec($ch);
          if ($body === false) {
              throw new RuntimeException(curl_error($ch));
          }
          $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

          // 429: o da conta tem corpo JSON, o do limite por IP não. Trate pelo status.
          if ($status === 429 && $attempt < MAX_429) {
              $retryAfter = max(1, (int) ($headers['retry-after'] ?? 1));
              echo "429: esperando {$retryAfter} s\n";
              sleep($retryAfter);
              continue;
          }
          if ($status < 200 || $status >= 300) {
              throw new RuntimeException(sprintf('HTTP %d (Request-Id %s)', $status, $headers['request-id'] ?? '-'));
          }

          // Balde vazio: espera encher antes da próxima chamada.
          if (($headers['ratelimit-remaining'] ?? null) === '0') {
              $reset = max(1, (int) ($headers['ratelimit-reset'] ?? 1));
              echo "RateLimit-Remaining 0: esperando {$reset} s\n";
              sleep($reset);
          }
          return json_decode($body, true);
      }
  }

  $total = 0;
  $cursor = null;
  do {
      $query = ['limit' => 100];
      if ($cursor !== null) {
          $query['cursor'] = $cursor;
      }
      $page = zouti_get('/v1/orders', $query);
      $total += count($page['data']);
      $cursor = $page['has_more'] ? $page['next_cursor'] : null;
  } while ($cursor !== null);

  echo "{$total} pedidos lidos\n";
  ```
</RequestExample>

<ResponseExample>
  ```json 429 da conta theme={null}
  {
    "error": {
      "type": "rate_limit_error",
      "code": "rate_limited",
      "message": "Limite de requisições excedido. Tente novamente em instantes.",
      "request_id": "req_8f2kq1x7c3m9v4b6n0zd5a"
    }
  }
  ```

  ```text 429 por IP theme={null}
  HTTP 429
  Retry-After: 300

  (sem corpo)
  ```
</ResponseExample>


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