How-to2026-09-289 min czytania

SDK TypeScript do API PDF: pięć minut do pierwszego wywołania i to, co zostaje po twojej stronie

Użyj @pdf123/sdk z TypeScriptu, żeby scalać pliki, dodawać znaki wodne, odczytywać informacje o pliku i łączyć kilka narzędzi w jedno żądanie. SDK wyłapuje błędnie wpisaną nazwę narzędzia lub parametru, zanim cokolwiek zostanie przesłane; odrzucenia z serwera niosą powód i wskazówkę, a ponawianie, anulowanie i częściowe niepowodzenia w partii zostają po twojej stronie.

PDF123 · Updated 2026-09-28

Gdy wywołujesz API PDF z Node, pakiet programistyczny (SDK) @pdf123/sdk wyłapuje błędnie wpisaną nazwę narzędzia lub parametru, zanim jakikolwiek plik zostanie przesłany. Nie ponawia prób, nie anuluje za ciebie i nie przesyła wyników strumieniowo. new Pdf123Client() domyślnie wskazuje anonimowo na https://pdf123.xyz; twoje pliki są tam przesyłane i tam przetwarzane, bo lokalnego silnika nie ma.

Diagram: jedno wywołanie przechodzi kolejno trzy bramki: kontrolę tsc podczas kompilacji, walidację w czasie działania przed wysłaniem żądania oraz błąd 400 zwracany przez serwer po przesłaniu; ponawianie i anulowanie zostają po stronie wywołującego, a dane wejściowe o łącznym rozmiarze ponad 95 MiB są przesyłane automatycznie we fragmentach

Dwa pliki PDF w folderze wystarczą, żeby je scalić

Potrzebujesz Node 20.3 lub nowszego albo Bun. Pakiet jest modułem ES: umieść kod w pliku .mjs albo najpierw uruchom npm pkg set type=module. Połóż a.pdf i b.pdf w bieżącym katalogu.

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" }));

Po node merge.mjs terminal wypisuje podaną ścieżkę wyjściową, merged.pdf, a plik leży w bieżącym katalogu. readFileInput i saveResult znajdują się w @pdf123/sdk/node. Główny punkt wejścia zależy tylko od fetch i przyjmuje dane wejściowe jako zwykłe { name, data }, więc środowisko bez systemu plików może użyć tego samego klienta. Wynik to { data, contentType, filename, json }; data jest całym Uint8Array, a Buffer.from(result.data) daje Buffer.

Połącz PDF to tylko jedno z narzędzi.

Pliki wracają w data, raporty w json

Każde narzędzie wywołuje się jako client.run(toolName, { input, params }). Kod poniżej jest ciągiem dalszym merge.mjs z poprzedniej sekcji, z client i saveResult już w zasięgu. To, skąd odczytasz wynik, zależy od pola returns z getTool, które ma wartość "file" albo "json": dla pierwszej użyj data, dla drugiej json.

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

// client pochodzi z merge.mjs z poprzedniej sekcji
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"

Pierwszy blok zapisuje plik ze znakiem wodnym do marked.pdf; w drugim info.json jest raportem. Nie musisz pamiętać nazw pól, wartości domyślnych ani dozwolonych wartości: getTool("watermark")?.fields to ten katalog, a pdfx describe watermark w wierszu poleceń czyta ten sam. TOOLS zawiera wszystkie 95 narzędzi. Pełna dokumentacja jest w przewodniku po SDK na stronie dla programistów.

Żeby scalić, potem dodać znak wodny, potem skompresować, możesz użyć pipeline albo trzech kolejnych wywołań run; wybór zależy od tego, czy chcesz mieć pliki pośrednie. Także tu kontynuujemy z powyższym client. pipeline składa trzy kroki w jedno żądanie, wyniki pośrednie zostają na serwerze, a wywołujący dostaje tylko ostatni krok, który poniższy kod zapisuje do out.pdf. Jeśli chcesz mieć plik z każdego kroku, wywołuj narzędzia osobno.

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" }));

Pipeline ma co najwyżej 8 kroków. Dziewiąty krok kończy się odpowiedzią HTTP 400: at most 8 pipeline steps allowed.

Anonimowo, z kluczem albo na własny serwer

Jeśli ustawisz PDFX_API_BASE, a żądania nadal trafiają na https://pdf123.xyz, to dlatego, że new Pdf123Client() nie czyta zmiennych środowiskowych; patrzy tylko na opcje konstruktora.

Do wywołań anonimowych użyj tego konstruktora bez zmian. Z kluczem napisz new Pdf123Client({ apiKey: process.env.PDFX_API_KEY }); nagłówek żądania to X-API-KEY.

Żeby wskazać własny serwer, użyj clientFromEnv() z @pdf123/sdk/node. Czyta PDFX_API_BASE i PDFX_API_KEY. Możesz też wprost napisać new Pdf123Client({ baseUrl: "http://localhost:8080" }). Jeśli adres jest nieosiągalny, wywołanie kończy się błędem; trybu offline nie ma. Co daje i ile kosztuje trzymanie plików we własnej sieci, opisuje Co naprawdę daje self-hosting (i ile kosztuje).

Błędny parametr zostaje wyłapany przed przesłaniem

Bez tego plik zostałby najpierw przesłany, a błędnie wpisana nazwa pola wyszłaby dopiero jako 400 z serwera. Typy parametrów każdego narzędzia są generowane z katalogu, więc trzy częste potknięcia kończą się porażką już na etapie tsc:

Co piszesz Błąd kompilatora (fragment)
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, ending with Did you mean to write 'watermarkText'?
params: { angle: 45 } (rotate przyjmuje tylko 90, 180, 270) Type '45' is not assignable to type …, followed by the allowed values

Poprawne wywołania, takie jak { angle: 90 } albo { watermarkText: "DRAFT", fontSize: 30 }, się kompilują. Pola liczbowe przyjmują zarówno liczby, jak i ciągi znaków, więc fontSize: 30 i fontSize: "30" są równoważne. Projekt, który nie używa TypeScriptu, dostaje te same błędy w czasie działania, a żądanie nigdy nie jest wysyłane: trzeci przypadek daje Field "angle" of "rotate" must be one of: 90, 180, 270, a błędnie wpisana nazwa narzędzia daje unknown_tool, z podobnymi nazwami wymienionymi w komunikacie.

Gdy serwer odrzuca, rozgałęziaj po reason

Gdy serwer odrzuca żądanie, SDK rzuca Pdf123Error z polami status, code, reason i problem.hint. Jeden code może mieć kilka wartości reason, więc sprawdzaj najpierw reason, a dopiero potem code. Trzy częste niepowodzenia, uruchomione lokalnie na tych samych danych, wyglądają tak:

Wejście status code reason hint (oryginał jest po angielsku)
Zaszyfrowany PDF, nie podano hasła 400 bad_request password_required Provide the document password, or unlock the PDF first
Błędne hasło 400 bad_request wrong_password Check the password and try again
To nie jest PDF albo plik jest uszkodzony 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" });
  }
}

Gdy znasz hasło, przekaż password dla pojedynczego pliku; odblokowanie i właściwe narzędzie odbywają się w jednym żądaniu. W przypadku uszkodzonego pliku najpierw spróbuj Napraw PDF.

Brak wyniku trzeba rozróżniać. Gdy PDF na CSV nie znajdzie w PDF żadnej tabeli, serwer zwraca 204, a SDK rzuca code: "no_content"; powód opisuje Pusty eksport tabeli (204): twój PDF prawdopodobnie nie ma kolumn. Warunkowe narzędzia filtrujące traktują fałszywy warunek jako normalny wynik: nie rzucają błędu i zwracają matched: false z pustym data.

Ponawianie, pamięć i anulowanie należą do wywołującego

Błędy sieci i odpowiedzi 5xx są rzucane bez zmian; SDK nigdy samo nie ponawia prób. Przy żądaniu zmieniającym stan przekaż własny idempotencyKey: ponowne wysłanie z tym samym kluczem zwraca pierwszy wynik zamiast przetwarzać od nowa, a mechanizm i limity opisuje Idempotency-Key: bezpieczne ponawianie zadań PDF. Wyniki są wczytywane do pamięci w całości, bez strumieniowania. Gdy duże są i dane wejściowe, i wyjściowe, policz pamięć, którą zajmują jednocześnie.

Anulowanie i przekroczenie czasu to dwie różne wartości code, a jedno wywołanie zgłasza tylko tę, która nastąpi pierwsza. Poniższy przykład testuje je w dwóch osobnych wywołaniach, żeby obie gałęzie faktycznie mogły się wykonać:

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") { /* anulowane przez ciebie */ }
}

try {
  await client.run("compress", { input, timeoutMs: 60_000 });
} catch (error) {
  if (error instanceof Pdf123Error && error.code === "timeout") { /* przekroczono timeoutMs; tym razem bez signal */ }
}

Gdy tylko zadziała sygnał przerwania (AbortSignal), żądanie jest odrzucane z code: "cancelled"; przekroczenie limitu czasu daje code: "timeout". Każde żądanie ma domyślny limit czasu 5 minut, który możesz zmienić przy tworzeniu klienta albo nadpisać dla pojedynczego wywołania przez timeoutMs. Przy przesyłaniu we fragmentach każde żądanie ma ten limit osobno. Anulowanie zamyka tylko to połączenie; nie ma gwarancji, że serwer przestanie przetwarzać.

Jeden plik zawodzi, reszta idzie dalej, a callbacki przychodzą w kolejności ukończenia

Dla partii danych wejściowych do narzędzia jednoplikowego użyj runBatch. Jeśli jeden plik zawiedzie, pozostałe są przetwarzane dalej. onResult jest wywoływane w chwili, gdy każdy plik się skończy, więc działa w kolejności ukończenia, a index to pozycja pliku w tablicy wejściowej; zwracana tablica zawsze jest w kolejności wejścia. Bajty zapisuj na dysk wewnątrz tego callbacka: retainData: false opróżnia data po powrocie z callbacka, więc wynik, którego tu nie zapiszesz przez saveResult, przepada. Jeśli przebieg zostanie przerwany, już zapisane pliki zostają.

Załóżmy, że w folderze są trzy dobre pliki PDF: a.pdf, b.pdf i zaszyfrowany locked.pdf (hasło secret), oraz zepsuty plik broken.pdf, który zawiera tylko kilka wpisanych znaków:

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,
});

Domyślnie przetwarzane są dwa pliki naraz; zmienisz to przez concurrency. fileSource(path) czyta plik z dysku dopiero wtedy, gdy przychodzi na niego kolej, więc duża partia dużych plików nigdy nie leży w pamięci naraz. passwordFor pozwala przetworzyć jednym przebiegiem partię mieszającą pliki zablokowane i niezablokowane. Po lokalnym uruchomieniu powyższych czterech plików callback dostał:

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

Kolejność ukończenia może się różnić przy każdym uruchomieniu; tak wyglądał po prostu jeden przebieg. retainData: false sprawia, że data w zwróconej tablicy jest puste; udane pliki zostały już zapisane do out/ przez powyższe saveResult. Gdy przekażesz idempotencyKey do runBatch, klucz faktycznie wysyłany dla każdego pliku to <key>:<index>.

Przesyłanie we fragmentach zaczyna się dopiero powyżej 95 MiB łącznie

Treść bezpośredniego żądania jest ograniczona do 100 MiB, a wszystko większe jest odrzucane; szczegóły w Gdy duży PDF zostaje odrzucony: limit treści żądania 100 MiB i błąd, który kieruje w złą stronę. Gdy wszystkie pliki w jednym żądaniu dają razem ponad 95 MiB, SDK przełącza się na przesyłanie we fragmentach, a twoje wywołanie client.run się nie zmienia. Domyślny maksymalny rozmiar pojedynczego przesłania na serwerze to 500 MiB; przy self-hostingu dostosujesz go przez PDFX_UPLOAD_MAX_BYTES.

Tę samą operację w curl, MCP i wierszu poleceń znajdziesz w Ta sama operacja, cztery klienty: przeglądarka, curl, MCP, pdfx: tamten wpis zestawia cztery wywołania obok siebie, a ten rozwija tylko SDK. Strona pakietu w npm to @pdf123/sdk.

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