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

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