Documentos grandes na API: upload por URL pré-assinada em vez de base64
O jeito mais simples de mandar um documento para a API é embutir o arquivo em base64 no corpo da requisição — e é o jeito certo até certo tamanho. Passado esse ponto, ele para de funcionar de um jeito particularmente irritante: o teste passou com o contrato de exemplo de 200 KB e a integração quebra em produção, no primeiro cliente que envia um PDF escaneado de 40 páginas. A causa não é o seu código. É que base64 cresce cerca de um terço sobre o binário e o corpo de uma requisição tem teto. A API oferece um segundo caminho para esse caso: pedir uma URL pré-assinada, enviar o arquivo direto ao armazenamento e depois confirmar. Três chamadas em vez de uma, e o arquivo nunca trafega pela API.
Regra prática: abaixo de 5 MB, mande em base64 e siga a vida. A partir daí, use presign → PUT direto no armazenamento → confirm. Se o tamanho do documento vem de upload do usuário final e você não controla, implemente o segundo caminho e use-o sempre.
Por que o base64 quebra, e quando
Base64 representa 3 bytes binários com 4 caracteres. A conta é direta: todo arquivo cresce cerca de 33% ao ser codificado. Um PDF de 6 MB vira quase 8 MB de texto no corpo da requisição, antes de qualquer outro campo do JSON.
Isso importa porque o corpo de uma requisição tem limite, e o limite vale para o payload codificado, não para o arquivo original. O resultado é a falha mais frustrante que existe: o desenvolvedor testou com o contrato de exemplo, funcionou, foi para produção — e a primeira pessoa que anexa um PDF escaneado de 40 páginas recebe um erro que não fala de tamanho de arquivo.
Vale notar de onde vem a percepção errada de folga: o arquivo no disco tem 6 MB, o desenvolvedor sabe que o limite é maior que isso, e a conclusão parece segura. O terço que o base64 acrescenta é invisível até você medir o corpo montado.
Os dois caminhos, lado a lado
| Base64 inline | URL pré-assinada | |
|---|---|---|
| Chamadas | 1 | 3 (presign, PUT, confirm) |
| Por onde os bytes passam | Corpo da requisição, pela API | Direto ao armazenamento, sem tocar a API |
| Overhead de codificação | +33% | Nenhum — vai binário |
| Recomendado para | Até 5 MB | Acima de 5 MB, ou tamanho imprevisível |
| Validação do documento | Sim | Sim, no confirm |
Devolve documentHash |
Sim | Sim |
As duas últimas linhas são o ponto que costuma preocupar quem avalia a mudança: o caminho pré-assinado não é uma porta dos fundos com menos garantias. A validação é a mesma e o hash resultante é o mesmo. O que muda é exclusivamente por onde os bytes trafegam.
O caminho simples, para referência
Um detalhe que gera 400 desnecessário: para qualquer arquivo que não seja PDF, filename com extensão é obrigatório. É por ela que o formato é determinado — não pelo conteúdo dos bytes.
O caminho pré-assinado, passo a passo
1. Pedir a URL
Guarde o uploadToken: é ele, e não o s3Key, que fecha o ciclo no passo 3.
2. Enviar o arquivo direto
Duas armadilhas neste passo, e as duas são silenciosas o bastante para custar uma tarde:
- Não mande
Authorization. A URL pré-assinada carrega a própria autorização nos parâmetros de consulta; acrescentar o cabeçalho do seu token costuma invalidar a assinatura da URL. O reflexo de "toda chamada leva o Bearer" é exatamente o que atrapalha aqui. - O
Content-Typetem de bater com o que você declarou no presign, caractere por caractere. A assinatura da URL cobre esse cabeçalho:application/pdfno presign eapplication/octet-streamno PUT são recusados.
3. Confirmar
O confirm não é burocracia. O PUT foi direto ao armazenamento, então a API ainda não sabe que o arquivo existe. É esta chamada que faz a API ler o objeto, validar o documento, calcular o hash e mover a transação para DOCUMENT_UPLOADED. Pular o passo 3 deixa o arquivo lá e a transação sem documento — um estado que confunde justamente porque o upload "funcionou".
O que pode dar errado
| Sintoma | Causa provável |
|---|---|
| PUT recusado pelo armazenamento | Content-Type diferente do declarado no presign, ou cabeçalho Authorization indevido |
| PUT recusado depois de um tempo | A URL expirou. Refaça o presign — ele pode ser repetido |
confirm devolve 404 ou erro de objeto |
O PUT não chegou a acontecer, ou foi para outra URL |
confirm devolve 422 |
Documento inválido, corrompido ou protegido por senha — o detalhe vem no corpo |
| 400 no presign ou no upload inline | Arquivo não-PDF sem filename com extensão |
O 422 merece um comentário. Ele não significa "seu upload falhou" — significa "o arquivo chegou e não serve": PDF corrompido, protegido por senha, ou fora dos critérios mínimos de qualidade. É um erro do documento, não do transporte, e repetir o upload do mesmo arquivo não resolve. Os demais códigos seguem o padrão de erro da API, descrito em códigos de erro e tratamento de falhas.
Uma decisão de implementação que economiza retrabalho
A tentação natural é implementar o base64 primeiro e "depois, se precisar", o pré-assinado. Vale pensar duas vezes quando o documento vem de fora:
- Se você gera o documento — contrato montado a partir de um modelo, recibo, proposta — o tamanho é previsível e o base64 basta.
- Se o documento vem de upload do usuário, o tamanho é uma variável que você não controla. Alguém vai anexar um escaneamento em 600 dpi. Implementar os dois caminhos e escolher por tamanho custa umas poucas linhas; descobrir o limite em produção custa um incidente.
Note que os dois ramos terminam no mesmo lugar: transação em DOCUMENT_UPLOADED, com documentHash. Do ponto de vista do resto da integração, o caminho escolhido é invisível — o que significa que trocar de um para o outro depois não obriga a mexer em mais nada.
Depois do upload
Com o documento no lugar, a transação segue o fluxo normal: criar a sessão de assinatura, conduzir as etapas da política e recuperar o resultado. Se você ainda não decidiu qual ponto de entrada usar — transação avulsa, sessão de assinatura ou envelope —, o guia transação, sessão ou envelope: qual usar compara os três. E o passo anterior a tudo isto, obter e usar o token, está em por que o seu token falha.
Para o outro extremo do ciclo — baixar o documento assinado e o pacote de evidências —, veja como baixar o documento assinado e o pacote de evidências.
Perguntas Frequentes
Quando devo usar o upload pré-assinado em vez do base64?
A recomendação da própria API é a partir de 5 MB. Abaixo disso, o base64 inline é mais simples e uma chamada só. Acima, o fluxo pré-assinado evita tanto o crescimento de um terço do base64 quanto o teto do corpo da requisição. Se o tamanho dos seus documentos é imprevisível — qualquer coisa que venha de upload do usuário final — implemente o pré-assinado e use-o sempre.
Quais são as três chamadas?
POST /v1/transactions/{id}/document/presign com contentType e filename devolve uploadUrl, uploadToken, s3Key e expiresIn. Você faz um PUT do arquivo binário direto na uploadUrl. Depois, POST /v1/transactions/{id}/document/confirm com o uploadToken fecha o ciclo e devolve o documentHash.
O PUT na URL pré-assinada leva o meu token de acesso?
Não — e mandar o cabeçalho Authorization nesse PUT costuma quebrar a assinatura da URL. A URL pré-assinada já carrega a própria autorização nos parâmetros de consulta. Envie o corpo binário e o Content-Type exatamente igual ao que você declarou no presign; qualquer divergência é recusada pelo armazenamento.
Por que preciso da terceira chamada, o confirm?
Porque o PUT vai direto ao armazenamento e a API não fica sabendo dele. O confirm é o que faz a API ler o objeto, validar o documento, calcular o documentHash e mover a transação para DOCUMENT_UPLOADED. Sem ele, o arquivo está lá e a transação continua sem documento.
A validação do PDF acontece nos dois caminhos?
Sim. Um PDF inválido, corrompido ou protegido por senha retorna 422 com o detalhe do problema — no document inline e no confirm igualmente. O caminho pré-assinado não é uma porta dos fundos que aceita qualquer arquivo: ele só muda por onde os bytes trafegam.
Que formatos são aceitos?
PDF e uma lista ampla de outros: .docx, .doc, .xlsx, .xls, .pptx, .ppt, .odt, .ods, .odp, .rtf, .txt, .csv, imagens (.jpg, .png, .tiff, .bmp, .svg), .xml, .json e .html. Para qualquer arquivo que não seja PDF, o campo filename com extensão é obrigatório. A resposta informa em documentFormat se o arquivo foi tratado como pdf ou generic.
A URL pré-assinada expira?
Sim, e o prazo vem na resposta em expiresIn, em segundos. É uma janela curta, dimensionada para o upload acontecer logo em seguida — não para guardar a URL e usá-la mais tarde. Se ela expirar antes do PUT, peça outra: o presign pode ser repetido.
Envie documentos de qualquer tamanho sem esbarrar no payload
O fluxo pré-assinado é uma chamada a mais e resolve a classe inteira de falha por tamanho de corpo. Teste os dois caminhos no sandbox gratuito antes de decidir qual usar.
Criar credenciais de homologação Fale com o time comercial