Ir para o conteúdo
6 min de leitura

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

PontoEm C#Em Java
Montar o multipartMultipartFormDataContent, prontoEscrito à mão, ou com OkHttp
Tipo do arquivoDefinido em Headers.ContentTypeDefinido no cabeçalho da parte
Reuso da conexãoUm HttpClient para o aplicativoUm HttpClient reutilizado
Erro com mensagemLer o corpo antes de checar o statusUsar ofByteArray, não ofFile
Tempo limiteDefinir Timeout no HttpClientDefinir 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.