---
seo_title: SDK Java de Conversión de Archivos: Cliente Maven | EnConvert
meta_desc: SDK oficial de EnConvert para Java 17+. Añade una dependencia de Maven o Gradle para convertir archivos y para perceive, discover, distill, ingest y watch.
keywords: sdk de conversión de archivos para java, convertir archivos en java, cliente api de conversión maven, librería de conversión de archivos gradle, url a pdf en java, docx a pdf en java, html a pdf en java, heic a webp en java, api de web scraping con java, página web a markdown en java, pipeline de ingesta rag en java, enconvert java sdk
---

# SDK de Conversión de Archivos para Java

`com.enconvert:enconvert-sdk` es el cliente oficial de Java para la API de EnConvert. Una única dependencia de Maven o Gradle te da conversión de archivos (URL a PDF, DOCX a PDF, HEIC a WebP, cualquier cosa a Markdown) más toda la superficie de inteligencia web V2: perceive, discover, lookup, distill, ingest y watch. Está dirigido a Java 17 y superior, se ejecuta sobre el `java.net.http.HttpClient` incorporado en el JDK y arrastra Gson como su única dependencia de terceros. Cada llamada es un método bloqueante corriente que devuelve un record tipado, y las conversiones largas se recuperan de forma transparente de los timeouts del proxy inverso mediante sondeo del estado del job.

<div class="alert alert-info">
<strong>Maven Central:</strong> <code>com.enconvert:enconvert-sdk:0.0.1</code> · <strong>Fuente:</strong> <a href="https://github.com/conversionapi/java-sdk">conversionapi/java-sdk</a> · <strong>Java:</strong> 17+ · <strong>Dependencias:</strong> solo Gson
</div>

---

## Instalación

```groovy
// build.gradle
dependencies {
    implementation 'com.enconvert:enconvert-sdk:0.0.1'
}
```

```kotlin
// build.gradle.kts
dependencies {
    implementation("com.enconvert:enconvert-sdk:0.0.1")
}
```

```xml
<!-- pom.xml -->
<dependency>
    <groupId>com.enconvert</groupId>
    <artifactId>enconvert-sdk</artifactId>
    <version>0.0.1</version>
</dependency>
```

De HTTP se encarga `java.net.http.HttpClient` del JDK. El único artefacto de terceros que viene con él es [Gson](https://github.com/google/gson) para JSON, declarado como dependencia `api` para que sea visible en tu classpath de compilación.

---

## Inicio rápido

```java
import com.enconvert.Enconvert;
import com.enconvert.model.ConversionResult;
import com.enconvert.model.UrlToPdfOptions;
import com.enconvert.model.v2.PerceiveOptions;
import com.enconvert.model.v2.PerceiveResult;

import java.util.List;

Enconvert client = new Enconvert(System.getenv("ENCONVERT_API_KEY"));

ConversionResult pdf = client.convertUrlToPdf("https://example.com",
        UrlToPdfOptions.builder().saveTo("page.pdf").build());
System.out.println(pdf.presignedUrl());

// Lee una página como debería hacerlo tu agente, con una puntuación de calidad adjunta.
PerceiveResult page = client.v2.perceive("https://example.com",
        PerceiveOptions.builder().outputs(List.of("markdown", "structured")).build());
System.out.println(page.outputs().get("markdown").url());
System.out.println(page.renderQuality());   // p. ej. 0.93
```

Cada clase de opciones es un builder inmutable y cada respuesta es un `record` de Java, así que los accesores se leen como `pdf.presignedUrl()` y `page.renderQuality()`. El cliente mantiene un único `HttpClient` compartido y ningún estado mutable por solicitud, de modo que una sola instancia puede ser un singleton o un bean de Spring compartido entre hilos. Los fragmentos siguientes omiten los imports: las opciones y los tipos de respuesta viven en `com.enconvert.model` (conversión) y `com.enconvert.model.v2` (inteligencia web), y las excepciones en `com.enconvert.exceptions`.

---

## Qué expone el cliente

`Enconvert` lleva la superficie de conversión directamente. La superficie de inteligencia web vive en el campo público final `client.v2`, una instancia de `EnconvertV2`.

| Grupo | Métodos | Devuelve |
|-------|---------|----------|
| URL individual | `convertUrlToPdf`, `convertUrlToScreenshot`, `convertUrlToMarkdown` | `ConversionResult` |
| Subida de archivo | `convertImage`, `convertDocument`, `convertToMarkdown`, `convertToPdf` | `ConversionResult` |
| Sitio completo | `convertWebsiteToPdf`, `convertWebsiteToScreenshot` | `BatchSubmission` |
| Estado | `getJobStatus`, `getBatchStatus`, `waitForBatch` | `JobStatus`, `BatchStatus` |
| `v2` perceive | `perceive`, `perceiveDirect`, `getPerceiveOperation`, `perceiveBatch`, `getPerceiveBatch`, `downloadPerceiveArtifact` | `PerceiveResult`, `PerceiveDirectResult`, `PerceiveBatchResult` |
| `v2` discover | `discover` | `DiscoverResult` |
| `v2` lookup | `lookup` | `LookupResult` |
| `v2` distill | `distill` | `DistillResult` |
| `v2` ingest | `ingest`, `ingestFiles`, `getIngestJob`, `listIngestJobs`, `cancelIngestJob`, `retryIngestWebhook`, `getWebhookSecret`, `rotateWebhookSecret` | `IngestJob`, `IngestJobList`, `WebhookRetryResult`, `WebhookSecret` |
| `v2` watch | `createWatcher`, `getWatcher`, `listWatchers`, `getWatcherSnapshots`, `updateWatcher`, `deleteWatcher` | `Watcher`, `WatcherList`, `WatcherSnapshotList` |

La mayoría de los métodos tienen una sobrecarga corta sin argumento de opciones, así que tanto `client.v2.perceive(url)` como `client.convertUrlToPdf(url)` compilan. `convertImage`, `distill` e `ingest` son las excepciones: cada uno recibe siempre su objeto de opciones, porque el formato de destino, el schema y el origen son obligatorios respectivamente.

---

## Conversión de archivos

Los endpoints de conversión cubren 43 pares `{input}-to-{output}` implementados, dos endpoints con detección automática (`anything-to-markdown` y `anything-to-pdf`) y los endpoints de renderizado en navegador. La referencia completa de parámetros está en [Parámetros y opciones](/es/docs/parameters-options).

### convertUrlToPdf

Renderiza cualquier URL pública a PDF.

```java
ConversionResult result = client.convertUrlToPdf("https://example.com",
        UrlToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder().pageSize("A4").orientation("landscape").build())
                .singlePage(false)
                .viewportWidth(1440)
                .saveTo("report.pdf")
                .build());
```

| Opción | Tipo | Por defecto | Descripción |
|--------|------|-------------|-------------|
| `saveTo` | `String` | ninguno | Ruta local donde escribir el PDF. Los directorios padre se crean automáticamente. |
| `singlePage` | `boolean` | `true` | `true` genera una única página continua. `false` pagina usando `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions` | ninguno | Tamaño de página, orientación, márgenes, escala, escala de grises, encabezado, pie. Ver [Opciones de PDF](#opciones-de-pdf). |
| `viewportWidth` | `int` | `1920` | Ancho del viewport del navegador en píxeles. |
| `viewportHeight` | `int` | `1080` | Alto del viewport del navegador en píxeles. |
| `loadMedia`, `enableScroll` | `boolean` | `true` | Espera a las imágenes y el vídeo antes de capturar, y desplaza de arriba abajo para disparar los cargadores diferidos. |
| `outputFilename` | `String` | auto | Sustituye el nombre de archivo generado. |
| `auth`, `cookies`, `headers` | `HttpBasicAuth`, `List<BrowserCookie>`, `Map<String, String>` | ninguno | Credenciales, cookies inyectadas y encabezados de solicitud extra para páginas tras un inicio de sesión. |

### convertUrlToScreenshot

Captura un PNG de cualquier URL. Las mismas opciones de viewport, medios, desplazamiento, nombre de archivo, autenticación, cookies y encabezados que `convertUrlToPdf`, menos `singlePage` y `pdfOptions`.

```java
client.convertUrlToScreenshot("https://example.com",
        UrlToScreenshotOptions.builder().viewportWidth(1440).saveTo("screenshot.png").build());
```

### convertUrlToMarkdown

Extrae Markdown limpio con sabor GitHub desde 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 un frontmatter YAML (title, description, url, links, images). El mismo conjunto de opciones que `convertUrlToScreenshot`.

```java
client.convertUrlToMarkdown("https://example.com/article",
        UrlToMarkdownOptions.builder().saveTo("article.md").build());
```

### convertImage

Convierte entre `jpeg`, `png`, `svg`, `heic` y `webp`, o rasteriza un PDF a JPEG.

```java
// Desde una ruta en disco
client.convertImage(Path.of("photo.heic"),
        ConvertImageOptions.builder("webp").saveTo("photo.webp").build());

// Rasteriza un PDF
client.convertImage(Path.of("scan.pdf"),
        ConvertImageOptions.builder("jpeg").saveTo("scan.jpeg").build());

// Desde bytes en memoria con un nombre de archivo explícito
byte[] bytes = Files.readAllBytes(Path.of("photo.heic"));
client.convertImage(new FileInput(bytes, "photo.heic"),
        ConvertImageOptions.builder("webp").build());
```

Existen tres sobrecargas de entrada en cada método de archivo: `java.nio.file.Path` (lectura desde disco), `byte[]` en crudo (el nombre de archivo pasa a ser `upload.bin` por defecto) y `com.enconvert.FileInput` cuando necesitas emparejar bytes en memoria con un nombre de archivo real. El formato de entrada se resuelve a partir de la extensión; el formato de salida es obligatorio.

| Opción | Tipo | Obligatorio | Descripción |
|--------|------|-------------|-------------|
| `outputFormat` | `String` | Sí | Se pasa a `ConvertImageOptions.builder(outputFormat)`. Uno de `jpeg`, `png`, `svg`, `heic`, `webp`. Los alias como `jpg` se normalizan. |
| `saveTo` | `String` | no | Ruta local donde escribir el resultado. |
| `outputFilename` | `String` | no | Sustituye el nombre de archivo generado. |

### convertDocument

Convierte documentos y formatos de datos estructurados. El formato de salida es `pdf` por defecto.

```java
// docx a pdf
client.convertDocument(Path.of("report.docx"),
        ConvertDocumentOptions.builder().saveTo("report.pdf").build());

// json a yaml
client.convertDocument(Path.of("data.json"),
        ConvertDocumentOptions.builder().outputFormat("yaml").saveTo("data.yaml").build());

// markdown a pdf con configuración de página
client.convertDocument(Path.of("README.md"),
        ConvertDocumentOptions.builder()
                .outputFormat("pdf")
                .pdfOptions(PdfOptions.builder()
                        .pageSize("A4")
                        .margins(new PdfMargins(20.0, 20.0, 25.0, 25.0))
                        .build())
                .saveTo("readme.pdf")
                .build());
```

**Extensiones de entrada reconocidas:** `.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 documento propio. Envía los archivos `.epub` a través de [`convertToPdf`](#converttopdf) o [`convertToMarkdown`](#converttomarkdown) en su lugar.

| Opción | Tipo | Por defecto | Descripción |
|--------|------|-------------|-------------|
| `outputFormat` | `String` | `"pdf"` | Formato de destino. |
| `saveTo` | `String` | ninguno | Ruta local donde escribir el resultado. |
| `outputFilename` | `String` | ninguno | Sustituye el nombre de archivo generado. |
| `pdfOptions` | `PdfOptions` | ninguno | Configuración de página, respetada cuando la salida es PDF. |

### Conversiones admitidas

`convertImage` y `convertDocument` validan el par `{input}-to-{output}` contra los endpoints que la API implementa realmente. Un par no admitido lanza `IllegalArgumentException` de inmediato, enumerando las salidas válidas para esa entrada, en lugar de pagar una ida y vuelta por una solicitud que no puede tener éxito.

| Entrada | Salidas |
|---------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `yaml` | `json` |
| `csv` | `json`, `xml` |
| `toml` | `json` |
| `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` |

La misma tabla se puede consultar en tiempo de ejecución a través de `com.enconvert.Formats`: `Formats.validOutputsFor("json")` devuelve `[csv, toml, xml, yaml]`, `Formats.validOutputsFor("pdf")` devuelve `[jpeg]` y `Formats.IMPLEMENTED_CONVERSIONS` contiene los 43 nombres de endpoint.

### convertToMarkdown

Envía cualquier documento admitido a través de un único endpoint con detección automática y recibe Markdown limpio. La jerarquía de encabezados sobrevive, lo que convierte a esto en una primera etapa natural para un pipeline de RAG.

```java
client.convertToMarkdown(Path.of("handbook.docx"),
        ConvertToMarkdownOptions.builder().saveTo("handbook.md").build());
```

Acepta PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, además de formatos ofimáticos heredados y ODF. El formato se detecta en el servidor, así que no hay comprobación de extensión en el cliente: cualquier archivo se sube tal cual. Las imágenes no están admitidas y se rechazan con `400`. Las únicas opciones son `saveTo` y `outputFilename`.

### convertToPdf

El otro endpoint con detección automática: casi cualquier cosa a PDF.

```java
// pptx a pdf
client.convertToPdf(Path.of("slides.pptx"),
        ConvertToPdfOptions.builder().saveTo("slides.pdf").build());

// pdf de paso directo, convertido a escala de grises
client.convertToPdf(Path.of("scan.pdf"),
        ConvertToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder().grayscale(true).build())
                .saveTo("scan-gray.pdf")
                .build());
```

Acepta formatos ofimáticos, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texto plano, imágenes rasterizadas, SVG, EPUB y un PDF existente como paso directo. EPUB se gestiona aquí porque no tiene un par de documento propio. Las opciones son `saveTo`, `outputFilename` y `pdfOptions`.

<div class="alert alert-warning">
<strong>En este endpoint solo se respeta <code>grayscale</code>.</strong> La geometría de página (tamaño de página, ancho y alto, orientación, márgenes, escala, encabezado, pie) es ignorada por <code>anything-to-pdf</code>. Cuando necesites la configuración de página completa, pasa por <code>convertDocument</code> o <code>convertUrlToPdf</code> en su lugar.
</div>

### Conversión de sitios completos

`convertWebsiteToPdf` y `convertWebsiteToScreenshot` descubren todas las páginas de un sitio, convierten cada una en segundo plano y empaquetan los resultados en un único ZIP. Ambos son asíncronos y devuelven un `BatchSubmission`. Ambos requieren una clave de API privada.

```java
BatchSubmission batch = client.convertWebsiteToPdf("https://example.com",
        WebsiteToPdfOptions.builder()
                .crawlMode("sitemap")                      // "auto" (por defecto), "sitemap", "full"
                .excludePatterns(List.of("/blog/tag/"))    // solo en modo de rastreo completo
                .notificationEmail("ops@example.com")
                .build());

System.out.println(batch.batchId() + " " + batch.urlCount() + " " + batch.discoveryMethod());

// Bloquea hasta que el lote salga de "processing", luego guarda el ZIP
BatchStatus status = client.waitForBatch(batch.batchId(),
        WaitForBatchOptions.builder().saveTo("site.zip").build());
System.out.println(status.completed() + " of " + status.total() + " pages converted");
```

`convertWebsiteToScreenshot` funciona igual y produce un ZIP de PNG. `waitForBatch` sondea cada 5 segundos por defecto, se rinde a los 30 minutos y acepta `intervalMs`, `timeoutMs` y `saveTo`. Al agotarse el tiempo lanza `ApiException` con estado `504`.

### Sondear el estado por tu cuenta

```java
JobStatus job = client.getJobStatus("job_abc123");
if ("success".equals(job.status())) System.out.println(job.presignedUrl());
if ("failed".equals(job.status())) System.err.println(job.error());
BatchStatus batch = client.getBatchStatus("bat_abc123");
if (!"processing".equals(batch.status())) System.out.println(batch.zipDownloadUrl());
```

---

## Inteligencia web (V2)

Todo lo que hay bajo `client.v2` convierte páginas web en datos listos para agentes. Cada lectura lleva `renderQuality`, una puntuación de 0.0 a 1.0 que indica con qué limpieza se renderizó realmente la página. Una página de desafío, un muro de cookies o el armazón vacío de una SPA vuelven con una puntuación baja y con `warnings()` y `deductions()` rellenos, en lugar de pasar por contenido real, de modo que una mala lectura nunca entra en silencio en el contexto de tu agente. El contenido se sigue devolviendo; simplemente queda marcado. Los fundamentos del modelo están en la [visión general de V2](/es/docs/v2-overview).

### Perceive

Renderiza una URL en los artefactos que pidas. Referencia del endpoint: [Perceive](/es/docs/v2-perceive).

```java
PerceiveResult page = client.v2.perceive("https://example.com",
        PerceiveOptions.builder()
                .outputs(List.of("markdown", "screenshot", "structured"))
                .extract(List.of("tables", "metadata"))
                .waitFor("css:main")
                .build());

System.out.println(page.renderQuality());                 // de 0.0 a 1.0
System.out.println(page.statusCode() + " " + page.deductions());  // p. ej. 200 {http_error=0.7}
System.out.println(page.outputs().get("markdown").url()); // URL firmada, 15 minutos
System.out.println(page.structured());                    // forma definida por quien llama
```

| Opción | Tipo | Por defecto | Descripción |
|--------|------|-------------|-------------|
| `outputs` | `List<String>` | `["markdown", "structured"]` | Cualquiera de `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `List<String>` | ninguno | Objetivos heurísticos: `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `schema` | `Map<String, Object>` | ninguno | Schema JSON para la extracción estructurada. |
| `waitFor`, `waitTimeoutMs` | `String`, `int` | ninguno, `30000` | Un selector CSS, opcionalmente con el prefijo `css:`, o `js:<expr>` que esperar, con un presupuesto de 0 a 60000 ms. |
| `jsCode` | `String` | ninguno | JavaScript ejecutado tras la navegación, máximo 20000 caracteres. |
| `viewport`, `mobile` | `PerceiveViewport`, `boolean` | 1920 x 1080, `false` | Ancho de 320 a 3840, alto de 240 a 2160, o emulación móvil. |
| `onlyMainContent` | `boolean` | `true` | Elimina navegación, encabezado, pie y banners de cookies del artefacto Markdown y del extracto `main_content`. |
| `cacheMode` | `String` | `"enabled"` | `enabled` reutiliza una caché de 1 hora, `bypass` la omite, `refresh` fuerza un nuevo renderizado. |
| `blockResources` | `List<String>` | ninguno | Tipos de recurso que el navegador no debe cargar, por ejemplo `image`, `font`, `script`. |
| `pdfOptions` | `PdfOptions` | ninguno | Solo tiene sentido cuando `outputs` contiene `pdf`. |
| `headers`, `cookies`, `auth` | `Map`, `List<BrowserCookie>`, `HttpBasicAuth` | ninguno | Encabezados de solicitud, cookies inyectadas, credenciales HTTP Basic. |
| `respectRobots` | `boolean` | ninguno | Respeta las reglas robots del sitio. |

<div class="alert alert-warning">
<strong>Todavía sin conectar.</strong> <code>proxyUrl</code>, <code>geolocation</code> y <code>actionChain</code> existen en el builder pero no están disponibles en el servidor y actualmente se rechazan con <code>422</code>.
</div>

Las URL de artefacto se firman durante 15 minutos y se vuelven a firmar en cada lectura de la operación, así que `client.v2.getPerceiveOperation(page.operationId())` te entrega enlaces frescos. Agrupa hasta 1000 URL con un único bloque de opciones compartido: los lotes pequeños se completan en línea, los más grandes vuelven con estado `queued`, así que sondéalos.

```java
PerceiveBatchResult batch = client.v2.perceiveBatch(
        List.of("https://a.example.com", "https://b.example.com"),
        PerceiveBatchOptions.builder()
                .outputs(List.of("markdown"))
                .outputMode("zip")          // "manifest" (por defecto) o "zip"
                .build());

PerceiveBatchResult done = client.v2.getPerceiveBatch(batch.jobId());
System.out.println(done.completed() + "/" + done.total());
```

Sáltate por completo la ida y vuelta de la URL firmada con `perceiveDirect`, que devuelve los bytes del artefacto en streaming. Requiere exactamente una salida que produzca artefacto (cualquiera salvo `structured`) y lanza `IllegalArgumentException` antes de enviar si pides más o menos:

```java
PerceiveDirectResult direct = client.v2.perceiveDirect("https://example.com",
        PerceiveOptions.builder().outputs(List.of("pdf")).build());

Files.write(Path.of(direct.filename()), direct.content());
System.out.println(direct.renderQuality() + " " + direct.contentType());

// Vuelve a descargar un artefacto almacenado de una operación anterior
PerceiveDirectResult stored = client.v2.downloadPerceiveArtifact(page.operationId(), "markdown");
```

`downloadPerceiveArtifact` acepta un nombre de salida nulo u omitido cuando la operación produjo exactamente un artefacto; en caso contrario devuelve `400` enumerando las salidas disponibles. Una vez que el artefacto almacenado caduca, devuelve `410`.

### Discover

Enumera las URL de un sitio sin renderizar nada. No interviene ningún navegador, así que es rápido. Referencia del endpoint: [Discover](/es/docs/v2-discover).

```java
DiscoverResult found = client.v2.discover("https://example.com",
        DiscoverOptions.builder()
                .mode("hybrid")                        // "sitemap", "crawl", "hybrid"
                .maxUrls(200)
                .maxDepth(3)
                .excludePatterns(List.of("/tag/"))
                .build());

System.out.println(found.total() + " urls, truncated=" + found.truncated());
found.urls().forEach(System.out::println);
```

| Opción | Tipo | Por defecto | Rango |
|--------|------|-------------|-------|
| `mode` | `String` | `"hybrid"` | `sitemap`, `crawl`, `hybrid` |
| `maxUrls` | `int` | `100` | de 1 a 1000 |
| `maxDepth` | `int` | `2` | de 1 a 5 |
| `includePatterns`, `excludePatterns` | `List<String>` | ninguno | Lista de permitidos y lista de bloqueados con expresiones regulares, máximo 50 cada una. La lista de bloqueados se aplica en segundo lugar. |
| `sameDomainOnly`, `respectRobots` | `boolean` | `true`, ninguno | Permanece en el dominio semilla y respeta las reglas robots del sitio. |

### Lookup

Búsqueda web por categorías, con renderizado automático opcional de los primeros resultados. Referencia del endpoint: [Lookup](/es/docs/v2-lookup).

```java
LookupResult search = client.v2.lookup("best static site generators",
        LookupOptions.builder()
                .category("web")        // web, news, images, scholar, patents, maps
                .numResults(10)
                .country("us")
                .timeFilter("month")    // hour, day, week, month, year
                .perceiveTop(3)         // renderiza automáticamente los 3 primeros resultados
                .build());

search.results().forEach(hit -> {
    System.out.println(hit.position() + " " + hit.title() + " " + hit.url());
    if (hit.perceive() != null) System.out.println("  quality " + hit.perceive().renderQuality());
});
```

Con `perceiveTop` por encima de 0 (de 0 a 10, por defecto 0), las URL de los N primeros resultados se renderizan mediante perceive y cada resultado lleva su `PerceiveResult` completo en línea en `hit.perceive()`. `numResults` va de 1 a 100 y su valor por defecto es 10; `page` va de 1 a 10.

### Distill

Extracción estructurada guiada por schema a través de una o varias páginas. Referencia del endpoint: [Distill](/es/docs/v2-distill).

```java
DistillResult extraction = client.v2.distill(
        DistillOptions.builder(Map.of("products", "list of product names with their listed price"))
                .urls(List.of("https://example.com/catalog"))
                .cssSchema(CssSchema.builder(".product-card", List.of(
                                CssField.builder("name", "text").selector("h3").build(),
                                CssField.builder("price", "text").selector(".price").build()))
                        .targetField("products")
                        .build())
                .build());

extraction.results().forEach(item ->
        System.out.println(item.data() + " via " + item.extractionTier()));
```

El `cssSchema` opcional ejecuta primero una pasada CSS gratuita; solo los campos que no puede resolver escalan al nivel del LLM, e `item.extractionTier()` informa de qué ruta produjo el registro (`css`, `llm`, `mixed` o `none`). También puedes descubrir las URL primero en lugar de enumerarlas:

```java
client.v2.distill(
        DistillOptions.builder(Map.of("title", "page title", "summary", "one-line summary"))
                .discoverFrom(new DistillDiscoverFrom("https://example.com", "sitemap", 10))
                .build());
```

Debe estar establecido exactamente uno de `urls` (máximo 50) y `discoverFrom`, y la factoría del builder exige `schema`. Ambas reglas se comprueban en el cliente y lanzan `IllegalArgumentException` antes de que salga ninguna solicitud.

### Ingest

Convierte un sitio entero, o un conjunto de documentos subidos, en JSONL troceado y listo para RAG mediante un único pipeline. Ingest siempre es asíncrono. Referencia del endpoint: [Ingest](/es/docs/v2-ingest).

```java
// Desde un sitio
IngestJob job = client.v2.ingest(IngestOptions.builder()
        .mode("sitemap")                                  // "urls" (por defecto), "sitemap", "crawl"
        .url("https://docs.example.com")
        .maxPages(100)
        .chunk(new IngestChunkOptions(512, 1))            // maxWords, sentenceOverlap
        .webhookUrl("https://my.app/hooks/enconvert")
        .build());

// O desde archivos subidos
IngestJob fileJob = client.v2.ingestFiles(
        List.of(new FileInput(Files.readAllBytes(Path.of("handbook.pdf")), "handbook.pdf"),
                new FileInput(Files.readAllBytes(Path.of("notes.docx")), "notes.docx")),
        IngestFilesOptions.builder().chunk(new IngestChunkOptions(512, 1)).build());

// Sondea hasta obtener el JSONL
IngestJob status = client.v2.getIngestJob(job.jobId());
if ("completed".equals(status.status())) {
    System.out.println(status.outputUrl() + " (" + status.totalChunks() + " chunks)");
}

client.v2.listIngestJobs(V2ListOptions.builder().limit(20).build());
client.v2.cancelIngestJob(job.jobId());   // idempotente
```

`ingestFiles` acepta PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT y MD, además de formatos ofimáticos heredados y ODF. El troceado usa por defecto 512 palabras (de 32 a 4000) con 1 frase de solapamiento (de 0 a 10). `mode` es `urls` por defecto, lo que exige una lista `urls` no vacía y prohíbe `url`; cualquier otro modo exige una `url` semilla y prohíbe `urls`. El SDK impone ese emparejamiento antes de enviar.

Los webhooks de finalización se firman con HMAC. Obtén el secreto y los nombres de encabezado que necesitas para verificar una entrega, rótalo cuando se filtre y vuelve a disparar una entrega que tu endpoint se perdió:

```java
WebhookSecret secret = client.v2.getWebhookSecret();
System.out.println(secret.signatureHeader() + " " + secret.signatureScheme());

client.v2.rotateWebhookSecret();          // las firmas antiguas dejan de verificarse de inmediato

WebhookRetryResult retry = client.v2.retryIngestWebhook(job.jobId());
System.out.println(retry.delivered() + " after " + retry.attempts() + " attempts");
```

### Watch

Monitorización recurrente de cambios en una URL, con notificación por correo electrónico y webhook. Referencia del endpoint: [Watch](/es/docs/v2-watch).

```java
Watcher watcher = client.v2.createWatcher("https://example.com/pricing",
        WatchCreateOptions.builder()
                .frequencyMinutes(60)      // de 60 a 43200, mínimo de una hora
                .diffMode("auto")          // auto, text, structured, tables, metadata
                .webhookUrl("https://my.app/hooks/changes")
                .notifyEmail(true)
                .build());

WatcherSnapshotList history = client.v2.getWatcherSnapshots(watcher.watcherId(),
        SnapshotListOptions.builder().limit(10).build());
history.snapshots().forEach(s ->
        System.out.println(s.checkedAt() + " changed=" + s.hasChanges()
                + " similarity=" + s.similarity()));

client.v2.updateWatcher(watcher.watcherId(), WatcherUpdate.builder().status("paused").build());
client.v2.updateWatcher(watcher.watcherId(), WatcherUpdate.builder().webhookUrl("").build());
client.v2.deleteWatcher(watcher.watcherId());   // borrado lógico, idempotente
```

`listWatchers()` y `getWatcher(watcherId)` los leen de vuelta. `updateWatcher` requiere al menos un campo y lanza `IllegalArgumentException` en caso contrario. Una cadena vacía explícita en `webhookUrl` borra el webhook, mientras que dejarlo nulo significa "sin cambios". `deleteWatcher` es un borrado lógico: devuelve el watcher marcado como eliminado con estado `deleted`, y a partir de ahí un watcher eliminado se lee como `404`.

<div class="alert alert-warning">
<strong>Los diffs de snapshot contienen contenido de página no confiable.</strong> <code>WatcherSnapshot.changes()</code> es texto en crudo tomado de la página monitorizada. Escápalo antes de renderizarlo en un panel, un correo electrónico o un mensaje de chat.
</div>

---

## Opciones de PDF

`PdfOptions` es compartido por `convertUrlToPdf`, `convertWebsiteToPdf`, `convertDocument`, `convertToPdf` y `PerceiveOptions.pdfOptions`.

```java
client.convertUrlToPdf("https://internal.example.com/report",
        UrlToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder()
                        .pageSize("A4")
                        .orientation("landscape")
                        .margins(new PdfMargins(10.0, 10.0, 15.0, 15.0))
                        .scale(0.9)
                        .header(new PdfHeaderFooter("Quarterly Report", 15.0))
                        .footer(new PdfHeaderFooter("Confidential", 12.0))
                        .build())
                .auth(new HttpBasicAuth("user", "pass"))
                .cookies(List.of(BrowserCookie.builder("session", "abc123").domain("internal.example.com").build()))
                .headers(Map.of("X-Tenant", "acme"))
                .saveTo("report.pdf")
                .build());
```

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `pageSize` | `String` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` y similares. |
| `pageWidth`, `pageHeight` | `double` | Dimensiones personalizadas. Establecidas juntas, prevalecen sobre `pageSize`. |
| `orientation` | `String` | `"portrait"` o `"landscape"`. |
| `margins` | `PdfMargins` | Record de `top`, `bottom`, `left`, `right`. Cualquier campo nulo se omite. |
| `scale` | `double` | Escala de renderizado, por ejemplo `0.9` para el 90 por ciento. |
| `grayscale` | `boolean` | Posprocesa el PDF a escala de grises. |
| `header`, `footer` | `PdfHeaderFooter` | Record de `content` (máximo 2000 caracteres) y `height`. |

`BrowserCookie` necesita un nombre y un valor, más `domain` o `url`; cuando se establece `domain` sin `path`, la API asigna `/` a `path` por defecto. No combines `auth` con un encabezado `Authorization` explícito, porque la API rechaza el conflicto.

---

## Manejo de errores

Toda excepción del SDK extiende `EnconvertException`, que a su vez extiende `RuntimeException`, así que nada obliga a una cláusula `throws` en tus puntos de llamada. Captura primero las subclases específicas.

```java
try {
    client.convertUrlToPdf("https://example.com");
} catch (AuthenticationException e) {
    System.err.println("Invalid or missing API key");
} catch (QuotaException e) {
    System.err.println("Request refused with 402: " + e.getMessage());
} catch (RateLimitException e) {
    System.err.println("Too many requests, back off and retry");
} catch (ApiException e) {
    System.err.println("API error [" + e.getStatusCode() + "]: " + e.getMessage());
}
```

| Clase | Se lanza en | Código de estado |
|-------|-------------|------------------|
| `AuthenticationException` | Clave de API ausente, inválida o sin permiso | `401`, `403` |
| `QuotaException` | HTTP 402 | `402` |
| `RateLimitException` | Demasiadas solicitudes | `429` |
| `ApiException` | Cualquier otra respuesta 4xx o 5xx | el código real |
| `EnconvertException` | Clase base, también lanzada ante un fallo de transporte, una solicitud interrumpida o un archivo de entrada ilegible | ninguno |

La validación del lado del cliente (un par de conversión no admitido, un schema de distill ausente, una actualización de watcher vacía, un número incorrecto de salidas para `perceiveDirect`) lanza `IllegalArgumentException` antes de realizar ninguna solicitud. El mapa de mensajes de las respuestas del servidor está en la referencia de [Códigos de error](/es/docs/error-codes).

---

## Recuperación de timeouts

Los renderizados de URL largos y las conversiones de documentos grandes pueden sobrevivir al timeout del proxy inverso incluso cuando la conversión en sí acaba teniendo éxito. El SDK lo gestiona de forma transparente:

1. Antes de cada solicitud de URL individual o de subida de archivo, el SDK genera un UUID y lo envía como `job_id`.
2. Si la solicitud vuelve con 5xx, el SDK pasa a sondear `GET /v1/convert/status/{jobId}` cada 3 segundos.
3. Ante `success` devuelve el resultado. Ante `failed` lanza `ApiException` con el mensaje de error del servidor.
4. El plazo de sondeo es de 5 minutos. Pasado ese punto lanza `ApiException(504, "Conversion timed out")`.

No escribes ningún código para esto. Si una respuesta correcta omite `job_id`, el SDK rellena el id que generó, de modo que `result.jobId()` siempre se puede usar con `getJobStatus`.

<div class="alert alert-info">
<strong>Los envíos de lotes de sitios web quedan excluidos a propósito.</strong> <code>convertWebsiteToPdf</code> y <code>convertWebsiteToScreenshot</code> no tienen una fila por job que sondear, así que un 5xx ahí significa que falló el propio envío y se expone directamente. Los endpoints V2 tampoco usan sondeo de jobs; sus flujos asíncronos pasan por <code>getPerceiveBatch</code> y <code>getIngestJob</code>.
</div>

---

## Configuración

```java
Enconvert client = Enconvert.builder(System.getenv("ENCONVERT_API_KEY"))
        .baseUrl("https://api.enconvert.com")
        .timeout(Duration.ofSeconds(300))
        .build();
```

Hay tres constructores disponibles como atajo: `new Enconvert(apiKey)`, `new Enconvert(apiKey, baseUrl)` y `new Enconvert(apiKey, baseUrl, timeout)`.

| Opción | Tipo | Por defecto | Descripción |
|--------|------|-------------|-------------|
| `apiKey` | `String` | obligatorio | Clave de API privada. Un valor nulo o vacío lanza `IllegalArgumentException`. |
| `baseUrl` | `String` | `https://api.enconvert.com` | URL base de la API. Las barras finales se eliminan. |
| `timeout` | `Duration` | 300 segundos | Se aplica tanto como timeout de conexión como timeout por solicitud. |

La clave viaja en el encabezado `X-API-Key`. Las URL de descarga prefirmadas se obtienen sin ella, ya que vienen firmadas. Los tipos de clave se tratan en [Autenticación](/es/docs/authentication); crea y gestiona claves en el [panel de control](/es/dashboard).

<div class="alert alert-warning">
<strong>Nunca incrustes la clave de API en el código.</strong> Léela desde una variable de entorno o desde tu gestor de secretos. El SDK es solo del lado del servidor: una clave privada no debe viajar dentro de un artefacto de escritorio o móvil que un usuario pueda desempaquetar.
</div>

---

## Forma del resultado

Toda conversión de un solo archivo o de una sola URL devuelve el mismo record:

```java
public record ConversionResult(
        String presignedUrl,
        String objectKey,
        String filename,
        Long fileSize,
        Double conversionTimeSeconds,
        String jobId) {}
```

La URL prefirmada tiene un tiempo limitado. Pasa `saveTo` (o descarga la URL tú mismo) y guarda los bytes en tu propio bucket si necesitas que le sobrevivan.

Los demás records de respuesta que tocarás con más frecuencia:

| Record | Accesores principales |
|--------|-----------------------|
| `JobStatus` | `status()` (`processing`, `success`, `failed`), `presignedUrl()`, `objectKey()`, `error()` |
| `BatchStatus` | `status()`, `total()`, `completed()`, `failed()`, `inProgress()`, `zipDownloadUrl()`, `items()` |
| `PerceiveResult` | `operationId()`, `renderQuality()`, `statusCode()`, `deductions()`, `outputs()`, `structured()`, `cacheHit()`, `warnings()` |
| `V2OutputArtifact` | `url()`, `objectKey()`, `sizeBytes()`, `contentType()`, `expiresIn()` |
| `PerceiveDirectResult` | `content()`, `contentType()`, `filename()`, `renderQuality()`, `contentHash()` |
| `IngestJob` | `jobId()`, `status()`, `pagesProcessed()`, `totalChunks()`, `outputUrl()`, `webhookDelivered()` |
| `Watcher` | `watcherId()`, `status()`, `frequencyMinutes()`, `checksCount()`, `nextCheckAt()`, `lastChangeAt()` |

Los campos cuya forma define tu propia solicitud (`structured`, `data`, `trackFields`, los `changes` del snapshot) se exponen como `JsonObject` de Gson y pasan intactos. Las enumeraciones con valores de cadena siguen siendo `String` en lugar de convertirse en constantes `enum` de Java, así que un valor más reciente de la API nunca rompe la deserialización en una build más antigua del SDK. `com.enconvert.model.v2.V2Enums` contiene cada valor aceptado como una constante a prueba de erratas.

---

## Código fuente e incidencias

- **Maven Central:** `com.enconvert:enconvert-sdk:0.0.1`
- **GitHub:** [conversionapi/java-sdk](https://github.com/conversionapi/java-sdk)
- **Licencia:** MIT
- **Otros clientes:** [todos los SDK](/es/docs/sdks) · [referencia de endpoints](/es/docs/endpoints-overview) · [precios](/es/pricing)

---

## Preguntas frecuentes

### ¿Cómo convierto archivos en Java con una dependencia de Maven?

Añade `com.enconvert:enconvert-sdk:0.0.1` a tu `pom.xml` o `build.gradle`, construye un cliente con `new Enconvert(System.getenv("ENCONVERT_API_KEY"))` y llama a un método tipado como `convertUrlToPdf`, `convertImage`, `convertDocument` o `convertToPdf`. Pasa `saveTo` en el builder de opciones para escribir la salida directamente en disco en lugar de gestionar tú la URL prefirmada.

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

Llama a `client.convertUrlToPdf(url, UrlToPdfOptions.builder().saveTo("page.pdf").build())`. Establece `singlePage(false)` para paginar usando `pdfOptions.pageSize` en lugar de producir una única página continua, y pasa `auth`, `cookies` o `headers` para una página tras un inicio de sesión.

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

Llama a `client.convertDocument(Path.of("report.docx"), ConvertDocumentOptions.builder().saveTo("report.pdf").build())`. El formato de salida es `pdf` por defecto, así que solo estableces `outputFormat` cuando quieres otra cosa, por ejemplo `yaml` a partir de una entrada `.json`. Para formatos sin un par propio, como EPUB o RTF, usa `convertToPdf`.

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

Llama a `client.convertImage(Path.of("photo.heic"), ConvertImageOptions.builder("webp").saveTo("photo.webp").build())`. El formato de entrada procede de la extensión del archivo y el formato de salida es el argumento obligatorio del builder. `jpeg`, `png`, `svg`, `heic` y `webp` se convierten todos entre sí, y `pdf` se rasteriza a `jpeg`. En este cliente no hay un método de compresión in situ.

### ¿Cómo extraigo una página web a Markdown limpio desde Java?

Dos caminos. `client.convertUrlToMarkdown(url, ...)` devuelve Markdown con sabor GitHub y frontmatter YAML como archivo descargable. `client.v2.perceive(url, PerceiveOptions.builder().outputs(List.of("markdown")).build())` devuelve el mismo contenido como un artefacto listo para agentes, con una puntuación `renderQuality`, opciones de extracción y control de caché. Usa perceive cuando una mala lectura tenga que ser detectable en lugar de silenciosa.

### ¿Qué es renderQuality y por qué toda lectura tiene una?

`renderQuality` es una puntuación de 0.0 a 1.0 adjunta a cada renderizado V2. Una puntuación alta significa que la página se renderizó limpiamente; una baja significa que algo se interpuso, como un desafío antibots, un muro de cookies, una pantalla de inicio de sesión, una página de error HTTP o el armazón vacío de una SPA. El contenido se sigue devolviendo, con `warnings()` y `deductions()` rellenos, para que tu pipeline pueda descartar o reintentar la lectura en lugar de alimentar un modelo con una página de desafío como si fuera el artículo.

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

Llama a `client.v2.ingest(IngestOptions.builder().mode("sitemap").url("https://docs.example.com").maxPages(100).chunk(new IngestChunkOptions(512, 1)).build())`. El job es asíncrono, así que sondea `getIngestJob(jobId)` hasta que el estado sea `completed` y lee `outputUrl()` para obtener el JSONL, o bien establece `webhookUrl` y verifica la firma HMAC con el secreto de `getWebhookSecret()`. Para documentos locales en lugar de un sitio, usa `ingestFiles`.

### ¿Cómo gestiona el SDK las conversiones que superan el timeout del proxy?

Antes de cada solicitud de URL individual o de subida de archivo genera un UUID y lo envía como `job_id`. Si la solicitud devuelve 5xx, sondea `GET /v1/convert/status/{jobId}` cada 3 segundos hasta que el job informe de `success` o `failed`, con un plazo de 5 minutos tras el cual lanza `ApiException(504, "Conversion timed out")`. Los envíos de lotes de sitios completos quedan excluidos, porque no tienen una fila por job que sondear.

### ¿Qué versión de Java requiere el SDK y qué arrastra consigo?

Java 17 o posterior. HTTP pasa por el `java.net.http.HttpClient` del JDK, y Gson es el único artefacto de terceros en el classpath. Las respuestas son records de Java, así que un `switch` moderno o un pattern match sobre ellas funciona como cabe esperar. El cliente es seguro entre hilos: mantén una instancia y compártela.
