SDK de Conversión de Archivos para Node.js#

@enconvert/node-sdk es el cliente oficial de JavaScript y TypeScript para la API de EnConvert. Trece métodos de conversión tipados se corresponden 1:1 con endpoints REST como POST /v1/convert/url-to-pdf, y un segundo espacio de nombres, client.v2, añade inteligencia web: percibir una URL y convertirla en artefactos listos para agentes, descubrir las URLs de un sitio, ejecutar una búsqueda web, extraer datos estructurados, ingerir un sitio en JSONL listo para RAG y vigilar páginas en busca de cambios. Está pensado para Node.js 18+ sin dependencias en tiempo de ejecución, construido sobre fetch, FormData y node:stream nativos, y se recupera de forma transparente de los tiempos de espera del proxy inverso sondeando el estado del trabajo. Se publica con builds duales ESM y CJS con declaraciones de TypeScript completas.

npm: @enconvert/node-sdk · Fuente: enconvert/node-sdk · Node: 18+

Instalación#

npm install @enconvert/node-sdk
pnpm add @enconvert/node-sdk
yarn add @enconvert/node-sdk

Inicio rápido#

import { Enconvert } from "@enconvert/node-sdk";

const client = new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! });

// V1: convierte una URL a PDF y transmítelo al disco.
const result = await client.convertUrlToPdf("https://example.com", {
    saveTo: "page.pdf",
});
console.log(result.presignedUrl);

// V2: lee una página como debería leerla tu agente, con una puntuación de calidad adjunta.
const op = await client.v2.perceive("https://example.com", {
    outputs: ["markdown", "structured"],
});
console.log(op.outputs.markdown.url, op.renderQuality); // p. ej. 0.93

El SDK funciona en todo runtime moderno de Node (Node 18+, Bun y Deno vía el especificador de npm). Es solo del lado del servidor, así que no incluyas tu clave de API privada en una aplicación de navegador.


Qué expone el cliente#

Un cliente, dos superficies. A ambas se llega desde la misma instancia de Enconvert y comparten una única clave de API.

Superficie Cómo se accede Qué cubre
Conversión de archivos client.convertUrlToPdf(...), client.convertImage(...), etc. Trece métodos tipados para renderizado de URLs, conversión de imágenes, compresión de imágenes y conversión de documentos, más el sondeo de trabajos y de lotes de sitios completos. Consulta Conversión de archivos.
Inteligencia web (V2) client.v2.perceive(...), client.v2.distill(...), etc. Veintitrés métodos repartidos en seis capacidades: perceive, discover, lookup, distill, ingest y watch. Consulta Inteligencia web (V2).

Los endpoints de V2 requieren una clave de API privada (sk_...); las claves públicas se rechazan. Consulta Autenticación para ver en qué se diferencian los dos tipos de clave, y el V1 y V2 para la superficie REST que hay detrás de client.v2.


Conversión de archivos#

La superficie de conversión expone trece métodos que se corresponden 1:1 con la API REST:

Método Endpoint Devuelve
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} (sondeado) BatchStatus

Cada método devuelve una promesa tipada. Todos los campos de opciones son opcionales salvo que se indique lo contrario. Los cuatro últimos son ayudantes de lotes de sitios completos: envían y sondean trabajos asíncronos, así que devuelven un BatchSubmission o un BatchStatus en lugar de un ConversionResult.


convertUrlToPdf#

Renderiza cualquier URL pública a PDF.

const result = await client.convertUrlToPdf("https://example.com", {
    pdfOptions: { pageSize: "A4", orientation: "landscape" },
    singlePage: false,
    viewportWidth: 1440,
    saveTo: "report.pdf",
});
Opción Tipo Predeterminado Descripción
saveTo string -- Ruta local a la que transmitir el PDF. Los directorios padre se crean automáticamente.
singlePage boolean true true produce una única página continua. false pagina usando pdfOptions.pageSize.
pdfOptions PdfOptions -- Tamaño de página, orientación, márgenes, escala, escala de grises, encabezado y pie. Consulta Opciones de PDF.
viewportWidth number 1920 Ancho del viewport del navegador en píxeles.
viewportHeight number 1080 Alto del viewport del navegador en píxeles.
loadMedia boolean true Espera a las imágenes y los vídeos antes de capturar.
enableScroll boolean true Hace scroll de arriba abajo para disparar la carga diferida.
outputFilename string automático Sustituye el nombre de archivo generado. Se añade .pdf si falta.

convertUrlToScreenshot#

Captura un PNG de página completa de cualquier URL.

const result = await client.convertUrlToScreenshot("https://example.com", {
    viewportWidth: 1440,
    saveTo: "screenshot.png",
});

Acepta las mismas opciones de viewport, medios, scroll y nombre de archivo que convertUrlToPdf (menos singlePage y pdfOptions).


convertUrlToMarkdown#

Extrae Markdown limpio con sabor GitHub de cualquier URL. El conversor elimina navegación, pies de página, anuncios y scripts, conserva el cuerpo principal del artículo y antepone frontmatter YAML (título, descripción, url, enlaces, imágenes).

const result = await client.convertUrlToMarkdown("https://example.com/article", {
    saveTo: "article.md",
});

Útil para construir pipelines de RAG, importar contenido de terceros a un CMS o generar datos de entrenamiento. Si quieres una puntuación de calidad de renderizado junto al Markdown, usa client.v2.perceive en su lugar.


convertImage#

Convierte entre jpeg, png, svg, heic y webp.

// Desde una ruta
await client.convertImage("photo.heic", {
    outputFormat: "webp",
    saveTo: "photo.webp",
});

// Desde bytes
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" },
);

// Rasteriza un SVG con un ancho fijo
await client.convertImage("logo.svg", { outputFormat: "png", width: 512, saveTo: "logo.png" });

El formato de entrada se detecta a partir de la extensión de la ruta o del nombre de archivo. El formato de salida es obligatorio.

Opción Tipo Obligatorio Descripción
outputFormat string Formato de destino: jpeg, png, svg, heic o webp (y jpeg para una entrada .pdf). Los alias jpg, yml, htm y md se normalizan. Los pares no admitidos lanzan un error antes de enviar la solicitud.
saveTo string -- Ruta local a la que transmitir el resultado.
outputFilename string -- Sustituye el nombre de archivo generado.
width number -- Solo para entrada SVG (svg-to-png, svg-to-jpeg, svg-to-webp), de 1 a 10000. Por sí solo escala proporcionalmente, tomando el alto de la relación de aspecto del SVG.
height number -- Solo para entrada SVG (svg-to-png, svg-to-jpeg, svg-to-webp), de 1 a 10000. Por sí solo escala proporcionalmente, tomando el ancho de la relación de aspecto del SVG.

Fija width y height a la vez para clavar un lienzo exacto, lo que puede cambiar la relación de aspecto. Omite ambos y la salida conserva el ancho, el alto o el viewBox intrínsecos del SVG. El total de píxeles de salida está limitado a 25.000.000. svg-to-heic no acepta ninguna de las dos opciones, y el SDK lanza un error antes de enviar la solicitud si se las pasas a cualquier otra conversión.


convertDocument#

Convierte documentos y formatos de datos. El outputFormat predeterminado es "pdf".

// docx a pdf
await client.convertDocument("report.docx", { saveTo: "report.pdf" });

// json a yaml
await client.convertDocument("data.json", {
    outputFormat: "yaml",
    saveTo: "data.yaml",
});

// markdown a pdf con configuración de página personalizada
await client.convertDocument("README.md", {
    outputFormat: "pdf",
    pdfOptions: { pageSize: "A4", margins: { top: 20, bottom: 20, left: 25, right: 25 } },
    saveTo: "readme.pdf",
});

Entradas admitidas: 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 no tiene un par de conversión de documentos propio. Envía los archivos .epub a través de convertToPdf o convertToMarkdown en su lugar.

Opción Tipo Predeterminado Descripción
outputFormat string "pdf" Formato de destino.
saveTo string -- Ruta local a la que transmitir el resultado.
outputFilename string -- Sustituye el nombre de archivo generado.
pdfOptions PdfOptions -- Configuración de página. Solo se respeta cuando la salida es PDF.

compressImage#

Reduce un PNG, JPEG o WebP sin cambiar su formato.

// Solo la pasada sin pérdida
const result = await client.compressImage("photo.jpg", { saveTo: "photo-small.jpg" });

// Apunta a un presupuesto de 200 KB
const capped = await client.compressImage("photo.jpg", {
    targetSizeKb: 200,
    saveTo: "photo-capped.jpg",
});

console.log(capped.fileSize);

Entradas admitidas: .png, .jpg, .jpeg, .webp.

La salida conserva el formato y la extensión de la entrada, así que no hay formato de salida que elegir. La primera etapa es sin pérdida: se eliminan los metadatos, se preservan el perfil ICC y la orientación EXIF, y el resultado nunca es mayor que la entrada. Fijar targetSizeKb añade una segunda etapa que reduce la escala con la relación de aspecto bloqueada hasta cumplir el presupuesto. Ese objetivo es de mejor esfuerzo: un presupuesto inalcanzable devuelve el archivo más pequeño logrado en lugar de un error, así que revisa result.fileSize. Los APNG animados y los WebP animados se rechazan con 400, y el lienzo decodificado está limitado a 40.000.000 de píxeles.

Opción Tipo Obligatorio Descripción
targetSizeKb number -- Presupuesto de tamaño en KB, entero, mínimo 1. Omítelo para ejecutar solo la pasada sin pérdida.
saveTo string -- Ruta local a la que transmitir el resultado.
outputFilename string -- Sustituye el nombre de archivo generado. Se conserva la extensión de entrada.

convertToMarkdown#

Convierte a Markdown cualquier documento, hoja de cálculo, presentación, ebook, archivo web o de texto plano admitido.

await client.convertToMarkdown("handbook.docx", {
    saveTo: "handbook.md",
});

Entradas admitidas (22): .csv, .doc, .docx, .epub, .htm, .html, .markdown, .md, .mdown, .mkd, .odp, .ods, .odt, .pdf, .ppt, .pptx, .rtf, .text, .txt, .xhtml, .xls, .xlsx.

La salida es un único archivo .md consciente de los encabezados y pensado para el chunking de RAG: la jerarquía de encabezados del documento sobrevive a la conversión, así que un chunker semántico puede dividir por encabezados en lugar de por recuentos arbitrarios de caracteres. Este endpoint no tiene opciones de PDF. Cualquier otra extensión lanza un error antes de hacer ninguna solicitud.

Opción Tipo Obligatorio Descripción
saveTo string -- Ruta local a la que transmitir el Markdown.
outputFilename string -- Sustituye el nombre de archivo generado.

Si además quieres que el chunking se haga por ti, entrega esos mismos archivos a client.v2.ingestFiles.


convertToPdf#

Convierte a PDF cualquier documento, imagen, ebook, archivo web o de texto plano admitido.

// docx a pdf
await client.convertToPdf("contract.docx", { saveTo: "contract.pdf" });

// html a pdf con geometría de página completa
await client.convertToPdf("invoice.html", {
    pdfOptions: { pageSize: "A4", margins: { top: 15, bottom: 15, left: 15, right: 15 } },
    saveTo: "invoice.pdf",
});

// pdf a pdf en escala de grises (paso directo)
await client.convertToPdf("scan.pdf", {
    pdfOptions: { grayscale: true },
    saveTo: "scan-gray.pdf",
});

Entradas admitidas (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.

Una entrada .pdf se acepta y se pasa directamente, así que con pdfOptions: { grayscale: true } este método sirve también como vía de normalización de PDF. EPUB se gestiona aquí también, ya que no tiene un par de conversión de documentos propio. Cualquier otra extensión lanza un error antes de hacer ninguna solicitud.

La geometría depende de la entrada. La geometría de página completa (tamaño de página, ancho y alto de página, orientación, márgenes, escala, encabezado, pie) se respeta para entradas HTML (.html, .htm, .xhtml), Markdown, texto plano, EPUB, imagen y SVG. Las entradas Office, ODF, iWork, RTF y CSV, más el paso directo de PDF, solo admiten grayscale y devuelven 400 si se fija una opción explícita de geometría. grayscale se respeta para cualquier entrada.
Opción Tipo Obligatorio Descripción
saveTo string -- Ruta local a la que transmitir el PDF.
outputFilename string -- Sustituye el nombre de archivo generado. Se añade .pdf si falta.
pdfOptions PdfOptions -- Configuración de página. Consulta la advertencia anterior sobre qué entradas respetan la geometría.

getJobStatus#

Sondea el estado de un trabajo asíncrono o recuperado.

const status = await client.getJobStatus("job_abc123");

if (status.status === "success") {
    console.log(status.presignedUrl);
} else if (status.status === "failed") {
    console.error(status.error);
}

Devuelve { status: "processing" | "success" | "failed", presignedUrl?, objectKey?, error? }.

Normalmente no necesitas llamar a esto directamente. El SDK sondea automáticamente cuando una solicitud síncrona devuelve 5xx. Consulta Recuperación de tiempos de espera más abajo.

Ayudantes para lotes de sitios completos#

convertWebsiteToPdf y convertWebsiteToScreenshot descubren las páginas de un sitio, las encolan todas y agrupan las salidas en un único ZIP. Ambos devuelven un BatchSubmission de inmediato; sondea con getBatchStatus o bloquea con waitForBatch. Las opciones compartidas son crawlMode ("auto", "sitemap" o "full"), includePatterns, excludePatterns, notificationEmail y callbackUrl; convertWebsiteToPdf añade singlePage y 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 acepta intervalMs (predeterminado 5_000), timeoutMs (predeterminado 1_800_000, treinta minutos) y saveTo. Lanza APIError(504, ...) si se cumple el plazo. Consulta el resumen de endpoints para la superficie REST.


Inteligencia web (V2)#

Todo lo que hay bajo client.v2 devuelve datos en los que un agente puede confiar, porque cada renderizado de V2 lleva una puntuación renderQuality de 0.0 a 1.0. Una página bloqueada, un desafío antibot, un muro de inicio de sesión, una página de error HTTP, un 404 blando o el shell vacío de una SPA vuelven con una puntuación baja más deductions y warnings con nombre, de modo que quedan marcados en lugar de confundirse con contenido real. El contenido se sigue devolviendo; tú decides qué hacer con él. Las puntuaciones por debajo de aproximadamente 0.40 significan que el renderizado no tuvo éxito en ningún sentido útil.

Veintitrés métodos repartidos en seis capacidades:

Método Endpoint Devuelve
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

Las opciones son camelCase en la superficie del SDK y se serializan al formato de transmisión snake_case de la API; las respuestas se mapean de vuelta a camelCase. Tus propios payloads (esquemas de extracción, datos extraídos, campos rastreados, entradas de diff) pasan intactos.


Perceive#

Renderiza una URL y produce los artefactos que pidas: Markdown, HTML limpio o en bruto, una captura de pantalla del viewport o de página completa, un PDF, una lista de enlaces, una lista de imágenes o datos estructurados. perceive es síncrono y devuelve la operación completada con URLs de artefacto firmadas durante 15 minutos. Referencia completa: 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);        // de 0.0 a 1.0
console.log(op.deductions);           // p. ej. { http_error: 0.7 }
console.log(op.outputs.markdown.url); // URL firmada de 15 minutos
console.log(op.structured);

if ((op.renderQuality ?? 0) < 0.4) {
    console.warn("Bad read, do not feed this to the model:", op.warnings);
}

// Vuelve a firmar las URLs de los artefactos más tarde sin volver a renderizar:
const again = await client.v2.getPerceiveOperation(op.operationId);
Opción Tipo Predeterminado Descripción
outputs PerceiveOutputName[] ["markdown", "structured"] Cualquiera de markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured.
extract PerceiveExtractName[] -- Objetivos heurísticos: tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all.
onlyMainContent boolean true Elimina navegación, encabezado, pie y avisos de cookies de la salida Markdown, protegido por una salvaguarda de fidelidad. false devuelve la página completa intacta.
schema Record<string, unknown> -- Esquema JSON para la extracción estructurada en el nivel de LLM.
waitFor string -- Selector CSS (opcionalmente "css:...") o "js:<expr>" que esperar antes de capturar.
waitTimeoutMs number 30000 De 0 a 60000.
jsCode string -- JavaScript ejecutado tras la navegación. Máximo 20000 caracteres.
viewport { width?, height? } 1920 x 1080 Ancho de 320 a 3840, alto de 240 a 2160.
headers Record<string, string> -- Encabezados de solicitud adicionales.
cookies BrowserCookie[] -- Cookies inyectadas antes de renderizar. Cada una necesita name, value y domain o url.
auth { username, password } -- Autenticación HTTP Basic.
cacheMode "enabled" \| "bypass" \| "refresh" "enabled" Caché de una hora. bypass la salta, refresh fuerza un nuevo renderizado.
pdfOptions PdfOptions -- Solo tiene sentido cuando outputs incluye "pdf". Consulta Opciones de PDF.
blockResources PerceiveResourceType[] -- Tipos de recurso que el navegador no debe cargar, por ejemplo ["image", "font", "media"].
respectRobots boolean -- Respeta las reglas de robots del sitio.
mobile boolean -- Renderiza con un perfil móvil.
directDownload boolean -- Solo en perceive. Es preferible perceiveDirect, que lo fija por ti.
Tres opciones están declaradas pero aún no operativas. proxyUrl, geolocation y actionChain están tipadas en PerceiveOptions, pero el servidor las rechaza actualmente con 422. Están reservadas, no son utilizables.

Descarga directa. perceiveDirect se salta el viaje de ida y vuelta de la URL firmada: el cuerpo de la respuesta HTTP son los bytes del artefacto y los metadatos viajan en los encabezados. Requiere exactamente una salida que produzca artefacto, y el SDK lanza un error localmente antes de enviar si no es así ("structured" puede acompañarla, pero permanece inline en el servidor y no se devuelve).

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

// Vuelve a descargar como bytes en bruto un artefacto almacenado de una operación anterior.
// `output` puede omitirse cuando la operación produjo exactamente un artefacto.
const raw = await client.v2.downloadPerceiveArtifact(op.operationId, "markdown");

downloadPerceiveArtifact devuelve 410 una vez que el artefacto almacenado ha caducado, y 400 (con la lista de salidas disponibles) si la operación produjo más de un artefacto y omitiste output.

Lotes. perceiveBatch acepta hasta 1000 URLs con un único bloque de opciones compartido. Los lotes pequeños se ejecutan inline y vuelven completados; los mayores devuelven el estado "queued", así que sondea getPerceiveBatch con el jobId devuelto.

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 es "manifest" (predeterminado, una entrada por URL en items) o "zip" (todos los artefactos exitosos agrupados cuando termina el trabajo). El endpoint de lotes rechaza directDownload; usa outputMode: "zip" en su lugar.


Discover#

Lista las URLs de un sitio sin renderizar nada. No interviene ningún navegador, así que es rápido y barato comparado con percibir cada página. Referencia completa: 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); // p. ej. { sitemap: 42, crawl: 30 }
for (const url of found.urls) console.log(url);
Opción Tipo Predeterminado Descripción
mode "sitemap" \| "crawl" \| "hybrid" "hybrid" Solo sitemap, solo rastreo HTTP, o ambos.
maxUrls number 100 De 1 a 1000. truncated es true cuando existían más URLs de las que permitía este tope.
maxDepth number 2 De 1 a 5. Profundidad de rastreo desde la URL semilla.
includePatterns string[] -- Lista de permitidos por regex, máximo 50 entradas.
excludePatterns string[] -- Lista de denegados por regex aplicada después de includePatterns, máximo 50 entradas.
sameDomainOnly boolean true Mantiene el rastreo en el dominio semilla.
respectRobots boolean -- Respeta las reglas de robots del sitio. robotsRespected en el resultado informa de lo que ocurrió.

Lookup#

Ejecuta una búsqueda web categorizada y, opcionalmente, percibe automáticamente los primeros resultados para que cada acierto lleve su propio PerceiveResult completo inline. Referencia completa: 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);
Opción Tipo Predeterminado Descripción
category "web" \| "news" \| "images" \| "scholar" \| "patents" \| "maps" "web" Vertical de búsqueda.
country string -- Código de país gl de Google, por ejemplo "us" o "in".
locale string -- Idioma de interfaz hl de Google, por ejemplo "en".
timeFilter "hour" \| "day" \| "week" \| "month" \| "year" -- Ventana de actualidad.
numResults number 10 De 1 a 100.
page number 1 De 1 a 10.
location string -- Ubicación en texto libre, por ejemplo "Austin, Texas".
autocorrect boolean true Deja que el proveedor corrija erratas evidentes.
perceiveTop number 0 De 0 a 10. Renderiza automáticamente las N primeras URLs de resultados; cada una es un renderizado completo en el navegador.

perceiveTop en el resultado informa de cuántos resultados se percibieron realmente, que puede ser menos de lo que pediste, y perceiveOperationIds te da los identificadores de operación para volver a firmar más tarde.


Distill#

Extracción estructurada guiada por esquema. Dale una forma y un conjunto de URLs (o un sitio que descubrir primero) y devuelve registros que coinciden con esa forma. Un cssSchema opcional responde todo lo que puede desde selectores antes de que algo escale al nivel de LLM. Referencia completa: 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);

Pasa exactamente uno de urls o discoverFrom; el SDK lanza un error localmente si pasas ambos o ninguno, y también lo lanza si falta schema o si no es un objeto.

// Descubre primero un sitio y luego destila cada página encontrada.
await client.v2.distill({
    discoverFrom: { url: "https://example.com", mode: "sitemap", maxPages: 10 },
    schema: { title: "page title", summary: "one-line summary" },
});
Opción Tipo Predeterminado Descripción
urls string[] -- URLs explícitas que destilar, máximo 50. Mutuamente excluyente con discoverFrom.
discoverFrom { url, mode?, maxPages? } -- Descubre primero y luego destila. maxPages va de 1 a 50, predeterminado 10, y limita tanto el descubrimiento como la destilación.
schema Record<string, unknown> obligatorio Un objeto JSON Schema ({ type: "object", properties: {...} }) o un mapa plano { campo: descripción }.
cssSchema CssSchema -- Pasada libre de selectores ejecutada antes de cualquier escalado a LLM.
waitFor string -- Selector CSS o "js:<expr>" que esperar.
waitTimeoutMs number 30000 De 0 a 60000.
headers Record<string, string> -- Encabezados de solicitud adicionales.
cookies BrowserCookie[] -- Cookies inyectadas antes de renderizar.
respectRobots boolean -- Respeta las reglas de robots del sitio.

Un CssSchema tiene un baseSelector (el contenedor que se repite, un registro por coincidencia), una lista fields, un name opcional y un targetField opcional que nombra la propiedad del esquema de salida que llenan los registros. Cada campo es { name, type, selector?, attribute?, pattern?, default?, transform?, fields? }, donde type es uno de text, attribute, html, regex, nested, list, nested_list. attribute es obligatorio para los campos attribute, pattern para los campos regex, y un array fields no vacío para los tipos anidados (profundidad máxima de 5).


Ingest#

Convierte un sitio, o un montón de documentos subidos, en JSONL fragmentado y listo para RAG. Ingest siempre es asíncrono: ambos puntos de entrada devuelven un IngestJob en cola, y o bien lo sondeas o bien configuras un webhook. Referencia completa: Ingest.

// Desde un sitio.
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",
});

// Desde archivos subidos: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD,
// y documentos de ofimática heredados o ODF.
const fileJob = await client.v2.ingestFiles(["handbook.pdf", "notes.docx"], {
    chunk: { maxWords: 512, sentenceOverlap: 1 },
});

// Sondea cualquiera de los dos igual. Estados no terminales: 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
Opción Tipo Predeterminado Descripción
mode "urls" \| "sitemap" \| "crawl" "urls" "urls" necesita urls y rechaza url. "sitemap" y "crawl" necesitan una url semilla y rechazan urls. Ambas reglas se comprueban localmente antes de la solicitud. La unión IngestMode también tiene "files", que es lo que ingestFiles informa en su trabajo; no lo pases aquí.
url string -- URL semilla para sitemap y crawl.
urls string[] -- URLs explícitas para el modo "urls", máximo 1000.
maxPages number 50 Tope de descubrimiento para sitemap y crawl, de 1 a 1000.
maxDepth number 2 De 1 a 5.
sameDomainOnly boolean true Mantiene el rastreo en el dominio semilla.
includePatterns / excludePatterns string[] -- Lista de permitidos y lista de denegados por regex.
respectRobots boolean -- Respeta las reglas de robots del sitio.
waitFor / waitTimeoutMs string / number -- / 30000 Espera de renderizado por página.
chunk { maxWords?, sentenceOverlap? } 512 / 1 maxWords va de 32 a 4000, sentenceOverlap de 0 a 10.
webhookUrl string -- Webhook de finalización, firmado con HMAC.

ingestFiles acepta un FileInput[], es decir, cadenas de ruta, Uint8Array / Buffer u objetos { data, filename, contentType? }, mezclados como quieras. Solo toma chunk y webhookUrl, y lanza un error localmente con una lista vacía.

Firma de webhooks. Los webhooks de finalización se firman con HMAC. Obtén el secreto (se crea en la primera llamada) para verificar las entregas, rótalo cuando lo necesites y vuelve a entregar un webhook que tu endpoint se haya perdido.

const secret = await client.v2.getWebhookSecret();
console.log(secret.signatureHeader, secret.timestampHeader);
console.log(secret.signatureScheme, secret.replayToleranceSeconds);

// Rotarlo invalida de inmediato las firmas hechas con el secreto anterior.
const rotated = await client.v2.rotateWebhookSecret();

// Vuelve a entregar el webhook de un trabajo completado.
const retry = await client.v2.retryIngestWebhook(job.jobId);
console.log(retry.delivered, retry.attempts, retry.statusCode, retry.detail);

retryIngestWebhook devuelve 409 cuando el trabajo no está completado y 400 cuando el trabajo no tiene webhook configurado.


Watch#

Crea un vigilante que vuelve a renderizar una URL con una cadencia fija y te avisa cuando la página cambia, por correo, por webhook o por ambos. Referencia completa: 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: "" }); // limpia el webhook
await client.v2.deleteWatcher(watcher.watcherId);                     // borrado suave, idempotente
Opción Tipo Predeterminado Descripción
frequencyMinutes number 60 Minutos entre comprobaciones, de 60 a 43200. El mínimo de una hora es estricto.
diffMode "auto" \| "text" \| "structured" \| "tables" \| "metadata" "auto" "auto" deja que el motor de diff elija según el tipo de contenido.
trackFields Record<string, unknown> -- Subconjunto de campos o selectores para acotar el diff.
webhookUrl string -- Webhook de notificación de cambios, firmado con HMAC.
notifyEmail boolean true Envía un correo al propietario del proyecto cuando hay cambios.

updateWatcher toma los mismos campos más status ("active" o "paused") y requiere al menos uno de ellos; el SDK lanza un error localmente ante una actualización vacía. Pasar webhookUrl: "" limpia explícitamente el webhook. Borrar es un borrado suave: deleteWatcher devuelve el vigilante marcado como eliminado con estado "deleted", y un vigilante borrado se lee como 404 desde getWatcher.

Cada instantánea lleva checkedAt, hasChanges, similarity (de 0.0 a 1.0 frente a la captura anterior), renderQuality, changeCount y un array changes.

Los diffs de instantáneas contienen contenido de página no confiable. Las entradas de snapshot.changes vienen directamente de la página vigilada. Escápalas antes de renderizarlas en HTML o de escribirlas en un visor de logs.

Opciones de PDF#

Se pasan mediante el campo pdfOptions en convertUrlToPdf, convertDocument, convertToPdf, convertWebsiteToPdf y client.v2.perceive (cuando outputs incluye "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 Descripción
pageSize string "A4", "A3", "Letter", "Legal", etc.
pageWidth / pageHeight number Geometría de página explícita, como alternativa a pageSize.
orientation "portrait" \| "landscape" Por defecto es vertical.
margins { top, bottom, left, right } (mm) Los cuatro son opcionales.
scale number Escala de renderizado, p. ej. 0.9 para el 90%.
grayscale boolean Posprocesa el PDF con Ghostscript para pasarlo a escala de grises.
header PdfHeaderFooter { content?, height? }. content está limitado a 2000 caracteres.
footer PdfHeaderFooter La misma forma que header.

Cada parámetro se describe por completo en Trabajos síncronos y asíncronos.

Manejo de errores#

Los errores son clases de excepción tipadas que puedes comparar con instanceof. La misma jerarquía cubre tanto los métodos de conversión como 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;
    }
}
Clase Se lanza en Código de estado
AuthenticationError Clave inválida, ausente o revocada 401, 403 (ambos informan statusCode 401)
QuotaError Se lanza ante un HTTP 402 402
RateLimitError Demasiadas solicitudes 429
APIError Cualquier otro 4xx / 5xx el código real
EnconvertError Clase base de todas las anteriores --

QuotaError y RateLimitError extienden ambas APIError, que a su vez extiende EnconvertError, así que ordena tus comprobaciones instanceof de la más específica a la más general. Cada APIError lleva un campo statusCode.

Algunos fallos nunca llegan a la red: una extensión de archivo no admitida, una llamada a distill con urls y discoverFrom a la vez, una llamada a ingest cuyo modo y argumentos no concuerdan, una llamada a perceiveDirect con más de una salida de artefacto, o una llamada a updateWatcher sin campos. Esos lanzan un Error simple de forma local para que encuentres el fallo en desarrollo.

El mapa completo de mensajes de error está en la referencia de Códigos de error.


Recuperación de tiempos de espera#

Las conversiones largas de URL a PDF o de documentos grandes pueden superar el límite de 60 a 120 segundos del proxy inverso, incluso cuando la conversión acaba teniendo éxito en el servidor. El SDK lo gestiona de forma transparente en los métodos de conversión de V1:

  1. Antes de cada solicitud, el SDK genera un UUID y lo envía como job_id en el cuerpo de la solicitud.
  2. Si la solicitud original devuelve 5xx, el SDK pasa en silencio a sondear GET /v1/convert/status/{job_id} cada 3 segundos.
  3. En cuanto el trabajo queda registrado como success, el SDK devuelve el resultado. En cuanto queda registrado como failed, el SDK lanza APIError.
  4. El plazo de sondeo es de 5 minutos. Si se supera, el SDK lanza APIError(504, "Conversion timed out").

No necesitas escribir nada de código para esto, simplemente funciona. Fija timeout en el constructor si quieres acotar la solicitud inicial.

V2 usa objetos de trabajo explícitos en lugar de recuperación implícita: perceiveBatch e ingest devuelven un identificador que sondeas con getPerceiveBatch y getIngestJob, e ingest puede llamar a un webhook en su lugar.


Configuración#

const client = new Enconvert({
    apiKey: process.env.ENCONVERT_API_KEY!,
    timeout: 300_000, // ms, 5 min por defecto
    baseUrl: "https://api.enconvert.com", // sustitúyelo para gateways autoalojados
});
Opción Tipo Predeterminado Descripción
apiKey string -- (obligatorio) Clave de API privada (sk_...). El constructor lanza un error de inmediato si falta.
timeout number 300_000 Tiempo de espera de la solicitud en ms. Aborta el fetch subyacente mediante AbortController.
baseUrl string https://api.enconvert.com URL base de la API. Las barras finales se eliminan.

La clave viaja como encabezado X-API-Key en cada solicitud, tanto de V1 como de V2. client.v2 se construye por ti y comparte la clave, la URL base y el tiempo de espera del cliente, así que no hay nada más que configurar.

Nunca incrustes la clave de API en el código. Léela de una variable de entorno o de tu gestor de secretos. Cualquiera que consiga tu clave privada puede ejecutar conversiones y operaciones de V2 en tu cuenta. Rota las claves desde el panel de control.

Forma del resultado#

Cada método de conversión devuelve un ConversionResult:

interface ConversionResult {
    presignedUrl: string;          // URL firmada para descargar la salida (1 hora)
    objectKey: string;             // clave del objeto en el almacenamiento
    filename: string;              // nombre de archivo del lado del servidor
    fileSize?: number;             // bytes
    conversionTimeSeconds?: number;
    jobId?: string;                // presente cuando hubo sondeo de recuperación
}

La URL prefirmada es válida durante una hora. Si necesitas acceso permanente, descarga el archivo (usa saveTo o busca la URL tú mismo) y guárdalo en tu propio bucket.

Los resultados de V2 tienen otra forma. Un PerceiveResult lleva operationId, status, url, urlFinal, contentHash, renderQuality, statusCode, deductions, cacheHit, un mapa outputs indexado por nombre de salida, structured, extractionTier, tokens, costCents, durationMs, optionsEcho, error y warnings. Cada entrada de outputs es un V2OutputArtifact de { url?, objectKey, sizeBytes, contentType, expiresIn }, donde expiresIn va en segundos y su valor predeterminado es 900. Por tanto, las URLs de artefactos de V2 duran 15 minutos en lugar de una hora, y se vuelven a firmar en cada lectura, así que llamar de nuevo a getPerceiveOperation(operationId) te da enlaces frescos sin volver a renderizar la página.


TypeScript#

Las definiciones de tipos se publican con el paquete, así que no hace falta instalar ningún @types/.... El paquete se publica de forma dual (ESM + CJS) con exports, types y .d.ts / .d.cts correctos, de modo que funciona en cualquier modo de resolución de módulos de Node.

import type {
    ClientOptions,
    CompressImageOptions,
    ConversionResult,
    ConvertDocumentOptions,
    ConvertImageOptions,
    ConvertToMarkdownOptions,
    ConvertToPdfOptions,
    FileInput,
    JobStatus,
    PdfOptions,
    UrlToMarkdownOptions,
    UrlToPdfOptions,
    UrlToScreenshotOptions,
    // Los tipos de V2 vienen del mismo punto de entrada.
    DiscoverOptions, DiscoverResult,
    DistillOptions, DistillResult,
    IngestJob, IngestOptions,
    LookupOptions, LookupResult,
    PerceiveOptions, PerceiveResult, PerceiveOutputName,
    PerceiveBatchResult, PerceiveDirectResult,
    Watcher, WatcherSnapshotList,
} from "@enconvert/node-sdk";

La propia clase EnconvertV2 también se exporta, por si quieres tipar el parámetro de una función como el espacio de nombres de V2.


Actualización#

El paquete incluye una pequeña CLI, enconvert-sdk, para mantenerse al día por sí solo.

npx enconvert-sdk upgrade
npx enconvert-sdk upgrade --dry-run
npx enconvert-sdk version

upgrade detecta npm, pnpm, yarn o bun a partir del gestor de paquetes del entorno y siempre imprime el comando de instalación exacto antes de ejecutarlo, así que nada le ocurre a tu lockfile sin que lo veas. --dry-run imprime ese comando y se detiene. version informa de la versión del SDK instalada.


Código fuente e incidencias#


Preguntas frecuentes#

¿Cómo convierto archivos en Node.js con un paquete de npm?#

Instala @enconvert/node-sdk, crea un cliente con tu clave de API (new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! })) y llama a un método tipado como convertUrlToPdf, convertImage o convertDocument. Pasa saveTo para transmitir el resultado directamente al disco.

¿Cómo extraigo una página web a Markdown limpio en Node.js?#

Llama a client.v2.perceive(url, { outputs: ["markdown"] }). Obtienes una URL firmada de 15 minutos al Markdown en op.outputs.markdown.url más una puntuación renderQuality de la lectura. Si prefieres los bytes directamente en lugar de una URL, llama a client.v2.perceiveDirect(url, { outputs: ["markdown"] }) y lee result.content.

¿Qué es renderQuality y por qué importa?#

renderQuality es una puntuación de 0.0 a 1.0 adjunta a cada renderizado de V2. Un desafío antibot, un muro de inicio de sesión, una página de error HTTP, un 404 blando o el shell vacío de una SPA puntúan bajo y vuelven con deductions y warnings con nombre, de modo que una lectura defectuosa queda marcada en lugar de entrar sin ruido en el contexto de tu agente como si fuera la página real. Las puntuaciones por debajo de aproximadamente 0.40 significan que el renderizado falló en la práctica, aunque la solicitud devolviera 200.

¿Cómo convierto HEIC a WebP en Node.js?#

Llama a convertImage con el archivo HEIC (una ruta o un objeto de búfer { data, filename }) y outputFormat: "webp". El SDK convierte entre jpeg, png, svg, heic y webp; el formato de entrada se detecta a partir de la extensión del nombre de archivo.

¿Cómo comprimo una imagen en Node.js sin cambiar su formato?#

Llama a compressImage con un archivo .png, .jpg, .jpeg o .webp. La salida conserva el formato y la extensión de la entrada, elimina los metadatos preservando el perfil ICC y la orientación EXIF, y nunca es mayor que la entrada. Añade targetSizeKb para reducir la escala hacia un presupuesto de tamaño; el objetivo es de mejor esfuerzo, así que lee result.fileSize para ver qué se logró realmente.

¿Cómo convierto cualquier documento a Markdown para un pipeline de RAG?#

Llama a convertToMarkdown con el archivo y pasa saveTo para escribir el .md directamente en disco. Acepta 22 extensiones entre Office, OpenDocument, PDF, EPUB, HTML, CSV y texto plano, y devuelve un único archivo Markdown consciente de los encabezados, de modo que tu chunker puede dividir por los encabezados del propio documento en lugar de por recuentos arbitrarios de caracteres.

¿Cómo convierto un sitio web entero en fragmentos listos para RAG en Node.js?#

Llama a client.v2.ingest({ mode: "sitemap", url, maxPages, chunk: { maxWords: 512, sentenceOverlap: 1 } }). Ingest siempre es asíncrono, así que sondea client.v2.getIngestJob(job.jobId) hasta que status sea "completed" y lee outputUrl para el JSONL firmado, o fija webhookUrl y deja que el webhook de finalización te avise. Para documentos locales en lugar de un sitio, client.v2.ingestFiles([...]) ejecuta el mismo pipeline.

¿Cómo extraigo JSON estructurado de una página en Node.js?#

Llama a client.v2.distill({ urls, schema }) donde schema es o bien un objeto JSON Schema o bien un mapa plano { campo: descripción }. Añade un cssSchema y la pasada de selectores responde todo lo que puede antes de que algo escale al nivel de LLM; result.extractionTier, fieldsFromCss y fieldsFromLlm te dicen qué nivel hizo el trabajo.

¿Cómo monitorizo los cambios de una página web en Node.js?#

Llama a client.v2.createWatcher(url, { frequencyMinutes: 60, diffMode: "auto", webhookUrl }). El mínimo de una hora es estricto, así que 60 es la cadencia mínima. Lee el historial con getWatcherSnapshots, pausa con updateWatcher(id, { status: "paused" }) y elimina con deleteWatcher, que es un borrado suave e idempotente.

¿Cómo gestiona el SDK las conversiones largas que chocan con el tiempo de espera del proxy inverso?#

Antes de cada solicitud de conversión de V1, el SDK genera un UUID y lo envía como job_id; si la solicitud devuelve 5xx, sondea en silencio GET /v1/convert/status/{job_id} cada 3 segundos hasta que el trabajo sea success o failed. El plazo de sondeo es de 5 minutos, tras los cuales lanza APIError(504, "Conversion timed out"). V2 usa identificadores de trabajo explícitos, sondeados con getPerceiveBatch o getIngestJob.

¿Puedo usar el SDK de Node.js en una aplicación de navegador?#

No, el SDK es solo del lado del servidor, porque se autentica con una clave de API privada (sk_...) que nunca debe empaquetarse en código del lado del cliente. Funciona en Node 18+, Bun y Deno vía el especificador de npm.

¿Cuánto tiempo es válida la URL de descarga prefirmada?#

El presignedUrl de cada ConversionResult es válido durante una hora. Las URLs de artefactos de V2 son válidas durante 15 minutos y se vuelven a firmar en cada lectura, así que getPerceiveOperation(operationId) te entrega enlaces frescos. Para acceso permanente, descarga el archivo y guárdalo en tu propio bucket.