API de PDF com Go: juntar PDFs com mime/multipart
Como juntar PDFs pela API em Go com mime/multipart: o campo files repetido, o escritor fechado antes do envio, a ordem do resultado e os erros de cota.
Para juntar PDFs pela API em Go, use mime/multipart: envie cada arquivo como uma parte de nome files, repetida, feche o escritor antes de enviar e mande o cabeçalho Authorization com a chave apdf_live_. A rota é um POST para https://api.ajustapdf.com/pdf/merge, e a resposta é um único PDF, na ordem das partes.
Por que a junção pede o campo files
A junção de PDFs recebe vários arquivos num único envio. A documentação OpenAPI descreve o campo files como uma lista, com a instrução de repetir o campo para cada arquivo. Um PDF só não junta: são necessários pelo menos dois arquivos no mesmo envio.
A ordem do resultado é a ordem em que as partes entram no formulário. Se a sua aplicação guarda os arquivos numa lista, percorra essa lista na mesma ordem em que monta o corpo, e não numa ordem de dicionário ou de nome de arquivo.
O programa completo
O programa abaixo recebe os caminhos dos PDFs na linha de comando, em ordem, monta o corpo com mime/multipart e grava o resultado em unido.pdf. A chave vem da variável de ambiente AJUSTAPDF_KEY. Ele compila com a biblioteca padrão, sem dependências:
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
func main() {
chave := os.Getenv("AJUSTAPDF_KEY")
arquivos := os.Args[1:]
if len(arquivos) < 2 {
fmt.Println("uso: juntar a.pdf b.pdf [c.pdf ...]")
os.Exit(1)
}
var corpo bytes.Buffer
escritor := multipart.NewWriter(&corpo)
for _, caminho := range arquivos {
f, err := os.Open(caminho)
if err != nil {
panic(err)
}
parte, err := escritor.CreateFormFile("files", filepath.Base(caminho))
if err != nil {
panic(err)
}
if _, err := io.Copy(parte, f); err != nil {
panic(err)
}
f.Close()
}
if err := escritor.Close(); err != nil {
panic(err)
}
req, err := http.NewRequest("POST", "https://api.ajustapdf.com/pdf/merge", &corpo)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", escritor.FormDataContentType())
req.Header.Set("Authorization", "Bearer "+chave)
cliente := &http.Client{Timeout: 3 * time.Minute}
resp, err := cliente.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
dados, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if resp.StatusCode == http.StatusOK && strings.HasPrefix(resp.Header.Get("Content-Type"), "application/pdf") {
if err := os.WriteFile("unido.pdf", dados, 0o644); err != nil {
panic(err)
}
fmt.Println("Uso hoje:", resp.Header.Get("X-Quota-Used"), "de", resp.Header.Get("X-Quota-Limit"))
return
}
fmt.Fprintln(os.Stderr, resp.StatusCode, string(dados))
os.Exit(1)
}Três detalhes decidem se o envio funciona. O escritor precisa ser fechado com escritor.Close() antes de enviar, ou falta o limite final do formulário e o servidor recebe um corpo incompleto. O cabeçalho Content-Type precisa vir de escritor.FormDataContentType(), que traz o limite certo. E o nome do campo é files em todas as partes, não um nome diferente por arquivo.
Ler a resposta sem tratar erro como PDF
O cliente padrão do Go não lança erro em respostas 4xx ou 5xx. Uma resposta de cota esgotada chega como resposta normal, e cabe a você conferir resp.StatusCode. Quando o status é 200, o corpo é o PDF juntado, com Content-Type application/pdf, e pode ser gravado com io.Copy num arquivo. Quando não é, o corpo é JSON, com os campos error e message.
Confira também o cabeçalho resp.Header.Get("Content-Type") antes de gravar. Ele é a segunda barreira: se um proxy no meio trocar a resposta por uma página HTML, o status pode continuar 200, mas o tipo não será application/pdf.
Defina um Timeout no cliente. Sem ele, uma conexão travada espera para sempre. A junção de arquivos grandes leva mais tempo que uma requisição comum, então o valor precisa ser folgado, mas finito.
Erros que a junção pode devolver
| Situação | Código e error | O que fazer |
|---|---|---|
| Cota diária do plano esgotada | 402, QUOTA_EXCEEDED | Espere o dia seguinte ou mude de plano |
| Um dos arquivos passa do limite por arquivo | 402, FILE_TOO_LARGE | Comprima ou divida o arquivo antes da junção |
| Mais arquivos do que o plano aceita no envio | 402, TOO_MANY_FILES | Junte em lotes menores e depois junte os resultados |
| Chave revogada ou digitada errada | 401, INVALID_API_KEY | Gere outra chave em /conta |
| Arquivo com senha de abertura ou que não é PDF | 400, PASSWORD_REQUIRED ou INVALID_PDF | Desbloqueie ou confira o arquivo antes da junção |
| Menos de dois arquivos no envio | 400, BAD_INPUT | Mande ao menos dois PDFs no campo files |
| Processamento acima do tempo permitido | 504, TIMEOUT | Tente de novo com menos arquivos |
Senha, marcadores e outros detalhes do PDF
A junção usa o qpdf, que copia as páginas e mantém anotações e campos de formulário editáveis. O que fica de fora são marcadores, camadas, rótulos e metadados do documento. Para quem arquiva, o efeito mais comum é o PDF final não trazer o título nem o autor do original. Se o seu sistema depende disso, grave esses dados à parte.
Um PDF com senha de abertura precisa ser desbloqueado antes de entrar na junção (o guia juntar PDF com senha: por que falha detalha o erro, e o de erro ao juntar PDF cobre arquivo corrompido ou grande). Para isso, use a ferramenta de desbloquear PDF com a senha que você conhece. Um PDF com senha só de permissão, que impede imprimir ou copiar, junta sem ser desbloqueado, e o resultado sai sem criptografia.
Onde a API para
A junção por API é a mesma operação do site, e cada chamada gasta uma operação da cota da conta dona da chave. Para um lote grande ou documentos sensíveis, a alternativa é rodar a junção no seu próprio servidor, com o qpdf, que faz a mesma operação sem enviar os arquivos para fora. Quem chama a API a partir de PHP ou Java encontra o mesmo contrato nos guias de PHP e de Java e C#, e o guia de curl serve para testar a chave antes do código; para PHP, vale ver antes se o PDF não nasce melhor numa biblioteca, como em dompdf, TCPDF ou mPDF. Para juntar uma pasta inteira em lote, veja processar uma pasta de PDFs: script e limites. Para um assistente de IA em vez de um programa, o servidor MCP em https://api.ajustapdf.com/mcp aceita a mesma chave.
Perguntas frequentes
Como juntar PDFs pela API com Go?
Monte um corpo multipart com mime/multipart, enviando cada PDF como uma parte de nome files, feche o escritor antes de enviar e mande o Authorization com Bearer e a chave. Faça um POST para https://api.ajustapdf.com/pdf/merge. A resposta de sucesso é um único PDF, na ordem em que as partes entraram.
Qual campo usar para enviar vários PDFs na API de junção?
O campo files, repetido uma vez por arquivo. O campo file, no singular, é para as ferramentas de um arquivo só, como a compressão. A junção exige pelo menos dois arquivos, e a ordem do PDF final segue a ordem das partes no formulário.
Por que a API de junção não aceita PDF com senha de abertura?
Porque a junção precisa ler as páginas, e um PDF com senha de abertura só se lê com a senha. Remova a senha antes, com a ferramenta de desbloquear e a senha que você conhece. Um PDF com senha só de permissão junta sem desbloquear, mas o resultado sai sem criptografia.
Por que o PDF juntado perdeu os marcadores do original?
A junção copia páginas, anotações e campos de formulário, mas deixa de fora marcadores, camadas, rótulos e metadados do documento. Se os marcadores são necessários, recrie-os no PDF final ou guarde o original junto com o resultado.