Limites e novas tentativas
Limites
| Limite | Valor | Ao passar |
|---|---|---|
| Tamanho do PDF | 25 MiB | 413 pdf_too_large |
| Páginas | 100, cada uma até A2 | 202, depois error com processing_failed |
Idempotency-Key | 1 a 128 caracteres | 422 invalid_request |
| Envios ao mesmo tempo, por chave | 2 | 429 too_many_requests |
| Envios ao mesmo tempo, no total | 3 | 503 capacity_busy |
Consultas GET têm fila própria e não esperam envios.
Retry-After
429 e 503 vêm com o cabeçalho Retry-After (segundos) e são respondidos antes de ler o corpo: o PDF não foi recebido. Espere ao menos o Retry-After e reenvie a requisição inteira com a mesma Idempotency-Key.
Quando repetir
| Situação | Repetir? |
|---|---|
429, 503 | Sim, depois do Retry-After, com a mesma Idempotency-Key. |
Timeout, falha de rede, 500, 502, 504 | Sim, com a mesma Idempotency-Key e backoff. |
GET em 429 ou 503 | Sim, com o mesmo backoff. |
Demais 4xx | Não; corrija a requisição antes. |
Use backoff exponencial com jitter (por exemplo 2 s, 4 s, 8 s, até 60 s) e um limite de tentativas. Como o envio é idempotente, repetir nunca cria documento duplicado nem nova cobrança.
curl
# curl --retry já respeita Retry-After e reenvia o corpo.
curl --retry 5 --retry-max-time 120 \
"https://finstat.com.br/v1/documents?cnpj=00000000000191&period_end=2025-12-31" \
-H "Authorization: Bearer $FINSTAT_API_KEY" \
-H "Idempotency-Key: lote-2025-12-0042" \
-H "Content-Type: application/pdf" \
--data-binary @balanco.pdfNode
// O SDK repete sozinho em 429, 503, 5xx e falha de rede, com a mesma chave.
const finstat = new Finstat(process.env.FINSTAT_API_KEY!, { maxNetworkRetries: 4 });Python
import random
import time
import httpx
RETRY = {429, 500, 502, 503, 504}
def send(request: httpx.Request, attempts: int = 5) -> httpx.Response:
with httpx.Client(timeout=60) as client:
for attempt in range(attempts):
try:
response = client.send(request)
except httpx.TransportError:
response = None
if response is not None and response.status_code not in RETRY:
return response
retry_after = response.headers.get("Retry-After") if response else None
delay = float(retry_after) if retry_after else min(60, 2 ** (attempt + 1))
time.sleep(delay + random.uniform(0, 1))
raise RuntimeError("finstat: tentativas esgotadas")Consultas
Prefira o webhook a consultar a situação em laço. Sem webhook, consulte a cada minuto ou mais; com conferência humana, a liberação pode levar horas.