How-to2026-09-2810 min de leitura

SDK TypeScript para a API de PDF: cinco minutos até a primeira chamada e o que ele deixa por sua conta

Use o @pdf123/sdk em TypeScript para juntar arquivos, adicionar marcas d'água, ler informações do arquivo e encadear várias ferramentas em uma única requisição. Ele pega um nome de ferramenta ou parâmetro errado antes de qualquer envio; as recusas do servidor trazem motivo e dica, enquanto novas tentativas, cancelamento e falhas parciais em lote ficam com você.

PDF123 · Updated 2026-09-28

Quando você chama a API de PDF a partir do Node, o kit de desenvolvimento (SDK) @pdf123/sdk pega um nome de ferramenta ou de parâmetro escrito errado antes de qualquer arquivo ser enviado. Ele não refaz tentativas, não cancela por você e não entrega resultados em streaming. new Pdf123Client() aponta por padrão, de forma anônima, para https://pdf123.xyz; seus arquivos são enviados para lá e processados lá, pois não há motor local.

Diagrama: uma chamada passa por três portões em sequência, a checagem do tsc na compilação, a validação em tempo de execução antes de a requisição ser enviada e o 400 que o servidor devolve depois do envio; novas tentativas e cancelamento ficam com quem chama, e entradas que somam mais de 95 MiB são enviadas em partes automaticamente

Dois PDFs numa pasta bastam para juntar

Você precisa do Node 20.3 ou mais novo, ou do Bun. O pacote é um módulo ES: coloque o código num arquivo .mjs ou rode antes npm pkg set type=module. Deixe a.pdf e b.pdf no diretório atual.

npm install @pdf123/sdk
// merge.mjs
import { Pdf123Client } from "@pdf123/sdk";
import { readFileInput, saveResult } from "@pdf123/sdk/node";

const client = new Pdf123Client();
const merged = await client.run("merge", {
  input: [await readFileInput("a.pdf"), await readFileInput("b.pdf")],
});
console.log(await saveResult(merged, { output: "merged.pdf" }));

Depois de node merge.mjs, o terminal imprime o caminho de saída que você passou, merged.pdf, e o arquivo está no diretório atual. readFileInput e saveResult ficam em @pdf123/sdk/node. A entrada raiz depende apenas de fetch e aceita entradas como simples { name, data }, então um ambiente sem sistema de arquivos pode usar o mesmo cliente. Um resultado é { data, contentType, filename, json }; data é um Uint8Array inteiro, e Buffer.from(result.data) devolve um Buffer.

Juntar PDF é apenas uma das ferramentas.

Arquivos voltam em data, relatórios em json

Toda ferramenta é client.run(toolName, { input, params }). O código abaixo continua o merge.mjs da seção anterior, com client e saveResult já no escopo. Onde ler o resultado depende do campo returns de getTool, que é "file" ou "json": use data no primeiro caso e json no segundo.

import { getTool } from "@pdf123/sdk";
import { readFileInput, saveResult } from "@pdf123/sdk/node";

// client vem do merge.mjs da seção anterior
const marked = await client.run("watermark", {
  input: await readFileInput("a.pdf"),
  params: { watermarkText: "DRAFT", fontSize: 40 },
});
console.log(await saveResult(marked, { output: "marked.pdf" }));

const info = await client.run("get-info", { input: await readFileInput("a.pdf") });
console.log(info.json.FileSize, info.json.Encrypted);
console.log(getTool("get-info")?.returns); // "json"

O primeiro bloco grava o arquivo com marca d'água em marked.pdf; no segundo, info.json é o relatório. Você não precisa decorar nomes de campos, valores padrão nem valores permitidos: getTool("watermark")?.fields é esse catálogo, e pdfx describe watermark na linha de comando lê o mesmo. TOOLS reúne as 95 ferramentas. A referência completa está no guia do SDK na página para desenvolvedores.

Para juntar, depois aplicar marca d'água e depois comprimir, você pode usar pipeline ou três chamadas run seguidas; qual escolher depende de você querer ou não os arquivos intermediários. Também aqui o código continua a partir do client acima. pipeline reúne os três passos numa única requisição, os resultados intermediários ficam no servidor e quem chama recebe só o último passo, que o código abaixo grava em out.pdf. Se você quer o arquivo de cada etapa, chame-as separadamente.

const result = await client.pipeline(
  [{ tool: "merge" }, { tool: "watermark", params: { watermarkText: "DRAFT" } }, { tool: "compress" }],
  [await readFileInput("a.pdf"), await readFileInput("b.pdf")],
);
console.log(await saveResult(result, { output: "out.pdf" }));

Um pipeline tem no máximo 8 passos. Um 9º passo recebe HTTP 400: at most 8 pipeline steps allowed.

Anônimo, com chave ou apontando para o seu próprio servidor

Se você definiu PDFX_API_BASE e mesmo assim a chamada vai para https://pdf123.xyz, é porque new Pdf123Client() não lê variáveis de ambiente; ele olha apenas as opções do construtor.

Para chamadas anônimas, use o construtor como está. Com chave, escreva new Pdf123Client({ apiKey: process.env.PDFX_API_KEY }); o cabeçalho da requisição é X-API-KEY.

Para apontar para o seu próprio servidor, use clientFromEnv() de @pdf123/sdk/node. Ele lê PDFX_API_BASE e PDFX_API_KEY. Também dá para escrever direto new Pdf123Client({ baseUrl: "http://localhost:8080" }). Se o endereço estiver inacessível, a chamada falha; não existe modo offline. O que você ganha, e quanto custa, quando os arquivos ficam na sua própria rede está em O que o self-host realmente traz (e quanto custa).

Um parâmetro errado é pego antes do envio

Sem isso, o arquivo subiria primeiro e um nome de campo escrito errado só apareceria como um 400 do servidor. Os tipos de parâmetros de cada ferramenta são gerados a partir do catálogo, então três deslizes comuns falham já na etapa do tsc:

O que você escreve Erro do compilador (trecho)
client.run("rotat", { input }) Argument of type '"rotat"' is not assignable to parameter of type 'ToolId'
params: { watermarkTxt: "DRAFT" } Object literal may only specify known properties, but 'watermarkTxt' does not exist, terminando com Did you mean to write 'watermarkText'?
params: { angle: 45 } (rotate aceita apenas 90, 180, 270) Type '45' is not assignable to type …, seguido dos valores permitidos

Chamadas corretas como { angle: 90 } ou { watermarkText: "DRAFT", fontSize: 30 } compilam. Campos numéricos aceitam números e strings, então fontSize: 30 e fontSize: "30" são equivalentes. Um projeto que não usa TypeScript recebe os mesmos erros em tempo de execução, e a requisição nunca é enviada: o terceiro caso dá Field "angle" of "rotate" must be one of: 90, 180, 270, e um nome de ferramenta errado dá unknown_tool, com nomes parecidos listados na mensagem.

Quando o servidor recusa, ramifique por reason

Quando o servidor recusa uma requisição, o SDK lança Pdf123Error com status, code, reason e problem.hint. Um mesmo code pode ter vários valores de reason, então verifique reason primeiro e recorra a code se necessário. Três falhas comuns, executadas localmente com a mesma entrada, ficam assim:

Entrada status code reason hint (o original está em inglês)
PDF criptografado, sem senha informada 400 bad_request password_required Provide the document password, or unlock the PDF first
Senha errada 400 bad_request wrong_password Check the password and try again
Não é um PDF, ou arquivo danificado 400 invalid_document invalid_pdf Upload a valid, undamaged PDF; you can try repairing it first
import { Pdf123Client, Pdf123Error } from "@pdf123/sdk";
import { readFileInput } from "@pdf123/sdk/node";

const client = new Pdf123Client();
const input = await readFileInput("locked.pdf");
try {
  await client.run("compress", { input });
} catch (error) {
  if (!(error instanceof Pdf123Error)) throw error;
  console.error(error.status, error.code, error.reason, error.problem?.hint);
  if (error.reason === "password_required") {
    await client.run("compress", { input, password: "secret" });
  }
}

Quando você sabe a senha, passe password para um único arquivo; o desbloqueio e a ferramenta de destino acontecem na mesma requisição. Para um arquivo danificado, tente antes Reparar PDF.

É preciso distinguir o "sem resultado". Quando PDF para CSV não encontra nenhuma tabela no PDF, o servidor devolve 204 e o SDK lança code: "no_content"; o motivo está em Exportação de tabela vazia (204): seu PDF provavelmente não tem colunas. As ferramentas de filtro condicional tratam uma condição falsa como resultado normal: não lançam erro e devolvem matched: false com data vazio.

Novas tentativas, memória e cancelamento são com quem chama

Erros de rede e respostas 5xx são lançados como chegam; o SDK nunca refaz a tentativa por conta própria. Para uma requisição que altera estado, passe o seu próprio idempotencyKey: reenviar com a mesma chave devolve o primeiro resultado em vez de processar de novo, e o mecanismo e os limites estão em Idempotency-Key: novas tentativas seguras em tarefas de PDF. Os resultados são lidos inteiros na memória, sem streaming. Quando entrada e saída são grandes, conte a memória que ocupam ao mesmo tempo.

Cancelamento e timeout são dois valores de code diferentes, e uma chamada informa apenas o que acontecer primeiro. O exemplo abaixo os testa em duas chamadas separadas, para que os dois ramos possam realmente rodar:

import { Pdf123Client, Pdf123Error } from "@pdf123/sdk";
import { readFileInput } from "@pdf123/sdk/node";

const client = new Pdf123Client();
const input = await readFileInput("a.pdf");

const controller = new AbortController();
setTimeout(() => controller.abort(), 10_000);
try {
  await client.run("compress", { input, signal: controller.signal });
} catch (error) {
  if (error instanceof Pdf123Error && error.code === "cancelled") { /* você cancelou */ }
}

try {
  await client.run("compress", { input, timeoutMs: 60_000 });
} catch (error) {
  if (error instanceof Pdf123Error && error.code === "timeout") { /* passou de timeoutMs; desta vez sem signal */ }
}

Assim que o sinal de aborto (AbortSignal) dispara, a requisição é rejeitada com code: "cancelled"; estourar o tempo limite dá code: "timeout". Cada requisição tem um timeout padrão de 5 minutos, que você pode mudar ao criar o cliente ou sobrescrever por chamada com timeoutMs. Num envio em partes, cada requisição carrega esse timeout por conta própria. Cancelar apenas fecha esta conexão; não há garantia de que o servidor pare de processar.

Um arquivo falha, os outros continuam, e os callbacks chegam na ordem de conclusão

Para um lote de entradas numa ferramenta de arquivo único, use runBatch. Se um arquivo falha, os outros seguem. onResult é chamado no instante em que cada arquivo termina, então roda na ordem de conclusão, e index é a posição do arquivo no array de entrada; o array devolvido está sempre na ordem de entrada. Grave os bytes em disco dentro desse callback: retainData: false esvazia data quando o callback retorna, então um resultado que você não passar para saveResult aqui se perde. Se a execução for interrompida, os arquivos já gravados permanecem.

Suponha que a pasta tenha três PDFs bons, a.pdf, b.pdf e um locked.pdf criptografado (senha secret), mais um arquivo quebrado, broken.pdf, que contém apenas alguns caracteres digitados:

import { Pdf123Client } from "@pdf123/sdk";
import { fileSource, saveResult } from "@pdf123/sdk/node";

const client = new Pdf123Client();
const results = await client.runBatch("compress", [
  fileSource("a.pdf"), fileSource("broken.pdf"), fileSource("b.pdf"), fileSource("locked.pdf"),
], {
  concurrency: 2,
  passwordFor: (file) => (file.name === "locked.pdf" ? "secret" : undefined),
  onResult: async (entry, index) => {
    console.log(index, entry.input.name, entry.ok ? "ok" : entry.error.reason);
    if (entry.ok) await saveResult(entry.result, { output: "out/" });
  },
  retainData: false,
});

Por padrão, dois arquivos são processados por vez; mude isso com concurrency. fileSource(path) lê um arquivo do disco só quando chega a sua vez, então um lote grande de arquivos grandes nunca fica todo na memória ao mesmo tempo. passwordFor permite que um lote misto de arquivos protegidos e não protegidos rode de uma só vez. Executando os quatro arquivos acima localmente, o callback recebeu:

1 broken.pdf invalid_pdf
0 a.pdf ok
2 b.pdf ok
3 locked.pdf ok

A ordem de conclusão pode mudar a cada execução; esta é apenas a de uma delas. retainData: false deixa data vazio no array devolvido; os arquivos bem-sucedidos já tinham sido gravados em out/ pelo saveResult acima. Quando você passa idempotencyKey para runBatch, a chave realmente enviada para cada arquivo é <key>:<index>.

O envio em partes só começa acima de 95 MiB no total

Um corpo de requisição direto é limitado a 100 MiB, e o que passar disso é recusado; veja os detalhes em Quando um PDF grande não sobe: o limite de 100 MiB por requisição e o erro que aponta para o lado errado. Quando todos os arquivos de uma requisição somam mais de 95 MiB, o SDK passa para o envio em partes, e a sua chamada client.run não muda. O máximo padrão do servidor para um único envio é 500 MiB; ao hospedar por conta própria, ajuste-o com PDFX_UPLOAD_MAX_BYTES.

Para a mesma operação escrita em curl, MCP e linha de comando, veja A mesma operação em quatro clientes: navegador, curl, MCP e pdfx: aquele texto coloca as quatro chamadas lado a lado, e este apenas detalha o SDK. A página do pacote no npm é @pdf123/sdk.

Open tool
Process in the browser — no watermark, files removed after the job.
Open tool