TypeScript-SDK für die PDF-API: in fünf Minuten zum ersten Aufruf, und was es dir überlässt
Mit @pdf123/sdk aus TypeScript PDFs zusammenführen, Wasserzeichen setzen, Dateiinfos lesen und mehrere Werkzeuge in einer Anfrage verketten. Ein falsch geschriebener Werkzeug- oder Parametername fällt auf, bevor etwas hochgeladen wird; Ablehnungen des Servers enthalten Grund und Hinweis, Wiederholungen, Abbruch und Teilfehler in einem Stapel bleiben deine Sache.

Wenn du die PDF-API aus Node aufrufst, fängt das Software Development Kit (SDK) @pdf123/sdk einen falsch geschriebenen Werkzeug- oder Parameternamen ab, bevor eine Datei hochgeladen wird. Es wiederholt nichts, bricht nicht für dich ab und streamt keine Ergebnisse. new Pdf123Client() zeigt standardmäßig anonym auf https://pdf123.xyz; deine Dateien werden dorthin hochgeladen und dort verarbeitet, denn es gibt keine lokale Engine.
Zwei PDFs in einem Ordner genügen zum Zusammenführen
Du brauchst Node 20.3 oder neuer, oder Bun. Das Paket ist ein ES-Modul: Lege den Code in eine .mjs-Datei oder führe vorher npm pkg set type=module aus. Lege a.pdf und b.pdf ins aktuelle Verzeichnis.
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" }));
Nach node merge.mjs gibt das Terminal den übergebenen Ausgabepfad merged.pdf aus, und die Datei liegt im aktuellen Verzeichnis. readFileInput und saveResult stehen in @pdf123/sdk/node. Der Haupteinstieg hängt nur von fetch ab und nimmt Eingaben als einfaches { name, data } entgegen, sodass auch eine Umgebung ohne Dateisystem denselben Client nutzen kann. Ein Ergebnis hat die Form { data, contentType, filename, json }; data ist ein vollständiges Uint8Array, und Buffer.from(result.data) liefert dir einen Buffer.
PDF zusammenführen ist nur eines der Werkzeuge.
Dateien kommen in data zurück, Berichte in json
Jedes Werkzeug wird mit client.run(toolName, { input, params }) aufgerufen. Der folgende Code setzt merge.mjs aus dem vorigen Abschnitt fort, client und saveResult sind also schon vorhanden. Wo du das Ergebnis liest, hängt vom Feld returns aus getTool ab, das entweder "file" oder "json" ist: Für das erste nimmst du data, für das zweite json.
import { getTool } from "@pdf123/sdk";
import { readFileInput, saveResult } from "@pdf123/sdk/node";
// client comes from merge.mjs in the previous section
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"
Der erste Block schreibt die Datei mit Wasserzeichen nach marked.pdf; im zweiten ist info.json der Bericht. Feldnamen, Standardwerte und erlaubte Werte musst du dir nicht merken: getTool("watermark")?.fields ist dieser Katalog, und pdfx describe watermark auf der Kommandozeile liest denselben. TOOLS enthält alle 95 Werkzeuge. Die vollständige Referenz steht im SDK-Leitfaden auf der Entwicklerseite.
Zum Zusammenführen, dann Wasserzeichen setzen, dann Komprimieren kannst du pipeline oder drei run-Aufrufe hintereinander nehmen; was passt, hängt davon ab, ob du die Zwischendateien willst. Auch das setzt das obige client fort. pipeline fasst die drei Schritte zu einer Anfrage zusammen, die Zwischenergebnisse bleiben auf dem Server, und der Aufrufer bekommt nur den letzten Schritt, den der folgende Code nach out.pdf schreibt. Wenn du die Datei jedes Schritts brauchst, rufe sie einzeln auf.
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" }));
Eine Pipeline hat höchstens 8 Schritte. Ein 9. Schritt führt zu HTTP 400: at most 8 pipeline steps allowed.
Anonym, mit Schlüssel oder auf den eigenen Server gerichtet
Wenn du PDFX_API_BASE gesetzt hast und trotzdem https://pdf123.xyz erreichst, liegt das daran, dass new Pdf123Client() keine Umgebungsvariablen liest; es beachtet nur seine Konstruktor-Optionen.
Für anonyme Aufrufe nimmst du den Konstruktor unverändert. Mit Schlüssel schreibst du new Pdf123Client({ apiKey: process.env.PDFX_API_KEY }); der Anfrage-Header heißt X-API-KEY.
Um auf einen eigenen Server zu zeigen, nutze clientFromEnv() aus @pdf123/sdk/node. Es liest PDFX_API_BASE und PDFX_API_KEY. Du kannst auch direkt new Pdf123Client({ baseUrl: "http://localhost:8080" }) schreiben. Ist die Adresse nicht erreichbar, schlägt der Aufruf fehl; einen Offline-Modus gibt es nicht. Was du gewinnst und was es kostet, wenn die Dateien im eigenen Netz bleiben, steht in Was Selbst-Hosting wirklich bringt (und was es kostet).
Ein falscher Parameter fällt vor dem Hochladen auf
Ohne das würde zuerst die Datei hochgeladen, und ein falsch geschriebener Feldname zeigte sich erst als 400 vom Server. Die Parametertypen jedes Werkzeugs werden aus dem Katalog erzeugt, sodass drei häufige Fehler schon bei tsc scheitern:
| Was du schreibst | Compilerfehler (Auszug) |
|---|---|
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, am Ende Did you mean to write 'watermarkText'? |
params: { angle: 45 } (rotate akzeptiert nur 90, 180, 270) |
Type '45' is not assignable to type …, gefolgt von den erlaubten Werten |
Korrekte Aufrufe wie { angle: 90 } oder { watermarkText: "DRAFT", fontSize: 30 } lassen sich kompilieren. Numerische Felder akzeptieren Zahlen und Zeichenketten, fontSize: 30 und fontSize: "30" sind also gleichwertig. Ein Projekt ohne TypeScript bekommt dieselben Fehler zur Laufzeit, und die Anfrage wird nie gesendet: Der dritte Fall ergibt Field "angle" of "rotate" must be one of: 90, 180, 270, und ein falsch geschriebener Werkzeugname ergibt unknown_tool, mit ähnlichen Namen in der Meldung.
Wenn der Server ablehnt, verzweige nach reason
Lehnt der Server eine Anfrage ab, wirft das SDK Pdf123Error mit status, code, reason und problem.hint. Ein code kann mehrere reason-Werte haben; prüfe also zuerst reason und falle auf code zurück. Drei häufige Fehler, lokal mit derselben Eingabe ausgeführt, sehen so aus:
| Eingabe | status | code | reason | hint (das Original ist englisch) |
|---|---|---|---|---|
| Verschlüsselte PDF, kein Passwort angegeben | 400 | bad_request |
password_required |
Provide the document password, or unlock the PDF first |
| Falsches Passwort | 400 | bad_request |
wrong_password |
Check the password and try again |
| Keine PDF oder beschädigte Datei | 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" });
}
}
Wenn du das Passwort kennst, übergib password für eine einzelne Datei; Entsperren und Zielwerkzeug laufen in derselben Anfrage. Bei einer beschädigten Datei versuche zuerst PDF reparieren.
„Kein Ergebnis“ muss man unterscheiden. Findet PDF in CSV keine Tabelle in der PDF, liefert der Server 204, und das SDK wirft code: "no_content"; den Grund erklärt Leerer Export von Tabellen (204): Deine PDF hat vermutlich keine Spalten. Bedingte Filterwerkzeuge behandeln eine falsche Bedingung als normales Ergebnis: Sie werfen nichts und geben matched: false mit leerem data zurück.
Wiederholungen, Speicher und Abbruch sind Sache des Aufrufers
Netzwerkfehler und 5xx-Antworten werden unverändert geworfen; das SDK wiederholt nie von selbst. Bei einer Anfrage, die etwas verändert, übergib einen eigenen idempotencyKey: Wird mit demselben Schlüssel erneut gesendet, kommt das erste Ergebnis zurück, statt noch einmal zu verarbeiten; Mechanismus und Grenzen stehen in Idempotency-Key: sichere Wiederholungen für PDF-Aufgaben. Ergebnisse werden vollständig in den Speicher gelesen, ohne Streaming. Sind Eingabe und Ausgabe groß, rechne den Speicher ein, den beide gleichzeitig belegen.
Abbruch und Zeitüberschreitung sind zwei verschiedene code-Werte. Ein Aufruf meldet nur den Wert, der zuerst eintritt. Das Beispiel unten testet beide in zwei getrennten Aufrufen, damit tatsächlich beide Zweige laufen können:
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") { /* du hast abgebrochen */ }
}
try {
await client.run("compress", { input, timeoutMs: 60_000 });
} catch (error) {
if (error instanceof Pdf123Error && error.code === "timeout") { /* timeoutMs überschritten; diesmal kein signal */ }
}
Sobald das Abbruchsignal (AbortSignal) ausgelöst wird, wird die Anfrage mit code: "cancelled" abgewiesen; wird das Zeitlimit überschritten, ergibt das code: "timeout". Jede Anfrage hat standardmäßig ein Zeitlimit von 5 Minuten, das du beim Erzeugen des Clients ändern oder pro Aufruf mit timeoutMs überschreiben kannst. Bei einem Upload in Teilen trägt jede Anfrage dieses Zeitlimit für sich. Ein Abbruch schließt nur diese Verbindung; es gibt keine Garantie, dass der Server die Verarbeitung beendet.
Eine Datei scheitert, die übrigen laufen weiter, und Callbacks kommen in Abschlussreihenfolge
Für einen Stapel von Eingaben an ein Einzeldatei-Werkzeug nimmst du runBatch. Scheitert eine Datei, laufen die anderen weiter. onResult wird in dem Moment aufgerufen, in dem jede Datei fertig ist, also in Abschlussreihenfolge, und index ist die Position der Datei im Eingabe-Array; das zurückgegebene Array ist immer in Eingabereihenfolge. Schreibe die Bytes innerhalb dieses Callbacks auf die Platte: retainData: false leert data, sobald der Callback zurückkehrt, ein Ergebnis, das du hier nicht mit saveResult speicherst, ist also weg. Wird der Lauf unterbrochen, bleiben die bereits geschriebenen Dateien erhalten.
Angenommen, im Ordner liegen drei gute PDFs, a.pdf, b.pdf und eine verschlüsselte locked.pdf (Passwort secret), dazu eine kaputte Datei broken.pdf, die nur ein paar getippte Zeichen enthält:
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,
});
Standardmäßig werden zwei Dateien gleichzeitig verarbeitet; das änderst du mit concurrency. fileSource(path) liest eine Datei erst von der Platte, wenn sie an der Reihe ist, sodass ein großer Stapel großer Dateien nie ganz im Speicher liegt. Mit passwordFor läuft ein Stapel aus gesperrten und ungesperrten Dateien in einem Zug. Beim lokalen Lauf mit den vier obigen Dateien erhielt der Callback:
1 broken.pdf invalid_pdf
0 a.pdf ok
2 b.pdf ok
3 locked.pdf ok
Die Abschlussreihenfolge kann bei jedem Lauf anders sein; dies ist nur, wie ein Lauf aussah. retainData: false macht data im zurückgegebenen Array leer; die erfolgreichen Dateien wurden vom saveResult oben schon nach out/ geschrieben. Übergibst du idempotencyKey an runBatch, lautet der tatsächlich gesendete Schlüssel pro Datei <key>:<index>.
Der Upload in Teilen beginnt erst ab insgesamt mehr als 95 MiB
Ein direkter Anfragekörper ist auf 100 MiB begrenzt, und alles Größere wird abgelehnt; Einzelheiten stehen in Wenn ein großes PDF beim Hochladen abgelehnt wird: das 100-MiB-Limit für den Anfragekörper und der Fehler, der in die falsche Richtung führt. Summieren sich alle Dateien einer Anfrage auf mehr als 95 MiB, wechselt das SDK zum Upload in Teilen, und dein client.run-Aufruf bleibt unverändert. Das Standardmaximum des Servers für einen einzelnen Upload beträgt 500 MiB; beim Selbst-Hosting stellst du es mit PDFX_UPLOAD_MAX_BYTES ein.
Dieselbe Operation in curl, MCP und Kommandozeile findest du in Dieselbe Operation, vier Clients: Browser, curl, MCP, pdfx: Dort stehen die vier Aufrufe nebeneinander, und dieser Beitrag vertieft nur das SDK. Die Seite des Pakets auf npm ist @pdf123/sdk.