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.

Maven Central: com.enconvert:enconvert-kotlin:0.0.1 · Sorgente: conversionapi/kotlin-sdk · Richiede: JDK 17+ · Licenza: MIT

Installazione#

// Gradle, Kotlin DSL
dependencies {
    implementation("com.enconvert:enconvert-kotlin:0.0.1")
}
// Gradle, Groovy DSL
dependencies {
    implementation 'com.enconvert:enconvert-kotlin:0.0.1'
}
<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#

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

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.

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.

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

Non combinare auth con un header Authorization. L'API rifiuta il conflitto invece di indovinare quale dei due intendevi.

convertUrlToScreenshot#

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

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.

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.

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.

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:

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.

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'è.

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.

client.convertToPdf("slides.pptx", ConvertToPdfOptions(saveTo = "slides.pdf"))
client.convertToPdf("scan.pdf", ConvertToPdfOptions(pdfOptions = PdfOptions(grayscale = true), saveTo = "gray.pdf"))
Su questo endpoint viene rispettata solo pdfOptions.grayscale. 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 convertDocument oppure da convertUrlToPdf.

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.

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.

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")
}
Raramente serve chiamarlo tu stesso. L'SDK lo interroga già quando una richiesta sincrona restituisce 5xx. Vedi Recupero dei timeout.

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

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.

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.

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.

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.

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.

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:

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.

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.

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:

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.

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.

I diff degli snapshot contengono contenuto di pagina non attendibile. WatcherSnapshot.changes è testo grezzo prelevato dalla pagina osservata. Effettua l'escape prima di renderizzarlo in HTML, in una dashboard o in un messaggio di chat.

Opzioni PDF#

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

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.


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.

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.


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#

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.
Non inserire mai la chiave API direttamente nel codice. 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.

Struttura del risultato#

Ogni metodo di conversione restituisce un ConversionResult:

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:

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#


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.

Dove ottengo una chiave API?#

Crea una chiave privata nella tua 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, e i prezzi coprono l'aspetto commerciale.