Ir para o conteúdo
5 min de leitura

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çãoCódigo e errorO que fazer
Cota diária do plano esgotada402, QUOTA_EXCEEDEDEspere o dia seguinte ou mude de plano
Um dos arquivos passa do limite por arquivo402, FILE_TOO_LARGEComprima ou divida o arquivo antes da junção
Mais arquivos do que o plano aceita no envio402, TOO_MANY_FILESJunte em lotes menores e depois junte os resultados
Chave revogada ou digitada errada401, INVALID_API_KEYGere outra chave em /conta
Arquivo com senha de abertura ou que não é PDF400, PASSWORD_REQUIRED ou INVALID_PDFDesbloqueie ou confira o arquivo antes da junção
Menos de dois arquivos no envio400, BAD_INPUTMande ao menos dois PDFs no campo files
Processamento acima do tempo permitido504, TIMEOUTTente 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.