SDK Node.js per la conversione file#
@enconvert/node-sdk è il client JavaScript e TypeScript ufficiale per l'API EnConvert. Tredici metodi di conversione tipizzati corrispondono 1:1 agli endpoint REST come POST /v1/convert/url-to-pdf, e un secondo namespace, client.v2, aggiunge la web intelligence: percepire un URL in artefatti pronti per gli agenti, scoprire gli URL di un sito, eseguire una ricerca web, distillare dati strutturati, ingerire un sito in JSONL pronto per RAG e sorvegliare le pagine alla ricerca di cambiamenti. Richiede Node.js 18+ con zero dipendenze runtime, è costruito su fetch, FormData e node:stream nativi e recupera in modo trasparente i timeout del reverse-proxy facendo polling sullo stato del job. Viene distribuito con build ESM e CJS duali e dichiarazioni TypeScript complete.
@enconvert/node-sdk · Sorgente: enconvert/node-sdk · Node: 18+
Installazione#
npm install @enconvert/node-sdk
pnpm add @enconvert/node-sdk
yarn add @enconvert/node-sdk
Guida rapida#
import { Enconvert } from "@enconvert/node-sdk";
const client = new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! });
// V1: converte un URL in PDF e lo scrive su disco in streaming.
const result = await client.convertUrlToPdf("https://example.com", {
saveTo: "page.pdf",
});
console.log(result.presignedUrl);
// V2: legge una pagina come dovrebbe farlo il tuo agente, con un punteggio di qualità allegato.
const op = await client.v2.perceive("https://example.com", {
outputs: ["markdown", "structured"],
});
console.log(op.outputs.markdown.url, op.renderQuality); // ad es. 0.93
L'SDK funziona su ogni runtime Node moderno (Node 18+, Bun, Deno tramite lo specificatore npm). È solo lato server, quindi non includere la tua chiave API privata nel bundle di un'applicazione browser.
Cosa espone il client#
Un solo client, due superfici. Entrambe si raggiungono dalla stessa istanza Enconvert e condividono un'unica chiave API.
| Superficie | Come si raggiunge | Cosa copre |
|---|---|---|
| Conversione file | client.convertUrlToPdf(...), client.convertImage(...) e così via |
Tredici metodi tipizzati per il rendering di URL, la conversione di immagini, la compressione di immagini, la conversione di documenti, più il polling dei job e dei batch su interi siti. Vedi Conversione file. |
| Web intelligence (V2) | client.v2.perceive(...), client.v2.distill(...) e così via |
Ventitré metodi su sei capacità: perceive, discover, lookup, distill, ingest, watch. Vedi Web intelligence (V2). |
Gli endpoint V2 richiedono una chiave API privata (sk_...); le chiavi pubbliche vengono rifiutate. Consulta Autenticazione per capire in cosa differiscono i due tipi di chiave, e la V1 e V2 per la superficie REST che sta dietro a client.v2.
Conversione file#
La superficie di conversione espone tredici metodi che corrispondono 1:1 all'API REST:
| Metodo | Endpoint | Restituisce |
|---|---|---|
convertUrlToPdf(url, options?) |
POST /v1/convert/url-to-pdf |
ConversionResult |
convertUrlToScreenshot(url, options?) |
POST /v1/convert/url-to-screenshot |
ConversionResult |
convertUrlToMarkdown(url, options?) |
POST /v1/convert/url-to-markdown |
ConversionResult |
convertImage(file, options) |
POST /v1/convert/{from}-to-{to} |
ConversionResult |
convertDocument(file, options?) |
POST /v1/convert/{from}-to-{to} |
ConversionResult |
compressImage(file, options?) |
POST /v1/convert/compress-image |
ConversionResult |
convertToMarkdown(file, options?) |
POST /v1/convert/anything-to-markdown |
ConversionResult |
convertToPdf(file, options?) |
POST /v1/convert/anything-to-pdf |
ConversionResult |
getJobStatus(jobId) |
GET /v1/convert/status/{jobId} |
JobStatus |
convertWebsiteToPdf(url, options?) |
POST /v1/convert/website-to-pdf |
BatchSubmission |
convertWebsiteToScreenshot(url, options?) |
POST /v1/convert/website-to-screenshot |
BatchSubmission |
getBatchStatus(batchId) |
GET /v1/convert/batch/{batchId} |
BatchStatus |
waitForBatch(batchId, options?) |
GET /v1/convert/batch/{batchId} (con polling) |
BatchStatus |
Ogni metodo restituisce una promise tipizzata. Tutti i campi delle opzioni sono facoltativi se non indicato diversamente. Gli ultimi quattro sono helper batch per interi siti: inviano e interrogano job asincroni, quindi restituiscono un BatchSubmission o un BatchStatus invece di un ConversionResult.
convertUrlToPdf#
Esegue il rendering in PDF di qualsiasi URL pubblico.
const result = await client.convertUrlToPdf("https://example.com", {
pdfOptions: { pageSize: "A4", orientation: "landscape" },
singlePage: false,
viewportWidth: 1440,
saveTo: "report.pdf",
});
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
saveTo |
string |
-- | Percorso locale su cui scrivere il PDF in streaming. Le directory superiori vengono create automaticamente. |
singlePage |
boolean |
true |
true produce una sola pagina continua. false impagina usando pdfOptions.pageSize. |
pdfOptions |
PdfOptions |
-- | Dimensione pagina, orientamento, margini, scala, scala di grigi, intestazione e piè di pagina. Vedi Opzioni PDF. |
viewportWidth |
number |
1920 |
Larghezza del viewport del browser in pixel. |
viewportHeight |
number |
1080 |
Altezza del viewport del browser in pixel. |
loadMedia |
boolean |
true |
Attende immagini e video prima della cattura. |
enableScroll |
boolean |
true |
Scorre dall'alto in basso per far scattare i lazy loader. |
outputFilename |
string |
automatico | Sovrascrive il nome file generato. .pdf viene aggiunto se manca. |
convertUrlToScreenshot#
Cattura un PNG a pagina intera di qualsiasi URL.
const result = await client.convertUrlToScreenshot("https://example.com", {
viewportWidth: 1440,
saveTo: "screenshot.png",
});
Accetta le stesse opzioni di viewport, media, scroll e nome file di convertUrlToPdf (esclusi singlePage e pdfOptions).
convertUrlToMarkdown#
Estrae Markdown GitHub-Flavored pulito da qualsiasi URL. Il convertitore rimuove navigazione, piè di pagina, pubblicità e script, mantiene il corpo principale dell'articolo e antepone un frontmatter YAML (titolo, descrizione, url, link, immagini).
const result = await client.convertUrlToMarkdown("https://example.com/article", {
saveTo: "article.md",
});
Utile per costruire pipeline RAG, importare contenuti di terze parti in un CMS o generare dati di addestramento. Se vuoi un punteggio di qualità del render insieme al Markdown, usa invece client.v2.perceive.
convertImage#
Converte tra jpeg, png, svg, heic e webp.
// Da un percorso
await client.convertImage("photo.heic", {
outputFormat: "webp",
saveTo: "photo.webp",
});
// Da byte
import { readFile } from "node:fs/promises";
const buf = await readFile("photo.heic");
await client.convertImage(
{ data: buf, filename: "photo.heic" },
{ outputFormat: "webp", saveTo: "photo.webp" },
);
// Rasterizza un SVG a una larghezza fissa
await client.convertImage("logo.svg", { outputFormat: "png", width: 512, saveTo: "logo.png" });
Il formato di input viene rilevato dall'estensione del percorso o del nome file. Il formato di output è obbligatorio.
| Opzione | Tipo | Obbligatoria | Descrizione |
|---|---|---|---|
outputFormat |
string |
Sì | Formato di destinazione: jpeg, png, svg, heic o webp (e jpeg per un input .pdf). Gli alias jpg, yml, htm e md vengono normalizzati. Le coppie non supportate sollevano un errore prima che la richiesta parta. |
saveTo |
string |
-- | Percorso locale su cui scrivere il risultato in streaming. |
outputFilename |
string |
-- | Sovrascrive il nome file generato. |
width |
number |
-- | Solo per input SVG (svg-to-png, svg-to-jpeg, svg-to-webp), da 1 a 10000. Da sola scala in modo proporzionale, ricavando l'altezza dalle proporzioni dell'SVG. |
height |
number |
-- | Solo per input SVG (svg-to-png, svg-to-jpeg, svg-to-webp), da 1 a 10000. Da sola scala in modo proporzionale, ricavando la larghezza dalle proporzioni dell'SVG. |
Imposta sia width sia height per fissare un canvas esatto, cosa che può cambiare le proporzioni. Ometti entrambe e l'output conserva la larghezza, l'altezza o il viewBox intrinseci dell'SVG. Il totale dei pixel in uscita è limitato a 25.000.000. Nessuna delle due opzioni è accettata da svg-to-heic, e l'SDK solleva un errore prima di inviare la richiesta se le passi a qualsiasi altra conversione.
convertDocument#
Converte documenti e formati dati. Il valore predefinito di outputFormat è "pdf".
// docx in pdf
await client.convertDocument("report.docx", { saveTo: "report.pdf" });
// json in yaml
await client.convertDocument("data.json", {
outputFormat: "yaml",
saveTo: "data.yaml",
});
// markdown in pdf con impostazioni di pagina personalizzate
await client.convertDocument("README.md", {
outputFormat: "pdf",
pdfOptions: { pageSize: "A4", margins: { top: 20, bottom: 20, left: 25, right: 25 } },
saveTo: "readme.pdf",
});
Input supportati: doc, docx, xls, xlsx, ppt, pptx, html, htm, odt, ods, odp, ots, pages, numbers, markdown (.md, .markdown), csv, json, xml, yaml (.yaml, .yml), toml.
EPUB non ha una coppia di conversione documenti dedicata. Passa i file .epub attraverso convertToPdf o convertToMarkdown.
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
outputFormat |
string |
"pdf" |
Formato di destinazione. |
saveTo |
string |
-- | Percorso locale su cui scrivere il risultato in streaming. |
outputFilename |
string |
-- | Sovrascrive il nome file generato. |
pdfOptions |
PdfOptions |
-- | Impostazioni di pagina. Considerate solo quando l'output è PDF. |
compressImage#
Riduce un PNG, un JPEG o un WebP senza cambiarne il formato.
// Solo passaggio lossless
const result = await client.compressImage("photo.jpg", { saveTo: "photo-small.jpg" });
// Punta a un budget di 200 KB
const capped = await client.compressImage("photo.jpg", {
targetSizeKb: 200,
saveTo: "photo-capped.jpg",
});
console.log(capped.fileSize);
Input supportati: .png, .jpg, .jpeg, .webp.
L'output conserva il formato e l'estensione dell'input, quindi non c'è un formato di destinazione da scegliere. Il primo stadio è lossless: i metadati vengono rimossi, il profilo ICC e l'orientamento EXIF vengono preservati, e il risultato non è mai più grande dell'input. Impostare targetSizeKb aggiunge un secondo stadio che riduce le dimensioni mantenendo bloccate le proporzioni finché il budget non è rispettato. Quel target è "best effort": un budget irraggiungibile restituisce il file più piccolo ottenuto invece di un errore, quindi controlla result.fileSize. APNG animate e WebP animate vengono rifiutate con 400, e il canvas decodificato è limitato a 40.000.000 di pixel.
| Opzione | Tipo | Obbligatoria | Descrizione |
|---|---|---|---|
targetSizeKb |
number |
-- | Budget di dimensione in KB, intero, minimo 1. Omettilo per eseguire solo il passaggio lossless. |
saveTo |
string |
-- | Percorso locale su cui scrivere il risultato in streaming. |
outputFilename |
string |
-- | Sovrascrive il nome file generato. L'estensione dell'input viene mantenuta. |
convertToMarkdown#
Converte in Markdown qualsiasi documento, foglio di calcolo, presentazione, ebook, file web o file di testo semplice supportato.
await client.convertToMarkdown("handbook.docx", {
saveTo: "handbook.md",
});
Input supportati (22): .csv, .doc, .docx, .epub, .htm, .html, .markdown, .md, .mdown, .mkd, .odp, .ods, .odt, .pdf, .ppt, .pptx, .rtf, .text, .txt, .xhtml, .xls, .xlsx.
L'output è un unico file .md consapevole delle intestazioni e pensato per il chunking RAG: la gerarchia di intestazioni del documento sopravvive alla conversione, quindi un chunker semantico può dividere sulle intestazioni invece che su conteggi arbitrari di caratteri. Su questo endpoint non ci sono opzioni PDF. Qualsiasi altra estensione solleva un errore prima che venga fatta una richiesta.
| Opzione | Tipo | Obbligatoria | Descrizione |
|---|---|---|---|
saveTo |
string |
-- | Percorso locale su cui scrivere il Markdown in streaming. |
outputFilename |
string |
-- | Sovrascrive il nome file generato. |
Se vuoi che anche il chunking sia fatto per te, passa gli stessi file a client.v2.ingestFiles.
convertToPdf#
Converte in PDF qualsiasi documento, immagine, ebook, file web o file di testo semplice supportato.
// docx in pdf
await client.convertToPdf("contract.docx", { saveTo: "contract.pdf" });
// html in pdf con geometria di pagina completa
await client.convertToPdf("invoice.html", {
pdfOptions: { pageSize: "A4", margins: { top: 15, bottom: 15, left: 15, right: 15 } },
saveTo: "invoice.pdf",
});
// pdf in pdf in scala di grigi (passthrough)
await client.convertToPdf("scan.pdf", {
pdfOptions: { grayscale: true },
saveTo: "scan-gray.pdf",
});
Input supportati (36): .bmp, .csv, .doc, .docx, .epub, .gif, .heic, .heif, .htm, .html, .jpeg, .jpg, .markdown, .md, .mdown, .mkd, .numbers, .odp, .ods, .odt, .ots, .pages, .pdf, .png, .ppt, .pptx, .rtf, .svg, .text, .tif, .tiff, .txt, .webp, .xhtml, .xls, .xlsx.
Un input .pdf viene accettato e restituito così com'è, quindi con pdfOptions: { grayscale: true } questo metodo funziona anche come percorso di normalizzazione PDF. Anche EPUB viene gestito qui, dato che non ha una coppia di conversione documenti dedicata. Qualsiasi altra estensione solleva un errore prima che venga fatta una richiesta.
.html, .htm, .xhtml), Markdown, testo semplice, EPUB, immagini e SVG. Gli input Office, ODF, iWork, RTF e CSV, oltre al passthrough PDF, supportano solo grayscale e restituiscono 400 se viene impostata un'opzione di geometria esplicita. grayscale è invece rispettato per ogni tipo di input.
| Opzione | Tipo | Obbligatoria | Descrizione |
|---|---|---|---|
saveTo |
string |
-- | Percorso locale su cui scrivere il PDF in streaming. |
outputFilename |
string |
-- | Sovrascrive il nome file generato. .pdf viene aggiunto se manca. |
pdfOptions |
PdfOptions |
-- | Impostazioni di pagina. Vedi l'avvertenza qui sopra su quali input rispettano la geometria. |
getJobStatus#
Interroga lo stato di un job asincrono o recuperato.
const status = await client.getJobStatus("job_abc123");
if (status.status === "success") {
console.log(status.presignedUrl);
} else if (status.status === "failed") {
console.error(status.error);
}
Restituisce { status: "processing" | "success" | "failed", presignedUrl?, objectKey?, error? }.
Helper batch per interi siti#
convertWebsiteToPdf e convertWebsiteToScreenshot individuano le pagine di un sito, le accodano tutte e raccolgono gli output in un unico ZIP. Entrambi restituiscono subito un BatchSubmission; interroga con getBatchStatus oppure bloccati con waitForBatch. Le opzioni condivise sono crawlMode ("auto", "sitemap" o "full"), includePatterns, excludePatterns, notificationEmail e callbackUrl; convertWebsiteToPdf aggiunge singlePage e pdfOptions.
const batch = await client.convertWebsiteToPdf("https://example.com", {
crawlMode: "sitemap",
excludePatterns: ["/tag/"],
});
const done = await client.waitForBatch(batch.batchId, { saveTo: "site.zip" });
console.log(done.status, done.completed, done.failed, done.zipDownloadUrl);
waitForBatch accetta intervalMs (default 5_000), timeoutMs (default 1_800_000, trenta minuti) e saveTo. Solleva APIError(504, ...) se la scadenza viene superata. Vedi la panoramica degli endpoint per la superficie REST.
Web intelligence (V2)#
Tutto ciò che sta sotto client.v2 restituisce dati di cui un agente può fidarsi, perché ogni render V2 porta con sé un punteggio renderQuality da 0.0 a 1.0. Una pagina bloccata, una sfida anti-bot, un muro di login, una pagina di errore HTTP, un soft 404 o uno shell SPA vuoto tornano con un punteggio basso più deductions e warnings con un nome, così vengono segnalati invece di essere scambiati per contenuto reale. Il contenuto viene comunque restituito; sta a te decidere cosa farne. Punteggi sotto circa 0.40 significano che il render non è riuscito in alcun senso utile.
Ventitré metodi su sei capacità:
| Metodo | Endpoint | Restituisce |
|---|---|---|
v2.perceive(url, options?) |
POST /v2/perceive |
PerceiveResult |
v2.perceiveDirect(url, options?) |
POST /v2/perceive |
PerceiveDirectResult |
v2.getPerceiveOperation(operationId) |
GET /v2/perceive/{operationId} |
PerceiveResult |
v2.downloadPerceiveArtifact(operationId, output?) |
GET /v2/perceive/{operationId} |
PerceiveDirectResult |
v2.perceiveBatch(urls, options?) |
POST /v2/perceive/batch |
PerceiveBatchResult |
v2.getPerceiveBatch(jobId) |
GET /v2/perceive/batch/{jobId} |
PerceiveBatchResult |
v2.discover(url, options?) |
POST /v2/discover |
DiscoverResult |
v2.lookup(query, options?) |
POST /v2/lookup |
LookupResult |
v2.distill(options) |
POST /v2/distill |
DistillResult |
v2.ingest(options) |
POST /v2/ingest |
IngestJob |
v2.ingestFiles(files, options?) |
POST /v2/ingest/files |
IngestJob |
v2.listIngestJobs(options?) |
GET /v2/ingest |
IngestJobList |
v2.getIngestJob(jobId) |
GET /v2/ingest/{jobId} |
IngestJob |
v2.cancelIngestJob(jobId) |
DELETE /v2/ingest/{jobId} |
IngestJob |
v2.retryIngestWebhook(jobId) |
POST /v2/ingest/{jobId}/retry-webhook |
WebhookRetryResult |
v2.getWebhookSecret() |
GET /v2/ingest/webhook-secret |
WebhookSecret |
v2.rotateWebhookSecret() |
POST /v2/ingest/webhook-secret/rotate |
WebhookSecret |
v2.createWatcher(url, options?) |
POST /v2/watch |
Watcher |
v2.listWatchers(options?) |
GET /v2/watch |
WatcherList |
v2.getWatcher(watcherId) |
GET /v2/watch/{watcherId} |
Watcher |
v2.getWatcherSnapshots(watcherId, options?) |
GET /v2/watch/{watcherId}/snapshots |
WatcherSnapshotList |
v2.updateWatcher(watcherId, updates) |
PATCH /v2/watch/{watcherId} |
Watcher |
v2.deleteWatcher(watcherId) |
DELETE /v2/watch/{watcherId} |
Watcher |
Le opzioni sono in camelCase sulla superficie dell'SDK e vengono serializzate nel formato wire snake_case dell'API; le risposte vengono rimappate in camelCase. I tuoi payload (schemi di estrazione, dati estratti, campi tracciati, voci di diff) passano invariati.
Perceive#
Esegue il rendering di un URL negli artefatti che richiedi: Markdown, HTML pulito o grezzo, uno screenshot del viewport o a pagina intera, un PDF, un elenco di link, un elenco di immagini o dati strutturati. perceive è sincrono e restituisce l'operazione completata con URL firmati per gli artefatti validi 15 minuti. Riferimento completo: Perceive.
const op = await client.v2.perceive("https://example.com", {
outputs: ["markdown", "screenshot", "structured"],
extract: ["tables", "metadata"],
onlyMainContent: true,
waitFor: "css:.article-body",
viewport: { width: 1440, height: 900 },
});
console.log(op.renderQuality); // da 0.0 a 1.0
console.log(op.deductions); // ad es. { http_error: 0.7 }
console.log(op.outputs.markdown.url); // URL firmato, 15 minuti
console.log(op.structured);
if ((op.renderQuality ?? 0) < 0.4) {
console.warn("Bad read, do not feed this to the model:", op.warnings);
}
// Rifirma gli URL degli artefatti più tardi senza rieseguire il rendering:
const again = await client.v2.getPerceiveOperation(op.operationId);
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
outputs |
PerceiveOutputName[] |
["markdown", "structured"] |
Uno o più tra markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured. |
extract |
PerceiveExtractName[] |
-- | Target euristici: tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all. |
onlyMainContent |
boolean |
true |
Rimuove navigazione, header, footer e banner dei cookie dall'output Markdown, protetto da una verifica di fedeltà. false restituisce la pagina intera intatta. |
schema |
Record<string, unknown> |
-- | Schema JSON per l'estrazione strutturata sul tier LLM. |
waitFor |
string |
-- | Selettore CSS (facoltativamente "css:...") o "js:<expr>" da attendere prima della cattura. |
waitTimeoutMs |
number |
30000 |
Da 0 a 60000. |
jsCode |
string |
-- | JavaScript eseguito dopo la navigazione. Massimo 20000 caratteri. |
viewport |
{ width?, height? } |
1920 x 1080 |
Larghezza da 320 a 3840, altezza da 240 a 2160. |
headers |
Record<string, string> |
-- | Header di richiesta aggiuntivi. |
cookies |
BrowserCookie[] |
-- | Cookie iniettati prima del rendering. Ognuno richiede name, value e o domain o url. |
auth |
{ username, password } |
-- | HTTP Basic Auth. |
cacheMode |
"enabled" \| "bypass" \| "refresh" |
"enabled" |
Cache di un'ora. bypass la salta, refresh forza un nuovo rendering. |
pdfOptions |
PdfOptions |
-- | Ha senso solo quando outputs include "pdf". Vedi Opzioni PDF. |
blockResources |
PerceiveResourceType[] |
-- | Tipi di risorsa che il browser non deve caricare, per esempio ["image", "font", "media"]. |
respectRobots |
boolean |
-- | Rispetta le regole robots del sito. |
mobile |
boolean |
-- | Esegue il rendering con un profilo mobile. |
directDownload |
boolean |
-- | Solo per perceive. Preferisci perceiveDirect, che la imposta al posto tuo. |
proxyUrl, geolocation e actionChain sono tipizzate su PerceiveOptions ma al momento vengono rifiutate lato server con 422. Sono riservate, non utilizzabili.
Download diretto. perceiveDirect salta il giro di andata e ritorno con l'URL firmato: il corpo della risposta HTTP contiene i byte dell'artefatto e i metadati viaggiano negli header. Richiede esattamente un output che produca un artefatto, e l'SDK solleva un errore in locale prima di inviare se non è così ("structured" può viaggiare insieme, ma resta inline lato server e non viene restituito).
import { writeFile } from "node:fs/promises";
const direct = await client.v2.perceiveDirect("https://example.com", { outputs: ["markdown"] });
console.log(direct.contentType, direct.renderQuality, direct.sourceStatusCode);
await writeFile(direct.filename ?? "page.md", direct.content);
// Riscarica come byte grezzi un artefatto salvato da un'operazione precedente.
// `output` si può omettere quando l'operazione ha prodotto esattamente un artefatto.
const raw = await client.v2.downloadPerceiveArtifact(op.operationId, "markdown");
downloadPerceiveArtifact restituisce 410 quando l'artefatto salvato è scaduto, e 400 (elencando gli output disponibili) se l'operazione ha prodotto più di un artefatto e hai omesso output.
Batch. perceiveBatch accetta fino a 1000 URL con un unico blocco di opzioni condivise. I batch piccoli girano inline e tornano già completati; quelli più grandi restituiscono lo stato "queued", quindi interroga getPerceiveBatch con il jobId restituito.
const batch = await client.v2.perceiveBatch(["https://example.com/a", "https://example.com/b"], {
outputs: ["markdown"],
outputMode: "zip",
});
let job = await client.v2.getPerceiveBatch(batch.jobId);
while (job.status === "queued" || job.status === "processing") {
await new Promise((r) => setTimeout(r, 3000));
job = await client.v2.getPerceiveBatch(batch.jobId);
}
console.log(job.completed, job.failed, job.zip?.url);
outputMode è "manifest" (predefinito, una voce per URL in items) oppure "zip" (tutti gli artefatti riusciti raccolti insieme una volta finito il job). L'endpoint batch rifiuta directDownload; usa invece outputMode: "zip".
Discover#
Elenca gli URL di un sito senza eseguire alcun rendering. Non entra in gioco nessun browser, quindi è veloce ed economico rispetto al percepire ogni singola pagina. Riferimento completo: Discover.
const found = await client.v2.discover("https://example.com", {
mode: "hybrid",
maxUrls: 200,
maxDepth: 3,
excludePatterns: ["/tag/", "/author/"],
sameDomainOnly: true,
});
console.log(found.total, found.truncated, found.sources); // ad es. { sitemap: 42, crawl: 30 }
for (const url of found.urls) console.log(url);
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
mode |
"sitemap" \| "crawl" \| "hybrid" |
"hybrid" |
Solo sitemap, solo crawl HTTP, o entrambi. |
maxUrls |
number |
100 |
Da 1 a 1000. truncated è true quando esistevano più URL di quanti questo limite ne permettesse. |
maxDepth |
number |
2 |
Da 1 a 5. Profondità del crawl a partire dall'URL di partenza. |
includePatterns |
string[] |
-- | Allowlist di espressioni regolari, massimo 50 voci. |
excludePatterns |
string[] |
-- | Denylist di espressioni regolari applicata dopo includePatterns, massimo 50 voci. |
sameDomainOnly |
boolean |
true |
Mantiene il crawl sul dominio di partenza. |
respectRobots |
boolean |
-- | Rispetta le regole robots del sito. robotsRespected nel risultato riporta cosa è successo. |
Lookup#
Esegue una ricerca web categorizzata e, facoltativamente, percepisce in automatico i primi risultati, così ogni hit porta con sé il proprio PerceiveResult completo inline. Riferimento completo: Lookup.
const search = await client.v2.lookup("best static site generators", {
category: "web",
numResults: 10,
country: "us",
locale: "en",
timeFilter: "month",
perceiveTop: 3,
});
for (const hit of search.results) {
console.log(hit.position, hit.title, hit.url);
if (hit.perceive) {
console.log(" quality:", hit.perceive.renderQuality);
console.log(" markdown:", hit.perceive.outputs.markdown?.url);
}
}
console.log(search.answerBox, search.knowledgeGraph);
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
category |
"web" \| "news" \| "images" \| "scholar" \| "patents" \| "maps" |
"web" |
Verticale di ricerca. |
country |
string |
-- | Codice paese gl di Google, per esempio "us" o "in". |
locale |
string |
-- | Lingua dell'interfaccia hl di Google, per esempio "en". |
timeFilter |
"hour" \| "day" \| "week" \| "month" \| "year" |
-- | Finestra temporale di recency. |
numResults |
number |
10 |
Da 1 a 100. |
page |
number |
1 |
Da 1 a 10. |
location |
string |
-- | Località in testo libero, per esempio "Austin, Texas". |
autocorrect |
boolean |
true |
Lascia che il provider corregga i refusi evidenti. |
perceiveTop |
number |
0 |
Da 0 a 10. Esegue il rendering automatico dei primi N URL dei risultati; ognuno è un render completo del browser. |
perceiveTop nel risultato riporta quanti risultati sono stati effettivamente percepiti, che possono essere meno di quanti ne avevi chiesti, e perceiveOperationIds ti dà gli id delle operazioni per rifirmare gli URL più tardi.
Distill#
Estrazione strutturata guidata da uno schema. Dagli una forma e un insieme di URL (o un sito da scoprire prima) e restituisce record conformi a quella forma. Un cssSchema facoltativo risponde a tutto ciò che può a partire dai selettori, prima che qualcosa venga escalato al tier LLM. Riferimento completo: Distill.
const extraction = await client.v2.distill({
urls: ["https://example.com/pricing"],
schema: { plans: "list of plan names with monthly prices" },
cssSchema: {
baseSelector: ".plan-card",
fields: [
{ name: "name", type: "text", selector: "h3" },
{ name: "price", type: "text", selector: ".price" },
{ name: "url", type: "attribute", selector: "a", attribute: "href" },
],
},
});
const first = extraction.results[0];
console.log(first.data);
console.log(first.extractionTier); // "css" | "llm" | "mixed" | "none"
console.log(first.fieldsFromCss, first.fieldsFromLlm, first.renderQuality);
Passa esattamente uno tra urls e discoverFrom; l'SDK solleva un errore in locale se li passi entrambi o nessuno dei due, e lo solleva anche se schema manca o non è un oggetto.
// Scopri prima un sito, poi distilla ogni pagina trovata.
await client.v2.distill({
discoverFrom: { url: "https://example.com", mode: "sitemap", maxPages: 10 },
schema: { title: "page title", summary: "one-line summary" },
});
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
urls |
string[] |
-- | URL espliciti da distillare, massimo 50. Mutuamente esclusivo con discoverFrom. |
discoverFrom |
{ url, mode?, maxPages? } |
-- | Scopre prima, poi distilla. maxPages va da 1 a 50, default 10, e limita sia la scoperta sia la distillazione. |
schema |
Record<string, unknown> |
obbligatorio | Un oggetto JSON-Schema ({ type: "object", properties: {...} }) oppure una mappa piatta { campo: descrizione }. |
cssSchema |
CssSchema |
-- | Passaggio gratuito sui selettori eseguito prima di qualsiasi escalation all'LLM. |
waitFor |
string |
-- | Selettore CSS o "js:<expr>" da attendere. |
waitTimeoutMs |
number |
30000 |
Da 0 a 60000. |
headers |
Record<string, string> |
-- | Header di richiesta aggiuntivi. |
cookies |
BrowserCookie[] |
-- | Cookie iniettati prima del rendering. |
respectRobots |
boolean |
-- | Rispetta le regole robots del sito. |
Un CssSchema ha un baseSelector (il contenitore ripetuto, un record per ogni corrispondenza), un elenco fields, un name facoltativo e un targetField facoltativo che indica la proprietà dello schema di output che i record vanno a riempire. Ogni campo è { name, type, selector?, attribute?, pattern?, default?, transform?, fields? } dove type è uno tra text, attribute, html, regex, nested, list, nested_list. attribute è obbligatorio per i campi attribute, pattern per i campi regex, e un array fields non vuoto per i tipi annidati (profondità massima 5).
Ingest#
Trasforma un sito, o una pila di documenti caricati, in JSONL suddiviso in chunk e pronto per RAG. Ingest è sempre asincrono: entrambi i punti di ingresso restituiscono un IngestJob in coda, e tu lo interroghi oppure configuri un webhook. Riferimento completo: Ingest.
// Da un sito.
const job = await client.v2.ingest({
mode: "sitemap",
url: "https://docs.example.com",
maxPages: 100,
chunk: { maxWords: 512, sentenceOverlap: 1 },
webhookUrl: "https://my.app/hooks/enconvert",
});
// Da file caricati: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD,
// e documenti office legacy o ODF.
const fileJob = await client.v2.ingestFiles(["handbook.pdf", "notes.docx"], {
chunk: { maxWords: 512, sentenceOverlap: 1 },
});
// Interroga entrambi allo stesso modo. Stati non terminali: queued, discovering, processing.
let status = await client.v2.getIngestJob(job.jobId);
while (!["completed", "failed", "canceled"].includes(status.status)) {
await new Promise((r) => setTimeout(r, 5000));
status = await client.v2.getIngestJob(job.jobId);
}
console.log(status.totalChunks, status.outputUrl, status.errorMessage);
const page = await client.v2.listIngestJobs({ limit: 20, skip: 0 });
console.log(page.jobs.length, page.hasMore);
await client.v2.cancelIngestJob(job.jobId); // idempotente
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
mode |
"urls" \| "sitemap" \| "crawl" |
"urls" |
"urls" richiede urls e rifiuta url. "sitemap" e "crawl" richiedono un url di partenza e rifiutano urls. Entrambe le regole vengono applicate in locale prima della richiesta. L'unione IngestMode include anche "files", che è ciò che ingestFiles riporta sul suo job; non passarlo qui. |
url |
string |
-- | URL di partenza per sitemap e crawl. |
urls |
string[] |
-- | URL espliciti per la modalità "urls", massimo 1000. |
maxPages |
number |
50 |
Limite di scoperta per sitemap e crawl, da 1 a 1000. |
maxDepth |
number |
2 |
Da 1 a 5. |
sameDomainOnly |
boolean |
true |
Mantiene il crawl sul dominio di partenza. |
includePatterns / excludePatterns |
string[] |
-- | Allowlist e denylist di espressioni regolari. |
respectRobots |
boolean |
-- | Rispetta le regole robots del sito. |
waitFor / waitTimeoutMs |
string / number |
-- / 30000 |
Attesa di rendering per singola pagina. |
chunk |
{ maxWords?, sentenceOverlap? } |
512 / 1 |
maxWords va da 32 a 4000, sentenceOverlap da 0 a 10. |
webhookUrl |
string |
-- | Webhook di completamento, firmato in HMAC. |
ingestFiles accetta un FileInput[], ossia stringhe di percorso, Uint8Array / Buffer oppure oggetti { data, filename, contentType? }, in qualsiasi combinazione. Prende solo chunk e webhookUrl, e solleva un errore in locale su un elenco vuoto.
Firma dei webhook. I webhook di completamento sono firmati in HMAC. Recupera il segreto (viene creato alla prima chiamata) per verificare le consegne, ruotalo quando serve e riconsegna un webhook che il tuo endpoint ha perso.
const secret = await client.v2.getWebhookSecret();
console.log(secret.signatureHeader, secret.timestampHeader);
console.log(secret.signatureScheme, secret.replayToleranceSeconds);
// La rotazione invalida immediatamente le firme fatte con il segreto precedente.
const rotated = await client.v2.rotateWebhookSecret();
// Riconsegna il webhook di un job completato.
const retry = await client.v2.retryIngestWebhook(job.jobId);
console.log(retry.delivered, retry.attempts, retry.statusCode, retry.detail);
retryIngestWebhook restituisce 409 quando il job non è completato e 400 quando il job non ha alcun webhook configurato.
Watch#
Crea un watcher che riesegue il rendering di un URL a cadenza fissa e ti avvisa quando la pagina cambia, via email, via webhook o entrambi. Riferimento completo: Watch.
const watcher = await client.v2.createWatcher("https://example.com/pricing", {
frequencyMinutes: 60,
diffMode: "auto",
webhookUrl: "https://my.app/hooks/changes",
notifyEmail: true,
});
console.log(watcher.watcherId, watcher.nextCheckAt);
const list = await client.v2.listWatchers({ limit: 20 });
const one = await client.v2.getWatcher(watcher.watcherId);
const history = await client.v2.getWatcherSnapshots(watcher.watcherId, { limit: 10 });
for (const snap of history.snapshots) {
console.log(snap.checkedAt, snap.hasChanges, snap.similarity, snap.changeCount);
}
await client.v2.updateWatcher(watcher.watcherId, { status: "paused" });
await client.v2.updateWatcher(watcher.watcherId, { webhookUrl: "" }); // azzera il webhook
await client.v2.deleteWatcher(watcher.watcherId); // soft delete, idempotente
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
frequencyMinutes |
number |
60 |
Minuti tra un controllo e l'altro, da 60 a 43200. Il limite minimo di un'ora è rigido. |
diffMode |
"auto" \| "text" \| "structured" \| "tables" \| "metadata" |
"auto" |
"auto" lascia scegliere al motore di diff in base al tipo di contenuto. |
trackFields |
Record<string, unknown> |
-- | Sottoinsieme di campi o selettori per restringere il diff. |
webhookUrl |
string |
-- | Webhook di notifica dei cambiamenti, firmato in HMAC. |
notifyEmail |
boolean |
true |
Invia un'email al proprietario del progetto quando ci sono cambiamenti. |
updateWatcher accetta gli stessi campi più status ("active" o "paused") e ne richiede almeno uno; l'SDK solleva un errore in locale su un aggiornamento vuoto. Passare webhookUrl: "" in modo esplicito azzera il webhook. L'eliminazione è una soft delete: deleteWatcher restituisce il watcher marcato come eliminato con stato "deleted", e un watcher eliminato risulta 404 da getWatcher.
Ogni snapshot porta con sé checkedAt, hasChanges, similarity (da 0.0 a 1.0 rispetto alla cattura precedente), renderQuality, changeCount e un array changes.
snapshot.changes arrivano direttamente dalla pagina sorvegliata. Applica l'escape prima di renderizzarle in HTML o di scriverle in un visualizzatore di log.
Opzioni PDF#
Si passano tramite il campo pdfOptions su convertUrlToPdf, convertDocument, convertToPdf, convertWebsiteToPdf e client.v2.perceive (quando outputs include "pdf").
const result = await client.convertUrlToPdf("https://example.com", {
pdfOptions: {
pageSize: "A4",
orientation: "landscape",
margins: { top: 10, bottom: 10, left: 15, right: 15 },
scale: 0.9,
grayscale: false,
},
saveTo: "report.pdf",
});
| Campo | Tipo | Descrizione |
|---|---|---|
pageSize |
string |
"A4", "A3", "Letter", "Legal", ecc. |
pageWidth / pageHeight |
number |
Geometria di pagina esplicita, in alternativa a pageSize. |
orientation |
"portrait" \| "landscape" |
Il valore predefinito è verticale. |
margins |
{ top, bottom, left, right } (mm) |
Tutti e quattro sono facoltativi. |
scale |
number |
Scala di rendering, ad es. 0.9 per il 90%. |
grayscale |
boolean |
Post-elabora il PDF con Ghostscript convertendolo in scala di grigi. |
header |
PdfHeaderFooter |
{ content?, height? }. content è limitato a 2000 caratteri. |
footer |
PdfHeaderFooter |
Stessa forma di header. |
Ogni parametro è descritto per esteso in Job sync e async.
Gestione degli errori#
Gli errori sono classi di eccezione tipizzate che puoi riconoscere con instanceof. La stessa gerarchia copre sia i metodi di conversione sia client.v2.
import {
Enconvert,
APIError,
AuthenticationError,
QuotaError,
RateLimitError,
} from "@enconvert/node-sdk";
try {
await client.v2.perceive("https://example.com", { outputs: ["markdown"] });
} catch (e) {
if (e instanceof AuthenticationError) {
console.error("Invalid API key. Check ENCONVERT_API_KEY.");
} else if (e instanceof QuotaError) {
console.error("Request rejected with 402.");
} else if (e instanceof RateLimitError) {
console.error("Too many requests. Back off and retry.");
} else if (e instanceof APIError) {
console.error(`API error [${e.statusCode}]: ${e.message}`);
} else {
throw e;
}
}
| Classe | Sollevata su | Codice di stato |
|---|---|---|
AuthenticationError |
Chiave non valida, mancante o revocata | 401, 403 (entrambi riportano statusCode 401) |
QuotaError |
Sollevata su HTTP 402 | 402 |
RateLimitError |
Troppe richieste | 429 |
APIError |
Qualsiasi altro 4xx / 5xx | il codice effettivo |
EnconvertError |
Classe base di tutte le precedenti | -- |
QuotaError e RateLimitError estendono entrambe APIError, che estende EnconvertError, quindi ordina i tuoi controlli instanceof dal più specifico al meno specifico. Ogni APIError porta con sé un campo statusCode.
Alcuni errori non raggiungono mai la rete: un'estensione di file non supportata, una chiamata distill con sia urls sia discoverFrom, una chiamata ingest in cui modalità e argomenti non concordano, una chiamata perceiveDirect con più di un output di artefatto, o una chiamata updateWatcher senza campi. Questi casi sollevano un semplice Error in locale, così trovi lo sbaglio in fase di sviluppo.
La mappa completa dei messaggi di errore è nel riferimento Codici di errore.
Recupero dei timeout#
Le conversioni URL-to-PDF lunghe o quelle di documenti di grandi dimensioni possono superare il limite di timeout del reverse-proxy di 60-120 secondi, anche quando la conversione alla fine riesce sul server. L'SDK gestisce la cosa in modo trasparente sui metodi di conversione V1:
- Prima di ogni richiesta, l'SDK genera un UUID e lo invia come
job_idnel corpo della richiesta. - Se la richiesta originale restituisce 5xx, l'SDK passa silenziosamente al polling di
GET /v1/convert/status/{job_id}ogni 3 secondi. - Appena il job risulta registrato come
success, l'SDK restituisce il risultato. Appena risulta registrato comefailed, l'SDK sollevaAPIError. - La scadenza del polling è di 5 minuti. Se viene superata, l'SDK solleva
APIError(504, "Conversion timed out").
Non devi scrivere codice per tutto questo, funziona e basta. Imposta timeout sul costruttore se vuoi limitare la richiesta iniziale.
V2 usa oggetti job espliciti invece del recupero implicito: perceiveBatch e ingest restituiscono un id che interroghi con getPerceiveBatch e getIngestJob, e ingest può chiamare un webhook al posto tuo.
Configurazione#
const client = new Enconvert({
apiKey: process.env.ENCONVERT_API_KEY!,
timeout: 300_000, // ms, default 5 minuti
baseUrl: "https://api.enconvert.com", // override per gateway self-hosted
});
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
apiKey |
string |
-- (obbligatoria) | Chiave API privata (sk_...). Il costruttore solleva subito un errore se manca. |
timeout |
number |
300_000 |
Timeout della richiesta in ms. Interrompe la fetch sottostante tramite AbortController. |
baseUrl |
string |
https://api.enconvert.com |
URL base dell'API. Le barre finali vengono rimosse. |
La chiave viaggia come header X-API-Key su ogni richiesta, sia V1 sia V2. client.v2 viene costruito per te e condivide chiave, URL base e timeout del client, quindi non c'è nulla di ulteriore da configurare.
Forma del risultato#
Ogni metodo di conversione restituisce un ConversionResult:
interface ConversionResult {
presignedUrl: string; // URL firmato per scaricare l'output (1 ora)
objectKey: string; // chiave dell'oggetto nello storage
filename: string; // nome file lato server
fileSize?: number; // byte
conversionTimeSeconds?: number;
jobId?: string; // presente quando il recupero timeout ha fatto polling
}
L'URL pre-firmato è valido per un'ora. Se ti serve un accesso permanente, scarica il file (usa saveTo, oppure recupera tu stesso l'URL) e archivialo nel tuo bucket.
I risultati V2 hanno una forma diversa. Un PerceiveResult porta con sé operationId, status, url, urlFinal, contentHash, renderQuality, statusCode, deductions, cacheHit, una mappa outputs indicizzata per nome di output, structured, extractionTier, tokens, costCents, durationMs, optionsEcho, error e warnings. Ogni voce in outputs è un V2OutputArtifact fatto di { url?, objectKey, sizeBytes, contentType, expiresIn }, dove expiresIn è espresso in secondi e vale 900 per impostazione predefinita. Gli URL degli artefatti V2 durano quindi 15 minuti invece di un'ora, e vengono rifirmati a ogni lettura, quindi chiamare di nuovo getPerceiveOperation(operationId) ti dà link freschi senza rieseguire il rendering della pagina.
TypeScript#
Le definizioni di tipo sono incluse nel pacchetto, quindi non serve installare alcun @types/.... Il pacchetto è pubblicato in doppia forma (ESM + CJS) con exports, types e .d.ts / .d.cts corretti, così funziona con qualsiasi modalità di risoluzione dei moduli di Node.
import type {
ClientOptions,
CompressImageOptions,
ConversionResult,
ConvertDocumentOptions,
ConvertImageOptions,
ConvertToMarkdownOptions,
ConvertToPdfOptions,
FileInput,
JobStatus,
PdfOptions,
UrlToMarkdownOptions,
UrlToPdfOptions,
UrlToScreenshotOptions,
// I tipi V2 arrivano dallo stesso entry point.
DiscoverOptions, DiscoverResult,
DistillOptions, DistillResult,
IngestJob, IngestOptions,
LookupOptions, LookupResult,
PerceiveOptions, PerceiveResult, PerceiveOutputName,
PerceiveBatchResult, PerceiveDirectResult,
Watcher, WatcherSnapshotList,
} from "@enconvert/node-sdk";
Anche la classe EnconvertV2 è esportata, se vuoi tipizzare il parametro di una funzione come namespace V2.
Aggiornamento#
Il pacchetto include una piccola CLI, enconvert-sdk, per mantenersi aggiornato.
npx enconvert-sdk upgrade
npx enconvert-sdk upgrade --dry-run
npx enconvert-sdk version
upgrade rileva npm, pnpm, yarn o bun dal package manager in uso e stampa sempre il comando di installazione esatto prima di eseguirlo, così non succede nulla al tuo lockfile senza che tu lo veda. --dry-run stampa quel comando e si ferma. version riporta la versione dell'SDK installata.
Sorgente e segnalazioni#
- npm: @enconvert/node-sdk
- GitHub: enconvert/node-sdk
- Licenza: MIT
- Altri linguaggi: Tutti gli SDK
Domande frequenti#
Come converto file in Node.js con un pacchetto npm?#
Installa @enconvert/node-sdk, crea un client con la tua chiave API (new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! })) e chiama un metodo tipizzato come convertUrlToPdf, convertImage o convertDocument. Passa saveTo per scrivere il risultato direttamente su disco in streaming.
Come estraggo una pagina web in Markdown pulito con Node.js?#
Chiama client.v2.perceive(url, { outputs: ["markdown"] }). Ottieni un URL firmato valido 15 minuti verso il Markdown in op.outputs.markdown.url, più un punteggio renderQuality per la lettura. Se vuoi i byte direttamente invece di un URL, chiama client.v2.perceiveDirect(url, { outputs: ["markdown"] }) e leggi result.content.
Cos'è renderQuality e perché è importante?#
renderQuality è un punteggio da 0.0 a 1.0 allegato a ogni render V2. Una sfida anti-bot, un muro di login, una pagina di errore HTTP, un soft 404 o uno shell SPA vuoto ottengono tutti un punteggio basso e tornano con deductions e warnings con un nome, così una lettura sbagliata viene segnalata invece di entrare in silenzio nel contesto del tuo agente come se fosse la pagina vera. Punteggi sotto circa 0.40 significano che il render è fallito nei fatti, anche se la richiesta ha restituito 200.
Come converto HEIC in WebP con Node.js?#
Chiama convertImage con il file HEIC (un percorso o un oggetto buffer { data, filename }) e outputFormat: "webp". L'SDK converte tra jpeg, png, svg, heic e webp; il formato di input viene rilevato dall'estensione del nome file.
Come comprimo un'immagine in Node.js senza cambiarne il formato?#
Chiama compressImage con un file .png, .jpg, .jpeg o .webp. L'output conserva formato ed estensione dell'input, rimuove i metadati preservando il profilo ICC e l'orientamento EXIF, e non è mai più grande dell'input. Aggiungi targetSizeKb per ridurre le dimensioni verso un budget; il target è "best effort", quindi leggi result.fileSize per vedere cosa è stato effettivamente ottenuto.
Come converto qualsiasi documento in Markdown per una pipeline RAG?#
Chiama convertToMarkdown con il file e passa saveTo per scrivere il .md direttamente su disco. Accetta 22 estensioni tra Office, OpenDocument, PDF, EPUB, HTML, CSV e testo semplice, e restituisce un unico file Markdown consapevole delle intestazioni, così il tuo chunker può dividere sulle intestazioni del documento invece che su conteggi arbitrari di caratteri.
Come trasformo un intero sito web in chunk pronti per RAG con Node.js?#
Chiama client.v2.ingest({ mode: "sitemap", url, maxPages, chunk: { maxWords: 512, sentenceOverlap: 1 } }). Ingest è sempre asincrono, quindi interroga client.v2.getIngestJob(job.jobId) finché status non è "completed" e leggi outputUrl per il JSONL firmato, oppure imposta webhookUrl e lascia che sia il webhook di completamento ad avvisarti. Per documenti locali invece che per un sito, client.v2.ingestFiles([...]) esegue la stessa pipeline.
Come estraggo JSON strutturato da una pagina con Node.js?#
Chiama client.v2.distill({ urls, schema }) dove schema è un oggetto JSON-Schema oppure una mappa piatta { campo: descrizione }. Aggiungi un cssSchema e il passaggio sui selettori risponde a tutto ciò che può prima che qualcosa venga escalato al tier LLM; result.extractionTier, fieldsFromCss e fieldsFromLlm ti dicono quale tier ha fatto il lavoro.
Come monitoro i cambiamenti di una pagina web con Node.js?#
Chiama client.v2.createWatcher(url, { frequencyMinutes: 60, diffMode: "auto", webhookUrl }). Il limite minimo di un'ora è rigido, quindi 60 è la cadenza minima. Leggi lo storico con getWatcherSnapshots, metti in pausa con updateWatcher(id, { status: "paused" }) ed elimina con deleteWatcher, che è una soft delete ed è idempotente.
Come gestisce l'SDK le conversioni lunghe che superano il timeout del reverse-proxy?#
Prima di ogni richiesta di conversione V1 l'SDK genera un UUID e lo invia come job_id; se la richiesta restituisce 5xx, interroga silenziosamente GET /v1/convert/status/{job_id} ogni 3 secondi finché il job non è success o failed. La scadenza del polling è di 5 minuti, dopodiché solleva APIError(504, "Conversion timed out"). V2 usa invece id di job espliciti, interrogati con getPerceiveBatch o getIngestJob.
Posso usare l'SDK Node.js in un'applicazione browser?#
No, l'SDK è solo lato server, perché si autentica con una chiave API privata (sk_...) che non deve mai finire nel bundle del codice lato client. Funziona su Node 18+, Bun e Deno tramite lo specificatore npm.
Per quanto tempo è valido l'URL di download pre-firmato?#
Il presignedUrl presente in ogni ConversionResult è valido per un'ora. Gli URL degli artefatti V2 sono validi 15 minuti e vengono rifirmati a ogni lettura, quindi getPerceiveOperation(operationId) ti consegna link freschi. Per un accesso permanente, scarica il file e archivialo nel tuo bucket.