---
seo_title: SDK Java Conversione File | Client API Maven EnConvert
meta_desc: SDK Java ufficiale di EnConvert per Java 17+. Una sola dipendenza Maven o Gradle per convertire file e per perceive, discover, distill, ingest e watch.
keywords: sdk java conversione file, convertire file in java, client api conversione file maven, libreria gradle conversione file, url in pdf java, docx in pdf java, html in pdf java, heic in webp java, api web scraping java, pagina web in markdown java, pipeline ingestion rag java, sdk java enconvert
---

# SDK Java per la Conversione dei File

`com.enconvert:enconvert-sdk` è il client Java ufficiale per l'API EnConvert. Una sola dipendenza Maven o Gradle ti dà la conversione dei file (da URL a PDF, da DOCX a PDF, da HEIC a WebP, qualsiasi file in Markdown) più la superficie di web intelligence V2: perceive, discover, lookup, distill, ingest e watch. Richiede Java 17 o versioni successive, funziona sul `java.net.http.HttpClient` incluso nel JDK e porta con sé Gson come unica dipendenza di terze parti. Ogni chiamata è un semplice metodo bloccante che restituisce un record tipizzato, e le conversioni lunghe recuperano in modo trasparente dai timeout del reverse proxy effettuando il polling dello stato del job.

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

---

## Installazione

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

L'HTTP è gestito da `java.net.http.HttpClient` del JDK. L'unico artefatto di terze parti che viene incluso è [Gson](https://github.com/google/gson) per il JSON, dichiarato come dipendenza `api` in modo che sia visibile sul tuo classpath di compilazione.

---

## Avvio rapido

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

// Leggi una pagina come dovrebbe fare il tuo agente, con un punteggio di qualità allegato.
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());   // ad es. 0.93
```

Ogni classe di opzioni è un builder immutabile e ogni risposta è un `record` Java, quindi gli accessori si leggono come `pdf.presignedUrl()` e `page.renderQuality()`. Il client mantiene un unico `HttpClient` condiviso e nessuno stato mutabile per richiesta, quindi una singola istanza può essere un singleton oppure un bean Spring condiviso tra i thread. Gli snippet qui sotto omettono gli import: le classi di opzioni e i tipi di risposta stanno in `com.enconvert.model` (conversione) e `com.enconvert.model.v2` (web intelligence), le eccezioni in `com.enconvert.exceptions`.

---

## Cosa espone il client

`Enconvert` porta direttamente la superficie di conversione. La superficie di web intelligence vive sul campo pubblico final `client.v2`, un'istanza di `EnconvertV2`.

| Gruppo | Metodi | Restituisce |
|-------|---------|---------|
| URL singolo | `convertUrlToPdf`, `convertUrlToScreenshot`, `convertUrlToMarkdown` | `ConversionResult` |
| Upload di file | `convertImage`, `convertDocument`, `convertToMarkdown`, `convertToPdf` | `ConversionResult` |
| Intero sito | `convertWebsiteToPdf`, `convertWebsiteToScreenshot` | `BatchSubmission` |
| Stato | `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 maggior parte dei metodi ha un overload breve senza argomento di opzioni, quindi sia `client.v2.perceive(url)` sia `client.convertUrlToPdf(url)` compilano. `convertImage`, `distill` e `ingest` sono le eccezioni: ognuno richiede sempre il proprio oggetto di opzioni, perché servono rispettivamente il formato di destinazione, lo schema e la sorgente.

---

## Conversione dei file

Gli endpoint di conversione coprono 43 coppie `{input}-to-{output}` implementate, due endpoint con rilevamento automatico (`anything-to-markdown` e `anything-to-pdf`) e gli endpoint di rendering nel browser. Il riferimento completo dei parametri si trova in [Parametri e opzioni](/it/docs/parameters-options).

### convertUrlToPdf

Esegue il rendering di qualsiasi URL pubblico in 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());
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `saveTo` | `String` | nessuno | Percorso locale su cui scrivere il PDF. Le directory padre vengono create automaticamente. |
| `singlePage` | `boolean` | `true` | `true` produce una singola pagina continua. `false` pagina utilizzando `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions` | nessuno | Dimensione pagina, orientamento, margini, scala, scala di grigi, intestazione, piè di pagina. Vedi [Opzioni PDF](#opzioni-pdf). |
| `viewportWidth` | `int` | `1920` | Larghezza del viewport del browser in pixel. |
| `viewportHeight` | `int` | `1080` | Altezza del viewport del browser in pixel. |
| `loadMedia`, `enableScroll` | `boolean` | `true` | Attende immagini e video prima della cattura, e scorre dall'alto verso il basso per attivare i caricamenti lazy. |
| `outputFilename` | `String` | auto | Sovrascrive il nome file generato. |
| `auth`, `cookies`, `headers` | `HttpBasicAuth`, `List<BrowserCookie>`, `Map<String, String>` | nessuno | Credenziali, cookie iniettati e header di richiesta aggiuntivi per pagine protette da login. |

### convertUrlToScreenshot

Cattura un PNG di qualsiasi URL. Le stesse opzioni di viewport, media, scroll, nome file, auth, cookie e header di `convertUrlToPdf`, meno `singlePage` e `pdfOptions`.

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

### convertUrlToMarkdown

Estrae Markdown pulito in stile GitHub Flavored da un URL. Navigazione, footer, pubblicità e script vengono rimossi, il corpo principale dell'articolo viene mantenuto e viene anteposto un frontmatter YAML (titolo, descrizione, url, link, immagini). Stesso insieme di opzioni di `convertUrlToScreenshot`.

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

### convertImage

Converte tra `jpeg`, `png`, `svg`, `heic` e `webp`, oppure rasterizza un PDF in JPEG.

```java
// Da un percorso su disco
client.convertImage(Path.of("photo.heic"),
        ConvertImageOptions.builder("webp").saveTo("photo.webp").build());

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

// Da byte in memoria con un nome file esplicito
byte[] bytes = Files.readAllBytes(Path.of("photo.heic"));
client.convertImage(new FileInput(bytes, "photo.heic"),
        ConvertImageOptions.builder("webp").build());
```

Su ogni metodo che accetta file esistono tre overload di input: `java.nio.file.Path` (lettura da disco), `byte[]` grezzi (il nome file diventa `upload.bin`) e `com.enconvert.FileInput` quando devi abbinare byte in memoria a un nome file reale. Il formato di input viene ricavato dall'estensione; il formato di output è obbligatorio.

| Opzione | Tipo | Obbligatorio | Descrizione |
|--------|------|----------|-------------|
| `outputFormat` | `String` | Sì | Passato a `ConvertImageOptions.builder(outputFormat)`. Uno tra `jpeg`, `png`, `svg`, `heic`, `webp`. Gli alias come `jpg` vengono normalizzati. |
| `saveTo` | `String` | no | Percorso locale su cui scrivere il risultato. |
| `outputFilename` | `String` | no | Sovrascrive il nome file generato. |

### convertDocument

Converte documenti e formati di dati strutturati. Il formato di output predefinito è `pdf`.

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

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

// da markdown a pdf con impostazioni di pagina
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());
```

**Estensioni di input riconosciute:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`.

EPUB non ha una coppia documentale dedicata. Invia invece i file `.epub` a [`convertToPdf`](#converttopdf) oppure a [`convertToMarkdown`](#converttomarkdown).

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `outputFormat` | `String` | `"pdf"` | Formato di destinazione. |
| `saveTo` | `String` | nessuno | Percorso locale su cui scrivere il risultato. |
| `outputFilename` | `String` | nessuno | Sovrascrive il nome file generato. |
| `pdfOptions` | `PdfOptions` | nessuno | Impostazioni di pagina, rispettate quando l'output è PDF. |

### Conversioni supportate

`convertImage` e `convertDocument` validano la coppia `{input}-to-{output}` rispetto agli endpoint che l'API implementa davvero. Una coppia non supportata genera subito `IllegalArgumentException`, elencando gli output validi per quell'input, invece di pagare un round trip per una richiesta che non può riuscire.

| Input | Output |
|-------|---------|
| `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` | tra loro, tutte le 20 coppie |
| `pdf` | `jpeg` |

La stessa tabella è interrogabile a runtime tramite `com.enconvert.Formats`: `Formats.validOutputsFor("json")` restituisce `[csv, toml, xml, yaml]`, `Formats.validOutputsFor("pdf")` restituisce `[jpeg]` e `Formats.IMPLEMENTED_CONVERSIONS` contiene tutti i 43 nomi di endpoint.

### convertToMarkdown

Invia qualsiasi documento supportato a un unico endpoint con rilevamento automatico e ottieni Markdown pulito. La gerarchia delle intestazioni sopravvive, il che rende questo passaggio un primo stadio naturale per una pipeline RAG.

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

Accetta PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, oltre ai formati office legacy e ODF. Il formato viene rilevato lato server, quindi non c'è alcun controllo dell'estensione lato client: qualsiasi file viene caricato così com'è. Le immagini non sono supportate e vengono rifiutate con `400`. Le uniche opzioni sono `saveTo` e `outputFilename`.

### convertToPdf

L'altro endpoint con rilevamento automatico: quasi qualsiasi cosa in PDF.

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

// pdf in passthrough, convertito in scala di grigi
client.convertToPdf(Path.of("scan.pdf"),
        ConvertToPdfOptions.builder()
                .pdfOptions(PdfOptions.builder().grayscale(true).build())
                .saveTo("scan-gray.pdf")
                .build());
```

Accetta office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, testo semplice, immagini raster, SVG, EPUB e un PDF esistente in passthrough. L'EPUB viene gestito qui perché non ha una coppia documentale dedicata. Le opzioni sono `saveTo`, `outputFilename` e `pdfOptions`.

<div class="alert alert-warning">
<strong>Su questo endpoint viene rispettato solo <code>grayscale</code>.</strong> La geometria di pagina (dimensione pagina, larghezza e altezza, orientamento, margini, scala, intestazione, piè di pagina) viene ignorata da <code>anything-to-pdf</code>. Quando ti serve l'impostazione completa della pagina, passa invece da <code>convertDocument</code> oppure <code>convertUrlToPdf</code>.
</div>

### Conversione di interi siti

`convertWebsiteToPdf` e `convertWebsiteToScreenshot` individuano ogni pagina di un sito, convertono ciascuna in background e raccolgono i risultati in un unico ZIP. Entrambi sono asincroni e restituiscono un `BatchSubmission`. Entrambi richiedono una chiave API privata.

```java
BatchSubmission batch = client.convertWebsiteToPdf("https://example.com",
        WebsiteToPdfOptions.builder()
                .crawlMode("sitemap")                      // "auto" (predefinito), "sitemap", "full"
                .excludePatterns(List.of("/blog/tag/"))    // solo in modalità full crawl
                .notificationEmail("ops@example.com")
                .build());

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

// Blocca finché il batch non esce da "processing", poi salva lo ZIP
BatchStatus status = client.waitForBatch(batch.batchId(),
        WaitForBatchOptions.builder().saveTo("site.zip").build());
System.out.println(status.completed() + " of " + status.total() + " pages converted");
```

`convertWebsiteToScreenshot` funziona in modo identico e produce uno ZIP di PNG. `waitForBatch` esegue il polling ogni 5 secondi per impostazione predefinita, si arrende dopo 30 minuti e accetta `intervalMs`, `timeoutMs` e `saveTo`. In caso di timeout genera `ApiException` con stato `504`.

### Eseguire il polling dello stato da soli

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

---

## Web intelligence (V2)

Tutto ciò che sta sotto `client.v2` trasforma le pagine web in dati pronti per gli agenti. Ogni lettura porta con sé `renderQuality`, un punteggio da 0.0 a 1.0 che dice quanto pulitamente la pagina è stata effettivamente renderizzata. Una pagina di challenge, un cookie wall o uno shell SPA vuoto tornano con un punteggio basso e con `warnings()` e `deductions()` popolati, invece di essere spacciati per contenuto reale, così una lettura difettosa non entra mai silenziosamente nel contesto del tuo agente. Il contenuto viene comunque restituito: viene solo segnalato. Le basi del modello si trovano nella [panoramica V2](/it/docs/v2-overview).

### Perceive

Esegue il rendering di un URL negli artefatti che richiedi. Riferimento dell'endpoint: [Perceive](/it/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());                 // da 0.0 a 1.0
System.out.println(page.statusCode() + " " + page.deductions());  // ad es. 200 {http_error=0.7}
System.out.println(page.outputs().get("markdown").url()); // URL firmato, 15 minuti
System.out.println(page.structured());                    // forma definita dal chiamante
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `outputs` | `List<String>` | `["markdown", "structured"]` | Uno o più tra `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. |
| `extract` | `List<String>` | nessuno | Obiettivi euristici: `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `schema` | `Map<String, Object>` | nessuno | Schema JSON per l'estrazione strutturata. |
| `waitFor`, `waitTimeoutMs` | `String`, `int` | nessuno, `30000` | Un selettore CSS, eventualmente con prefisso `css:`, oppure `js:<expr>` da attendere, con un budget da 0 a 60000 ms. |
| `jsCode` | `String` | nessuno | JavaScript eseguito dopo la navigazione, massimo 20000 caratteri. |
| `viewport`, `mobile` | `PerceiveViewport`, `boolean` | 1920 x 1080, `false` | Larghezza da 320 a 3840, altezza da 240 a 2160, oppure emulazione mobile. |
| `onlyMainContent` | `boolean` | `true` | Rimuove navigazione, header, footer e banner dei cookie dall'artefatto Markdown e dall'estrazione `main_content`. |
| `cacheMode` | `String` | `"enabled"` | `enabled` riutilizza una cache di 1 ora, `bypass` la salta, `refresh` forza un nuovo rendering. |
| `blockResources` | `List<String>` | nessuno | Tipi di risorsa che il browser non deve caricare, per esempio `image`, `font`, `script`. |
| `pdfOptions` | `PdfOptions` | nessuno | Ha effetto solo quando `outputs` contiene `pdf`. |
| `headers`, `cookies`, `auth` | `Map`, `List<BrowserCookie>`, `HttpBasicAuth` | nessuno | Header di richiesta, cookie iniettati, credenziali HTTP Basic. |
| `respectRobots` | `boolean` | nessuno | Rispetta le regole robots del sito. |

<div class="alert alert-warning">
<strong>Non ancora collegati.</strong> <code>proxyUrl</code>, <code>geolocation</code> e <code>actionChain</code> esistono sul builder ma non sono disponibili lato server e attualmente vengono rifiutati con <code>422</code>.
</div>

Gli URL degli artefatti sono firmati per 15 minuti e vengono rifirmati a ogni lettura dell'operazione, quindi `client.v2.getPerceiveOperation(page.operationId())` ti consegna link freschi. Puoi raggruppare fino a 1000 URL con un unico blocco di opzioni condiviso: i batch piccoli si completano inline, quelli più grandi tornano con stato `queued`, quindi vanno interrogati con il polling.

```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" (predefinito) oppure "zip"
                .build());

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

Salta del tutto il round trip dell'URL firmato con `perceiveDirect`, che restituisce in streaming i byte dell'artefatto. Richiede esattamente un output che produca artefatti (qualsiasi cosa tranne `structured`) e genera `IllegalArgumentException` prima dell'invio se ne chiedi di più o di meno:

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

// Riscarica un artefatto archiviato di un'operazione precedente
PerceiveDirectResult stored = client.v2.downloadPerceiveArtifact(page.operationId(), "markdown");
```

`downloadPerceiveArtifact` accetta un nome di output null oppure omesso quando l'operazione ha prodotto esattamente un artefatto, altrimenti restituisce `400` elencando gli output disponibili. Una volta che l'artefatto archiviato è scaduto, restituisce `410`.

### Discover

Enumera gli URL di un sito senza renderizzare nulla. Non è coinvolto alcun browser, quindi è veloce. Riferimento dell'endpoint: [Discover](/it/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);
```

| Opzione | Tipo | Predefinito | Intervallo |
|--------|------|---------|-------|
| `mode` | `String` | `"hybrid"` | `sitemap`, `crawl`, `hybrid` |
| `maxUrls` | `int` | `100` | da 1 a 1000 |
| `maxDepth` | `int` | `2` | da 1 a 5 |
| `includePatterns`, `excludePatterns` | `List<String>` | nessuno | Allowlist e denylist regex, massimo 50 ciascuna. La denylist viene applicata per seconda. |
| `sameDomainOnly`, `respectRobots` | `boolean` | `true`, nessuno | Resta sul dominio seed, e rispetta le regole robots del sito. |

### Lookup

Ricerca web categorizzata, con rendering automatico opzionale dei risultati migliori. Riferimento dell'endpoint: [Lookup](/it/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)         // renderizza automaticamente i 3 risultati migliori
                .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` maggiore di 0 (da 0 a 10, predefinito 0), gli URL dei primi N risultati vengono renderizzati tramite perceive e ogni risultato porta inline il suo `PerceiveResult` completo su `hit.perceive()`. `numResults` va da 1 a 100 e vale 10 per impostazione predefinita; `page` va da 1 a 10.

### Distill

Estrazione strutturata guidata da schema su una o più pagine. Riferimento dell'endpoint: [Distill](/it/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()));
```

Il `cssSchema` opzionale esegue prima un passaggio CSS gratuito; solo i campi a cui non riesce a rispondere passano al livello LLM, e `item.extractionTier()` riporta quale percorso ha prodotto il record (`css`, `llm`, `mixed` oppure `none`). Puoi anche individuare prima gli URL invece di elencarli:

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

Esattamente uno tra `urls` (massimo 50) e `discoverFrom` deve essere impostato, e `schema` è richiesto dalla factory del builder. Entrambe le regole vengono verificate lato client e generano `IllegalArgumentException` prima che parta qualsiasi richiesta.

### Ingest

Trasforma un intero sito, o un insieme di documenti caricati, in JSONL suddiviso in chunk e pronto per il RAG attraverso un'unica pipeline. Ingest è sempre asincrono. Riferimento dell'endpoint: [Ingest](/it/docs/v2-ingest).

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

// Oppure da file caricati
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());

// Esegui il polling per il 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` accetta PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, oltre ai formati office legacy e ODF. Il chunking usa per impostazione predefinita 512 parole (da 32 a 4000) con 1 frase di sovrapposizione (da 0 a 10). `mode` vale `urls` per impostazione predefinita, il che richiede un elenco `urls` non vuoto e vieta `url`; ogni altra modalità richiede un `url` seed e vieta `urls`. L'SDK verifica quell'abbinamento prima dell'invio.

I webhook di completamento sono firmati con HMAC. Recupera il secret e i nomi degli header che ti servono per verificare una consegna, ruotalo quando trapela e rispedisci una consegna che il tuo endpoint ha perso:

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

client.v2.rotateWebhookSecret();          // le vecchie firme smettono subito di essere valide

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

### Watch

Monitoraggio ricorrente delle modifiche su un URL, con notifica via email e webhook. Riferimento dell'endpoint: [Watch](/it/docs/v2-watch).

```java
Watcher watcher = client.v2.createWatcher("https://example.com/pricing",
        WatchCreateOptions.builder()
                .frequencyMinutes(60)      // da 60 a 43200, minimo orario
                .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());   // soft delete, idempotente
```

`listWatchers()` e `getWatcher(watcherId)` li rileggono. `updateWatcher` richiede almeno un campo e altrimenti genera `IllegalArgumentException`. Una stringa vuota esplicita per `webhookUrl` cancella il webhook, mentre lasciarlo null significa che non cambia nulla. `deleteWatcher` è una cancellazione soft: restituisce il watcher marcato come eliminato con stato `deleted`, e un watcher eliminato viene poi letto come `404`.

<div class="alert alert-warning">
<strong>I diff degli snapshot contengono contenuto di pagina non attendibile.</strong> <code>WatcherSnapshot.changes()</code> è testo grezzo prelevato dalla pagina monitorata. Effettuane l'escape prima di renderizzarlo in una dashboard, in un'email o in un messaggio di chat.
</div>

---

## Opzioni PDF

`PdfOptions` è condiviso da `convertUrlToPdf`, `convertWebsiteToPdf`, `convertDocument`, `convertToPdf` e `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 | Descrizione |
|-------|------|-------------|
| `pageSize` | `String` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` e simili. |
| `pageWidth`, `pageHeight` | `double` | Dimensioni personalizzate. Impostati insieme, hanno la precedenza su `pageSize`. |
| `orientation` | `String` | `"portrait"` oppure `"landscape"`. |
| `margins` | `PdfMargins` | Record di `top`, `bottom`, `left`, `right`. Qualsiasi campo null viene omesso. |
| `scale` | `double` | Scala di rendering, ad esempio `0.9` per il 90 percento. |
| `grayscale` | `boolean` | Post-elabora il PDF in scala di grigi. |
| `header`, `footer` | `PdfHeaderFooter` | Record di `content` (massimo 2000 caratteri) e `height`. |

`BrowserCookie` richiede un nome e un valore più `domain` oppure `url`; quando `domain` è impostato senza `path`, l'API imposta `path` a `/` per impostazione predefinita. Non combinare `auth` con un header `Authorization` esplicito, perché l'API rifiuta il conflitto.

---

## Gestione degli errori

Ogni eccezione dell'SDK estende `EnconvertException`, che a sua volta estende `RuntimeException`, quindi nulla impone una clausola `throws` sui tuoi punti di chiamata. Cattura prima le sottoclassi specifiche.

```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());
}
```

| Classe | Generata per | Codice di stato |
|-------|-----------|-------------|
| `AuthenticationException` | Chiave API mancante, non valida o non autorizzata | `401`, `403` |
| `QuotaException` | HTTP 402 | `402` |
| `RateLimitException` | Troppe richieste | `429` |
| `ApiException` | Qualsiasi altra risposta 4xx o 5xx | il codice effettivo |
| `EnconvertException` | Classe base, generata anche in caso di errore di trasporto, richiesta interrotta o file di input illeggibile | nessuno |

La validazione lato client (una coppia di conversione non supportata, uno schema distill mancante, un aggiornamento watcher vuoto, il numero sbagliato di output per `perceiveDirect`) genera `IllegalArgumentException` prima che venga inviata qualsiasi richiesta. La mappa dei messaggi per le risposte del server si trova nel riferimento [Codici di errore](/it/docs/error-codes).

---

## Recupero dei timeout

I rendering di URL lunghi e le conversioni di documenti di grandi dimensioni possono superare il timeout del reverse proxy anche quando la conversione stessa alla fine riesce. L'SDK gestisce il caso in modo trasparente:

1. Prima di ogni richiesta con URL singolo o upload di file, l'SDK genera un UUID e lo invia come `job_id`.
2. Se la richiesta torna con 5xx, l'SDK passa al polling di `GET /v1/convert/status/{jobId}` ogni 3 secondi.
3. Su `success` restituisce il risultato. Su `failed` genera `ApiException` con il messaggio di errore del server.
4. Il limite di tempo per il polling è di 5 minuti. Superato quello, genera `ApiException(504, "Conversion timed out")`.

Non devi scrivere codice per questo. Se una risposta riuscita omette `job_id`, l'SDK reinserisce l'id che ha generato, così `result.jobId()` è sempre utilizzabile con `getJobStatus`.

<div class="alert alert-info">
<strong>Gli invii batch dei siti web sono esclusi di proposito.</strong> <code>convertWebsiteToPdf</code> e <code>convertWebsiteToScreenshot</code> non hanno una riga per singolo job da interrogare, quindi un 5xx in quel punto significa che l'invio stesso è fallito e viene riportato direttamente. Nemmeno gli endpoint V2 usano il polling dei job: i loro flussi asincroni passano da <code>getPerceiveBatch</code> e <code>getIngestJob</code>.
</div>

---

## Configurazione

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

Sono disponibili tre costruttori come scorciatoia: `new Enconvert(apiKey)`, `new Enconvert(apiKey, baseUrl)` e `new Enconvert(apiKey, baseUrl, timeout)`.

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `apiKey` | `String` | obbligatorio | Chiave API privata. Un valore null o vuoto genera `IllegalArgumentException`. |
| `baseUrl` | `String` | `https://api.enconvert.com` | URL base dell'API. Gli slash finali vengono rimossi. |
| `timeout` | `Duration` | 300 secondi | Applicato sia come timeout di connessione sia come timeout per singola richiesta. |

La chiave viaggia nell'header `X-API-Key`. Gli URL di download presigned vengono recuperati senza di essa, dato che sono già firmati. I tipi di chiave sono trattati in [Autenticazione](/it/docs/authentication); crea e gestisci le chiavi nella [dashboard](/it/dashboard).

<div class="alert alert-warning">
<strong>Non inserire mai la chiave API direttamente nel codice.</strong> Leggila da una variabile d'ambiente o dal tuo secret manager. L'SDK è solo lato server: una chiave privata non deve finire dentro un artefatto desktop o mobile che un utente può aprire.
</div>

---

## Struttura del risultato

Ogni conversione di un singolo file o di un singolo URL restituisce lo stesso record:

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

L'URL presigned ha una durata limitata. Passa `saveTo` (oppure recupera l'URL tu stesso) e archivia i byte nel tuo bucket se ti servono oltre quella durata.

Gli altri record di risposta che toccherai più spesso:

| Record | Accessori principali |
|--------|---------------|
| `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()` |

I campi la cui forma è definita dalla tua richiesta (`structured`, `data`, `trackFields`, `changes` degli snapshot) sono esposti come `JsonObject` di Gson e passano intatti. Le enumerazioni con valori stringa restano `String` invece di diventare costanti `enum` Java, così un valore API più recente non rompe mai la deserializzazione su una build più vecchia dell'SDK. `com.enconvert.model.v2.V2Enums` contiene ogni valore accettato come costante a prova di refuso.

---

## Sorgente e problemi

- **Maven Central:** `com.enconvert:enconvert-sdk:0.0.1`
- **GitHub:** [conversionapi/java-sdk](https://github.com/conversionapi/java-sdk)
- **Licenza:** MIT
- **Altri client:** [tutti gli SDK](/it/docs/sdks) · [riferimento degli endpoint](/it/docs/endpoints-overview) · [prezzi](/it/pricing)

---

## Domande frequenti

### Come converto i file in Java con una dipendenza Maven?

Aggiungi `com.enconvert:enconvert-sdk:0.0.1` al tuo `pom.xml` oppure al `build.gradle`, costruisci un client con `new Enconvert(System.getenv("ENCONVERT_API_KEY"))` e chiama un metodo tipizzato come `convertUrlToPdf`, `convertImage`, `convertDocument` oppure `convertToPdf`. Passa `saveTo` sul builder delle opzioni per scrivere l'output direttamente su disco invece di gestire tu stesso l'URL presigned.

### Come converto un URL in PDF in Java?

Chiama `client.convertUrlToPdf(url, UrlToPdfOptions.builder().saveTo("page.pdf").build())`. Imposta `singlePage(false)` per paginare usando `pdfOptions.pageSize` invece di produrre una singola pagina continua, e passa `auth`, `cookies` oppure `headers` per una pagina protetta da login.

### Come converto DOCX in PDF in Java?

Chiama `client.convertDocument(Path.of("report.docx"), ConvertDocumentOptions.builder().saveTo("report.pdf").build())`. Il formato di output predefinito è `pdf`, quindi imposti `outputFormat` solo quando vuoi qualcos'altro, per esempio `yaml` da un input `.json`. Per i formati senza una coppia dedicata, come EPUB o RTF, usa `convertToPdf`.

### Come converto HEIC in WebP in Java?

Chiama `client.convertImage(Path.of("photo.heic"), ConvertImageOptions.builder("webp").saveTo("photo.webp").build())`. Il formato di input viene ricavato dall'estensione del file e il formato di output è l'argomento obbligatorio del builder. `jpeg`, `png`, `svg`, `heic` e `webp` si convertono tutti tra loro, e `pdf` viene rasterizzato in `jpeg`. Su questo client non esiste un metodo di compressione sul posto.

### Come estraggo una pagina web in Markdown pulito da Java?

Ci sono due strade. `client.convertUrlToMarkdown(url, ...)` restituisce Markdown in stile GitHub Flavored con frontmatter YAML come file scaricabile. `client.v2.perceive(url, PerceiveOptions.builder().outputs(List.of("markdown")).build())` restituisce lo stesso contenuto come artefatto pronto per gli agenti, con un punteggio `renderQuality`, opzioni di estrazione e controllo della cache. Usa perceive quando una lettura difettosa deve essere rilevabile invece che silenziosa.

### Che cos'è renderQuality e perché ogni lettura ne ha uno?

`renderQuality` è un punteggio da 0.0 a 1.0 associato a ogni rendering V2. Un punteggio alto significa che la pagina è stata renderizzata in modo pulito; un punteggio basso significa che qualcosa si è messo in mezzo, per esempio una challenge anti-bot, un cookie wall, una schermata di login, una pagina di errore HTTP o uno shell SPA vuoto. Il contenuto viene comunque restituito, con `warnings()` e `deductions()` popolati, così la tua pipeline può scartare o ripetere la lettura invece di dare in pasto a un modello una pagina di challenge come se fosse l'articolo.

### Come trasformo un sito di documentazione in chunk pronti per il RAG in Java?

Chiama `client.v2.ingest(IngestOptions.builder().mode("sitemap").url("https://docs.example.com").maxPages(100).chunk(new IngestChunkOptions(512, 1)).build())`. Il job è asincrono, quindi puoi interrogare `getIngestJob(jobId)` finché lo stato non è `completed` e leggere `outputUrl()` per il JSONL, oppure impostare `webhookUrl` e verificare la firma HMAC con il secret restituito da `getWebhookSecret()`. Per documenti locali invece di un sito, usa `ingestFiles`.

### Come gestisce l'SDK le conversioni che superano il timeout del proxy?

Prima di ogni richiesta con URL singolo o upload di file genera un UUID e lo invia come `job_id`. Se la richiesta restituisce 5xx, interroga `GET /v1/convert/status/{jobId}` ogni 3 secondi finché il job non riporta `success` oppure `failed`, con un limite di 5 minuti oltre il quale genera `ApiException(504, "Conversion timed out")`. Gli invii batch di interi siti sono esclusi, perché non hanno una riga per singolo job da interrogare.

### Quale versione di Java richiede l'SDK e che cosa porta con sé?

Java 17 o versioni successive. L'HTTP passa dal `java.net.http.HttpClient` del JDK, e Gson è l'unico artefatto di terze parti sul classpath. Le risposte sono record Java, quindi uno `switch` moderno o un pattern match su di esse funziona come previsto. Il client è thread safe: tieni una sola istanza e condividila.
