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.
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).
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"))
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")
}
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.
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:
- Prima di ogni richiesta genera un id di job esadecimale di 32 caratteri e lo invia come
job_idnel corpo JSON o come campo multipart. - Se la richiesta torna 5xx, l'SDK smette di fidarsi della risposta e interroga
GET /v1/convert/status/{job_id}ogni 3 secondi. Un404mentre la riga del job è ancora in scrittura significa "continua ad attendere". - Su
successl'SDK mappa il payload su un normaleConversionResult. SufailedsollevaApiExceptioncon il messaggio di errore del server. - 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. |
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#
- Maven Central:
com.enconvert:enconvert-kotlin:0.0.1 - GitHub: conversionapi/kotlin-sdk
- Licenza: MIT
- Altri linguaggi: consulta l'elenco completo degli SDK
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.