API de PDF com Java ou C#: multipart e tratamento de erro
Como chamar a API de PDF com Java ou C#: MultipartFormDataContent no C#, corpo multipart no Java, leitura do erro sem perder a mensagem e a chave fora do app.
Para comprimir um PDF pela API com Java ou C#, envie um POST multipart para https://api.ajustapdf.com/pdf/compress, com o arquivo no campo file, o nível no campo level e o cabeçalho Authorization com a chave apdf_live_. Em C#, o pacote padrão já monta o multipart. Em Java, é preciso escrever o corpo ou usar uma biblioteca.
O que muda entre as duas linguagens
O contrato HTTP é o mesmo nas duas: a mesma URL, o mesmo campo file, o mesmo Authorization com Bearer e a mesma resposta. Muda a parte que monta o corpo. Em C#, a classe MultipartFormDataContent já existe no namespace System.Net.Http. Em Java, o HttpClient do JDK envia a requisição, mas não tem um montador de multipart: você escreve o limite (boundary) à mão ou usa uma biblioteca, como o OkHttp, cuja classe MultipartBody.Builder faz esse trabalho. Para ler ou gerar PDF dentro da própria aplicação Java, sem chamar um serviço, a escolha de biblioteca está em iText ou PDFBox.
A API faz uma operação por chamada, e cada compressão gasta uma operação da cota diária da conta dona da chave. A chave é criada em /conta, na seção Chaves de API, e aparece completa só na criação.
C#: MultipartFormDataContent
Em C#, a classe MultipartFormDataContent monta o formulário. Reutilize um único HttpClient na vida do aplicativo, porque criar um por chamada desperdiça conexões. Para gerar relatórios em C# no próprio sistema, em vez de chamar a API, veja QuestPDF ou iText. O exemplo usa instruções de nível superior, do .NET 6 em diante:
using System.Net.Http.Headers;
var chave = Environment.GetEnvironmentVariable("AJUSTAPDF_KEY");
using var cliente = new HttpClient { Timeout = TimeSpan.FromSeconds(120) };
cliente.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", chave);
using var form = new MultipartFormDataContent();
var arquivo = new StreamContent(File.OpenRead("contrato.pdf"));
arquivo.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(arquivo, "file", "contrato.pdf");
form.Add(new StringContent("media"), "level");
using var resposta = await cliente.PostAsync("https://api.ajustapdf.com/pdf/compress", form);
var corpo = await resposta.Content.ReadAsByteArrayAsync();
if (resposta.IsSuccessStatusCode && resposta.Content.Headers.ContentType?.MediaType == "application/pdf")
{
await File.WriteAllBytesAsync("contrato-comprimido.pdf", corpo);
var usadas = resposta.Headers.TryGetValues("x-quota-used", out var valores) ? valores.First() : "?";
Console.WriteLine($"Uso hoje: {usadas}");
}
else
{
Console.Error.WriteLine($"{(int)resposta.StatusCode} {System.Text.Encoding.UTF8.GetString(corpo)}");
}Sem o tipo application/pdf no arquivo, o StreamContent envia o conteúdo como application/octet-stream. Defina o tipo sempre, mesmo quando o nome do arquivo já diz que é PDF. O corpo é lido como bytes antes de olhar o status, para que a mensagem de um erro não se perca.
Java: o corpo montado à mão, ou com OkHttp
Com o java.net.http, cada parte do formulário começa com uma linha de limite, traz o cabeçalho Content-Disposition com o nome do campo e, no arquivo, o nome do arquivo e o Content-Type. O corpo termina com o limite seguido de dois hífens. O cabeçalho da requisição diz multipart/form-data com o mesmo limite, e o corpo vai como um array de bytes para BodyPublishers.ofByteArray. O JDK 11 ou superior basta.
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.UUID;
public class Comprimir {
public static void main(String[] args) throws Exception {
String chave = System.getenv("AJUSTAPDF_KEY");
Path arquivo = Path.of("contrato.pdf");
String limite = "----ajustapdf" + UUID.randomUUID();
ByteArrayOutputStream corpo = new ByteArrayOutputStream();
corpo.write(("--" + limite + "\r\n"
+ "Content-Disposition: form-data; name=\"level\"\r\n\r\n"
+ "media\r\n").getBytes(StandardCharsets.UTF_8));
corpo.write(("--" + limite + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"" + arquivo.getFileName() + "\"\r\n"
+ "Content-Type: application/pdf\r\n\r\n").getBytes(StandardCharsets.UTF_8));
corpo.write(Files.readAllBytes(arquivo));
corpo.write(("\r\n--" + limite + "--\r\n").getBytes(StandardCharsets.UTF_8));
HttpRequest pedido = HttpRequest.newBuilder(URI.create("https://api.ajustapdf.com/pdf/compress"))
.header("Authorization", "Bearer " + chave)
.header("Content-Type", "multipart/form-data; boundary=" + limite)
.timeout(Duration.ofSeconds(120))
.POST(HttpRequest.BodyPublishers.ofByteArray(corpo.toByteArray()))
.build();
HttpResponse<byte[]> resposta = HttpClient.newHttpClient()
.send(pedido, HttpResponse.BodyHandlers.ofByteArray());
String tipo = resposta.headers().firstValue("content-type").orElse("");
if (resposta.statusCode() == 200 && tipo.startsWith("application/pdf")) {
Files.write(Path.of("contrato-comprimido.pdf"), resposta.body());
System.out.println("Uso hoje: " + resposta.headers().firstValue("x-quota-used").orElse("?"));
} else {
System.err.println(resposta.statusCode() + " " + new String(resposta.body(), StandardCharsets.UTF_8));
}
}
}Com o OkHttp, o trabalho cabe em poucas linhas: MultipartBody.Builder() com o tipo FORM, um addFormDataPart para o arquivo com o tipo application/pdf e outro para o campo level. Qualquer que seja o caminho, o envio precisa do cabeçalho Authorization com Bearer e a chave, e a URL é a mesma. Para ler a resposta, use BodyHandlers.ofByteArray(), que devolve o corpo como um HttpResponse<byte[]>, com o status em statusCode().
Os pontos que mudam o resultado
| Ponto | Em C# | Em Java |
|---|---|---|
| Montar o multipart | MultipartFormDataContent, pronto | Escrito à mão, ou com OkHttp |
| Tipo do arquivo | Definido em Headers.ContentType | Definido no cabeçalho da parte |
| Reuso da conexão | Um HttpClient para o aplicativo | Um HttpClient reutilizado |
| Erro com mensagem | Ler o corpo antes de checar o status | Usar ofByteArray, não ofFile |
| Tempo limite | Definir Timeout no HttpClient | Definir timeout no HttpRequest |
As duas armadilhas de tratamento de erro
Em C#, a chamada EnsureSuccessStatusCode lança uma exceção quando o status não é de sucesso, e a exceção não carrega o corpo. Como o corpo do erro é o JSON com a mensagem da API, o código acaba mostrando apenas que houve falha. Leia o conteúdo primeiro, depois confira IsSuccessStatusCode.
Em Java, o handler ofFile grava a resposta inteira no arquivo antes de você olhar o status. Se a chamada falhar com um erro de cota ou de chave, o arquivo de saída fica com o JSON, e um sistema que confia na existência do arquivo passa a tratar um erro como PDF. Use ofByteArray, confira o status e só então grave.
Respostas de erro que você vai ver
A API responde com JSON quando algo impede a compressão. Os campos são error, com um código em maiúsculas, e message, com a explicação em português. Os códigos mais comuns são INVALID_API_KEY, com status 401, para chave revogada ou errada; QUOTA_EXCEEDED, com 402, para a cota diária esgotada; FILE_TOO_LARGE, também com 402, para arquivo acima do limite do plano; e TOO_MANY_REQUESTS, com 429, quando a rede de origem enviou muitas operações no dia.
Um código 504, TIMEOUT, indica que o processamento levou mais tempo que o permitido. Nesse caso, a repetição costuma funcionar, mas o arquivo grande deve ser reduzido ou dividido antes de uma nova tentativa.
A chave não pode ir para o aplicativo
Num aplicativo instalado no celular ou num programa de desktop, a chave fica exposta a quem baixar o programa e olhar os arquivos. Ela deve ficar no servidor do seu sistema, e as chamadas à API devem passar por ele; é o caso de um app que gera o PDF no aparelho, como em gerar PDF no Android com Kotlin. Se a chave vazar, revogue-a na seção Chaves de API e crie outra; a revogação é imediata para as chamadas seguintes.
Onde a API para
Cada ferramenta do site é um caminho próprio sob /pdf/, com campos próprios. A compressão é a operação deste guia. Para juntar, dividir e as demais operações, o documento OpenAPI em ajustapdf.com/openapi.json lista os campos de cada uma. Quem usa PHP ou Go encontra o mesmo contrato nos guias irmãos: o de PHP e o de Go. Para testar a chave antes de escrever código, use o curl no terminal, e para lotes grandes veja processar uma pasta de PDFs; se o envio cair no meio, o guia de upload de PDF que falha no meio ajuda, e para integrar um assistente de IA, o servidor MCP em https://api.ajustapdf.com/mcp aceita a mesma chave.
Perguntas frequentes
Como enviar um PDF com Java para a API de compressão?
Monte um corpo multipart com o campo file, o campo level com o valor media e o cabeçalho Authorization com Bearer e a chave apdf_live_. O java.net.http do JDK não monta multipart sozinho, então escreva o corpo com o limite ou use uma biblioteca como o OkHttp, que monta o formulário pronto.
Como usar MultipartFormDataContent para comprimir PDF em C#?
Crie um MultipartFormDataContent, adicione o arquivo com StreamContent e o tipo application/pdf, e o campo level como StringContent. Envie com um HttpClient reutilizado e o cabeçalho Authorization com Bearer. Leia o corpo da resposta antes de checar o status, para não perder a mensagem de erro.
Por que o EnsureSuccessStatusCode esconde a mensagem de erro da API?
Porque ele lança uma exceção e descarta o corpo da resposta, que é justamente o JSON com error e message. Leia o conteúdo com ReadAsByteArrayAsync, verifique IsSuccessStatusCode e só depois trate o resultado como PDF.
Como salvar o PDF comprimido em Java sem gravar o erro no arquivo?
Use BodyHandlers.ofByteArray, que devolve o corpo na memória. Confira o statusCode e o Content-Type, e grave em disco só quando o status for 200 e o tipo for application/pdf. Com BodyHandlers.ofFile, o JSON de erro vai parar no arquivo de saída.