SDK Kotlin de conversion de fichiers#

com.enconvert:enconvert-kotlin est le client EnConvert officiel pour Kotlin et la JVM, compilé pour le JDK 17. Il convertit des fichiers sur 43 paires de formats implémentées (DOCX vers PDF, HEIC vers WebP, JSON vers YAML, URL vers PDF, anything to Markdown), et il lit des pages web en direct pour en tirer du Markdown, du JSON, des captures d'écran et du JSONL prêt pour le RAG, le tout prêt à être consommé par un agent, via l'espace de noms client.v2. Les options et les réponses sont des data classes Kotlin idiomatiques, avec arguments nommés et valeurs par défaut raisonnables ; le HTTP passe par le java.net.http.HttpClient du JDK lui-même, et les conversions lentes se remettent des timeouts de reverse proxy en interrogeant le statut du job.

Maven Central : com.enconvert:enconvert-kotlin:0.0.1 · Source : conversionapi/kotlin-sdk · Requiert : JDK 17+ · Licence : MIT

Installation#

// Gradle, DSL Kotlin
dependencies {
    implementation("com.enconvert:enconvert-kotlin:0.0.1")
}
// Gradle, DSL Groovy
dependencies {
    implementation 'com.enconvert:enconvert-kotlin:0.0.1'
}
<dependency>
  <groupId>com.enconvert</groupId>
  <artifactId>enconvert-kotlin</artifactId>
  <version>0.0.1</version>
</dependency>

La seule dépendance d'exécution est org.jetbrains.kotlinx:kotlinx-serialization-json. Tout le reste vient du JDK : les requêtes sortent via java.net.http.HttpClient et les corps multipart sont assemblés par le SDK lui-même. La toolchain cible la JVM 17, donc n'importe quel runtime JDK 17 ou plus récent convient.


Démarrage rapide#

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

    // Convertit une page en direct en PDF et l'écrit directement sur le disque.
    val pdf = client.convertUrlToPdf("https://example.com", UrlToPdfOptions(saveTo = "page.pdf"))
    println(pdf.presignedUrl)

    // Lit la même page comme votre agent devrait le faire, avec un score de qualité attaché.
    val op = client.v2.perceive(
        "https://example.com",
        PerceiveOptions(outputs = listOf(PerceiveOutputName.MARKDOWN, PerceiveOutputName.STRUCTURED)),
    )
    println("${op.outputs["markdown"]?.url} ${op.renderQuality}") // p. ex. 0.93
}

Chaque type EnConvert vit dans le paquet com.enconvert, et les extraits ci-dessous omettent les imports ; les types du JDK comme java.nio.file.Files et java.nio.file.Path apparaissent sans qualification pour la même raison. Chaque méthode est bloquante, puisqu'il n'y a aucune fonction suspend : depuis une coroutine, enveloppez donc l'appel dans withContext(Dispatchers.IO). Le client s'authentifie avec une clé API privée envoyée dans l'en-tête X-API-Key, ce qui le réserve au côté serveur : ne livrez jamais la clé dans une application Android ou dans quoi que ce soit d'autre que vous distribuez. Voir Authentification pour les types de clés.


Ce que le client expose#

Enconvert constitue toute la surface. La conversion de fichiers vit sur le client lui-même ; l'intelligence web vit sur l'espace de noms v2, accessible via client.v2.

Surface Accessible via Couvre
Conversion de fichiers client.<method>() URL vers PDF, capture d'écran, Markdown ; paires d'images et de documents ; anything-to-PDF et anything-to-Markdown ; lots à l'échelle d'un site ; statut des jobs et des lots
Intelligence web client.v2.<method>() Perceive, discover, lookup, distill, ingest, watch : 23 méthodes sur 21 endpoints REST

Douze méthodes de conversion correspondent à l'API REST décrite dans la vue d'ensemble des endpoints :

Méthode Endpoint Renvoie
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} (interrogé en boucle) BatchStatus

Les quatre méthodes d'envoi de fichier ont chacune quatre surcharges. Le premier argument peut être un chemin String, un java.nio.file.Path, un simple ByteArray (le nom de fichier vaut alors upload.bin), ou un FileInput(data, filename, contentType?) lorsque vous disposez d'octets bruts et voulez les nommer vous-même.


Conversion de fichiers#

convertUrlToPdf#

Effectue le rendu de n'importe quelle URL publique en 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}")
Option Type Défaut Description
render UrlRenderOptions UrlRenderOptions() Fenêtre d'affichage, médias, défilement, nom de fichier, accès navigateur.
saveTo String? -- Chemin local vers lequel écrire le PDF. Les répertoires parents sont créés pour vous.
singlePage Boolean true true produit une seule page continue. false pagine en utilisant pdfOptions.pageSize.
pdfOptions PdfOptions? -- Géométrie de page. Voir Options PDF.

UrlRenderOptions est partagé par toutes les conversions basées sur une URL. Il porte viewportWidth et viewportHeight (1920 x 1080 par défaut), loadMedia et enableScroll (tous deux à true par défaut, ce qui attend les médias et fait défiler la page de haut en bas pour déclencher le chargement paresseux), outputFilename, et trois champs d'accès navigateur : auth (un HttpBasicAuth), cookies (une List<BrowserCookie>, 50 au maximum) et headers (20 au maximum, les en-têtes hop-by-hop sont rejetés).

Ne combinez pas auth avec un en-tête Authorization. L'API rejette le conflit plutôt que de deviner ce que vous vouliez dire.

convertUrlToScreenshot#

Capture un PNG de n'importe quelle URL. UrlToScreenshotOptions ne porte que render et saveTo.

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

convertUrlToMarkdown#

Extrait un Markdown GitHub-Flavored propre à partir d'une URL. Le convertisseur supprime la navigation, les pieds de page, les publicités et les scripts, conserve le corps principal de l'article, et ajoute en tête un frontmatter YAML avec le titre, la description, l'url, les liens et les images.

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

Lorsque vous voulez aussi un score de qualité, un ensemble d'artefacts ou une extraction structurée issus du même rendu, utilisez plutôt client.v2.perceive.

convertImage#

Convertit entre jpeg, png, svg, heic et webp dans n'importe quel sens, ou rastérise un PDF en JPEG. ConvertImageOptions prend un outputFormat obligatoire, plus saveTo et outputFilename facultatifs.

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"),
)

Le format d'entrée est résolu à partir de l'extension du nom de fichier. Le format de sortie est normalisé pour vous, "jpg" se résout donc en jpeg. Les paires non prises en charge lèvent une EnconvertException avant tout appel réseau, avec la liste des sorties valides dans le message. Vous pouvez interroger la table de formats directement :

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

convertDocument#

Convertit des documents et des formats de données. outputFormat vaut "pdf" par défaut.

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",
    ),
)

Entrées prises en charge : .doc, .docx, .xls, .xlsx, .ppt, .pptx, .html, .htm, .odt, .ods, .odp, .ots, .pages, .numbers, .md, .markdown, .csv, .json, .xml, .yaml, .yml, .toml.

Les 43 paires implémentées, exactement telles que le SDK les contrôle :

Entrée Sorties
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 les uns vers les autres, les 20 paires
pdf jpeg

EPUB n'a pas de paire documentaire dédiée. Faites passer les fichiers .epub par convertToPdf ou convertToMarkdown. Les options sont outputFormat, saveTo, outputFilename et pdfOptions (pris en compte uniquement lorsque la sortie est un PDF).

convertToMarkdown#

Convertit un fichier envoyé, de presque n'importe quel format documentaire, en Markdown propre. Le format est détecté automatiquement côté serveur : le SDK n'effectue donc aucune vérification d'extension et envoie le fichier tel quel.

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

Accepté : PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, plus les formats Office hérités et ODF. Les images ne sont pas prises en charge ici.

La sortie est un unique fichier .md qui respecte la hiérarchie des titres, ce qui en fait une première étape naturelle pour un pipeline RAG : un découpeur sémantique peut segmenter sur la hiérarchie de titres propre au document plutôt que sur un nombre de caractères arbitraire. Cet endpoint n'accepte aucune option PDF ; saveTo et outputFilename sont les seules options.

convertToPdf#

Convertit un fichier envoyé, de presque n'importe quel format, en PDF. Les entrées acceptées couvrent Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texte brut, images matricielles, SVG, EPUB, ainsi qu'un PDF existant en passthrough.

client.convertToPdf("slides.pptx", ConvertToPdfOptions(saveTo = "slides.pdf"))
client.convertToPdf("scan.pdf", ConvertToPdfOptions(pdfOptions = PdfOptions(grayscale = true), saveTo = "gray.pdf"))
Seul pdfOptions.grayscale est pris en compte sur cet endpoint. La taille de page, l'orientation, les marges, l'échelle, l'en-tête et le pied de page sont ignorés ici. Lorsque vous avez besoin de la géométrie de page complète, passez plutôt par convertDocument ou convertUrlToPdf.

convertWebsiteToPdf et convertWebsiteToScreenshot#

Découvrez toutes les pages d'un site, convertissez-les en arrière-plan et récupérez une archive ZIP unique. Les deux méthodes sont asynchrones : elles renvoient immédiatement un BatchSubmission, et vous interrogez avec getBatchStatus ou bloquez avec 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 fonctionne à l'identique et produit un ZIP de PNG. WebsiteConversionOptions porte render, crawlMode (AUTO, SITEMAP, FULL), includePatterns, excludePatterns, notificationEmail et callbackUrl. waitForBatch interroge toutes les 5 secondes et abandonne au bout de 30 minutes, deux valeurs remplaçables via WaitForBatchOptions(intervalMs, timeoutMs, saveTo) ; en cas de dépassement, il lève une ApiException avec le statut 504.

getJobStatus#

Interroge un job de conversion asynchrone ou récupéré après timeout.

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")
}
Vous avez rarement besoin de l'appeler vous-même. Le SDK l'interroge déjà lorsqu'une requête synchrone renvoie une 5xx. Voir Récupération après timeout.

Intelligence web (V2)#

Chaque lecture V2 porte renderQuality, un score de 0.0 à 1.0 qui décrit à quel point la page a réellement été rendue proprement. Une page anti-bot, un mur de cookies, une barrière de connexion ou une coquille SPA vide revient avec un score faible, une table deductions remplie qui nomme les contrôles déclenchés, et des warnings, tandis que le contenu lui-même est quand même renvoyé. Une mauvaise lecture est signalée plutôt que d'entrer discrètement dans le contexte de votre agent. statusCode indique le statut HTTP de la réponse finale du document principal, et contentHash vous permet de constater que rien n'a changé depuis la lecture précédente. Commencez par la vue d'ensemble V2 pour les concepts derrière les six capacités.

Capacité Méthodes sur 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#

Effectue le rendu d'une URL dans les artefacts que vous demandez. Synchrone : l'appel renvoie une opération terminée dont les URL d'artefacts sont signées pour 15 minutes. Référence complète dans 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)
Option Type Défaut Description
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?>? -- Schéma JSON pour l'extraction structurée.
waitFor / waitTimeoutMs String? / Int? -- / 30000 Un sélecteur CSS (éventuellement préfixé par css:) ou js:<expr> à attendre, et son budget, de 0 à 60000.
jsCode String? -- JavaScript exécuté après la navigation, 20000 caractères maximum.
viewport PerceiveViewport? 1920 x 1080 width de 320 à 3840, height de 240 à 2160.
headers / cookies / auth -- -- En-têtes supplémentaires, cookies injectés, identifiants HTTP Basic.
cacheMode PerceiveCacheMode? ENABLED ENABLED (cache d'1 heure), BYPASS, REFRESH.
pdfOptions PdfOptions? -- N'a de sens que lorsque outputs inclut PDF.
blockResources List<PerceiveResourceType>? -- Types de ressources que le navigateur ne doit pas charger.
respectRobots / mobile Boolean? -- Respecter robots.txt ; effectuer le rendu avec un profil mobile.
onlyMainContent Boolean? true Supprime la navigation, l'en-tête, le pied de page et les bandeaux de cookies de l'artefact Markdown et de l'extrait main_content.
directDownload Boolean? -- Renvoie les octets de l'artefact au lieu d'une enveloppe JSON. Préférez perceiveDirect.

proxyUrl, geolocation et actionChain existent sur PerceiveOptions mais ne sont pas encore disponibles côté serveur : ils sont actuellement rejetés avec un 422.

Traitez jusqu'à 1000 URL par lot derrière un bloc d'options partagé. Les petits lots se terminent en ligne ; les plus gros reviennent en QUEUED, interrogez donc l'identifiant de job. getPerceiveOperation resigne les URL d'artefacts de n'importe quelle opération antérieure.

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 fraîchement signées

Lorsque vous ne voulez que les octets, perceiveDirect renvoie l'artefact sur la même requête et évite l'aller-retour par URL signée. Il exige exactement une sortie produisant un artefact, c'est-à-dire n'importe quoi sauf STRUCTURED, et lève une EnconvertException en local si vous en demandez zéro ou plus d'une.

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}")

// Retélécharge un artefact stocké d'une opération antérieure.
val markdown = client.v2.downloadPerceiveArtifact(op.operationId, PerceiveOutputName.MARKDOWN)
println(markdown.content.toString(Charsets.UTF_8))

downloadPerceiveArtifact accepte un output nul lorsque l'opération n'a produit qu'un seul artefact, et renvoie 410 dès que l'artefact stocké dépasse sa fenêtre de rétention.

Discover#

Énumère les URL d'un site sans aucun rendu navigateur. Référence complète dans 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}")
Option Type Défaut Description
mode DiscoverMode? HYBRID SITEMAP, CRAWL ou HYBRID (sitemap plus exploration HTTP).
maxUrls / maxDepth Int? 100 / 2 1-1000 et 1-5.
includePatterns / excludePatterns List<String>? -- Liste d'autorisation et liste d'exclusion en expressions régulières, 50 entrées maximum chacune. La liste d'exclusion est appliquée en second.
sameDomainOnly Boolean? true Rester sur le domaine de départ.
respectRobots Boolean? -- Respecter robots.txt.

DiscoverResult.sources indique les comptages bruts par source avant déduplication, par exemple {sitemap=42, crawl=30}.

Lookup#

Lance une recherche web catégorisée et, si vous le souhaitez, perçoit les premiers résultats dans le même appel. Référence complète dans 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}") }
}
Option Type Défaut Description
category LookupCategory? WEB WEB, NEWS, IMAGES, SCHOLAR, PATENTS, MAPS.
country / locale String? -- Code pays Google gl et langue d'interface hl.
timeFilter LookupTimeFilter? -- HOUR, DAY, WEEK, MONTH, YEAR.
numResults / page Int? 10 / 1 1-100 et 1-10.
location String? -- Localisation en texte libre, par exemple "Austin, Texas".
autocorrect Boolean? true Laisser le fournisseur corriger les fautes de frappe.
perceiveTop Int? 0 Percevoir automatiquement les N premières URL de résultat, 0-10. Chacune déclenche un rendu navigateur complet.

Le résultat porte aussi answerBox, knowledgeGraph, perceiveOperationIds et total.

Distill#

Pointez un schéma vers quelques pages et récupérez des données structurées. Une passe CSS optionnelle répond à tout ce qu'elle peut avant toute escalade vers le niveau LLM. Référence complète dans 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)
}

Ou découvrez d'abord les URL et distillez-les une à une :

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"),
    ),
)

Fournissez exactement l'un des deux : urls ou discoverFrom. Passer les deux, ou aucun, lève une EnconvertException avant que la requête ne quitte votre processus.

Option Type Défaut Description
urls List<String>? -- URL explicites à distiller, 50 au maximum.
discoverFrom DistillDiscoverFrom? -- Découvrir d'abord les URL d'un site. maxPages va de 1 à 50, avec 10 par défaut.
schema Map<String, Any?> obligatoire Un objet JSON Schema, ou une table plate {field to description}.
cssSchema CssSchema? -- Passe CSS exécutée avant toute escalade LLM.
waitFor / waitTimeoutMs String? / Int? -- / 30000 Sélecteur ou expression js: à attendre, et son budget.
headers / cookies / respectRobots -- -- Mêmes contrôles de rendu que perceive.

CssField.type vaut l'un de TEXT, ATTRIBUTE, HTML, REGEX, NESTED, LIST, NESTED_LIST. ATTRIBUTE exige attribute, REGEX exige pattern, et les trois variantes imbriquées exigent une liste fields non vide, jusqu'à cinq niveaux de profondeur.

Ingest#

Transforme un site entier, ou une pile de documents envoyés, en JSONL découpé et prêt pour le RAG à travers un seul pipeline. Ingest est toujours asynchrone. Référence complète dans 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 vaut URLS par défaut, ce qui exige une liste urls non vide et rejette url. SITEMAP et CRAWL exigent une url de départ et rejettent urls. Le SDK applique les deux règles en local et lève une EnconvertException plutôt que d'envoyer une requête qui ne peut pas aboutir.

Les fichiers envoyés passent par ingestFiles, qui partage le même cycle de vie de job sous le mode FILES et accepte PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les documents Office hérités et 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)
Option Type Défaut Description
mode IngestMode? URLS URLS, SITEMAP, CRAWL, FILES.
url / urls String? / List<String>? -- URL de départ pour SITEMAP et CRAWL ; URL explicites (1000 au maximum) pour URLS.
maxPages / maxDepth Int? 50 / 2 Plafonds de découverte, 1-1000 et 1-5.
sameDomainOnly Boolean? true Rester sur le domaine de départ.
includePatterns / excludePatterns / respectRobots -- -- Mêmes contrôles de découverte que discover.
waitFor / waitTimeoutMs String? / Int? -- / 30000 Attente de rendu par page.
chunk IngestChunkOptions? -- maxWords de 32 à 4000 (512 par défaut), sentenceOverlap de 0 à 10 (1 par défaut).
webhookUrl String? -- Webhook de fin, signé en HMAC.

Gestion des jobs et plomberie des webhooks :

client.v2.listIngestJobs(V2ListOptions(limit = 20))   // du plus récent au plus ancien
client.v2.cancelIngestJob(job.jobId)                  // idempotent

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

client.v2.rotateWebhookSecret()         // les anciennes signatures cessent immédiatement d'être valides
client.v2.retryIngestWebhook(job.jobId) // renvoie le webhook d'un job terminé

Watch#

Refait le rendu d'une URL à intervalle fixe et vous notifie quand elle change. Référence complète dans 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 = "")) // efface le webhook
client.v2.deleteWatcher(watcher.watcherId)                                 // suppression logique, idempotente
Option Type Défaut Description
frequencyMinutes Int? 60 Minutes entre deux vérifications, 60-43200. Le plancher horaire est strict.
diffMode WatchDiffMode? AUTO AUTO, TEXT, STRUCTURED, TABLES, METADATA.
trackFields Map<String, Any?>? -- Sous-ensemble de champs ou de sélecteurs que le moteur de diff doit surveiller.
webhookUrl String? -- Webhook de changement, signé en HMAC avec le même secret qu'ingest.
notifyEmail Boolean? true Notifier le propriétaire du projet par e-mail en cas de changement.

updateWatcher exige au moins un champ et lève une EnconvertException sur un WatcherUpdate vide. Son status n'accepte que ACTIVE ou PAUSED ; la suppression passe par deleteWatcher, qui renvoie le watcher marqué supprimé avec le statut DELETED. getWatcher sur un watcher supprimé répond 404.

Les diffs de snapshots contiennent du contenu de page non fiable. WatcherSnapshot.changes est du texte brut extrait de la page surveillée. Échappez-le avant de l'afficher dans du HTML, un tableau de bord ou un message de chat.

Options PDF#

PdfOptions est partagé par convertUrlToPdf, convertDocument, convertToPdf (niveaux de gris uniquement), convertWebsiteToPdf et 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",
    ),
)
Champ Type Description
pageSize String? "A4", "A3", "Letter", "Legal", et ainsi de suite.
pageWidth / pageHeight Double? Géométrie personnalisée. Ensemble, elles remplacent pageSize.
orientation PdfOrientation? PORTRAIT ou LANDSCAPE. Portrait par défaut.
margins PdfMargins? top, bottom, left, right, tous des doubles facultatifs, en mm.
scale Double? Échelle de rendu, par exemple 0.9 pour 90 pour cent.
grayscale Boolean? Post-traite le PDF en niveaux de gris.
header / footer PdfHeaderFooter? content (2000 caractères maximum) et height.

Seuls les champs que vous définissez réellement sont sérialisés sur le réseau : un PdfOptions partiellement rempli ne remplace donc jamais une valeur par défaut du serveur à laquelle vous n'avez pas touché. La matrice complète des paramètres se trouve dans Paramètres et options.


Gestion des erreurs#

Chaque échec est une EnconvertException ou une de ses sous-classes : un seul catch peut donc servir de filet de sécurité pendant que des sous-classes précises traitent les cas qui vous intéressent.

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 Levée sur Code de statut
AuthenticationException Clé API invalide, absente ou révoquée 401, 403
QuotaException Levée sur un HTTP 402 402
RateLimitException Trop de requêtes 429
ApiException Toute autre réponse 4xx ou 5xx le code réel
EnconvertException Classe de base, plus la validation côté client comme une paire de conversion non prise en charge ou un objet d'options mal formé --

ApiException expose la propriété brute statusCode, et son message s'affiche sous la forme [<statusCode>] <server message>, avec le champ detail ou error du serveur extrait du corps JSON. L'ordre des catch compte : les trois classes précises étendent toutes ApiException, qui étend EnconvertException, listez-les donc en premier. La table des messages est documentée dans Codes d'erreur.


Récupération après timeout#

Les rendus URL vers PDF longs et les conversions de gros documents peuvent dépasser un timeout de reverse proxy de 60 à 120 secondes même lorsque la conversion réussit côté serveur. Le SDK gère cela sur les méthodes de conversion V1 :

  1. Avant chaque requête, il génère un identifiant de job hexadécimal de 32 caractères et l'envoie comme job_id dans le corps JSON ou comme champ multipart.
  2. Si la requête revient en 5xx, le SDK cesse de faire confiance à la réponse et interroge GET /v1/convert/status/{job_id} toutes les 3 secondes. Un 404 pendant que la ligne du job est encore en cours d'écriture signifie « continuez d'attendre ».
  3. Sur success, le SDK transforme la charge utile en un ConversionResult normal. Sur failed, il lève une ApiException portant le message d'erreur du serveur.
  4. Le délai est de 5 minutes, après quoi il lève ApiException(504, "Conversion timed out").

Les réponses réussies qui omettent job_id (c'est le cas du chemin URL synchrone) reçoivent l'identifiant généré par le client : result.jobId est donc toujours quelque chose que vous pouvez passer à getJobStatus. Deux exceptions délibérées : convertWebsiteToPdf et convertWebsiteToScreenshot sautent ce repli, car une soumission de site n'a pas de ligne de job par unité et une 5xx y signifie que la soumission elle-même a échoué. Les méthodes V2 le sautent aussi, puisque chaque endpoint V2 dispose de son propre mécanisme d'interrogation ou de webhook.


Configuration#

val client = Enconvert(
    apiKey = System.getenv("ENCONVERT_API_KEY"),
    timeout = 300_000,                      // ms, 5 minutes
    baseUrl = "https://api.enconvert.com",  // à remplacer pour une passerelle auto-hébergée
)
Paramètre Type Défaut Description
apiKey String obligatoire Clé API privée. Une valeur vide fait lever une IllegalArgumentException par le constructeur.
timeout Long 300_000 Timeout par requête, en millisecondes, appliqué au HttpRequest sous-jacent.
baseUrl String https://api.enconvert.com URL de base de l'API. Les barres obliques finales sont supprimées.
Ne codez jamais la clé API en dur. Lisez-la depuis une variable d'environnement, une propriété Gradle ou votre gestionnaire de secrets, et gardez-la hors de tout artefact livré sur l'appareil d'un utilisateur. Quiconque détient votre clé privée peut lancer des requêtes sur votre projet.

Forme du résultat#

Chaque méthode de conversion renvoie 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 présignée est un lien signé temporaire. Passez saveTo si vous voulez les octets sur le disque immédiatement, ou téléchargez l'URL vous-même et rangez le fichier dans votre propre bucket pour un accès permanent.

Les lectures V2 renvoient plutôt un PerceiveResult, où se trouvent les signaux de fiabilité :

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

op.operationId    // "per_..."
op.status         // PerceiveStatus.COMPLETED
op.url            // URL demandée ; op.urlFinal après les redirections
op.renderQuality  // Double?, de 0.0 à 1.0
op.statusCode     // Int?, statut HTTP du document principal
op.deductions     // Map<String, Double>, p. ex. {http_error=0.7}. Vide sur un rendu propre.
op.warnings       // List<String>
op.cacheHit       // Boolean ; op.contentHash est le SHA-256 du contenu rendu
op.outputs        // Map<String, V2OutputArtifact> indexée par nom de sortie
op.structured     // Map<String, Any?>?, présent quand extract ou schema a été utilisé
op.extractionTier // HEURISTIC, CSS ou LLM
op.tokens         // V2Tokens(input, output) ; op.costCents et op.durationMs à côté

Chaque V2OutputArtifact porte url, objectKey, sizeBytes, contentType et expiresIn (900 secondes). Les URL d'artefacts sont resignées à chaque appel de getPerceiveOperation : stockez donc l'operationId, pas l'URL. Les charges utiles non typées (schémas d'extraction, données extraites, champs suivis, entrées de diff, extras de recherche) traversent la frontière sous forme de Map<String, Any?> et se convertissent sans perte dans les deux sens : rien de ce que vous mettez dans un schéma n'est remodelé en sortie.


Sources et signalements#


Questions fréquentes#

Comment convertir des fichiers en Kotlin ?#

Ajoutez com.enconvert:enconvert-kotlin:0.0.1 à votre build Gradle ou Maven, construisez Enconvert(apiKey = System.getenv("ENCONVERT_API_KEY")), et appelez une méthode typée comme convertDocument, convertImage ou convertUrlToPdf. Passez saveTo dans l'objet d'options pour écrire la sortie directement dans un fichier local plutôt que de télécharger vous-même l'URL présignée.

Comment convertir un DOCX en PDF en Kotlin ?#

Appelez client.convertDocument("report.docx", ConvertDocumentOptions(saveTo = "report.pdf")). Le format de sortie vaut pdf par défaut, vous pouvez donc laisser outputFormat non défini. Le format d'entrée est résolu à partir de l'extension du fichier, et .doc comme .docx pointent vers la même conversion. Pour la géométrie de page, passez un PdfOptions via ConvertDocumentOptions.pdfOptions.

Comment convertir une URL en PDF en Kotlin ?#

Appelez client.convertUrlToPdf(url, UrlToPdfOptions(saveTo = "page.pdf")). Mettez singlePage = false pour paginer avec pdfOptions.pageSize, et utilisez UrlRenderOptions pour changer la fenêtre d'affichage, désactiver le chargement des médias ou couper la passe de défilement qui déclenche le chargement paresseux.

Comment convertir du HEIC en WebP sur la JVM ?#

Appelez client.convertImage("photo.heic", ConvertImageOptions(outputFormat = "webp", saveTo = "photo.webp")). Les 20 paires entre jpeg, png, svg, heic et webp sont implémentées, plus la rastérisation pdf vers jpeg. Une paire non prise en charge lève une EnconvertException avant tout appel réseau, et validOutputsFor("heic") liste les cibles valides en amont.

Comment récupérer une page web en Markdown depuis Kotlin ?#

Deux possibilités. client.convertUrlToMarkdown(url, UrlToMarkdownOptions(saveTo = "page.md")) vous donne un fichier Markdown avec un frontmatter YAML. client.v2.perceive(url, PerceiveOptions(outputs = listOf(PerceiveOutputName.MARKDOWN))) vous donne le même contenu plus renderQuality, deductions, warnings et statusCode, ce qui est exactement ce qu'il vous faut lorsqu'un agent va lire le résultat sans supervision.

Que signifie renderQuality et quand faut-il rejeter une page ?#

renderQuality va de 0.0 à 1.0 et décrit à quel point la page a été rendue proprement, pas la qualité du contenu. Les pages anti-bot, les murs de connexion, les erreurs HTTP et les coquilles SPA vides le font baisser, et deductions nomme chaque contrôle déclenché, par exemple {http_error=0.7}. Le contenu est toujours renvoyé pour que vous puissiez l'inspecter. Un schéma courant consiste à considérer tout ce qui est en dessous de 0.5 comme suspect, puis à redemander la page avec cacheMode = PerceiveCacheMode.REFRESH ou à la router vers un humain.

Comment transformer un site de documentation en chunks RAG depuis Kotlin ?#

Appelez client.v2.ingest(IngestOptions(mode = IngestMode.SITEMAP, url = "https://docs.example.com", chunk = IngestChunkOptions(maxWords = 512, sentenceOverlap = 1))). Ingest est toujours asynchrone : interrogez getIngestJob(jobId) jusqu'à ce que le statut soit COMPLETED et lisez outputUrl pour récupérer le JSONL, ou définissez webhookUrl et laissez le webhook de fin vous trouver. Les documents locaux passent par ingestFiles avec les mêmes réglages de découpage.

Le SDK bloque-t-il le thread appelant ?#

Oui. Chaque méthode appelle HttpClient.send de manière synchrone, et il n'y a ni fonction suspend ni builder de coroutine dans le SDK. waitForBatch et l'interrogateur interne de récupération après timeout endorment le thread courant entre deux tentatives. Depuis une coroutine, enveloppez les appels dans withContext(Dispatchers.IO) ; depuis un framework serveur, gardez-les hors du pool de threads qui traite les requêtes.

Puis-je appeler ce SDK depuis Java ?#

Oui, puisque ce sont des classes JVM ordinaires, mais les arguments par défaut de Kotlin ne sont pas exposés à Java sous forme de surcharges : un appelant Java doit donc passer tous les arguments de constructeur d'une data class d'options. Si votre base de code est en Java, utilisez plutôt le SDK Java distinct listé sur la page des SDK.

Où obtenir une clé API ?#

Créez une clé privée dans votre tableau de bord. Elle est envoyée dans l'en-tête X-API-Key à chaque requête, gardez-la donc côté serveur. Les types de clés et leurs portées sont traités dans Authentification, et la tarification couvre le volet commercial.