SDK TypeScript per l'API PDF: cinque minuti alla prima chiamata e cosa lascia a te
Usa @pdf123/sdk da TypeScript per unire file, aggiungere filigrane, leggere le informazioni di un file e concatenare più strumenti in una sola richiesta. Segnala un nome di strumento o un parametro sbagliato prima di caricare qualsiasi cosa; i rifiuti del server includono un motivo e un suggerimento, mentre nuovi tentativi, annullamento e fallimenti parziali in un batch restano a tuo carico.

Quando chiami l'API PDF da Node, l'SDK (software development kit) @pdf123/sdk intercetta un nome di strumento o un parametro scritto male prima che venga caricato qualsiasi file. Non ritenta da solo, non annulla al posto tuo e non restituisce i risultati in streaming. new Pdf123Client() punta in modo anonimo a https://pdf123.xyz per impostazione predefinita; i tuoi file vengono caricati e elaborati lì, perché non esiste un motore locale.
Bastano due PDF in una cartella per unirli
Servono Node 20.3 o successivo, oppure Bun. Il pacchetto è un modulo ES: metti il codice in un file .mjs, oppure esegui prima npm pkg set type=module. Metti a.pdf e b.pdf nella cartella corrente.
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" }));
Dopo node merge.mjs il terminale stampa il percorso di output che hai indicato, merged.pdf, e il file si trova nella cartella corrente. readFileInput e saveResult stanno in @pdf123/sdk/node. Il punto di ingresso principale dipende solo da fetch e accetta input come semplici { name, data }, quindi un ambiente senza file system può usare lo stesso client. Un risultato è { data, contentType, filename, json }; data è un Uint8Array intero, e Buffer.from(result.data) ti dà un Buffer.
Unisci PDF è solo uno degli strumenti.
I file tornano in data, i report in json
Ogni strumento si chiama con client.run(toolName, { input, params }). Il codice qui sotto continua merge.mjs della sezione precedente, con client e saveResult già disponibili. Dove leggere il risultato dipende dal campo returns di getTool, che vale "file" oppure "json": usa data nel primo caso, json nel secondo.
import { getTool } from "@pdf123/sdk";
import { readFileInput, saveResult } from "@pdf123/sdk/node";
// client viene da merge.mjs, nella sezione precedente
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"
Il primo blocco scrive il file con la filigrana in marked.pdf; nel secondo, info.json è il report. Non serve memorizzare nomi di campo, valori predefiniti o valori ammessi: getTool("watermark")?.fields è quel catalogo, e pdfx describe watermark da riga di comando legge lo stesso. TOOLS contiene tutti i 95 strumenti. Il riferimento completo è nella guida all'SDK nella pagina per sviluppatori.
Per unire, poi aggiungere la filigrana, poi comprimere, puoi usare pipeline oppure tre chiamate run di seguito; dipende dal fatto che ti servano o no i file intermedi. Anche qui si continua dal client di sopra. pipeline raccoglie i tre passaggi in una sola richiesta, i risultati intermedi restano sul server e chi chiama riceve solo l'ultimo passaggio, che il codice qui sotto scrive in out.pdf. Se vuoi il file di ogni passaggio, chiamali separatamente.
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" }));
Una pipeline ha al massimo 8 passaggi. Un 9º passaggio riceve HTTP 400: at most 8 pipeline steps allowed.
In modo anonimo, con una chiave o verso il tuo server
Se imposti PDFX_API_BASE e le chiamate vanno comunque a https://pdf123.xyz, è perché new Pdf123Client() non legge le variabili d'ambiente; guarda soltanto le opzioni del costruttore.
Per le chiamate anonime usa il costruttore così com'è. Con una chiave scrivi new Pdf123Client({ apiKey: process.env.PDFX_API_KEY }); l'intestazione della richiesta è X-API-KEY.
Per puntare al tuo server usa clientFromEnv() da @pdf123/sdk/node. Legge PDFX_API_BASE e PDFX_API_KEY. Puoi anche scrivere direttamente new Pdf123Client({ baseUrl: "http://localhost:8080" }). Se l'indirizzo non è raggiungibile la chiamata fallisce; non esiste una modalità offline. Cosa ottieni e quanto costa tenere i file nella tua rete è spiegato in Cosa ti dà davvero il self-hosting (e quanto costa).
Un parametro sbagliato viene intercettato prima del caricamento
Senza questo controllo il file verrebbe caricato per primo e un nome di campo scritto male emergerebbe solo come 400 dal server. I tipi dei parametri di ogni strumento sono generati dal catalogo, quindi tre sviste comuni falliscono già nella fase tsc:
| Cosa scrivi | Errore del compilatore (estratto) |
|---|---|
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, che termina con Did you mean to write 'watermarkText'? |
params: { angle: 45 } (rotate accetta solo 90, 180, 270) |
Type '45' is not assignable to type …, seguito dai valori ammessi |
Le chiamate corrette come { angle: 90 } o { watermarkText: "DRAFT", fontSize: 30 } compilano. I campi numerici accettano sia numeri sia stringhe, quindi fontSize: 30 e fontSize: "30" sono equivalenti. Un progetto che non usa TypeScript ottiene gli stessi errori a runtime e la richiesta non viene mai inviata: il terzo caso dà Field "angle" of "rotate" must be one of: 90, 180, 270, e un nome di strumento sbagliato dà unknown_tool, con i nomi simili elencati nel messaggio.
Quando il server rifiuta, decidi in base a reason
Quando il server rifiuta una richiesta, l'SDK lancia Pdf123Error con status, code, reason e problem.hint. Uno stesso code può avere più valori di reason, quindi controlla prima reason e ripiega su code. Tre errori comuni, eseguiti in locale con lo stesso input, si presentano così:
| Input | status | code | reason | hint (l'originale è in inglese) |
|---|---|---|---|---|
| PDF cifrato, nessuna password fornita | 400 | bad_request |
password_required |
Provide the document password, or unlock the PDF first |
| Password errata | 400 | bad_request |
wrong_password |
Check the password and try again |
| Non è un PDF, o file danneggiato | 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 conosci la password, passa password per un singolo file; lo sblocco e lo strumento di destinazione avvengono nella stessa richiesta. Per un file danneggiato prova prima Ripara PDF.
Bisogna distinguere i casi di «nessun risultato». Quando PDF in CSV non trova alcuna tabella nel PDF, il server risponde 204 e l'SDK lancia code: "no_content"; il motivo è spiegato in Esportazione tabella vuota (204): probabilmente il PDF non ha colonne. Gli strumenti di filtro condizionale trattano una condizione falsa come un esito normale: non lanciano nulla e restituiscono matched: false con data vuoto.
Nuovi tentativi, memoria e annullamento spettano a chi chiama
Gli errori di rete e le risposte 5xx vengono lanciati così come sono; l'SDK non ritenta mai da solo. Per una richiesta che modifica lo stato, passa un tuo idempotencyKey: rinviare con la stessa chiave restituisce il primo risultato invece di elaborare di nuovo, e il meccanismo e i limiti sono in Idempotency-Key: nuovi tentativi sicuri per le attività PDF. I risultati vengono letti in memoria per intero, senza streaming. Quando sia l'input sia l'output sono grandi, conta la memoria che occupano nello stesso momento.
Annullamento e timeout sono due valori di code diversi, e una singola chiamata ne segnala soltanto uno, quello che si verifica per primo. L'esempio qui sotto li verifica in due chiamate separate, così che entrambi i rami possano davvero essere eseguiti:
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") { /* l'hai annullata tu */ }
}
try {
await client.run("compress", { input, timeoutMs: 60_000 });
} catch (error) {
if (error instanceof Pdf123Error && error.code === "timeout") { /* superato timeoutMs; questa volta nessun signal */ }
}
Appena scatta il segnale di interruzione (AbortSignal), la richiesta viene rifiutata con code: "cancelled"; superare il timeout dà code: "timeout". Ogni richiesta ha un timeout predefinito di 5 minuti, che puoi cambiare quando crei il client o sovrascrivere per singola chiamata con timeoutMs. In un caricamento a blocchi, ogni richiesta porta con sé quel timeout per conto proprio. L'annullamento chiude soltanto questa connessione; non c'è garanzia che il server smetta di elaborare.
Un file fallisce, gli altri proseguono e i callback arrivano in ordine di completamento
Per un batch di input verso uno strumento a file singolo usa runBatch. Se un file fallisce, gli altri proseguono. onResult viene chiamato nel momento in cui ogni file termina, quindi viene eseguito in ordine di completamento, e index è la posizione del file nell'array di input; l'array restituito è sempre nell'ordine degli input. Scrivi i byte su disco dentro questo callback: retainData: false svuota data una volta che il callback ritorna, quindi un risultato che qui non salvi con saveResult è perso. Se l'esecuzione viene interrotta, i file già scritti restano.
Supponi che la cartella contenga tre PDF validi, a.pdf, b.pdf e un locked.pdf cifrato (password secret), più un file rotto broken.pdf che contiene solo qualche carattere digitato:
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,
});
Per impostazione predefinita vengono elaborati due file alla volta; cambialo con concurrency. fileSource(path) legge un file dal disco solo quando arriva il suo turno, quindi un grande batch di file grandi non sta mai tutto in memoria insieme. passwordFor permette di eseguire in un colpo solo un batch che mischia file bloccati e non bloccati. Eseguendo in locale i quattro file qui sopra, il callback ha ricevuto:
1 broken.pdf invalid_pdf
0 a.pdf ok
2 b.pdf ok
3 locked.pdf ok
L'ordine di completamento può cambiare a ogni esecuzione; questo è soltanto ciò che è successo in una. retainData: false rende vuoto data nell'array restituito; i file riusciti erano già stati scritti in out/ dal saveResult qui sopra. Quando passi idempotencyKey a runBatch, la chiave effettivamente inviata per ogni file è <key>:<index>.
Il caricamento a blocchi parte solo sopra i 95 MiB in totale
Il corpo di una richiesta diretta è limitato a 100 MiB e qualsiasi cosa più grande viene rifiutata; per i dettagli vedi Quando un PDF grande non si carica: il limite di 100 MiB per richiesta e l'errore che indica la direzione sbagliata. Quando tutti i file di una richiesta sommati superano 95 MiB, l'SDK passa al caricamento a blocchi, e la tua chiamata client.run non cambia. Il massimo predefinito del server per un singolo caricamento è 500 MiB; se ospiti tu il servizio, regolalo con PDFX_UPLOAD_MAX_BYTES.
Per la stessa operazione scritta con curl, MCP e riga di comando vedi La stessa operazione, quattro client: browser, curl, MCP, pdfx: quel post mette le quattro chiamate una accanto all'altra, e questo sviluppa soltanto l'SDK. La pagina del pacchetto su npm è @pdf123/sdk.