Limites e consumo
Uma integração que respeita os limites é mais rápida do que uma que os ignora, porque não
passa metade do tempo tratando 429. Esta página tem os números, o que cada
contador de consumo mede e as práticas que separam uma integração que roda por anos de uma
que precisa de babá.
Todos os limites em uma tabela
| Limite | Valor | Ao estourar |
|---|---|---|
| Requisições por minuto | 60 por chave | 429 com Retry-After |
| Tamanho do arquivo | 20 MB por PDF, apenas application/pdf | 413 |
| Páginas por documento | 300 | 422 |
| Signatários por documento | 50 | 422 |
| Campos por documento | 500 | 422 |
| Itens por página de listagem | 1 a 100, padrão 25 | o valor é limitado a 100, sem erro |
| Validade do cursor | 24 horas | 422, refaça a listagem |
| Validade da URL de download | 15 minutos, uso único | 404, peça outra |
| Reenvio de convite | 3 por signatário por hora | 429 |
| Validação pública | 30 por minuto por IP, rajada de 10 | 429 |
| Autenticação do painel | 10 por minuto por IP | 429 |
| Chaves de idempotência | lembradas por 24 horas | depois disso a chave é tratada como nova |
| Resposta do seu webhook | 10 segundos | conta como falha e entra em retentativa |
>>> PENDENTE DE BACKEND <<<
Os valores de 60 requisições por minuto e 20 MB por arquivo vêm da configuração real da plataforma. Os demais números desta tabela são o desenho do contrato e ainda não foram exercidos contra o servidor. Trate-os como ordem de grandeza confiável, não como garantia contratual, até a publicação da API.
Limite de requisição
São 60 requisições por minuto, contadas por chave de API, em janela deslizante. Cada resposta traz o estado do seu orçamento, e o cliente que lê esses cabeçalhos nunca precisa descobrir o limite por tentativa e erro:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1786031111
Quando você estoura, a resposta é 429 e vem com o tempo exato de espera:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1786031111
X-Request-Id: req_01J9M4X7QK2ZB8N3P6R0T5V2WY
Retry-After é em segundos e é exato, não estimativa. Respeitá-lo é mais
rápido do que tentar de novo antes: cada tentativa antecipada volta a estourar o limite e
ainda conta.
// Um regulador de vazao simples: em vez de bater no limite e tratar
// 429, mantenha-se abaixo dele. Sai mais barato e o log fica limpo.
class Regulador {
/** @param {number} porMinuto deixe folga: 50 de 60 e um bom alvo. */
constructor(porMinuto = 50) {
this.intervaloMs = 60000 / porMinuto;
this.proximo = 0;
}
async esperarVez() {
const agora = Date.now();
const alvo = Math.max(agora, this.proximo);
this.proximo = alvo + this.intervaloMs;
if (alvo > agora) {
await new Promise((r) => setTimeout(r, alvo - agora));
}
}
/** Freia de verdade quando o servidor mandar frear. */
ajustarPor(resposta) {
const restante = Number(resposta.headers.get("X-RateLimit-Remaining"));
const reset = Number(resposta.headers.get("X-RateLimit-Reset"));
// Menos de 10% de folga: pare ate a janela virar.
if (Number.isFinite(restante) && restante < 6 && Number.isFinite(reset)) {
this.proximo = Math.max(this.proximo, reset * 1000);
}
}
}
const regulador = new Regulador(50);
export async function chamarRegulado(url, opcoes) {
await regulador.esperarVez();
const resposta = await fetch(url, opcoes);
regulador.ajustarPor(resposta);
if (resposta.status === 429) {
const espera = Number(resposta.headers.get("Retry-After") || 5);
await new Promise((r) => setTimeout(r, espera * 1000));
return chamarRegulado(url, opcoes);
}
return resposta;
}#!/usr/bin/env python3
"""Regulador de vazao: mantenha-se abaixo do limite em vez de trata-lo."""
import threading
import time
import requests
class Regulador:
def __init__(self, por_minuto: int = 50):
# Deixe folga: 50 de 60 e um bom alvo.
self.intervalo = 60.0 / por_minuto
self.proximo = 0.0
self.trava = threading.Lock()
def esperar_vez(self) -> None:
with self.trava:
agora = time.monotonic()
alvo = max(agora, self.proximo)
self.proximo = alvo + self.intervalo
espera = alvo - time.monotonic()
if espera > 0:
time.sleep(espera)
def ajustar_por(self, resposta: requests.Response) -> None:
"""Freia de verdade quando o servidor mandar frear."""
try:
restante = int(resposta.headers.get("X-RateLimit-Remaining", "60"))
reset = int(resposta.headers.get("X-RateLimit-Reset", "0"))
except ValueError:
return
# Menos de 10% de folga: pare ate a janela virar.
if restante < 6 and reset:
with self.trava:
self.proximo = max(self.proximo, time.monotonic() + max(0, reset - time.time()))
regulador = Regulador(50)
def chamar_regulado(metodo: str, url: str, **kwargs) -> requests.Response:
while True:
regulador.esperar_vez()
resposta = requests.request(metodo, url, timeout=60, **kwargs)
regulador.ajustar_por(resposta)
if resposta.status_code != 429:
return resposta
time.sleep(float(resposta.headers.get("Retry-After", "5")))Precisa de mais que 60 por minuto? Antes de pedir aumento, confira se a causa não é sondagem: quem consulta o estado de cada documento aberto a cada minuto gasta a cota inteira sem produzir nada. Trocar sondagem por webhook costuma reduzir o volume em uma ordem de grandeza. Se depois disso o limite continuar apertado, fale conosco com o número de documentos por dia e o padrão de uso.
Paginação por cursor
Toda listagem usa cursor, e não número de página. Os parâmetros são limite,
de 1 a 100, e cursor, que vem da página anterior.
A razão de não haver ?pagina=3 é que o acervo muda enquanto você o percorre.
Com página numerada, um documento criado entre a leitura da página 1 e a da página 2
empurra tudo para baixo, e um item da página 1 reaparece na 2 enquanto outro nunca é lido.
Em uma lista de vinte contratos isso é um incômodo; em uma conciliação noturna de dez mil,
é um contrato que o seu sistema nunca viu. O cursor aponta para uma posição estável no
conjunto, e a inserção concorrente não desloca nada.
# 1. Primeira pagina: so limite.
GET /v1/documentos?limite=100
# 2. Resposta:
# { "itens": [...], "total": 412, "proximoCursor": "Y3Vyc29y..." }
# 3. Proxima pagina: repita os MESMOS filtros e acrescente o cursor.
GET /v1/documentos?limite=100&cursor=Y3Vyc29y...
# 4. Pare quando proximoCursor vier nulo ou ausente. NUNCA pare
# com menos itens que o limite: sao coisas diferentes.
Três regras que evitam os erros mais comuns:
- Repita os filtros em toda página. O cursor não guarda os seus filtros; ele guarda a posição. Mudar o filtro no meio do percurso dá resultado indefinido.
- Não decomponha nem construa cursor. Ele é opaco e o formato pode mudar sem aviso. É base64 hoje porque precisava ser alguma coisa.
- Não guarde cursor a longo prazo. Vale 24 horas. Para retomar uma sincronização diária, use
criado_decom o horário da última execução, que é estável.
Um exemplo completo de percurso paginado, com tratamento de 429, está em Guias.
Tamanho e formato de arquivo
| Regra | Valor |
|---|---|
| Tamanho máximo | 20 MB por arquivo |
| Formato | PDF apenas. Não convertemos DOCX, XLSX nem imagem. |
| Páginas | até 300 |
| Versão do PDF | 1.4 a 2.0 |
| Protegido por senha | recusado com 422 |
| Já assinado digitalmente | recusado com 422: assinar por cima invalidaria a assinatura anterior |
| Com formulário AcroForm preenchido | recusado com 422: os campos entrariam em conflito com os nossos |
| Varredura antivírus | todo upload é varrido antes de ficar disponível |
O limite de 20 MB é imposto na borda, pelo servidor web, antes de a aplicação ver a
requisição. Isso significa que um arquivo de 30 MB pode receber a recusa antes de o upload
terminar, e o sintoma no seu lado pode ser uma conexão encerrada em vez de um
413 limpo. Confira o tamanho antes de enviar.
#!/usr/bin/env bash
# Confere o tamanho e, se passar do limite, reduz sem perder legibilidade.
set -euo pipefail
PDF="${1:?informe o PDF}"
LIMITE=$((25 * 1024 * 1024))
TAMANHO=$(stat -c%s "$PDF" 2>/dev/null || stat -f%z "$PDF")
echo "tamanho: $((TAMANHO / 1024 / 1024)) MB"
if [ "$TAMANHO" -le "$LIMITE" ]; then
echo "OK, cabe."
exit 0
fi
# Quase sempre o excesso vem de pagina digitalizada em 600 dpi.
# /ebook mantem 150 dpi, que e legivel e imprime bem.
gs -sDEVICE=pdfwrite \
-dCompatibilityLevel=1.7 \
-dPDFSETTINGS=/ebook \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile="${PDF%.pdf}-reduzido.pdf" \
"$PDF"
echo "gerado: ${PDF%.pdf}-reduzido.pdf"
echo "CONFIRA O RESULTADO antes de enviar: reducao agressiva pode"
echo "tornar ilegivel um documento que sera prova em juizo."
Medição de consumo
Você precisa saber o que custa antes de a fatura chegar. São nove contadores; cinco
importam para praticamente toda integração e estão marcados abaixo. Todos são consultáveis
a qualquer momento por
GET /v1/consumo.
| Contador | Conta quando | Não conta quando |
|---|---|---|
ENVELOPE_ENVIADO |
Um documento sai do rascunho, por POST /v1/documentos/{documentoId}/enviar ou por POST /v1/envelopes/{envelopeId}/enviar. É a unidade da franquia do plano, e um envelope conta um tenha ele um documento ou seis. |
Criar rascunho, reenviar convite, lembrete automático, cancelar, mover. |
DOCUMENTO_ENVIADO |
Um PDF é recebido, varrido, rasterizado e preparado. Um por arquivo enviado. | Instanciar modelo já processado. Por isso modelo sai mais barato em volume. |
ASSINATURA_COLETADA |
Um signatário conclui a assinatura. Um documento com três signatários conta três. | Recusa, expiração, visualização sem assinar. |
REQUISICAO_API |
Toda requisição autenticada que devolve 2xx, 4xx de negócio ou 404. |
429, 5xx, /healthz, /readyz e as rotas públicas de validação. |
SMS_ENVIADO |
Um SMS sai: convite por metodoEntrega: "SMS", código do fator SMS_OTP, ou lembrete por SMS. Cada reenvio de código conta de novo. |
SMS recusado pela operadora antes do envio. |
WHATSAPP_ENVIADO |
Uma mensagem de WhatsApp é entregue. | Falha de entrega por número inválido. |
VERIFICACAO_BIOMETRICA |
Uma prova de vida é processada, aprovada ou não. >>> PENDENTE DE BACKEND <<< | O signatário desistir antes de enviar a selfie. |
CARIMBO_TEMPO |
Um carimbo do tempo é solicitado à autoridade, no nível ICP_BRASIL. >>> PENDENTE DE BACKEND <<< |
Níveis ELETRONICA, SEGURA e AVANCADA. |
ARMAZENAMENTO_MB_DIA |
Soma diária dos megabytes guardados: original, assinado, PAdES e certificado. | Arquivo já expurgado pela política de retenção. |
O contador que surpreende é o de SMS. Um documento com três
signatários exigindo SMS_OTP, em que dois pedem reenvio do código porque
demoraram a abrir, custa cinco SMS, e não três. Se o volume for grande e o risco não
exigir, TOTP entrega o mesmo segundo fator sem custo por uso; a
contrapartida é que o signatário precisa cadastrar o aplicativo autenticador na
primeira vez.
Prever o custo de um envio
Vale calcular antes, e a conta é direta. Um contrato de 12 páginas, dois signatários, os
dois com fatoresExigidos: ["EMAIL", "SMS_OTP"],
enviado a partir de modelo e acompanhado por webhook:
| Contador | Quantidade | De onde vem |
|---|---|---|
ENVELOPE_ENVIADO | 1 | o disparo |
DOCUMENTO_ENVIADO | 0 | o modelo já foi processado quando foi criado |
ASSINATURA_COLETADA | 2 | uma por signatário que assinou |
REQUISICAO_API | 3 | instanciar o modelo, ler o certificado, baixar o PDF |
SMS_ENVIADO | 2 a 4 | um código por signatário, mais os reenvios que eles pedirem |
Compare com o mesmo envio feito sem modelo e com sondagem de dois em dois minutos por
dois dias: DOCUMENTO_ENVIADO sobe para 1 e
REQUISICAO_API sobe de 3 para mais de mil. É esse contador que separa uma
integração barata de uma cara, e ele depende inteiramente de decisões suas.
Retenção
>>> PENDENTE DE BACKEND <<<
A política de retenção ainda não está definida. O mecanismo de expurgo existe e está deliberadamente desligado até haver política escrita, aprovada e com restauração de backup testada. Enquanto isso, nada é apagado automaticamente. Não construa premissa de prazo em cima do que está escrito abaixo: isto é a forma que a política vai ter, não os números dela.
Quando a política entrar, ela terá esta forma, e alguns prazos já são certos porque não dependem de decisão comercial:
| Item | Prazo |
|---|---|
| Documento concluído e certificado | definido pela sua organização, com mínimo legal aplicável ao tipo de documento |
| Documento em rascunho, nunca enviado | 90 dias sem alteração |
| Documento cancelado ou expirado | prazo próprio, mais curto que o de concluído |
| Trilha de auditoria | acompanha o documento; nunca é apagada antes dele |
| Histórico de entregas de webhook | 30 dias |
| Log de chamadas à API | 90 dias |
| Chaves de idempotência | 24 horas |
| Cursores de paginação | 24 horas |
| URLs de download | 15 minutos, uso único |
Guarde a sua própria cópia. Independentemente da política que vier, um
contrato de doze anos precisa sobreviver ao fornecedor que o processou. Baixe o PDF
assinado e o certificado quando o evento document.completed chegar, guarde
no seu armazenamento, e guarde junto o código de validação do certificado: com ele, a
autenticidade continua conferível pela validação pública. O código para fazer isso está
em Guias.
Sete práticas para não ser barrado
-
Troque sondagem por webhook
É a decisão de maior impacto, e sozinha resolve a maioria dos casos de
429. Sondar não faz o signatário assinar mais rápido; só gasta a sua cota descobrindo que nada mudou. -
Regule a vazão em vez de tratar o erro
Um regulador que segura em 50 por minuto nunca vê um
429. Tratar429depois de estourar já custou a requisição, e o log fica cheio de erro que não é erro. O código está acima. -
Use
limite=100em listagemPercorrer mil documentos com o padrão de 25 custa 40 requisições; com 100, custa 10. A resposta é maior, e isso quase nunca importa em rede de servidor.
-
Mande
Idempotency-Keyem toda criaçãoCom uma chave do seu domínio, retentativa depois de timeout não duplica documento e não consome envelope a mais. Sem ela, o mesmo timeout vira um contrato duplicado na caixa do cliente.
-
Prefira modelo a upload repetido
Instanciar modelo evita o contador
DOCUMENTO_ENVIADO, dispensa subir o mesmo PDF de novo e reduz o tempo de resposta. Em volume, a diferença é grande. -
Distribua o disparo em lote
Mil contratos disparados às 8h em ponto batem no limite. Espalhe pela hora, ou pelo dia: o signatário não percebe a diferença de dez minutos, e a sua integração termina sem um único
429. -
Registre o
X-Request-IdsempreEm sucesso e em falha. É o que transforma um chamado de suporte de três dias em uma resposta na primeira mensagem. Veja Erros.