How-to2026-08-277 min de leitura

Quando um PDF grande não sobe: o limite de 100 MiB por requisição e o erro que aponta para o lado errado

O corpo de uma requisição pode chegar a 100 MiB, ou 104,857,600 bytes, com o enquadramento multipart incluído. Acima do limite, os endpoints de operação respondem 400 com o código bad_request e um detalhe sobre um campo multipart que não pôde ser lido, não 413, então um problema de tamanho é lido como um problema de parâmetros. Com Idempotency-Key existe um segundo limite de 100 MiB no buffer de resposta; acima dele você recebe 500 e nada é gravado em cache.

PDF123 · Updated 2026-08-27

Um PDF grande que não sobe normalmente não está corrompido. O corpo da requisição bateu no limite por requisição: 100 MiB, ou 104,857,600 bytes. Ele é medido sobre o corpo inteiro, incluídos os delimitadores multipart e os cabeçalhos de cada campo, e essa cifra exata passa enquanto um byte a mais é rejeitado.

O erro que volta aponta para outro lugar. Os endpoints de operação respondem 400 com code igual a bad_request e um detalhe dizendo que não foi possível ler um campo multipart, sem mencionar tamanho em momento algum. Um cliente que ramifica por code arquiva isso como erro de parâmetro e sai conferindo os nomes dos campos, quando o que precisa mudar é o tamanho do arquivo.

Os mesmos 100 MiB também governam a direção contrária. Uma requisição com Idempotency-Key lê a resposta na memória antes de guardá-la em cache, contra a mesma cifra, e acima dela o chamador recebe 500 enquanto a operação já terminou. Um só número, dois modos de falha opostos.

                     100 MiB = 104,857,600 bytes (essa cifra exata passa)
                                   |
                +------------------+------------------+
                |                                     |
   entrada (corpo da requisição)                saída (corpo da resposta)
   corpo inteiro, delimitadores multipart e     só com Idempotency-Key,
   cabeçalhos de campo incluídos; uma operação   um erro de cache e um
   e um pipeline o compartilham                 2xx da origem
   acima: 400 + bad_request                     acima: 500, nada em cache
   (detalhe: um campo multipart falhou)

Nota da figura: um único limite, e os lados de entrada e de saída devolvem códigos de status diferentes com consequências opostas.

O limite conta o corpo inteiro da requisição, delimitadores incluídos

Os 100 MiB limitam o corpo de uma única requisição, não o tamanho de um arquivo nem o tamanho depois da descompactação.

  • Uma operação e um pipeline de várias etapas compartilham a cifra. Dividir o trabalho em 10 etapas dentro de uma chamada a /api/v1/pipeline não transforma o teto em 1 GB; o número de etapas afeta apenas o tempo de execução.
  • O valor-limite exato passa: um corpo de 104,857,600 bytes entra, 104,857,601 bytes não.
  • O corpo carrega os cabeçalhos de cada campo e os delimitadores, além dos bytes do arquivo, então a margem que sobra para um único arquivo é estritamente inferior a 100 MiB. Um arquivo de exatamente 104,857,600 bytes é rejeitado.

Esse último ponto é onde a prática escorrega mais facilmente: curl -F acrescenta os delimitadores para você, então comparar o tamanho de um arquivo com essa linha nunca dá certo.

Acima do limite você recebe 400 e bad_request

Uma resposta acima do limite não usa 413 e não traz nenhuma menção ao tamanho. Reproduza com um corpo um byte acima:

head -c 104857601 /dev/zero > /tmp/over.bin
curl -s -X POST "$API_BASE/api/v1/misc/compress-pdf" \
  -H "X-API-KEY: $API_KEY" \
  -F "fileInput=@/tmp/over.bin"
HTTP/1.1 400 Bad Request
content-type: application/problem+json

{ "code": "bad_request",
  "detail": "failed to read multipart field: Error parsing `multipart/form-data` request",
  "hint": "Fix request parameters or upload a valid PDF.",
  "status": 400,
  "title": "Bad Request",
  "type": "https://pdf123.xyz/developers/errors#bad_request" }

Vale ler três coisas em conjunto:

  • O status é 400. Uma falha ao ler o corpo é classificada aqui como bad_request e mesmo assim passa por problem+json, então um cliente escrito para "acima do limite significa 413" toma o ramo errado.
  • code é bad_request, uma entrada real na tabela de códigos de erro. Ele não cai num caso genérico; compartilha o mesmo código com um nome de campo digitado errado ou uma codificação multipart quebrada, e nada nessa tabela diz respeito a tamanho.
  • O hint manda subir um PDF válido. O arquivo que você subiu é muito provavelmente um PDF válido que por acaso tem algumas centenas de bytes a mais.

A pista, então, não está no status, mas na contagem de bytes do corpo: num 400 cujo detail contenha failed to read multipart field, meça o tamanho em vez de revisar o formulário.

413 até aparece neste site, só não nos endpoints de operação. Todo ponto de entrada que aceita upload de arquivo foi elevado a 100 MiB, enquanto os endpoints que não recebem upload continuam no padrão do framework HTTP (o axum, do Rust), 2 MiB, onde um corpo acima do limite recebe 413 e uma linha de texto simples:

head -c 2097153 /dev/zero > /tmp/big.json
curl -s -w '\n%{http_code}\n' -X POST "$API_BASE/api/v1/auth/login" \
  -H 'Content-Type: application/json' \
  --data-binary @/tmp/big.json
Failed to buffer the request body: length limit exceeded
413

Um mesmo fato, dois códigos de status e dois corpos de resposta conforme o endpoint. Transportar qualquer uma das duas experiências para a outra vai te enganar.

A outra falha com o mesmo número, na volta

Uma requisição com Idempotency-Key guarda a resposta em cache para que uma nova tentativa possa reapresentá-la. Guardar em cache significa ler antes o corpo da resposta na memória, e essa leitura é limitada pelos mesmos 100 MiB, embora exija um conjunto bem mais estreito de condições:

  1. A requisição trazia Idempotency-Key
  2. Houve erro de cache, então a chave era nova
  3. A operação de origem devolveu 2xx

As falhas não entram em cache e passam direto, então só uma saída grande e bem-sucedida chega a esse limite.

O que acontece ali é o ponto que vale lembrar: você recebe 500 e nada é gravado no cache. A operação já rodou, mas o chamador vê uma falha; como nada foi guardado, a nova tentativa executa tudo de novo. A chave de idempotência existe para eliminar trabalho duplicado e falha exatamente quando é mais necessária, vestindo um trabalho concluído de falha do servidor. O cache vive na memória do processo e nunca é gravado em disco, então um reinício o limpa; para a semântica, veja Idempotency-Key: novas tentativas seguras em tarefas de PDF.

100 MiB não é uma configuração ajustável da plataforma

A cifra não pode ser alterada. Nenhuma variável de ambiente a aumenta ou diminui, em nenhum dos sentidos; um número diferente significa mexer no código e reconstruir a imagem. Procure por ela como configuração de implantação e você não vai encontrar.

Ela também é mais que uma cota. Os bytes de um corpo de requisição são lidos por inteiro na memória antes do processamento, então cada upload grande simultâneo ocupa ao lado dele uma quantidade comparável. Elevar o teto significa aceitar junto um pico de memória mais alto: o número também é o que impede que uma única requisição derrube o processo.

Uma implantação auto-hospedada padrão não tem proxy reverso, e o portal não verifica o tamanho antes de enviar, então essa rejeição vem do próprio limite do servidor. Coloque o nginx na frente e você vai bater primeiro nele: client_max_body_size permite apenas 1 MiB por padrão e responde 413, uma forma próxima da comparação acima e fácil de confundir com o mesmo limite.

O que fazer quando você esbarra nele

Do mais barato ao mais caro:

  1. Meça antes de enviar. Compare a contagem de bytes do corpo com 104,857,600 antes de a requisição sair e deixe folga para os delimitadores. Isso é melhor do que ler um código de status depois.
  2. Comprima o arquivo para ficar abaixo do limite. O tamanho de um escaneamento vem principalmente da camada de imagem, e recomprimir costuma retirar uma parte visível dele. Comprimir um PDF roda no navegador, sem script.
  3. Divida o trabalho em várias chamadas. Quando o conteúdo é divisível, divida e envie em algumas requisições: dá menos trabalho do que elevar o teto, e não aumenta a memória que uma requisição ocupa.
  4. Só altere o limite se houver um requisito rígido de uma única requisição. Isso significa mexer no código, reconstruir e aceitar o custo de memória da seção anterior.

Esse limite obriga o cliente a decidir de antemão

As duas falhas levam a uma mesma conclusão: o cliente precisa calcular a cifra de 100 MiB antes de enviar. Na entrada, a resposta é bad_request, um código que não diz nada sobre tamanho; na saída, 500, que parece falha do servidor. Contar os bytes antes de a requisição sair é a única forma de julgar que não depende do que diz uma mensagem de erro.

Open tool
Process in the browser — no watermark, files removed after the job.
Open tool