---
seo_title: SDK Python de Conversión de Archivos: pip install enconvert
meta_desc: SDK oficial de EnConvert para Python. Instálalo con pip, convierte más de 40 formatos y usa el espacio de nombres V2 para percibir, extraer y vigilar la web.
keywords: sdk de conversión de archivos en python, convertir archivos con python, url a pdf en python, api de web scraping en python, docx a pdf en python, sdk de enconvert para python, pip install enconvert, heic a webp en python, html a markdown en python, pipeline de ingesta rag en python, monitorizar cambios de una web con python
---

# 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.

<div class="alert alert-info">
<strong>PyPI:</strong> <code>enconvert</code> &middot; <strong>Fuente:</strong> <a href="https://github.com/conversionapi/python-sdk">conversionapi/python-sdk</a> &middot; <strong>Python:</strong> 3.9+ &middot; <strong>Dependencia en tiempo de ejecución:</strong> <code>requests&gt;=2.28</code> &middot; <strong>Licencia:</strong> MIT
</div>

---

## Instalación

```bash
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.

```python
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](/es/dashboard), y consulta [Autenticación](/es/docs/authentication) 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](/es/docs/endpoints-overview).

---

## 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.

```python
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](#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]`).

<div class="alert alert-warning">
<strong>No combines <code>auth</code> con un encabezado <code>Authorization</code>.</strong> La API rechaza el conflicto en lugar de adivinar qué credencial gana. Elige una.
</div>

---

### convert_url_to_screenshot

Captura un PNG de cualquier URL.

```python
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.

```python
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`](#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.

```python
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.

```python
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.

```python
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.

```python
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`](#ingest).

---

### convert_to_pdf

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

```python
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.

<div class="alert alert-warning">
<strong>En este endpoint solo se respeta <code>pdf_options.grayscale</code>.</strong> 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 <code>convert_document</code> o <code>convert_url_to_pdf</code>.
</div>

---

### 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.

```python
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](#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:

```python
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](/es/docs/parameters-options).

---

## 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](/es/docs/v2-overview).

### Perceive

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

```python
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.

```python
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.

```python
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](/es/docs/v2-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.

```python
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](/es/docs/v2-discover).

### Lookup

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

```python
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](/es/docs/v2-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.

```python
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:

```python
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](/es/docs/v2-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.

```python
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`.

```python
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`.

```python
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](/es/docs/v2-ingest).

### Watch

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

```python
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`.

<div class="alert alert-warning">
<strong>Los diffs de instantáneas llevan contenido de página no confiable.</strong> Los dicts de <code>WatcherSnapshot.changes</code> vienen directamente del sitio vigilado. Escápalos antes de renderizarlos en HTML, en el cuerpo de un correo o en un mensaje de chat.
</div>

El motor de diff y los payloads de notificación están documentados en [Watch](/es/docs/v2-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`.

```python
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`.

```python
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](/es/docs/error-codes).

---

## 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

```python
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.

<div class="alert alert-warning">
<strong>Nunca incrustes la clave de API en el código.</strong> 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.
</div>

---

## Forma del resultado

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

```python
@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

- **PyPI:** [enconvert](https://pypi.org/project/enconvert/)
- **GitHub:** [conversionapi/python-sdk](https://github.com/conversionapi/python-sdk)
- **Python:** 3.9, 3.10, 3.11, 3.12, 3.13
- **Licencia:** MIT
- **Otros clientes:** [Todos los SDKs](/es/docs/sdks)

---

## 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ó.
