---
seo_title: SDK Kotlin per Conversione File: Client API JVM | EnConvert
meta_desc: SDK Kotlin ufficiale di EnConvert per JDK 17+. Data class idiomatiche per la conversione dei file e per percepire, scoprire, distillare e monitorare le pagine web.
keywords: sdk kotlin conversione file, convertire file in kotlin, url in pdf kotlin, api web scraping kotlin, docx in pdf kotlin, enconvert sdk kotlin, client api conversione file jvm, heic in webp kotlin, pagina web in markdown kotlin, pipeline ingestion rag kotlin, monitoraggio modifiche sito web kotlin, libreria conversione maven central
---

# SDK Kotlin per la conversione dei file

`com.enconvert:enconvert-kotlin` è il client EnConvert ufficiale per Kotlin e per la JVM, compilato con JDK 17. Converte file su 43 coppie di formati implementate (da DOCX a PDF, da HEIC a WebP, da JSON a YAML, da URL a PDF, da qualsiasi cosa a Markdown) e legge pagine web live trasformandole in Markdown, JSON, screenshot e JSONL pronto per il RAG attraverso il namespace `client.v2`. Opzioni e risposte sono data class Kotlin idiomatiche con argomenti nominati e valori predefiniti sensati, l'HTTP viaggia sullo `java.net.http.HttpClient` del JDK, e le conversioni lente si riprendono dai timeout del reverse proxy interrogando lo stato del job.

<div class="alert alert-info">
<strong>Maven Central:</strong> <code>com.enconvert:enconvert-kotlin:0.0.1</code> · <strong>Sorgente:</strong> <a href="https://github.com/conversionapi/kotlin-sdk">conversionapi/kotlin-sdk</a> · <strong>Richiede:</strong> JDK 17+ · <strong>Licenza:</strong> MIT
</div>

---

## Installazione

```kotlin
// Gradle, Kotlin DSL
dependencies {
    implementation("com.enconvert:enconvert-kotlin:0.0.1")
}
```

```groovy
// Gradle, Groovy DSL
dependencies {
    implementation 'com.enconvert:enconvert-kotlin:0.0.1'
}
```

```xml
<dependency>
  <groupId>com.enconvert</groupId>
  <artifactId>enconvert-kotlin</artifactId>
  <version>0.0.1</version>
</dependency>
```

L'unica dipendenza a runtime è `org.jetbrains.kotlinx:kotlinx-serialization-json`. Tutto il resto arriva dal JDK: le richieste escono tramite `java.net.http.HttpClient` e i corpi multipart vengono assemblati dall'SDK stesso. La toolchain punta a JVM 17, quindi qualsiasi runtime JDK 17 o successivo va bene.

---

## Avvio rapido

```kotlin
import com.enconvert.Enconvert
import com.enconvert.PerceiveOptions
import com.enconvert.PerceiveOutputName
import com.enconvert.UrlToPdfOptions

fun main() {
    val client = Enconvert(apiKey = System.getenv("ENCONVERT_API_KEY"))

    // Converte una pagina live in PDF e la trasmette direttamente su disco.
    val pdf = client.convertUrlToPdf("https://example.com", UrlToPdfOptions(saveTo = "page.pdf"))
    println(pdf.presignedUrl)

    // Legge la stessa pagina come dovrebbe farlo il tuo agente, con un punteggio di qualità allegato.
    val op = client.v2.perceive(
        "https://example.com",
        PerceiveOptions(outputs = listOf(PerceiveOutputName.MARKDOWN, PerceiveOutputName.STRUCTURED)),
    )
    println("${op.outputs["markdown"]?.url} ${op.renderQuality}") // es. 0.93
}
```

Ogni tipo EnConvert vive nel package `com.enconvert`, e gli esempi qui sotto omettono gli import; per lo stesso motivo i tipi del JDK come `java.nio.file.Files` e `java.nio.file.Path` compaiono senza qualificazione. Ogni metodo blocca, dato che non ci sono funzioni `suspend`, quindi da una coroutine avvolgi la chiamata in `withContext(Dispatchers.IO)`. Il client si autentica con una chiave API privata inviata nell'header `X-API-Key`, il che lo rende utilizzabile solo lato server: non distribuire mai la chiave dentro un'app Android o qualunque altra cosa tu rilasci. Consulta l'[autenticazione](/it/docs/authentication) per i tipi di chiave.

---

## Che cosa espone il client

`Enconvert` è l'intera superficie. La conversione dei file vive sul client stesso; la web intelligence vive sul namespace `v2`, raggiunto come `client.v2`.

| Superficie | Si raggiunge come | Copre |
|---------|-----------|--------|
| Conversione file | `client.<method>()` | Da URL a PDF, screenshot, Markdown; coppie di immagini e documenti; anything-to-PDF e anything-to-Markdown; batch su interi siti; stato di job e batch |
| Web intelligence | `client.v2.<method>()` | Perceive, discover, lookup, distill, ingest, watch: 23 metodi su 21 endpoint REST |

Dodici metodi di conversione si mappano sull'API REST descritta nella [panoramica degli endpoint](/it/docs/endpoints-overview):

| Metodo | Endpoint | Restituisce |
|--------|----------|---------|
| `convertUrlToPdf(url, opts?)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `convertUrlToScreenshot(url, opts?)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `convertUrlToMarkdown(url, opts?)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `convertImage(file, opts)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertDocument(file, opts?)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertToMarkdown(file, opts?)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `convertToPdf(file, opts?)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `convertWebsiteToPdf(url, opts?)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `convertWebsiteToScreenshot(url, opts?)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `getJobStatus(jobId)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `getBatchStatus(batchId)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `waitForBatch(batchId, opts?)` | `GET /v1/convert/batch/{batchId}` (con polling) | `BatchStatus` |

I quattro metodi di upload hanno ciascuno quattro overload. Il primo argomento può essere un percorso `String`, un `java.nio.file.Path`, un semplice `ByteArray` (il nome file vale `upload.bin` per impostazione predefinita) oppure un `FileInput(data, filename, contentType?)` quando hai byte grezzi e vuoi dare tu stesso un nome.

---

## Conversione dei file

### convertUrlToPdf

Esegue il rendering di qualsiasi URL pubblico in PDF.

```kotlin
val result = client.convertUrlToPdf(
    "https://example.com/report",
    UrlToPdfOptions(
        render = UrlRenderOptions(viewportWidth = 1440),
        singlePage = false,
        pdfOptions = PdfOptions(pageSize = "A4", orientation = PdfOrientation.LANDSCAPE, margins = PdfMargins(top = 10.0)),
        saveTo = "report.pdf",
    ),
)
println("${result.filename} ${result.fileSize}")
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `render` | `UrlRenderOptions` | `UrlRenderOptions()` | Viewport, media, scroll, nome file, accesso al browser. |
| `saveTo` | `String?` | -- | Percorso locale su cui trasmettere il PDF. Le directory padre vengono create per te. |
| `singlePage` | `Boolean` | `true` | `true` produce una singola pagina continua. `false` impagina usando `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions?` | -- | Geometria di pagina. Vedi [Opzioni PDF](#opzioni-pdf). |

`UrlRenderOptions` è condiviso da ogni conversione basata su URL. Porta con sé `viewportWidth` e `viewportHeight` (predefiniti 1920 x 1080), `loadMedia` ed `enableScroll` (entrambi `true` per impostazione predefinita: attendono i media e scorrono dall'alto verso il basso perché scattino i caricamenti lazy), `outputFilename` e tre campi di accesso al browser: `auth` (un `HttpBasicAuth`), `cookies` (una `List<BrowserCookie>`, massimo 50) e `headers` (massimo 20, header hop-by-hop rifiutati).

<div class="alert alert-warning">
<strong>Non combinare <code>auth</code> con un header <code>Authorization</code>.</strong> L'API rifiuta il conflitto invece di indovinare quale dei due intendevi.
</div>

### convertUrlToScreenshot

Cattura un PNG di qualsiasi URL. `UrlToScreenshotOptions` porta con sé solo `render` e `saveTo`.

```kotlin
client.convertUrlToScreenshot(
    "https://example.com",
    UrlToScreenshotOptions(render = UrlRenderOptions(viewportWidth = 1440), saveTo = "shot.png"),
)
```

### convertUrlToMarkdown

Estrae Markdown pulito in stile GitHub-Flavored da un URL. Il convertitore rimuove navigazione, footer, pubblicità e script, mantiene il corpo principale dell'articolo e antepone un frontmatter YAML con titolo, descrizione, url, link e immagini.

```kotlin
val article = client.convertUrlToMarkdown("https://example.com/post", UrlToMarkdownOptions(saveTo = "article.md"))
println(article.presignedUrl)
```

Quando vuoi anche un punteggio di qualità, un pacchetto di artefatti o un'estrazione strutturata dallo stesso rendering, usa invece [`client.v2.perceive`](#perceive).

### convertImage

Converte tra `jpeg`, `png`, `svg`, `heic` e `webp` in qualsiasi direzione, oppure rasterizza un PDF in JPEG. `ConvertImageOptions` accetta un `outputFormat` obbligatorio più `saveTo` e `outputFilename` facoltativi.

```kotlin
client.convertImage("photo.heic", ConvertImageOptions(outputFormat = "webp", saveTo = "photo.webp"))
client.convertImage(Path.of("logo.svg"), ConvertImageOptions(outputFormat = "png", saveTo = "logo.png"))

val bytes = Files.readAllBytes(Path.of("photo.heic"))
client.convertImage(
    FileInput(data = bytes, filename = "photo.heic"),
    ConvertImageOptions(outputFormat = "webp", saveTo = "photo.webp"),
)
```

Il formato di input viene ricavato dall'estensione del nome file. Il formato di output viene normalizzato per te, quindi `"jpg"` diventa `jpeg`. Le coppie non supportate sollevano `EnconvertException` prima di qualsiasi chiamata di rete, con gli output validi elencati nel messaggio. Puoi interrogare direttamente la tabella dei formati:

```kotlin
validOutputsFor("pdf")  // [jpeg]
validOutputsFor("json") // [csv, toml, xml, yaml]
validOutputsFor("heic") // [jpeg, png, svg, webp]
```

### convertDocument

Converte documenti e formati di dati. `outputFormat` vale `"pdf"` per impostazione predefinita.

```kotlin
client.convertDocument("report.docx", ConvertDocumentOptions(saveTo = "report.pdf"))
client.convertDocument("data.json", ConvertDocumentOptions(outputFormat = "yaml", saveTo = "data.yaml"))
client.convertDocument(
    "README.md",
    ConvertDocumentOptions(
        outputFormat = "pdf",
        pdfOptions = PdfOptions(pageSize = "A4", margins = PdfMargins(top = 20.0, bottom = 20.0)),
        saveTo = "readme.pdf",
    ),
)
```

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

Le 43 coppie implementate, esattamente come le controlla l'SDK:

| Input | Output |
|-------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `csv` | `json`, `xml` |
| `yaml` | `json` |
| `toml` | `json` |
| `markdown` | `html`, `pdf` |
| `html` | `pdf` |
| `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers` | `pdf` |
| `jpeg`, `png`, `svg`, `heic`, `webp` | l'uno verso l'altro, tutte e 20 le coppie |
| `pdf` | `jpeg` |

EPUB non ha una coppia documentale dedicata. Passa i file `.epub` attraverso `convertToPdf` oppure `convertToMarkdown`. Le opzioni sono `outputFormat`, `saveTo`, `outputFilename` e `pdfOptions` (rispettata solo quando l'output è un PDF).

### convertToMarkdown

Converte in Markdown pulito un file caricato di quasi qualsiasi formato documentale. Il formato viene rilevato automaticamente lato server, quindi l'SDK non esegue alcun controllo dell'estensione e carica il file così com'è.

```kotlin
client.convertToMarkdown("handbook.docx", ConvertToMarkdownOptions(saveTo = "handbook.md"))
```

**Accettati:** PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, più i formati Office legacy e ODF. Qui le immagini non sono supportate.

L'output è un unico file `.md` strutturato per intestazioni, il che ne fa un primo stadio naturale per una pipeline RAG: un chunker semantico può suddividere sulla gerarchia di intestazioni del documento invece che su conteggi arbitrari di caratteri. Su questo endpoint non ci sono opzioni PDF; `saveTo` e `outputFilename` sono le uniche opzioni.

### convertToPdf

Converte in PDF un file caricato di quasi qualsiasi formato. L'input accettato copre Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, testo semplice, immagini raster, SVG, EPUB e un PDF già esistente come passthrough.

```kotlin
client.convertToPdf("slides.pptx", ConvertToPdfOptions(saveTo = "slides.pdf"))
client.convertToPdf("scan.pdf", ConvertToPdfOptions(pdfOptions = PdfOptions(grayscale = true), saveTo = "gray.pdf"))
```

<div class="alert alert-warning">
<strong>Su questo endpoint viene rispettata solo <code>pdfOptions.grayscale</code>.</strong> Dimensione della pagina, orientamento, margini, scala, intestazione e piè di pagina vengono ignorati qui. Quando ti serve la geometria di pagina completa, passa invece da <code>convertDocument</code> oppure da <code>convertUrlToPdf</code>.
</div>

### convertWebsiteToPdf e convertWebsiteToScreenshot

Individua ogni pagina di un sito web, converte ciascuna in background e raccoglie tutto in un unico ZIP. Entrambi sono asincroni: restituiscono subito un `BatchSubmission`, e tu interroghi con `getBatchStatus` oppure blocchi con `waitForBatch`.

```kotlin
val batch = client.convertWebsiteToPdf(
    "https://example.com",
    WebsiteToPdfOptions(
        website = WebsiteConversionOptions(crawlMode = CrawlMode.SITEMAP, excludePatterns = listOf("/tag/")),
    ),
)
val status = client.waitForBatch(batch.batchId, WaitForBatchOptions(saveTo = "site.zip"))
println("${status.completed} of ${status.total} converted, ${status.failed} failed")
```

`convertWebsiteToScreenshot` funziona in modo identico e produce uno ZIP di PNG. `WebsiteConversionOptions` porta con sé `render`, `crawlMode` (`AUTO`, `SITEMAP`, `FULL`), `includePatterns`, `excludePatterns`, `notificationEmail` e `callbackUrl`. `waitForBatch` interroga ogni 5 secondi e si arrende dopo 30 minuti, entrambi sovrascrivibili tramite `WaitForBatchOptions(intervalMs, timeoutMs, saveTo)`; allo scadere del tempo solleva `ApiException` con stato `504`.

### getJobStatus

Interroga un singolo job di conversione asincrono o recuperato.

```kotlin
val status = client.getJobStatus("job_abc123")
when (status.status) {
    JobStatusValue.SUCCESS -> println(status.presignedUrl)
    JobStatusValue.FAILED -> System.err.println(status.error)
    JobStatusValue.PROCESSING -> println("still running")
}
```

<div class="alert alert-info">
<strong>Raramente serve chiamarlo tu stesso.</strong> L'SDK lo interroga già quando una richiesta sincrona restituisce 5xx. Vedi <a href="#timeout-recovery">Recupero dei timeout</a>.
</div>

---

## Web intelligence (V2)

Ogni lettura V2 porta con sé `renderQuality`, un punteggio da 0.0 a 1.0 che descrive quanto pulitamente la pagina si è davvero renderizzata. Una pagina anti-bot, un muro di cookie, un cancello di login o un guscio SPA vuoto tornano con un punteggio basso, una mappa `deductions` popolata che dice quali controlli sono scattati e dei `warnings`, mentre il contenuto stesso viene comunque restituito. Una lettura sbagliata viene segnalata invece di entrare in silenzio nel contesto del tuo agente. `statusCode` riporta lo stato HTTP della risposta finale del documento principale, e `contentHash` ti permette di capire che nulla è cambiato dall'ultima lettura. Parti dalla [panoramica V2](/it/docs/v2-overview) per i concetti dietro alle sei capacità.

| Capacità | Metodi su `client.v2` |
|-----------|------------------------|
| Perceive | `perceive`, `getPerceiveOperation`, `perceiveBatch`, `getPerceiveBatch`, `perceiveDirect`, `downloadPerceiveArtifact` |
| Discover | `discover` |
| Lookup | `lookup` |
| Distill | `distill` |
| Ingest | `ingest`, `ingestFiles`, `listIngestJobs`, `getIngestJob`, `cancelIngestJob`, `retryIngestWebhook`, `getWebhookSecret`, `rotateWebhookSecret` |
| Watch | `createWatcher`, `listWatchers`, `getWatcher`, `getWatcherSnapshots`, `updateWatcher`, `deleteWatcher` |

### Perceive

Renderizza un URL negli artefatti che chiedi. Sincrono: la chiamata restituisce un'operazione completata i cui URL agli artefatti sono firmati per 15 minuti. Riferimento completo in [Perceive](/it/docs/v2-perceive).

```kotlin
val op = client.v2.perceive(
    "https://example.com/pricing",
    PerceiveOptions(
        outputs = listOf(PerceiveOutputName.MARKDOWN, PerceiveOutputName.SCREENSHOT_FULL_PAGE, PerceiveOutputName.STRUCTURED),
        extract = listOf(PerceiveExtractName.TABLES, PerceiveExtractName.METADATA),
        viewport = PerceiveViewport(width = 1440),
        waitFor = "css:.pricing-table",
    ),
)

if ((op.renderQuality ?: 0.0) < 0.5) System.err.println("Low quality read: ${op.deductions} ${op.warnings}")
println(op.outputs["markdown"]?.url)
println(op.structured)
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `outputs` | `List<PerceiveOutputName>?` | `[MARKDOWN, STRUCTURED]` | `MARKDOWN`, `HTML_CLEANED`, `HTML_RAW`, `SCREENSHOT`, `SCREENSHOT_FULL_PAGE`, `PDF`, `LINKS`, `IMAGES`, `STRUCTURED`. |
| `extract` | `List<PerceiveExtractName>?` | -- | `TABLES`, `PRICES`, `CONTACTS`, `METADATA`, `MAIN_CONTENT`, `HEADINGS`, `STRUCTURED_DATA`, `TECHNOLOGIES`, `ALL`. |
| `schema` | `Map<String, Any?>?` | -- | Schema JSON per l'estrazione strutturata. |
| `waitFor` / `waitTimeoutMs` | `String?` / `Int?` | -- / `30000` | Un selettore CSS (facoltativamente con prefisso `css:`) oppure `js:<expr>` da attendere, e il relativo budget, da 0 a 60000. |
| `jsCode` | `String?` | -- | JavaScript eseguito dopo la navigazione, massimo 20000 caratteri. |
| `viewport` | `PerceiveViewport?` | 1920 x 1080 | `width` 320-3840, `height` 240-2160. |
| `headers` / `cookies` / `auth` | -- | -- | Header aggiuntivi, cookie iniettati, credenziali HTTP Basic. |
| `cacheMode` | `PerceiveCacheMode?` | `ENABLED` | `ENABLED` (cache di 1 ora), `BYPASS`, `REFRESH`. |
| `pdfOptions` | `PdfOptions?` | -- | Ha senso solo quando `outputs` include `PDF`. |
| `blockResources` | `List<PerceiveResourceType>?` | -- | Tipi di risorsa che il browser non deve caricare. |
| `respectRobots` / `mobile` | `Boolean?` | -- | Rispetta `robots.txt`; renderizza con un profilo mobile. |
| `onlyMainContent` | `Boolean?` | `true` | Rimuove navigazione, header, footer e banner dei cookie dall'artefatto Markdown e dall'estratto `main_content`. |
| `directDownload` | `Boolean?` | -- | Restituisce i byte dell'artefatto invece di un envelope JSON. Preferisci `perceiveDirect`. |

`proxyUrl`, `geolocation` e `actionChain` esistono su `PerceiveOptions` ma non sono ancora disponibili lato server e attualmente vengono rifiutati con `422`.

Raggruppa in batch fino a 1000 URL dietro un unico blocco di opzioni condiviso. I batch piccoli si completano inline; quelli più grandi tornano come `QUEUED`, quindi interroga l'id del job. `getPerceiveOperation` rifirma gli URL agli artefatti di qualsiasi operazione precedente.

```kotlin
val batch = client.v2.perceiveBatch(
    listOf("https://a.example.com", "https://b.example.com"),
    PerceiveBatchOptions(
        options = PerceiveOptions(outputs = listOf(PerceiveOutputName.MARKDOWN)),
        outputMode = PerceiveBatchOutputMode.ZIP,
    ),
)

var job = client.v2.getPerceiveBatch(batch.jobId)
while (job.status == PerceiveBatchStatus.QUEUED || job.status == PerceiveBatchStatus.PROCESSING) {
    Thread.sleep(5_000)
    job = client.v2.getPerceiveBatch(batch.jobId)
}
println("${job.completed}/${job.total} done, zip at ${job.zip?.url}")

val again = client.v2.getPerceiveOperation(op.operationId) // URL appena firmati
```

Quando vuoi i byte e nient'altro, `perceiveDirect` trasmette l'artefatto sulla stessa richiesta e salta il giro dell'URL firmato. Richiede esattamente un output che produca un artefatto, cioè qualsiasi cosa tranne `STRUCTURED`, e solleva `EnconvertException` localmente se ne chiedi zero o più di uno.

```kotlin
val direct = client.v2.perceiveDirect("https://example.com", PerceiveOptions(outputs = listOf(PerceiveOutputName.PDF)))
Files.write(Path.of(direct.filename ?: "page.pdf"), direct.content)
println("${direct.renderQuality} ${direct.sourceStatusCode} ${direct.warningsCount}")

// Riscarica un artefatto archiviato di un'operazione precedente.
val markdown = client.v2.downloadPerceiveArtifact(op.operationId, PerceiveOutputName.MARKDOWN)
println(markdown.content.toString(Charsets.UTF_8))
```

`downloadPerceiveArtifact` accetta un `output` nullo quando l'operazione ha prodotto esattamente un artefatto, e restituisce `410` una volta che l'artefatto archiviato supera la propria finestra di conservazione.

### Discover

Enumera gli URL di un sito senza alcun rendering nel browser. Riferimento completo in [Discover](/it/docs/v2-discover).

```kotlin
val found = client.v2.discover(
    "https://example.com",
    DiscoverOptions(mode = DiscoverMode.HYBRID, maxUrls = 200, maxDepth = 3, excludePatterns = listOf("/tag/")),
)
println("${found.total} urls, truncated=${found.truncated}, sources=${found.sources}")
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `mode` | `DiscoverMode?` | `HYBRID` | `SITEMAP`, `CRAWL` oppure `HYBRID` (sitemap più scansione HTTP). |
| `maxUrls` / `maxDepth` | `Int?` | `100` / `2` | 1-1000 e 1-5. |
| `includePatterns` / `excludePatterns` | `List<String>?` | -- | Allowlist e denylist con espressioni regolari, massimo 50 voci ciascuna. La denylist viene applicata per seconda. |
| `sameDomainOnly` | `Boolean?` | `true` | Resta sul dominio di partenza. |
| `respectRobots` | `Boolean?` | -- | Rispetta `robots.txt`. |

`DiscoverResult.sources` riporta i conteggi grezzi per singola fonte prima della deduplicazione, ad esempio `{sitemap=42, crawl=30}`.

### Lookup

Esegue una ricerca web per categorie e, facoltativamente, applica perceive ai primi risultati nella stessa chiamata. Riferimento completo in [Lookup](/it/docs/v2-lookup).

```kotlin
val search = client.v2.lookup(
    "best static site generators",
    LookupOptions(category = LookupCategory.WEB, numResults = 10, country = "us", timeFilter = LookupTimeFilter.MONTH, perceiveTop = 3),
)

for (hit in search.results) {
    println("${hit.position}. ${hit.title} ${hit.url}")
    hit.perceive?.let { println("   rendered at quality ${it.renderQuality}") }
}
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `category` | `LookupCategory?` | `WEB` | `WEB`, `NEWS`, `IMAGES`, `SCHOLAR`, `PATENTS`, `MAPS`. |
| `country` / `locale` | `String?` | -- | Codice paese `gl` di Google e lingua dell'interfaccia `hl`. |
| `timeFilter` | `LookupTimeFilter?` | -- | `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR`. |
| `numResults` / `page` | `Int?` | `10` / `1` | 1-100 e 1-10. |
| `location` | `String?` | -- | Località in testo libero, ad esempio `"Austin, Texas"`. |
| `autocorrect` | `Boolean?` | `true` | Lascia che il provider corregga i refusi. |
| `perceiveTop` | `Int?` | `0` | Applica automaticamente perceive ai primi N URL dei risultati, 0-10. Ciascuno esegue un rendering completo nel browser. |

Il risultato porta con sé anche `answerBox`, `knowledgeGraph`, `perceiveOperationIds` e `total`.

### Distill

Punta uno schema verso alcune pagine e ottieni dati strutturati. Un passaggio CSS facoltativo risponde a tutto ciò che può prima che qualsiasi cosa salga al livello LLM. Riferimento completo in [Distill](/it/docs/v2-distill).

```kotlin
val extraction = client.v2.distill(
    DistillOptions(
        urls = listOf("https://example.com/pricing"),
        schema = mapOf("plans" to "list of plan names with monthly prices"),
        cssSchema = CssSchema(
            baseSelector = ".plan-card",
            fields = listOf(
                CssField(name = "name", type = CssFieldType.TEXT, selector = "h3"),
                CssField(name = "price", type = CssFieldType.TEXT, selector = ".price"),
            ),
            targetField = "plans",
        ),
    ),
)

for (item in extraction.results) {
    println("${item.url} tier=${item.extractionTier} css=${item.fieldsFromCss} llm=${item.fieldsFromLlm}")
    println(item.data)
}
```

Oppure individua prima gli URL e distilla ciascuno di essi:

```kotlin
client.v2.distill(
    DistillOptions(
        discoverFrom = DistillDiscoverFrom(url = "https://example.com", mode = DiscoverMode.SITEMAP, maxPages = 10),
        schema = mapOf("title" to "page title", "summary" to "one-line summary"),
    ),
)
```

Fornisci esattamente uno tra `urls` e `discoverFrom`. Passarli entrambi, o nessuno dei due, solleva `EnconvertException` prima che la richiesta esca dal tuo processo.

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `urls` | `List<String>?` | -- | URL espliciti da distillare, massimo 50. |
| `discoverFrom` | `DistillDiscoverFrom?` | -- | Individua prima gli URL di un sito. `maxPages` va da 1 a 50, predefinito 10. |
| `schema` | `Map<String, Any?>` | obbligatorio | Un oggetto JSON Schema, oppure una mappa piatta `{field to description}`. |
| `cssSchema` | `CssSchema?` | -- | Passaggio CSS eseguito prima di qualsiasi escalation all'LLM. |
| `waitFor` / `waitTimeoutMs` | `String?` / `Int?` | -- / `30000` | Selettore o espressione `js:` da attendere, e il relativo budget. |
| `headers` / `cookies` / `respectRobots` | -- | -- | Stessi controlli di rendering di perceive. |

`CssField.type` è uno tra `TEXT`, `ATTRIBUTE`, `HTML`, `REGEX`, `NESTED`, `LIST`, `NESTED_LIST`. `ATTRIBUTE` richiede `attribute`, `REGEX` richiede `pattern`, e i tre tipi annidati richiedono una lista `fields` non vuota, fino a cinque livelli di profondità.

### Ingest

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

```kotlin
val job = client.v2.ingest(
    IngestOptions(
        mode = IngestMode.SITEMAP,
        url = "https://docs.example.com",
        maxPages = 100,
        chunk = IngestChunkOptions(maxWords = 512, sentenceOverlap = 1),
        webhookUrl = "https://my.app/hooks/enconvert",
    ),
)

var state = client.v2.getIngestJob(job.jobId)
while (state.status !in setOf(IngestStatus.COMPLETED, IngestStatus.FAILED, IngestStatus.CANCELED)) {
    Thread.sleep(10_000)
    state = client.v2.getIngestJob(job.jobId)
}
if (state.status == IngestStatus.COMPLETED) println("${state.totalChunks} chunks at ${state.outputUrl}")
```

`mode` vale `URLS` per impostazione predefinita, il che richiede una lista `urls` non vuota e rifiuta `url`. `SITEMAP` e `CRAWL` richiedono un `url` di partenza e rifiutano `urls`. L'SDK applica entrambe le regole localmente e solleva `EnconvertException` invece di inviare una richiesta che non può riuscire.

I file caricati passano da `ingestFiles`, che condivide lo stesso ciclo di vita del job in modalità `FILES` e accetta PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, e documenti Office legacy e ODF.

```kotlin
val paths = listOf(Path.of("handbook.pdf"), Path.of("notes.docx"))
val fileJob = client.v2.ingestFiles(
    paths.map { FileInput(data = Files.readAllBytes(it), filename = it.fileName.toString()) },
    IngestFilesOptions(chunk = IngestChunkOptions(maxWords = 512, sentenceOverlap = 1)),
)
println(fileJob.jobId)
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `mode` | `IngestMode?` | `URLS` | `URLS`, `SITEMAP`, `CRAWL`, `FILES`. |
| `url` / `urls` | `String?` / `List<String>?` | -- | URL di partenza per `SITEMAP` e `CRAWL`; URL espliciti (massimo 1000) per `URLS`. |
| `maxPages` / `maxDepth` | `Int?` | `50` / `2` | Limiti di scoperta, 1-1000 e 1-5. |
| `sameDomainOnly` | `Boolean?` | `true` | Resta sul dominio di partenza. |
| `includePatterns` / `excludePatterns` / `respectRobots` | -- | -- | Stessi controlli di scoperta di `discover`. |
| `waitFor` / `waitTimeoutMs` | `String?` / `Int?` | -- / `30000` | Attesa di rendering per pagina. |
| `chunk` | `IngestChunkOptions?` | -- | `maxWords` 32-4000 (predefinito 512), `sentenceOverlap` 0-10 (predefinito 1). |
| `webhookUrl` | `String?` | -- | Webhook di completamento, firmato in HMAC. |

Gestione dei job e impianto dei webhook:

```kotlin
client.v2.listIngestJobs(V2ListOptions(limit = 20))   // dal più recente
client.v2.cancelIngestJob(job.jobId)                  // idempotente

val secret = client.v2.getWebhookSecret()
println("${secret.signatureHeader} ${secret.signatureScheme} ${secret.replayToleranceSeconds}s")

client.v2.rotateWebhookSecret()         // le vecchie firme smettono subito di essere valide
client.v2.retryIngestWebhook(job.jobId) // rinvia il webhook di un job completato
```

### Watch

Rifà il rendering di un URL a cadenza fissa e ti avvisa quando cambia. Riferimento completo in [Watch](/it/docs/v2-watch).

```kotlin
val watcher = client.v2.createWatcher(
    "https://example.com/pricing",
    WatchCreateOptions(
        frequencyMinutes = 60,
        diffMode = WatchDiffMode.AUTO,
        webhookUrl = "https://my.app/hooks/changes",
        notifyEmail = true,
    ),
)

client.v2.listWatchers(V2ListOptions(limit = 20))
for (snap in client.v2.getWatcherSnapshots(watcher.watcherId, SnapshotListOptions(limit = 10)).snapshots) {
    println("${snap.checkedAt} changed=${snap.hasChanges} similarity=${snap.similarity}")
}

client.v2.updateWatcher(watcher.watcherId, WatcherUpdate(status = WatcherUpdateStatus.PAUSED))
client.v2.updateWatcher(watcher.watcherId, WatcherUpdate(webhookUrl = "")) // azzera il webhook
client.v2.deleteWatcher(watcher.watcherId)                                 // cancellazione logica, idempotente
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `frequencyMinutes` | `Int?` | `60` | Minuti tra un controllo e l'altro, 60-43200. Il minimo orario è invalicabile. |
| `diffMode` | `WatchDiffMode?` | `AUTO` | `AUTO`, `TEXT`, `STRUCTURED`, `TABLES`, `METADATA`. |
| `trackFields` | `Map<String, Any?>?` | -- | Sottoinsieme di campi o selettori che il motore di diff deve osservare. |
| `webhookUrl` | `String?` | -- | Webhook di modifica, firmato in HMAC con lo stesso segreto di ingest. |
| `notifyEmail` | `Boolean?` | `true` | Invia un'email al proprietario del progetto quando ci sono cambiamenti. |

`updateWatcher` richiede almeno un campo e solleva `EnconvertException` su un `WatcherUpdate` vuoto. Il suo `status` accetta solo `ACTIVE` oppure `PAUSED`; la cancellazione passa da `deleteWatcher`, che restituisce il watcher archiviato con stato `DELETED`. `getWatcher` su un watcher cancellato risponde `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 osservata. Effettua l'escape prima di renderizzarlo in HTML, in una dashboard o in un messaggio di chat.
</div>

---

## Opzioni PDF

`PdfOptions` è condiviso da `convertUrlToPdf`, `convertDocument`, `convertToPdf` (solo scala di grigi), `convertWebsiteToPdf` e `PerceiveOptions.pdfOptions`.

```kotlin
client.convertUrlToPdf(
    "https://example.com",
    UrlToPdfOptions(
        pdfOptions = PdfOptions(
            pageSize = "A4",
            orientation = PdfOrientation.LANDSCAPE,
            margins = PdfMargins(top = 10.0, bottom = 10.0, left = 15.0, right = 15.0),
            scale = 0.9,
            header = PdfHeaderFooter(content = "Quarterly report", height = 12.0),
        ),
        saveTo = "report.pdf",
    ),
)
```

| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `pageSize` | `String?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` e così via. |
| `pageWidth` / `pageHeight` | `Double?` | Geometria personalizzata. Insieme hanno la precedenza su `pageSize`. |
| `orientation` | `PdfOrientation?` | `PORTRAIT` oppure `LANDSCAPE`. Il valore predefinito è portrait. |
| `margins` | `PdfMargins?` | `top`, `bottom`, `left`, `right`, tutti double opzionali in mm. |
| `scale` | `Double?` | Scala di rendering, ad esempio `0.9` per il 90 percento. |
| `grayscale` | `Boolean?` | Post-elabora il PDF convertendolo in scala di grigi. |
| `header` / `footer` | `PdfHeaderFooter?` | `content` (massimo 2000 caratteri) e `height`. |

Solo i campi che imposti davvero vengono serializzati sulla rete, quindi un `PdfOptions` riempito parzialmente non sovrascrive mai un valore predefinito del server che non hai toccato. La matrice completa dei parametri si trova in [Parametri e opzioni](/it/docs/parameters-options).

---

## Gestione degli errori

Ogni fallimento è una `EnconvertException` o una sua sottoclasse, quindi un unico `catch` può farti da rete di sicurezza mentre le sottoclassi specifiche gestiscono i casi che ti interessano.

```kotlin
try {
    client.v2.perceive("https://example.com")
} catch (e: AuthenticationException) {
    System.err.println("Invalid or missing API key")
} catch (e: QuotaException) {
    System.err.println("Request rejected with 402")
} catch (e: RateLimitException) {
    System.err.println("Too many requests, back off and retry")
} catch (e: ApiException) {
    System.err.println("API error [${e.statusCode}]: ${e.message}")
} catch (e: EnconvertException) {
    System.err.println("Client-side validation failed: ${e.message}")
}
```

| Classe | Sollevata per | Codice di stato |
|-------|-----------|-------------|
| `AuthenticationException` | Chiave API non valida, mancante o revocata | `401`, `403` |
| `QuotaException` | Sollevata su HTTP 402 | `402` |
| `RateLimitException` | Troppe richieste | `429` |
| `ApiException` | Qualsiasi altra risposta 4xx o 5xx | il codice effettivo |
| `EnconvertException` | Classe base, più la validazione lato client come una coppia di conversione non supportata o un oggetto di opzioni malformato | -- |

`ApiException` espone la proprietà `statusCode` grezza, e il suo `message` viene reso come `[<statusCode>] <server message>` con il campo `detail` o `error` del server estratto dal corpo JSON. L'ordine dei `catch` conta: le tre classi più specifiche estendono tutte `ApiException`, che a sua volta estende `EnconvertException`, quindi elencale per prime. La mappa dei messaggi è documentata nei [codici di errore](/it/docs/error-codes).

---

## Recupero dei timeout

I rendering lunghi da URL a PDF e le conversioni di documenti di grandi dimensioni possono superare il timeout di 60-120 secondi di un reverse proxy anche quando la conversione riesce sul server. L'SDK se ne occupa sui metodi di conversione V1:

1. Prima di ogni richiesta genera un id di job esadecimale di 32 caratteri e lo invia come `job_id` nel corpo JSON o come campo multipart.
2. Se la richiesta torna 5xx, l'SDK smette di fidarsi della risposta e interroga `GET /v1/convert/status/{job_id}` ogni 3 secondi. Un `404` mentre la riga del job è ancora in scrittura significa "continua ad attendere".
3. Su `success` l'SDK mappa il payload su un normale `ConversionResult`. Su `failed` solleva `ApiException` con il messaggio di errore del server.
4. Il limite di tempo è di 5 minuti, dopo i quali solleva `ApiException(504, "Conversion timed out")`.

Le risposte riuscite che omettono `job_id` (lo fa il percorso URL sincrono) ricevono il riempimento con l'id generato dal client, così `result.jobId` è sempre qualcosa che puoi passare a `getJobStatus`. Due eccezioni deliberate: `convertWebsiteToPdf` e `convertWebsiteToScreenshot` saltano il fallback, perché l'invio di un sito web non ha una riga per singolo job e un 5xx lì significa che l'invio stesso è fallito. Anche i metodi V2 lo saltano, dato che ogni endpoint V2 ha il proprio meccanismo di polling o di webhook.

---

## Configurazione

```kotlin
val client = Enconvert(
    apiKey = System.getenv("ENCONVERT_API_KEY"),
    timeout = 300_000,                      // ms, 5 minuti
    baseUrl = "https://api.enconvert.com",  // sovrascrittura per un gateway self-hosted
)
```

| Parametro | Tipo | Predefinito | Descrizione |
|-----------|------|---------|-------------|
| `apiKey` | `String` | obbligatorio | Chiave API privata. Un valore vuoto fa sollevare `IllegalArgumentException` dal costruttore. |
| `timeout` | `Long` | `300_000` | Timeout per richiesta in millisecondi, applicato all'`HttpRequest` sottostante. |
| `baseUrl` | `String` | `https://api.enconvert.com` | URL base dell'API. Gli slash finali vengono rimossi. |

<div class="alert alert-warning">
<strong>Non inserire mai la chiave API direttamente nel codice.</strong> Leggila da una variabile d'ambiente, da una proprietà Gradle o dal tuo secret manager, e tienila fuori da qualsiasi artefatto che distribuisci sul dispositivo di un utente. Chiunque abbia la tua chiave privata può eseguire richieste a carico del tuo progetto.
</div>

---

## Struttura del risultato

Ogni metodo di conversione restituisce un `ConversionResult`:

```kotlin
public data class ConversionResult(
    val presignedUrl: String,
    val objectKey: String,
    val filename: String,
    val fileSize: Long? = null,
    val conversionTimeSeconds: Double? = null,
    val jobId: String? = null,
)
```

L'URL presigned è un link firmato temporaneo. Passa `saveTo` se vuoi i byte su disco subito, oppure scarica tu stesso l'URL e archivia il file nel tuo bucket per un accesso permanente.

Le letture V2 restituiscono invece un `PerceiveResult`, ed è lì che vivono i segnali di trasparenza:

```kotlin
val op = client.v2.perceive("https://example.com")

op.operationId    // "per_..."
op.status         // PerceiveStatus.COMPLETED
op.url            // URL richiesto; op.urlFinal dopo i redirect
op.renderQuality  // Double?, da 0.0 a 1.0
op.statusCode     // Int?, stato HTTP del documento principale
op.deductions     // Map<String, Double>, es. {http_error=0.7}. Vuota su un render pulito.
op.warnings       // List<String>
op.cacheHit       // Boolean; op.contentHash è lo SHA-256 del contenuto renderizzato
op.outputs        // Map<String, V2OutputArtifact> indicizzata per nome di output
op.structured     // Map<String, Any?>?, presente quando sono stati usati extract o schema
op.extractionTier // HEURISTIC, CSS oppure LLM
op.tokens         // V2Tokens(input, output); accanto ci sono op.costCents e op.durationMs
```

Ogni `V2OutputArtifact` porta con sé `url`, `objectKey`, `sizeBytes`, `contentType` ed `expiresIn` (900 secondi). Gli URL degli artefatti vengono rifirmati a ogni chiamata di `getPerceiveOperation`, quindi archivia l'`operationId`, non l'URL. I payload non tipizzati (schemi di estrazione, dati estratti, campi tracciati, voci di diff, extra di ricerca) attraversano il confine come `Map<String, Any?>` e si convertono senza perdite in entrambe le direzioni, quindi nulla di ciò che metti in uno schema viene rimodellato all'uscita.

---

## Sorgente e problemi

- **Maven Central:** `com.enconvert:enconvert-kotlin:0.0.1`
- **GitHub:** [conversionapi/kotlin-sdk](https://github.com/conversionapi/kotlin-sdk)
- **Licenza:** MIT
- **Altri linguaggi:** consulta l'[elenco completo degli SDK](/it/docs/sdks)

---

## Domande frequenti

### Come converto i file in Kotlin?

Aggiungi `com.enconvert:enconvert-kotlin:0.0.1` alla tua build Gradle o Maven, costruisci `Enconvert(apiKey = System.getenv("ENCONVERT_API_KEY"))` e chiama un metodo tipizzato come `convertDocument`, `convertImage` oppure `convertUrlToPdf`. Passa `saveTo` nell'oggetto delle opzioni per trasmettere l'output direttamente in un file locale invece di scaricare tu stesso l'URL presigned.

### Come converto DOCX in PDF in Kotlin?

Chiama `client.convertDocument("report.docx", ConvertDocumentOptions(saveTo = "report.pdf"))`. Il formato di output vale `pdf` per impostazione predefinita, quindi puoi lasciare `outputFormat` non impostato. Il formato di input viene ricavato dall'estensione del file, e `.doc` e `.docx` puntano entrambi alla stessa conversione. Per la geometria di pagina, passa un `PdfOptions` tramite `ConvertDocumentOptions.pdfOptions`.

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

Chiama `client.convertUrlToPdf(url, UrlToPdfOptions(saveTo = "page.pdf"))`. Imposta `singlePage = false` per impaginare con `pdfOptions.pageSize`, e usa `UrlRenderOptions` per cambiare il viewport, disattivare il caricamento dei media o disabilitare il passaggio di scroll che attiva i caricamenti lazy.

### Come converto HEIC in WebP sulla JVM?

Chiama `client.convertImage("photo.heic", ConvertImageOptions(outputFormat = "webp", saveTo = "photo.webp"))`. Tutte e 20 le coppie tra `jpeg`, `png`, `svg`, `heic` e `webp` sono implementate, più la rasterizzazione da `pdf` a `jpeg`. Una coppia non supportata solleva `EnconvertException` prima di qualsiasi chiamata di rete, e `validOutputsFor("heic")` elenca in anticipo le destinazioni valide.

### Come estraggo Markdown da una pagina web in Kotlin?

Ci sono due opzioni. `client.convertUrlToMarkdown(url, UrlToMarkdownOptions(saveTo = "page.md"))` ti dà un file Markdown con frontmatter YAML. `client.v2.perceive(url, PerceiveOptions(outputs = listOf(PerceiveOutputName.MARKDOWN)))` ti dà lo stesso contenuto più `renderQuality`, `deductions`, `warnings` e `statusCode`, che è quello che vuoi quando il risultato verrà letto da un agente senza supervisione.

### Che cosa significa renderQuality e quando dovrei scartare una pagina?

`renderQuality` va da 0.0 a 1.0 e descrive quanto pulitamente la pagina si è renderizzata, non quanto è buono il contenuto. Pagine anti-bot, muri di login, errori HTTP e gusci SPA vuoti lo fanno scendere, e `deductions` dà un nome a ciascun controllo scattato, ad esempio `{http_error=0.7}`. Il contenuto viene sempre restituito, così puoi ispezionarlo. Uno schema comune è considerare sospetto qualsiasi valore sotto 0.5 e rifare la richiesta con `cacheMode = PerceiveCacheMode.REFRESH` oppure inoltrarla a una persona.

### Come trasformo un sito di documentazione in chunk per il RAG da Kotlin?

Chiama `client.v2.ingest(IngestOptions(mode = IngestMode.SITEMAP, url = "https://docs.example.com", chunk = IngestChunkOptions(maxWords = 512, sentenceOverlap = 1)))`. Ingest è sempre asincrono: interroga `getIngestJob(jobId)` finché lo stato non è `COMPLETED` e leggi `outputUrl` per il JSONL, oppure imposta `webhookUrl` e lascia che sia il webhook di completamento a raggiungerti. I documenti locali passano da `ingestFiles` con le stesse impostazioni di chunk.

### L'SDK blocca il thread chiamante?

Sì. Ogni metodo chiama `HttpClient.send` in modo sincrono, e nell'SDK non ci sono funzioni `suspend` né builder di coroutine. `waitForBatch` e il poller interno di recupero dei timeout mettono in sleep il thread corrente tra un tentativo e l'altro. Da una coroutine, avvolgi le chiamate in `withContext(Dispatchers.IO)`; da un framework server, tienile fuori dal thread pool che gestisce le richieste.

### Posso chiamare questo SDK da Java?

Puoi, dato che si tratta di normali classi JVM, ma gli argomenti predefiniti di Kotlin non vengono esposti a Java come overload, quindi un chiamante Java deve passare ogni argomento del costruttore di una data class di opzioni. Se il tuo codice è in Java, usa invece l'SDK Java separato elencato nella [pagina degli SDK](/it/docs/sdks).

### Dove ottengo una chiave API?

Crea una chiave privata nella tua [dashboard](/it/dashboard). Viene inviata nell'header `X-API-Key` a ogni richiesta, quindi tienila lato server. I tipi di chiave e gli ambiti sono trattati nell'[autenticazione](/it/docs/authentication), e i [prezzi](/it/pricing) coprono l'aspetto commerciale.
