PPDF123
How-to2026-09-289 хв читання

TypeScript SDK для PDF API: п’ять хвилин до першого виклику і те, що SDK залишає вам

Використовуйте @pdf123/sdk з TypeScript, щоб об’єднувати файли, додавати водяні знаки, читати відомості про файл і зчіплювати кілька інструментів в один запит. SDK ловить помилку в назві інструмента чи параметра ще до завантаження; відмови сервера містять причину й підказку, а повторні спроби, скасування та часткові збої в пакеті лишаються на вашому боці.

PDF123 · Updated 2026-09-28

Коли ви викликаєте PDF API з Node, набір засобів розробки (SDK) @pdf123/sdk перехоплює помилку в назві інструмента чи параметра ще до завантаження будь-якого файлу. Він не повторює запити, не скасовує замість вас і не віддає результати потоком. new Pdf123Client() за замовчуванням анонімно звертається до https://pdf123.xyz; ваші файли завантажуються туди й там обробляються, бо локального рушія немає.

Діаграма: один виклик по черзі проходить три перевірки: tsc під час компіляції, перевірку під час виконання перед надсиланням запиту і 400, яку сервер повертає після завантаження; повторні спроби й скасування лишаються на викликачеві, а вхідні дані понад 95 MiB сумарно автоматично завантажуються частинами

Для об’єднання досить двох PDF у теці

Потрібен Node 20.3 або новіший, або Bun. Пакет є ES-модулем: покладіть код у файл .mjs або спершу виконайте npm pkg set type=module. Покладіть a.pdf і b.pdf у поточну теку.

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

Після node merge.mjs термінал виводить шлях виводу, який ви передали, merged.pdf, а сам файл лежить у поточній теці. readFileInput і saveResult містяться в @pdf123/sdk/node. Кореневий вхід залежить лише від fetch і приймає вхідні дані як звичайні { name, data }, тож середовище без файлової системи може використовувати того самого клієнта. Результат має вигляд { data, contentType, filename, json }; data є цілим Uint8Array, а Buffer.from(result.data) дає вам Buffer.

Об’єднати PDF — лише один з інструментів.

Файли повертаються в data, звіти в json

Кожен інструмент викликається як client.run(toolName, { input, params }). Код нижче продовжує merge.mjs з попереднього розділу, де client і saveResult уже в області видимості. Де читати результат, залежить від поля returns, яке повертає getTool: воно дорівнює "file" або "json". Для першого беріть data, для другого 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"

Перший блок записує файл із водяним знаком у marked.pdf; у другому info.json — це звіт. Запам’ятовувати назви полів, типові значення чи допустимі варіанти не треба: getTool("watermark")?.fields — це і є каталог, а pdfx describe watermark у командному рядку читає той самий. TOOLS містить усі 95 інструментів. Повний довідник — у посібнику з SDK на сторінці для розробників.

Щоб об’єднати, потім додати водяний знак, потім стиснути, можна скористатися pipeline або трьома викликами run поспіль; що саме, залежить від того, чи потрібні вам проміжні файли. І це знову продовжує код із client вище. pipeline згортає три кроки в один запит, проміжні результати лишаються на сервері, а викликач отримує тільки останній крок, який код нижче записує в out.pdf. Якщо вам потрібен файл після кожного кроку, викликайте їх окремо.

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

Конвеєр має щонайбільше 8 кроків. Дев’ятий крок отримує HTTP 400: at most 8 pipeline steps allowed.

Анонімно, з ключем або з вашим власним сервером

Якщо ви задали PDFX_API_BASE, а запити все одно йдуть на https://pdf123.xyz, то причина в тому, що new Pdf123Client() не читає змінні середовища; він дивиться лише на опції конструктора.

Для анонімних викликів користуйтеся конструктором як є. З ключем пишіть new Pdf123Client({ apiKey: process.env.PDFX_API_KEY }); заголовок запиту — X-API-KEY.

Щоб указати на власний сервер, скористайтеся clientFromEnv() з @pdf123/sdk/node. Він читає PDFX_API_BASE і PDFX_API_KEY. Можна також написати new Pdf123Client({ baseUrl: "http://localhost:8080" }) напряму. Якщо адреса недоступна, виклик завершується помилкою; режиму офлайн немає. Що ви отримуєте і чого це коштує, коли файли лишаються у вашій мережі, описано в матеріалі Що насправді дає власний хостинг (і чого це коштує).

Неправильний параметр ловиться до завантаження

Без цього файл спершу завантажився б, а помилка в назві поля проявилася б лише як 400 від сервера. Типи параметрів для кожного інструмента генеруються з каталогу, тож три поширені огріхи падають уже на етапі tsc:

Що ви пишете Помилка компілятора (уривок)
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, наприкінці Did you mean to write 'watermarkText'?
params: { angle: 45 } (rotate приймає лише 90, 180, 270) Type '45' is not assignable to type …, далі допустимі значення

Правильні виклики, як-от { angle: 90 } чи { watermarkText: "DRAFT", fontSize: 30 }, компілюються. Числові поля приймають і числа, і рядки, тож fontSize: 30 та fontSize: "30" рівнозначні. Проєкт без TypeScript отримує ті самі помилки під час виконання, і запит не надсилається: у третьому випадку це Field "angle" of "rotate" must be one of: 90, 180, 270, а помилка в назві інструмента дає unknown_tool зі схожими назвами в повідомленні.

Коли сервер відмовляє, розгалужуйтеся за reason

Коли сервер відхиляє запит, SDK кидає Pdf123Error з полями status, code, reason і problem.hint. Один code може мати кілька значень reason, тож перевіряйте спершу reason, а тоді code. Три поширені збої, відтворені локально на тих самих вхідних даних, виглядають так:

Вхідні дані status code reason hint (оригінал англійською)
Зашифрований PDF, пароль не вказано 400 bad_request password_required Provide the document password, or unlock the PDF first
Неправильний пароль 400 bad_request wrong_password Check the password and try again
Не PDF або пошкоджений файл 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" });
  }
}

Коли пароль вам відомий, передайте password для одного файлу; розблокування й цільовий інструмент виконуються в одному запиті. Для пошкодженого файлу спершу спробуйте Полагодити PDF.

«Немає результату» треба розрізняти. Коли PDF у CSV не знаходить у PDF жодної таблиці, сервер повертає 204, а SDK кидає code: "no_content"; причину описано в матеріалі Порожній експорт таблиці (204): у вашому PDF, найімовірніше, немає колонок. Умовні інструменти-фільтри вважають хибну умову нормальним результатом: вони не кидають помилку, а повертають matched: false з порожнім data.

Повторні спроби, пам’ять і скасування — справа викликача

Мережеві помилки й відповіді 5xx кидаються як є; SDK ніколи не повторює запит самостійно. Для запиту, що змінює стан, передайте власний idempotencyKey: повторне надсилання з тим самим ключем повертає перший результат замість нової обробки, а механізм і обмеження описано в матеріалі Idempotency-Key: безпечні повторні спроби для завдань PDF. Результати читаються в пам’ять повністю, без потокової передачі. Коли великі і вхідні, і вихідні дані, враховуйте пам’ять, яку вони займають одночасно.

Скасування й тайм-аут — це два різні значення code, і один виклик повідомляє лише про те, що настало раніше. Наведений нижче приклад перевіряє їх у двох окремих викликах, щоб обидві гілки справді могли виконатися:

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") { /* ви скасували запит */ }
}

try {
  await client.run("compress", { input, timeoutMs: 60_000 });
} catch (error) {
  if (error instanceof Pdf123Error && error.code === "timeout") { /* перевищено timeoutMs; цього разу без signal */ }
}

Щойно спрацьовує сигнал переривання (AbortSignal), запит відхиляється з code: "cancelled"; перевищення тайм-ауту дає code: "timeout". Кожен запит має типовий тайм-аут 5 хвилин, який можна змінити під час створення клієнта або перевизначити для окремого виклику через timeoutMs. У завантаженні частинами кожен запит має цей тайм-аут окремо. Скасування лише закриває це з’єднання; гарантії, що сервер припинить обробку, немає.

Один файл збоїть, решта триває, а колбеки приходять у порядку завершення

Для пакета вхідних даних до однофайлового інструмента скористайтеся runBatch. Якщо один файл завершується помилкою, інші виконуються далі. onResult викликається, щойно файл завершено, тобто в порядку завершення, а index — це позиція файлу у вхідному масиві; повернений масив завжди йде у вхідному порядку. Записуйте байти на диск усередині цього колбека: retainData: false спорожнює data, щойно колбек повернеться, тож результат, який ви тут не передали в saveResult, пропаде. Якщо виконання перервано, уже записані файли лишаються.

Припустімо, що в теці три справні PDF, a.pdf, b.pdf і зашифрований locked.pdf (пароль secret), а також зіпсований файл broken.pdf, у якому лише кілька надрукованих символів:

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

За замовчуванням одночасно обробляються два файли; змінити це можна через concurrency. fileSource(path) читає файл з диска лише тоді, коли доходить його черга, тож великий пакет великих файлів ніколи не опиняється в пам’яті весь одразу. passwordFor дозволяє пакету зі змішаними заблокованими й незаблокованими файлами пройти за один раз. Під час локального запуску з чотирма файлами вище колбек отримав:

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

Порядок завершення може відрізнятися в кожному запуску; це лише те, як виглядав один запуск. retainData: false робить data у поверненому масиві порожнім; успішні файли вже були записані в out/ викликом saveResult вище. Коли ви передаєте idempotencyKey у runBatch, ключ, що фактично надсилається для кожного файлу, має вигляд <key>:<index>.

Завантаження частинами починається лише понад 95 MiB сумарно

Тіло прямого запиту обмежене 100 MiB, а більше відхиляється; подробиці в матеріалі Коли великий PDF не завантажується: ліміт тіла запиту 100 MiB і помилка, що веде не туди. Коли всі файли в одному запиті разом перевищують 95 MiB, SDK переходить на завантаження частинами, а ваш виклик client.run не змінюється. Типовий максимум сервера для одного завантаження — 500 MiB; у разі власного хостингу його змінюють через PDFX_UPLOAD_MAX_BYTES.

Ту саму операцію в curl, MCP і командному рядку порівняно в матеріалі Та сама операція, чотири клієнти: браузер, curl, MCP, pdfx: там чотири виклики стоять поруч, а цей матеріал розгортає лише SDK. Сторінка пакета на npm — @pdf123/sdk.

Open tool
Process in the browser — no watermark, files removed after the job.
Open tool
Read next
Нумерація сторінок PDF: положення, початковий номер і пастка повернутих сторінок
Як пронумерувати сторінки наявного PDF онлайн: оберіть кут, задайте початковий номер, наприклад 101, і уникніть номерів набік, що з’являються на сторінках, збережених як повернуті.
Конвертація PDF у JPG або PNG: що змінює DPI і як експортувати одну сторінку
«PDF у зображення» рендерить кожну сторінку в ZIP, у PNG, якщо не вибрати JPEG. У формі в браузері є лише формат і DPI; поля API для вибору сторінок і WebP ігноруються. Виміряні розміри файлів при 150 і 300 DPI та як експортувати одну сторінку.
Фото з телефона в PDF: чому файл виходить величезним, а сторінка лягає набік
«Зображення в PDF» кладе кожне фото на окрему сторінку в повному піксельному розмірі, тож три JPEG по 3,2 МБ з телефона стали PDF на 41,5 МБ, а портретне фото потрапило на альбомну сторінку. Зменшіть фото перед конвертацією, а сторінку набік поверніть після неї.