SDK de Conversión de Archivos para Python#

enconvert es el cliente oficial de Python para la API de EnConvert: un pip install, una clave de API y métodos tipados para convertir archivos y para leer la web en vivo. Funciona con Python 3.9 o posterior con una única dependencia en tiempo de ejecución, requests, e incluye anotaciones de tipo inline más un marcador py.typed para que mypy y Pyright lo vean todo. El cliente tiene dos superficies. Doce métodos de conversión cubren 43 pares {entrada}-to-{salida} más URL a PDF, captura de pantalla y Markdown. El espacio de nombres client.v2 añade inteligencia web: perceive, discover, lookup, distill, ingest y watch.

PyPI: enconvert · Fuente: conversionapi/python-sdk · Python: 3.9+ · Dependencia en tiempo de ejecución: requests>=2.28 · Licencia: MIT

Instalación#

pip install enconvert

uv add enconvert y poetry add enconvert funcionan igual. requests es la única dependencia en tiempo de ejecución, y los stubs de tipos viajan dentro del wheel, así que no hay ningún paquete types- que perseguir.


Inicio rápido#

Lee una página como debería leerla tu agente, con una puntuación de calidad adjunta, y luego convierte un archivo local con el mismo cliente.

import os

from enconvert import Enconvert

client = Enconvert(api_key=os.environ["ENCONVERT_API_KEY"])

op = client.v2.perceive("https://example.com", outputs=["markdown", "structured"])
print(op.outputs["markdown"].url, op.render_quality)

print(client.convert_to_pdf("report.docx", save_to="report.pdf").presigned_url)

Todos los métodos son síncronos y bloqueantes. El SDK es solo del lado del servidor: se autentica con una clave de API privada, así que nunca lo envíes dentro de un cliente de escritorio, móvil o de navegador. Consigue una clave desde tu panel de control, y consulta Autenticación para ver cómo se delimitan las claves.


Qué expone el cliente#

Enconvert es toda la superficie pública. Los métodos de conversión cuelgan directamente del cliente; todo lo orientado a la web vive bajo client.v2.

Método de conversión Endpoint Devuelve
convert_url_to_pdf(url, ...) POST /v1/convert/url-to-pdf ConversionResult
convert_url_to_screenshot(url, ...) POST /v1/convert/url-to-screenshot ConversionResult
convert_url_to_markdown(url, ...) POST /v1/convert/url-to-markdown ConversionResult
convert_image(file, output_format=...) POST /v1/convert/{input}-to-{output} ConversionResult
convert_document(file, ...) POST /v1/convert/{input}-to-{output} ConversionResult
convert_to_markdown(file, ...) POST /v1/convert/anything-to-markdown ConversionResult
convert_to_pdf(file, ...) POST /v1/convert/anything-to-pdf ConversionResult
convert_website_to_pdf(url, ...) POST /v1/convert/website-to-pdf BatchSubmission
convert_website_to_screenshot(url, ...) POST /v1/convert/website-to-screenshot BatchSubmission
get_job_status(job_id) GET /v1/convert/status/{job_id} JobStatus
get_batch_status(batch_id) GET /v1/convert/batch/{batch_id} BatchStatus
wait_for_batch(batch_id, ...) GET /v1/convert/batch/{batch_id} (sondeado) BatchStatus
Capacidad de client.v2 Métodos Devuelve
Perceive perceive, perceive_direct, get_perceive_operation, download_perceive_artifact, perceive_batch, get_perceive_batch PerceiveResult, PerceiveDirectResult, PerceiveBatchResult
Discover, Lookup, Distill discover, lookup, distill DiscoverResult, LookupResult, DistillResult
Ingest ingest, ingest_files, list_ingest_jobs, get_ingest_job, cancel_ingest_job, retry_ingest_webhook, get_webhook_secret, rotate_webhook_secret IngestJob, IngestJobList, WebhookSecret, WebhookRetryResult
Watch create_watcher, list_watchers, get_watcher, get_watcher_snapshots, update_watcher, delete_watcher Watcher, WatcherList, WatcherSnapshotList

Todos los argumentos posteriores al primero posicional son solo de palabra clave, en snake_case y opcionales salvo que una tabla diga lo contrario. Los resultados son dataclasses congeladas, así que construye una instancia nueva en lugar de mutar una existente. Las formas REST están en el resumen de endpoints.


Conversión de archivos#

Todos los métodos de conversión aceptan save_to (un str o un os.PathLike; el resultado se transmite ahí y los directorios padre se crean) y output_filename (sustituye el nombre generado). Ambos se omiten en las tablas siguientes.

convert_url_to_pdf#

Renderiza a PDF cualquier URL accesible.

result = client.convert_url_to_pdf(
    "https://example.com", single_page=False, viewport_width=1440, save_to="report.pdf"
)
print(result.presigned_url, result.file_size)
Opción Tipo Predeterminado Descripción
single_page bool True True da una única página continua. False pagina usando pdf_options.page_size.
pdf_options PdfOptions -- Tamaño de página, orientación, márgenes, escala, escala de grises, encabezado, pie. Consulta Opciones de PDF.
viewport_width / viewport_height int 1920 / 1080 Tamaño del viewport del navegador en píxeles.

load_media y enable_scroll valen ambos True por defecto: el primero espera a las imágenes y el vídeo, el segundo hace scroll de arriba abajo para que se disparen los cargadores diferidos. Tres opciones más alcanzan páginas tras una barrera: auth (HttpBasicAuth), cookies (list[BrowserCookie]) y headers (dict[str, str]).

No combines auth con un encabezado Authorization. La API rechaza el conflicto en lugar de adivinar qué credencial gana. Elige una.

convert_url_to_screenshot#

Captura un PNG de cualquier URL.

client.convert_url_to_screenshot("https://example.com", viewport_width=1440, save_to="shot.png")

Toma las mismas opciones de viewport, medios, scroll, nombre de archivo y acceso del navegador que convert_url_to_pdf, menos single_page y pdf_options.


convert_url_to_markdown#

Extrae Markdown limpio con sabor GitHub de una URL. Se eliminan la navegación, los pies de página, los anuncios y los scripts, se conserva el cuerpo principal del artículo y se antepone frontmatter YAML con título, descripción, url, enlaces e imágenes.

client.convert_url_to_markdown("https://example.com/article", save_to="article.md")

El mismo conjunto de opciones que convert_url_to_screenshot. Cuando además quieras una puntuación de calidad, metadatos de la página o extracción estructurada en la misma lectura, usa v2.perceive en su lugar.


convert_image#

Convierte entre jpeg, png, svg, heic y webp, o rasteriza un PDF a JPEG. El formato de entrada sale de la extensión del nombre de archivo.

client.convert_image("photo.heic", output_format="webp", save_to="photo.webp")
client.convert_image("scan.pdf", output_format="jpeg", save_to="scan.jpeg")

output_format es obligatorio y debe ser uno de jpeg, png, svg, heic o webp; los alias jpg, yml, htm y md se normalizan por ti. file acepta una cadena de ruta, un os.PathLike, bytes en bruto o un envoltorio FileData(data, filename). Los bytes en bruto no llevan nombre de archivo y se suben como upload.bin, así que prefiere FileData siempre que la extensión importe.

from pathlib import Path

from enconvert import FileData

blob = FileData(data=Path("photo.heic").read_bytes(), filename="photo.heic")
client.convert_image(blob, output_format="webp", save_to="photo.webp")

convert_document#

Convierte documentos y formatos de datos. output_format vale "pdf" por defecto, y pdf_options se respeta cuando la salida es PDF.

from enconvert import PdfMargins, PdfOptions

client.convert_document("report.docx", save_to="report.pdf")
client.convert_document("data.json", output_format="yaml", save_to="data.yaml")
client.convert_document(
    "README.md", pdf_options=PdfOptions(page_size="A4", margins=PdfMargins(top=20)), save_to="r.pdf"
)

Entradas admitidas: .doc, .docx, .xls, .xlsx, .ppt, .pptx, .html, .htm, .odt, .ods, .odp, .ots, .pages, .numbers, .md, .markdown, .csv, .json, .xml, .yaml, .yml, .toml.

EPUB no tiene un par de documentos propio. Envía los .epub a través de convert_to_pdf o convert_to_markdown.


convert_to_markdown#

Detecta automáticamente en el servidor un documento subido y devuelve Markdown limpio. Este es el bloque de construcción de ingesta para RAG cuando se trata de un solo archivo.

client.convert_to_markdown("handbook.docx", save_to="handbook.md")

Entradas aceptadas: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, más formatos de ofimática heredados y ODF. Las imágenes no se admiten aquí, y el endpoint no acepta opciones de PDF. Para un sitio entero en lugar de un archivo, usa v2.ingest.


convert_to_pdf#

Detecta automáticamente en el servidor un archivo subido y devuelve un PDF.

from enconvert import PdfOptions

client.convert_to_pdf("slides.pptx", save_to="slides.pdf")
client.convert_to_pdf("scan.pdf", pdf_options=PdfOptions(grayscale=True), save_to="gray.pdf")

Entradas aceptadas: ofimática, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texto plano, imágenes rasterizadas, SVG, EPUB y un PDF existente como paso directo. Como una entrada .pdf se pasa directamente, esto sirve también como normalizador a escala de grises.

En este endpoint solo se respeta pdf_options.grayscale. Los demás campos de geometría de página se ignoran aquí. Cuando necesites una configuración de página real, encamina el archivo por convert_document o convert_url_to_pdf.

convert_website_to_pdf y convert_website_to_screenshot#

Descubre todas las páginas de un sitio, convierte cada una en segundo plano y recoge un único ZIP. Ambos son asíncronos por diseño y devuelven un BatchSubmission de inmediato.

batch = client.convert_website_to_pdf(
    "https://example.com", crawl_mode="sitemap", exclude_patterns=["/blog/tag/"]
)
print(batch.batch_id, batch.url_count, batch.discovery_method)

status = client.wait_for_batch(batch.batch_id, save_to="site.zip")
print(status.completed, "of", status.total, "pages converted")

for item in client.get_batch_status(batch.batch_id).items:
    print(item.source_url, item.status, item.download_url)
Opción Tipo Predeterminado Descripción
crawl_mode "auto" \| "sitemap" \| "full" valor del servidor sitemap lee solo sitemap.xml. full añade un rastreo en anchura. auto elige el modo más alto disponible para la clave.
include_patterns / exclude_patterns list[str] -- Conserva o descarta URLs por fragmento de ruta. Las exclusiones se aplican solo en modo de rastreo completo.
notification_email / callback_url str -- Dirección a la que escribir, y webhook al que llamar, cuando termina el lote.
single_page bool valor del servidor Solo PDF.
pdf_options PdfOptions -- Solo PDF. Consulta Opciones de PDF.

Las opciones de renderizado por página se aplican también a cada URL descubierta: viewport_width, viewport_height, load_media, enable_scroll, auth, cookies y headers. Todo lo que dejes sin fijar conserva el valor predeterminado del propio gateway.

wait_for_batch acepta interval_seconds (predeterminado 5.0), timeout_seconds (predeterminado 1800.0) y save_to. Lanza APIError(504, ...) si el lote sigue procesándose cuando se cumple el plazo.


Pares de conversión admitidos#

El SDK lleva una copia del mapa de conversiones del gateway y rechaza localmente un par no implementado, antes de cualquier viaje por la red, nombrando en el mensaje las salidas válidas para esa entrada.

Entrada Salidas
json csv, toml, xml, yaml
xml csv, json
yaml, toml json
csv json, xml
markdown html, pdf
html pdf
doc, excel, ppt, odt, ods, odp, ots, pages, numbers pdf
jpeg, png, svg, heic, webp entre sí, los 20 pares
pdf jpeg

Son 43 pares implementados. Compruébalos por código:

from enconvert import IMPLEMENTED_CONVERSIONS, valid_outputs_for

valid_outputs_for("json")                  # ['csv', 'toml', 'xml', 'yaml']
"heic-to-webp" in IMPLEMENTED_CONVERSIONS  # True

La referencia completa de parámetros vive en Parámetros y opciones.


Inteligencia web (V2)#

Cada lectura de V2 lleva render_quality, un float de 0.0 a 1.0 que dice con qué honestidad se renderizó la página. Una pantalla de desafío, un muro de cookies, una barrera de inicio de sesión, una página de error HTTP o el shell vacío de una SPA vuelven con una puntuación baja, un mapa deductions que nombra qué se activó y una lista warnings. El contenido se sigue devolviendo, solo que marcado, de modo que una lectura defectuosa nunca entra sin ruido en el contexto de tu agente. Trata la puntuación como una barrera y revísala antes de usar el texto. PerceiveResult, PerceiveDirectResult, DistillItem, LookupItem.perceive y WatcherSnapshot la exponen todos. Los conceptos están en el resumen de V2.

Perceive#

Renderiza una URL y produce artefactos listos para agentes. Síncrono: la llamada devuelve la operación completada con URLs de artefacto firmadas.

op = client.v2.perceive(
    "https://example.com",
    outputs=["markdown", "screenshot", "structured"],
    extract=["tables", "metadata"],
    only_main_content=True,
)

print(op.render_quality)            # de 0.0 a 1.0
print(op.status_code)               # estado HTTP de la propia página
print(op.deductions)                # p. ej. {"login_wall": 0.65}
print(op.outputs["markdown"].url)   # URL firmada, 15 minutos
print(op.structured)

if (op.render_quality or 0) < 0.6:
    print("Low-confidence read:", op.warnings)
Opción Tipo Predeterminado Descripción
outputs list[str] ["markdown", "structured"] Cualquiera de markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured.
extract list[str] -- Cualquiera de tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all.
wait_for str -- Selector CSS que esperar antes de capturar.
viewport PerceiveViewport 1920 x 1080 width de 320 a 3840, height de 240 a 2160.
cache_mode "enabled" \| "bypass" \| "refresh" valor del servidor Reutiliza, salta o reescribe el renderizado en caché.
block_resources list[str] -- Cualquiera de image, media, font, stylesheet, script, xhr, fetch, websocket, manifest, other.
only_main_content bool valor del servidor Elimina navegación, encabezados, pies y demás chrome de la página del contenido extraído.
direct_download bool False Devuelve bytes en bruto en lugar de una URL firmada. Solo lo acepta perceive; el endpoint de lotes lo rechaza.

También se aceptan: schema (dict, un esquema de extracción de forma libre que pasa intacto), js_code (str, ejecutado en la página antes de capturar), wait_timeout_ms (int), headers (dict[str, str]), cookies (list[BrowserCookie]), auth (HttpBasicAuth), proxy_url (str), geolocation (dict), action_chain (list[dict] con pasos guionizados de clic, escritura y scroll), pdf_options (PdfOptions, aplicado a la salida pdf), mobile (bool) y respect_robots (bool). perceive_batch toma el mismo conjunto salvo direct_download.

Las URLs de artefacto se vuelven a firmar en cada lectura, así que llama a client.v2.get_perceive_operation(op.operation_id) para obtener una fresca en lugar de cachear la cadena.

Bytes en bruto, sin viaje de ida y vuelta por URL firmada. perceive_direct fuerza direct_download y te entrega el propio artefacto, con los metadatos leídos de los encabezados de la respuesta.

direct = client.v2.perceive_direct("https://example.com", outputs=["markdown"])
print(direct.filename, direct.content_type, len(direct.content), direct.render_quality)

raw = client.v2.download_perceive_artifact(op.operation_id, output="markdown")

perceive_direct requiere exactamente una salida que produzca artefacto de entre markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links e images. structured puede acompañarla, pero permanece inline en el servidor; pasa cualquier otra cosa y el SDK lanza EnconvertError antes de enviar la solicitud. En download_perceive_artifact, output puede omitirse cuando la operación produjo exactamente un artefacto, y un artefacto que ha superado su ventana de retención responde 410.

Lotes. Hasta 1000 URLs comparten un bloque de opciones. Los lotes pequeños se completan inline; los mayores vuelven con estado "queued", así que sondea el identificador del trabajo.

batch = client.v2.perceive_batch(
    ["https://a.example.com", "https://b.example.com"], outputs=["markdown"], output_mode="zip"
)

done = client.v2.get_perceive_batch(batch.job_id)
print(done.status, done.completed, done.failed, done.pending)
for item in done.items:
    print(item.url, item.render_quality)

output_mode es "manifest" (predeterminado) o "zip"; cuando vale "zip", el paquete terminado está en done.zip.url. Detalles en Perceive.

Discover#

Enumera las URLs de un sitio sin renderizado en navegador, lo que lo convierte en el primer paso barato antes de percibir nada.

found = client.v2.discover(
    "https://example.com", mode="hybrid", max_urls=200, exclude_patterns=["/tag/"]
)
print(found.total, found.truncated, found.sources)
Opción Tipo Predeterminado Descripción
mode "sitemap" \| "crawl" \| "hybrid" valor del servidor Análisis de sitemap, un rastreo HTTP, o ambos fusionados y deduplicados.
max_urls / max_depth int valor del servidor Tope de URLs devueltas (truncated es True cuando había más) y profundidad de rastreo desde la semilla.
same_domain_only bool valor del servidor Se queda en el host semilla.
respect_robots bool valor del servidor Respeta robots.txt.

include_patterns y exclude_patterns (ambos list[str]) filtran el conjunto de resultados. DiscoverResult.sources guarda los recuentos brutos por fuente antes de deduplicar, como {"sitemap": 42, "crawl": 30}. Más en Discover.

Lookup#

Ejecuta una búsqueda web categorizada, renderizando opcionalmente los primeros aciertos en la misma llamada.

search = client.v2.lookup(
    "best static site generators", category="web", num_results=10, perceive_top=3
)

for hit in search.results:
    quality = hit.perceive.render_quality if hit.perceive else None
    print(hit.position, hit.title, hit.url, quality)
Opción Tipo Predeterminado Descripción
category "web" \| "news" \| "images" \| "scholar" \| "patents" \| "maps" valor del servidor Vertical de búsqueda.
time_filter "hour" \| "day" \| "week" \| "month" \| "year" -- Ventana de actualidad.
num_results / page int valor del servidor Resultados por página, y el número de página empezando en 1.
perceive_top int 0 Renderiza automáticamente las N primeras URLs de resultados. Cada una llega con su PerceiveResult completo en hit.perceive.

También se aceptan: country (str), locale (str), location (str) para consultas sensibles a la geografía y autocorrect (bool). LookupResult lleva answer_box, knowledge_graph, perceive_operation_ids y perceive_top, este último informando de cuántos resultados se renderizaron realmente, que puede ser menos de lo que pediste. La referencia está en Lookup.

Distill#

Extracción estructurada guiada por esquema a través de una o muchas páginas. Proporciona exactamente uno de urls o discover_from; schema siempre es obligatorio. Ambas reglas se comprueban en el cliente y lanzan EnconvertError antes de que salga ninguna solicitud.

from enconvert import CssField, CssSchema

extraction = client.v2.distill(
    urls=["https://example.com/pricing"],
    schema={"plans": "list of plan names with monthly prices"},
    css_schema=CssSchema(
        base_selector=".plan-card",
        fields=[
            CssField(name="name", type="text", selector="h3"),
            CssField(name="price", type="text", selector=".price"),
        ],
        target_field="plans",
    ),
)

for item in extraction.results:
    print(item.url, item.extraction_tier, item.fields_from_css, item.fields_from_llm, item.data)

El css_schema opcional se ejecuta primero y responde todo lo que los selectores alcanzan. Solo los campos que no cubre escalan al nivel del modelo de lenguaje, y extraction_tier en cada elemento informa de qué vía se ejecutó: css, llm, mixed o none.

Descubrir y destilar en una sola llamada:

from enconvert import DistillDiscoverFrom

client.v2.distill(
    discover_from=DistillDiscoverFrom(url="https://example.com", mode="sitemap", max_pages=10),
    schema={"title": "page title"},
)
Opción Tipo Predeterminado Descripción
schema dict -- (obligatorio) La forma de salida que quieres recibir, pasada intacta.
urls list[str] -- Lista explícita de páginas. Mutuamente excluyente con discover_from.
discover_from DistillDiscoverFrom -- url, mode opcional, max_pages opcional (de 1 a 50, predeterminado 10).
css_schema CssSchema -- Pasada de selectores ejecutada antes de cualquier llamada al modelo.

También se aceptan: wait_for (str), wait_timeout_ms (int), headers (dict[str, str]), cookies (list[BrowserCookie]) y respect_robots (bool). CssField admite los tipos text, attribute, html, regex, nested, list y nested_list, con attribute, pattern, default y transform (lowercase, uppercase, strip) opcionales, y fields anidados hasta cinco niveles de profundidad. Consulta Distill.

Ingest#

Convierte un sitio entero, una lista explícita de URLs o un montón de documentos subidos en JSONL fragmentado y listo para RAG. Ingest siempre es asíncrono.

import time

from enconvert import IngestChunkOptions

job = client.v2.ingest(
    mode="sitemap",
    url="https://docs.example.com",
    max_pages=100,
    chunk=IngestChunkOptions(max_words=512, sentence_overlap=1),
    webhook_url="https://my.app/hooks/enconvert",
)

status = client.v2.get_ingest_job(job.job_id)
while status.status in ("queued", "discovering", "processing"):
    time.sleep(10)
    status = client.v2.get_ingest_job(job.job_id)

print(status.status, status.pages_processed, status.total_chunks, status.output_url)

Los archivos subidos recorren el mismo ciclo de vida de trabajo bajo el modo files. Se aceptan PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, más formatos de ofimática heredados y ODF, y cada entrada puede ser una ruta, un os.PathLike, bytes en bruto o un FileData.

file_job = client.v2.ingest_files(["handbook.pdf", "notes.docx"])
Opción Tipo Predeterminado Descripción
mode "urls" \| "sitemap" \| "crawl" \| "files" "urls" urls necesita una lista urls no vacía y rechaza url. Todos los demás modos necesitan una url semilla y rechazan urls.
url / urls str / list[str] -- URL semilla para sitemap y crawl, o la lista explícita de páginas para el modo urls.
max_pages int valor del servidor Tope de páginas ingeridas.
chunk IngestChunkOptions -- max_words de 32 a 4000, predeterminado 512. sentence_overlap de 0 a 10, predeterminado 1. Combínalo con webhook_url (str), al que se llama cuando el trabajo alcanza un estado terminal.

También se aceptan opciones de modelado del rastreo y de renderizado: max_depth, same_domain_only, include_patterns, exclude_patterns, respect_robots, wait_for y wait_timeout_ms. ingest_files solo toma chunk y webhook_url.

for summary in client.v2.list_ingest_jobs(limit=20).jobs:
    print(summary.job_id, summary.status, summary.total_chunks)

client.v2.cancel_ingest_job(job.job_id)
client.v2.retry_ingest_webhook(job.job_id)

secret = client.v2.get_webhook_secret()
print(secret.signature_header, secret.signature_scheme, secret.replay_tolerance_seconds)
client.v2.rotate_webhook_secret()

cancel_ingest_job es idempotente; cancelar un trabajo que ya está en estado terminal lo devuelve sin cambios. retry_ingest_webhook responde 409 cuando el trabajo no está completado y 400 cuando no se configuró ningún webhook. rotate_webhook_secret invalida el secreto anterior de inmediato. Cobertura más profunda en Ingest.

Watch#

Vuelve a renderizar una URL con una cadencia fija y entérate cuando cambia.

watcher = client.v2.create_watcher(
    "https://example.com/pricing",
    frequency_minutes=60,
    diff_mode="auto",
    webhook_url="https://my.app/hooks/changes",
    notify_email=True,
)

for snap in client.v2.get_watcher_snapshots(watcher.watcher_id, limit=10).snapshots:
    print(snap.checked_at, snap.has_changes, snap.similarity, snap.change_count)

client.v2.list_watchers(limit=20)
client.v2.update_watcher(watcher.watcher_id, status="paused")
client.v2.update_watcher(watcher.watcher_id, webhook_url="")
client.v2.delete_watcher(watcher.watcher_id)
Opción Tipo Predeterminado Descripción
frequency_minutes int valor del servidor Intervalo entre comprobaciones. El mínimo es una hora.
diff_mode "auto" \| "text" \| "structured" \| "tables" \| "metadata" valor del servidor Qué capa de la página compara el motor de diff.
track_fields dict -- Campos con nombre que rastrear, pasados intactos.
webhook_url str -- Se llama en cada cambio detectado.
notify_email bool valor del servidor Envía las notificaciones de cambio por correo.

update_watcher necesita al menos un campo y lanza EnconvertError en caso contrario; status acepta "active" o "paused", y un webhook_url vacío limpia el webhook. delete_watcher es un borrado suave e idempotente que devuelve el vigilante marcado como eliminado con estado "deleted", tras lo cual el vigilante se lee como 404.

Los diffs de instantáneas llevan contenido de página no confiable. Los dicts de WatcherSnapshot.changes vienen directamente del sitio vigilado. Escápalos antes de renderizarlos en HTML, en el cuerpo de un correo o en un mensaje de chat.

El motor de diff y los payloads de notificación están documentados en Watch.


Opciones de PDF#

PdfOptions es una dataclass congelada compartida por convert_url_to_pdf, convert_website_to_pdf, convert_document, convert_to_pdf y la familia v2.perceive.

from enconvert import BrowserCookie, HttpBasicAuth, PdfHeaderFooter, PdfMargins, PdfOptions

client.convert_url_to_pdf(
    "https://internal.example.com/report",
    pdf_options=PdfOptions(
        page_size="A4",
        orientation="landscape",
        margins=PdfMargins(top=10, bottom=10, left=15, right=15),
        scale=0.9,
        header=PdfHeaderFooter(content="Quarterly Report", height=15),
        footer=PdfHeaderFooter(content="Confidential", height=12),
    ),
    auth=HttpBasicAuth(username="user", password="pass"),
    cookies=[BrowserCookie(name="session", value="abc123", domain="internal.example.com")],
    save_to="report.pdf",
)
Campo Tipo Descripción
page_size str "A4", "A3", "Letter", "Legal" y compañía.
page_width / page_height float Geometría de página personalizada. Juntos anulan page_size.
orientation "portrait" \| "landscape" Por defecto es vertical.
margins PdfMargins top, bottom, left, right, todos opcionales.
scale float Escala de renderizado, por ejemplo 0.9 para el 90 por ciento.
grayscale bool Posprocesa el PDF a escala de grises.
header PdfHeaderFooter content (hasta 2000 caracteres) y height.
footer PdfHeaderFooter content (hasta 2000 caracteres) y height.

BrowserCookie toma name, value y domain o url, más path, expires, http_only, secure y same_site ("Strict", "Lax", "None") opcionales. El SDK mapea http_only y same_site a sus claves camelCase de transmisión por ti.


Manejo de errores#

Los errores son clases de excepción, así que se capturan con except. Ordena los manejadores de lo específico a lo general: AuthenticationError, QuotaError y RateLimitError heredan todos de APIError, que hereda de EnconvertError.

from enconvert import APIError, AuthenticationError, EnconvertError, QuotaError, RateLimitError

try:
    op = client.v2.perceive("https://example.com")
except AuthenticationError:
    print("Invalid or missing API key. Check ENCONVERT_API_KEY.")
except QuotaError as e:
    print(f"402 from the API: {e.message}")
except RateLimitError:
    print("Too many requests. Back off and retry.")
except APIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
except EnconvertError as e:
    print(f"Rejected before the request was sent: {e}")
Clase Se lanza en Código de estado
AuthenticationError Clave inválida, ausente o revocada 401, 403
QuotaError HTTP 402 402
RateLimitError Demasiadas solicitudes 429
APIError Cualquier otro 4xx o 5xx el código real
EnconvertError Clase base, y validación del lado del cliente que nunca llega a la red --

APIError lleva status_code y message, y su str() se lee [404] Not found. Excepciones del lado del cliente con las que te puedes topar: un api_key vacío, una extensión de archivo no admitida, un par de conversión no implementado, una llamada a distill con ambos o con ninguno de urls y discover_from, un desajuste entre el modo y los argumentos de ingest, un payload vacío en update_watcher y una lista de salidas de perceive_direct que no se resuelve en exactamente un artefacto. El mapa completo de mensajes está en Códigos de error.


Recuperación de tiempos de espera#

Las conversiones largas de URLs y documentos pueden sobrevivir al tiempo de espera de un proxy inverso incluso cuando el servidor termina el trabajo. El SDK de Python se recupera de forma transparente:

  1. Antes de cada conversión de un solo archivo o una sola URL, el SDK genera una cadena hexadecimal UUID4 y la envía como job_id.
  2. Si esa solicitud vuelve con un 5xx, el SDK pasa a sondear GET /v1/convert/status/{job_id} cada 3 segundos. Un 404 ahí significa que la fila del trabajo todavía no está escrita, así que el sondeo continúa.
  3. Con success, el SDK devuelve el resultado como si nada hubiera pasado; con failed lanza APIError(500, ...) con el mensaje de error del servidor; y pasado el plazo de sondeo de 300 segundos lanza APIError(504, "Conversion timed out").

No escribes nada de código para esto. Cuando una respuesta llega por la vía de recuperación, ConversionResult.job_id queda poblado para que puedas correlacionarlo en tus logs.

Dos excepciones. convert_website_to_pdf y convert_website_to_screenshot se quedan fuera, porque el envío de un sitio web no tiene fila por trabajo, así que un 5xx ahí significa que falló el propio envío y sale a la superficie directamente. Los endpoints de V2 tampoco se sondean; usa sus propios identificadores de trabajo con get_perceive_batch o get_ingest_job.


Configuración#

client = Enconvert(
    api_key=os.environ["ENCONVERT_API_KEY"], timeout=300.0, base_url="https://api.enconvert.com"
)
Opción Tipo Predeterminado Descripción
api_key str -- (obligatorio) Clave de API privada. Un valor vacío lanza EnconvertError en la construcción.
timeout float 300.0 Tiempo de espera por solicitud en segundos, aplicado a cada llamada, subidas incluidas.
base_url str https://api.enconvert.com URL base de la API. Las barras finales se eliminan.

El cliente mantiene una requests.Session, así que las conexiones se agrupan entre llamadas; reutiliza un cliente en lugar de construir uno por solicitud. Tu clave viaja como encabezado X-API-Key, y las descargas de save_to acceden a la URL de almacenamiento firmada como un GET simple sin autenticar, transmitido a disco en bloques de 64 KB, así que la clave nunca sale del host de la API.

Nunca incrustes la clave de API en el código. Léela de una variable de entorno o de tu gestor de secretos, y mantenla en el servidor. Cualquiera que tenga tu clave privada puede ejecutar trabajo contra tu proyecto.

Forma del resultado#

Los métodos de conversión devuelven un ConversionResult congelado:

@dataclass(frozen=True)
class ConversionResult:
    presigned_url: str
    object_key: str
    filename: str
    file_size: int | None = None
    conversion_time_seconds: float | None = None
    job_id: str | None = None

La URL prefirmada es de corta duración. Pasa save_to, o busca la URL tú mismo, y guarda los bytes en tu propio bucket cuando necesites acceso duradero.

Tipo Devuelto por Campos clave
JobStatus get_job_status status (processing, success, failed), presigned_url, object_key, error
BatchSubmission, BatchStatus convert_website_to_*, get_batch_status, wait_for_batch batch_id, status, url_count, total, completed, failed, discovery_method, zip_download_url, items
PerceiveResult v2.perceive, v2.get_perceive_operation operation_id, render_quality, status_code, deductions, outputs, structured, warnings, cache_hit
PerceiveDirectResult v2.perceive_direct, v2.download_perceive_artifact content (bytes en bruto), content_type, filename, render_quality, source_status_code
IngestJob v2.ingest, v2.ingest_files, v2.get_ingest_job job_id, status, mode, pages_processed, total_chunks, output_url, error_message
Watcher los métodos de watch de v2 watcher_id, status, frequency_minutes, diff_mode, checks_count, next_check_at, last_change_at

Las URLs de artefactos de V2 se firman para 15 minutos y se vuelven a firmar cada vez que lees la operación, así que llama a get_perceive_operation en lugar de cachear una cadena de URL.


Código fuente e incidencias#


Preguntas frecuentes#

¿Cómo convierto archivos en Python?#

Ejecuta pip install enconvert, construye un cliente con Enconvert(api_key=os.environ["ENCONVERT_API_KEY"]) y llama a un método tipado como convert_document, convert_image, convert_to_pdf o convert_to_markdown. Pasa save_to="out.pdf" y el SDK transmite el archivo terminado directamente al disco en lugar de darte una URL que buscar tú mismo.

¿Cómo convierto una URL a PDF en Python?#

Llama a client.convert_url_to_pdf("https://example.com", save_to="page.pdf"). Fija single_page=False más pdf_options=PdfOptions(page_size="A4") para una salida paginada, y ajusta viewport_width, load_media o enable_scroll cuando una página necesite un lienzo más ancho o imágenes de carga diferida.

¿Cómo convierto DOCX a PDF en Python?#

O bien client.convert_document("report.docx", save_to="report.pdf"), ya que output_format vale "pdf" por defecto, o bien client.convert_to_pdf("report.docx", save_to="report.pdf") cuando quieras que el formato se detecte automáticamente en el servidor. La misma llamada gestiona XLSX, PPTX, ODT, ODS, ODP, OTS, Pages y Numbers.

¿Cómo convierto HEIC a WebP en Python?#

client.convert_image("photo.heic", output_format="webp", save_to="photo.webp"). El formato de entrada sale de la extensión del nombre de archivo, y jpeg, png, svg, heic y webp se convierten todos entre sí. ¿Trabajas desde memoria en lugar de desde disco? Envuelve los bytes en FileData(data=blob, filename="photo.heic") para que la extensión sobreviva.

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

Usa client.v2.perceive(url, outputs=["markdown"]) y lee op.outputs["markdown"].url, o client.v2.perceive_direct(url, outputs=["markdown"]) para recibir los bytes en el cuerpo de la respuesta. Añade only_main_content=True para descartar navegación y pies de página. Para una conversión simple sin puntuación ni extracción, convert_url_to_markdown es la llamada más ligera.

¿Qué significa render_quality y cuándo debería reintentar una página?#

Es un float de 0.0 a 1.0 en cada lectura de V2 que dice con qué honestidad se renderizó la página. Una pantalla de desafío, un muro de cookies, una barrera de inicio de sesión, una página de error HTTP o el shell vacío de una SPA puntúan bajo y nombran lo que se activó en deductions, con detalle en warnings y el propio estado HTTP de la página en status_code. Úsalo como barrera: trata una puntuación baja como una señal para reintentar con cache_mode="refresh", un selector wait_for que solo case con contenido real, o cookies distintas, en lugar de darle el texto a tu modelo.

¿Cómo convierto un sitio de documentación entero en fragmentos para RAG en Python?#

Llama a client.v2.ingest(mode="sitemap", url="https://docs.example.com", max_pages=100, chunk=IngestChunkOptions(max_words=512, sentence_overlap=1)). El trabajo es asíncrono, así que o bien sondeas get_ingest_job(job_id) hasta que el estado deje de ser queued, discovering y processing, o bien pasas webhook_url y esperas a que te llamen. El JSONL terminado está en output_url. Para documentos locales en lugar de un sitio, ingest_files ejecuta el mismo pipeline.

¿El SDK de Python admite asyncio?#

De forma nativa no. Todos los métodos son síncronos y están construidos sobre requests. Dentro de una aplicación asíncrona, envuelve las llamadas en await asyncio.to_thread(client.convert_to_pdf, "report.docx") o entrégalas a un concurrent.futures.ThreadPoolExecutor para que el bucle de eventos siga corriendo. El cliente se puede compartir entre hilos con seguridad porque mantiene una requests.Session agrupada.

¿Cómo sobrevive el SDK a un tiempo de espera del proxy en una conversión larga?#

Cada conversión de un solo archivo y de una sola URL envía un job_id generado. Si la solicitud devuelve 5xx, el SDK sondea GET /v1/convert/status/{job_id} cada 3 segundos durante hasta 300 segundos, devuelve normalmente en cuanto el trabajo informa success, lanza APIError(500, ...) si informa failed, y lanza APIError(504, "Conversion timed out") si se cumple el plazo. Los envíos de lotes de sitios web se saltan esta vía, ya que un fallo ahí significa que el propio envío no llegó.