# 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.

URL canônica: https://ajustapdf.com/blog/api-pdf-com-go

Publicado em 2026-10-20 · 5 min de leitura · por [AjustaPDF](https://ajustapdf.com/sobre#editorial)

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](/juntar-pdf) 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](/blog/nao-consigo-juntar-pdf-com-senha) detalha o erro, e o de [erro ao juntar PDF](/blog/erro-ao-juntar-pdf) cobre arquivo corrompido ou grande). Para isso, use a ferramenta de [desbloquear PDF](/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](/blog/api-pdf-com-php) e [de Java e C#](/blog/api-pdf-com-java-ou-csharp), e o [guia de curl](/blog/curl-api-pdf-terminal) 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](/blog/pdf-em-lote-pasta-inteira). 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.
