Java File Conversion SDK#
com.enconvert:enconvert-sdk is the official Java client for the EnConvert API. One Maven or Gradle dependency gives you file conversion (URL to PDF, DOCX to PDF, HEIC to WebP, anything to Markdown) plus the V2 web intelligence surface: perceive, discover, lookup, distill, ingest, and watch. It targets Java 17 and above, runs on the JDK's built in java.net.http.HttpClient, and pulls in Gson as its only third party dependency. Every call is a plain blocking method that returns a typed record, and long conversions transparently recover from reverse proxy timeouts by polling job status.
com.enconvert:enconvert-sdk:0.0.1 · Source: conversionapi/java-sdk · Java: 17+ · Dependencies: Gson only
Install#
// build.gradle
dependencies {
implementation 'com.enconvert:enconvert-sdk:0.0.1'
}
// build.gradle.kts
dependencies {
implementation("com.enconvert:enconvert-sdk:0.0.1")
}
<!-- pom.xml -->
<dependency>
<groupId>com.enconvert</groupId>
<artifactId>enconvert-sdk</artifactId>
<version>0.0.1</version>
</dependency>
HTTP is handled by java.net.http.HttpClient from the JDK. The only third party artifact that comes along is Gson for JSON, declared as an api dependency so it is visible on your compile classpath.
Quick start#
import com.enconvert.Enconvert;
import com.enconvert.model.ConversionResult;
import com.enconvert.model.UrlToPdfOptions;
import com.enconvert.model.v2.PerceiveOptions;
import com.enconvert.model.v2.PerceiveResult;
import java.util.List;
Enconvert client = new Enconvert(System.getenv("ENCONVERT_API_KEY"));
ConversionResult pdf = client.convertUrlToPdf("https://example.com",
UrlToPdfOptions.builder().saveTo("page.pdf").build());
System.out.println(pdf.presignedUrl());
// Read a page the way your agent should, with a quality score attached.
PerceiveResult page = client.v2.perceive("https://example.com",
PerceiveOptions.builder().outputs(List.of("markdown", "structured")).build());
System.out.println(page.outputs().get("markdown").url());
System.out.println(page.renderQuality()); // e.g. 0.93
Every options class is an immutable builder and every response is a Java record, so accessors read as pdf.presignedUrl() and page.renderQuality(). The client holds one shared HttpClient and no mutable per-request state, so a single instance can be a singleton or a Spring bean shared across threads. Snippets below omit imports: options and response types live in com.enconvert.model (conversion) and com.enconvert.model.v2 (web intelligence), exceptions in com.enconvert.exceptions.
What the client exposes#
Enconvert carries the conversion surface directly. The web intelligence surface lives on the public final field client.v2, an instance of EnconvertV2.
| Group | Methods | Returns |
|---|---|---|
| Single URL | convertUrlToPdf, convertUrlToScreenshot, convertUrlToMarkdown |
ConversionResult |
| File upload | convertImage, convertDocument, convertToMarkdown, convertToPdf |
ConversionResult |
| Whole site | convertWebsiteToPdf, convertWebsiteToScreenshot |
BatchSubmission |
| Status | getJobStatus, getBatchStatus, waitForBatch |
JobStatus, BatchStatus |
v2 perceive |
perceive, perceiveDirect, getPerceiveOperation, perceiveBatch, getPerceiveBatch, downloadPerceiveArtifact |
PerceiveResult, PerceiveDirectResult, PerceiveBatchResult |
v2 discover |
discover |
DiscoverResult |
v2 lookup |
lookup |
LookupResult |
v2 distill |
distill |
DistillResult |
v2 ingest |
ingest, ingestFiles, getIngestJob, listIngestJobs, cancelIngestJob, retryIngestWebhook, getWebhookSecret, rotateWebhookSecret |
IngestJob, IngestJobList, WebhookRetryResult, WebhookSecret |
v2 watch |
createWatcher, getWatcher, listWatchers, getWatcherSnapshots, updateWatcher, deleteWatcher |
Watcher, WatcherList, WatcherSnapshotList |
Most methods have a short overload with no options argument, so client.v2.perceive(url) and client.convertUrlToPdf(url) both compile. convertImage, distill, and ingest are the exceptions: each always takes its options object, because the target format, the schema, and the source are required respectively.
File conversion#
The conversion endpoints cover 43 implemented {input}-to-{output} pairs, two auto detecting endpoints (anything-to-markdown and anything-to-pdf), and the browser rendering endpoints. The full parameter reference is in Parameters and options.
convertUrlToPdf#
Render any public URL to PDF.
ConversionResult result = client.convertUrlToPdf("https://example.com",
UrlToPdfOptions.builder()
.pdfOptions(PdfOptions.builder().pageSize("A4").orientation("landscape").build())
.singlePage(false)
.viewportWidth(1440)
.saveTo("report.pdf")
.build());
| Option | Type | Default | Description |
|---|---|---|---|
saveTo |
String |
none | Local path to write the PDF to. Parent directories are created for you. |
singlePage |
boolean |
true |
true produces one continuous page. false paginates using pdfOptions.pageSize. |
pdfOptions |
PdfOptions |
none | Page size, orientation, margins, scale, grayscale, header, footer. See PDF options. |
viewportWidth |
int |
1920 |
Browser viewport width in pixels. |
viewportHeight |
int |
1080 |
Browser viewport height in pixels. |
loadMedia, enableScroll |
boolean |
true |
Wait for images and video before capture, and scroll top to bottom to trigger lazy loaders. |
outputFilename |
String |
auto | Override the generated filename. |
auth, cookies, headers |
HttpBasicAuth, List<BrowserCookie>, Map<String, String> |
none | Credentials, injected cookies, and extra request headers for pages behind a login. |
convertUrlToScreenshot#
Capture a PNG of any URL. Same viewport, media, scroll, filename, auth, cookie, and header options as convertUrlToPdf, minus singlePage and pdfOptions.
client.convertUrlToScreenshot("https://example.com",
UrlToScreenshotOptions.builder().viewportWidth(1440).saveTo("screenshot.png").build());
convertUrlToMarkdown#
Extract clean GitHub Flavored Markdown from a URL. Navigation, footers, ads, and scripts are stripped, the main article body is kept, and YAML frontmatter (title, description, url, links, images) is prepended. Same option set as convertUrlToScreenshot.
client.convertUrlToMarkdown("https://example.com/article",
UrlToMarkdownOptions.builder().saveTo("article.md").build());
convertImage#
Convert between jpeg, png, svg, heic, and webp, or rasterize a PDF to JPEG.
// From a path on disk
client.convertImage(Path.of("photo.heic"),
ConvertImageOptions.builder("webp").saveTo("photo.webp").build());
// Rasterize a PDF
client.convertImage(Path.of("scan.pdf"),
ConvertImageOptions.builder("jpeg").saveTo("scan.jpeg").build());
// From in-memory bytes with an explicit filename
byte[] bytes = Files.readAllBytes(Path.of("photo.heic"));
client.convertImage(new FileInput(bytes, "photo.heic"),
ConvertImageOptions.builder("webp").build());
Three input overloads exist on every file method: java.nio.file.Path (read from disk), raw byte[] (the filename defaults to upload.bin), and com.enconvert.FileInput when you need to pair in-memory bytes with a real filename. The input format is resolved from the extension; the output format is required.
| Option | Type | Required | Description |
|---|---|---|---|
outputFormat |
String |
Yes | Passed to ConvertImageOptions.builder(outputFormat). One of jpeg, png, svg, heic, webp. Aliases such as jpg are normalized. |
saveTo |
String |
no | Local path to write the result to. |
outputFilename |
String |
no | Override the generated filename. |
convertDocument#
Convert documents and structured data formats. The output format defaults to pdf.
// docx to pdf
client.convertDocument(Path.of("report.docx"),
ConvertDocumentOptions.builder().saveTo("report.pdf").build());
// json to yaml
client.convertDocument(Path.of("data.json"),
ConvertDocumentOptions.builder().outputFormat("yaml").saveTo("data.yaml").build());
// markdown to pdf with page setup
client.convertDocument(Path.of("README.md"),
ConvertDocumentOptions.builder()
.outputFormat("pdf")
.pdfOptions(PdfOptions.builder()
.pageSize("A4")
.margins(new PdfMargins(20.0, 20.0, 25.0, 25.0))
.build())
.saveTo("readme.pdf")
.build());
Recognized input extensions: .doc, .docx, .xls, .xlsx, .ppt, .pptx, .html, .htm, .odt, .ods, .odp, .ots, .pages, .numbers, .md, .markdown, .csv, .json, .xml, .yaml, .yml, .toml.
EPUB has no dedicated document pair. Send .epub files through convertToPdf or convertToMarkdown instead.
| Option | Type | Default | Description |
|---|---|---|---|
outputFormat |
String |
"pdf" |
Target format. |
saveTo |
String |
none | Local path to write the result to. |
outputFilename |
String |
none | Override the generated filename. |
pdfOptions |
PdfOptions |
none | Page setup, honored when the output is PDF. |
Supported conversions#
convertImage and convertDocument validate the {input}-to-{output} pair against the endpoints the API actually implements. An unsupported pair throws IllegalArgumentException immediately, listing the valid outputs for that input, instead of paying a round trip for a request that cannot succeed.
| Input | Outputs |
|---|---|
json |
csv, toml, xml, yaml |
xml |
csv, json |
yaml |
json |
csv |
json, xml |
toml |
json |
markdown |
html, pdf |
html |
pdf |
doc, excel, ppt, odt, ods, odp, ots, pages, numbers |
pdf |
jpeg, png, svg, heic, webp |
each other, all 20 pairs |
pdf |
jpeg |
The same table is queryable at runtime through com.enconvert.Formats: Formats.validOutputsFor("json") returns [csv, toml, xml, yaml], Formats.validOutputsFor("pdf") returns [jpeg], and Formats.IMPLEMENTED_CONVERSIONS holds all 43 endpoint names.
convertToMarkdown#
Send any supported document through one auto detecting endpoint and get clean Markdown back. The heading hierarchy survives, which makes this a natural first stage for a RAG pipeline.
client.convertToMarkdown(Path.of("handbook.docx"),
ConvertToMarkdownOptions.builder().saveTo("handbook.md").build());
Accepts PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT and MD, plus legacy and ODF office formats. The format is detected server side, so there is no client side extension check: any file is uploaded as-is. Images are not supported and are rejected with 400. The only options are saveTo and outputFilename.
convertToPdf#
The other auto detecting endpoint: almost anything to PDF.
// pptx to pdf
client.convertToPdf(Path.of("slides.pptx"),
ConvertToPdfOptions.builder().saveTo("slides.pdf").build());
// pdf passthrough, converted to grayscale
client.convertToPdf(Path.of("scan.pdf"),
ConvertToPdfOptions.builder()
.pdfOptions(PdfOptions.builder().grayscale(true).build())
.saveTo("scan-gray.pdf")
.build());
Accepts office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, plain text, raster images, SVG, EPUB, and an existing PDF as a passthrough. EPUB is handled here because it has no dedicated document pair. Options are saveTo, outputFilename, and pdfOptions.
grayscale is honored on this endpoint. Page geometry (page size, width and height, orientation, margins, scale, header, footer) is ignored by anything-to-pdf. When you need full page setup, go through convertDocument or convertUrlToPdf instead.
Whole site conversion#
convertWebsiteToPdf and convertWebsiteToScreenshot discover every page of a site, convert each one in the background, and bundle the results into a single ZIP. Both are asynchronous and return a BatchSubmission. Both require a private API key.
BatchSubmission batch = client.convertWebsiteToPdf("https://example.com",
WebsiteToPdfOptions.builder()
.crawlMode("sitemap") // "auto" (default), "sitemap", "full"
.excludePatterns(List.of("/blog/tag/")) // full crawl mode only
.notificationEmail("[email protected]")
.build());
System.out.println(batch.batchId() + " " + batch.urlCount() + " " + batch.discoveryMethod());
// Block until the batch leaves "processing", then save the ZIP
BatchStatus status = client.waitForBatch(batch.batchId(),
WaitForBatchOptions.builder().saveTo("site.zip").build());
System.out.println(status.completed() + " of " + status.total() + " pages converted");
convertWebsiteToScreenshot works identically and produces a ZIP of PNGs. waitForBatch polls every 5 seconds by default, gives up after 30 minutes, and accepts intervalMs, timeoutMs, and saveTo. On timeout it throws ApiException with status 504.
Polling status yourself#
JobStatus job = client.getJobStatus("job_abc123");
if ("success".equals(job.status())) System.out.println(job.presignedUrl());
if ("failed".equals(job.status())) System.err.println(job.error());
BatchStatus batch = client.getBatchStatus("bat_abc123");
if (!"processing".equals(batch.status())) System.out.println(batch.zipDownloadUrl());
Web intelligence (V2)#
Everything under client.v2 turns web pages into agent ready data. Every read carries renderQuality, a score from 0.0 to 1.0 that says how cleanly the page actually rendered. A challenge page, a cookie wall, or an empty SPA shell comes back with a low score and populated warnings() and deductions() rather than being passed off as real content, so a bad read never quietly enters your agent's context. The content is still returned; it is just flagged. Background on the model is in the V2 overview.
Perceive#
Render one URL into the artifacts you ask for. Endpoint reference: Perceive.
PerceiveResult page = client.v2.perceive("https://example.com",
PerceiveOptions.builder()
.outputs(List.of("markdown", "screenshot", "structured"))
.extract(List.of("tables", "metadata"))
.waitFor("css:main")
.build());
System.out.println(page.renderQuality()); // 0.0 to 1.0
System.out.println(page.statusCode() + " " + page.deductions()); // e.g. 200 {http_error=0.7}
System.out.println(page.outputs().get("markdown").url()); // signed URL, 15 minutes
System.out.println(page.structured()); // caller-defined shape
| Option | Type | Default | Description |
|---|---|---|---|
outputs |
List<String> |
["markdown", "structured"] |
Any of markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured. |
extract |
List<String> |
none | Heuristic targets: tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all. |
schema |
Map<String, Object> |
none | JSON schema for structured extraction. |
waitFor, waitTimeoutMs |
String, int |
none, 30000 |
A CSS selector, optionally prefixed css:, or js:<expr> to await, with a budget of 0 to 60000 ms. |
jsCode |
String |
none | JavaScript run after navigation, max 20000 characters. |
viewport, mobile |
PerceiveViewport, boolean |
1920 x 1080, false |
Width 320 to 3840, height 240 to 2160, or mobile emulation. |
onlyMainContent |
boolean |
true |
Strip nav, header, footer, and cookie banners from the Markdown artifact and the main_content extract. |
cacheMode |
String |
"enabled" |
enabled reuses a 1 hour cache, bypass skips it, refresh forces a re-render. |
blockResources |
List<String> |
none | Resource types the browser should not load, for example image, font, script. |
pdfOptions |
PdfOptions |
none | Only meaningful when outputs contains pdf. |
headers, cookies, auth |
Map, List<BrowserCookie>, HttpBasicAuth |
none | Request headers, injected cookies, HTTP Basic credentials. |
respectRobots |
boolean |
none | Honor the site's robots rules. |
proxyUrl, geolocation, and actionChain exist on the builder but are not available server side and are currently rejected with 422.
Artifact URLs are signed for 15 minutes and re-signed on every read of the operation, so client.v2.getPerceiveOperation(page.operationId()) hands you fresh links. Batch up to 1000 URLs with one shared options block: small batches complete inline, larger ones come back with status queued, so poll them.
PerceiveBatchResult batch = client.v2.perceiveBatch(
List.of("https://a.example.com", "https://b.example.com"),
PerceiveBatchOptions.builder()
.outputs(List.of("markdown"))
.outputMode("zip") // "manifest" (default) or "zip"
.build());
PerceiveBatchResult done = client.v2.getPerceiveBatch(batch.jobId());
System.out.println(done.completed() + "/" + done.total());
Skip the signed URL round trip entirely with perceiveDirect, which streams the artifact bytes back. It requires exactly one artifact producing output (anything except structured) and throws IllegalArgumentException before sending if you ask for more or fewer:
PerceiveDirectResult direct = client.v2.perceiveDirect("https://example.com",
PerceiveOptions.builder().outputs(List.of("pdf")).build());
Files.write(Path.of(direct.filename()), direct.content());
System.out.println(direct.renderQuality() + " " + direct.contentType());
// Re-download a stored artifact of an earlier operation
PerceiveDirectResult stored = client.v2.downloadPerceiveArtifact(page.operationId(), "markdown");
downloadPerceiveArtifact accepts a null or omitted output name when the operation produced exactly one artifact, otherwise it returns 400 listing the available outputs. Once the stored artifact has aged out it returns 410.
Discover#
Enumerate a site's URLs without rendering anything. No browser is involved, so it is fast. Endpoint reference: Discover.
DiscoverResult found = client.v2.discover("https://example.com",
DiscoverOptions.builder()
.mode("hybrid") // "sitemap", "crawl", "hybrid"
.maxUrls(200)
.maxDepth(3)
.excludePatterns(List.of("/tag/"))
.build());
System.out.println(found.total() + " urls, truncated=" + found.truncated());
found.urls().forEach(System.out::println);
| Option | Type | Default | Range |
|---|---|---|---|
mode |
String |
"hybrid" |
sitemap, crawl, hybrid |
maxUrls |
int |
100 |
1 to 1000 |
maxDepth |
int |
2 |
1 to 5 |
includePatterns, excludePatterns |
List<String> |
none | Regex allowlist and denylist, max 50 each. The denylist is applied second. |
sameDomainOnly, respectRobots |
boolean |
true, none |
Stay on the seed domain, and honor the site's robots rules. |
Lookup#
Categorized web search, optionally auto rendering the top results. Endpoint reference: Lookup.
LookupResult search = client.v2.lookup("best static site generators",
LookupOptions.builder()
.category("web") // web, news, images, scholar, patents, maps
.numResults(10)
.country("us")
.timeFilter("month") // hour, day, week, month, year
.perceiveTop(3) // auto-render the top 3 hits
.build());
search.results().forEach(hit -> {
System.out.println(hit.position() + " " + hit.title() + " " + hit.url());
if (hit.perceive() != null) System.out.println(" quality " + hit.perceive().renderQuality());
});
With perceiveTop above 0 (0 to 10, default 0), the top N result URLs are rendered through perceive and each hit carries its full PerceiveResult inline on hit.perceive(). numResults runs 1 to 100 and defaults to 10; page runs 1 to 10.
Distill#
Schema driven structured extraction across one or more pages. Endpoint reference: Distill.
DistillResult extraction = client.v2.distill(
DistillOptions.builder(Map.of("products", "list of product names with their listed price"))
.urls(List.of("https://example.com/catalog"))
.cssSchema(CssSchema.builder(".product-card", List.of(
CssField.builder("name", "text").selector("h3").build(),
CssField.builder("price", "text").selector(".price").build()))
.targetField("products")
.build())
.build());
extraction.results().forEach(item ->
System.out.println(item.data() + " via " + item.extractionTier()));
The optional cssSchema runs a free CSS pass first; only the fields it cannot answer escalate to the LLM tier, and item.extractionTier() reports which path produced the record (css, llm, mixed, or none). You can also discover the URLs first instead of listing them:
client.v2.distill(
DistillOptions.builder(Map.of("title", "page title", "summary", "one-line summary"))
.discoverFrom(new DistillDiscoverFrom("https://example.com", "sitemap", 10))
.build());
Exactly one of urls (max 50) and discoverFrom must be set, and schema is required by the builder factory. Both rules are checked client side and throw IllegalArgumentException before any request goes out.
Ingest#
Turn a whole site, or a set of uploaded documents, into chunked RAG ready JSONL through one pipeline. Ingest is always asynchronous. Endpoint reference: Ingest.
// From a site
IngestJob job = client.v2.ingest(IngestOptions.builder()
.mode("sitemap") // "urls" (default), "sitemap", "crawl"
.url("https://docs.example.com")
.maxPages(100)
.chunk(new IngestChunkOptions(512, 1)) // maxWords, sentenceOverlap
.webhookUrl("https://my.app/hooks/enconvert")
.build());
// Or from uploaded files
IngestJob fileJob = client.v2.ingestFiles(
List.of(new FileInput(Files.readAllBytes(Path.of("handbook.pdf")), "handbook.pdf"),
new FileInput(Files.readAllBytes(Path.of("notes.docx")), "notes.docx")),
IngestFilesOptions.builder().chunk(new IngestChunkOptions(512, 1)).build());
// Poll for the JSONL
IngestJob status = client.v2.getIngestJob(job.jobId());
if ("completed".equals(status.status())) {
System.out.println(status.outputUrl() + " (" + status.totalChunks() + " chunks)");
}
client.v2.listIngestJobs(V2ListOptions.builder().limit(20).build());
client.v2.cancelIngestJob(job.jobId()); // idempotent
ingestFiles accepts PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT and MD, plus legacy and ODF office formats. Chunking defaults to 512 words (32 to 4000) with 1 sentence of overlap (0 to 10). mode defaults to urls, which requires a non-empty urls list and forbids url; every other mode requires a seed url and forbids urls. The SDK enforces that pairing before sending.
Completion webhooks are HMAC signed. Fetch the secret and the header names you need to verify a delivery, rotate it when it leaks, and re-fire a delivery your endpoint missed:
WebhookSecret secret = client.v2.getWebhookSecret();
System.out.println(secret.signatureHeader() + " " + secret.signatureScheme());
client.v2.rotateWebhookSecret(); // old signatures stop verifying immediately
WebhookRetryResult retry = client.v2.retryIngestWebhook(job.jobId());
System.out.println(retry.delivered() + " after " + retry.attempts() + " attempts");
Watch#
Recurring change monitoring on a URL, with email and webhook notification. Endpoint reference: Watch.
Watcher watcher = client.v2.createWatcher("https://example.com/pricing",
WatchCreateOptions.builder()
.frequencyMinutes(60) // 60 to 43200, hourly floor
.diffMode("auto") // auto, text, structured, tables, metadata
.webhookUrl("https://my.app/hooks/changes")
.notifyEmail(true)
.build());
WatcherSnapshotList history = client.v2.getWatcherSnapshots(watcher.watcherId(),
SnapshotListOptions.builder().limit(10).build());
history.snapshots().forEach(s ->
System.out.println(s.checkedAt() + " changed=" + s.hasChanges()
+ " similarity=" + s.similarity()));
client.v2.updateWatcher(watcher.watcherId(), WatcherUpdate.builder().status("paused").build());
client.v2.updateWatcher(watcher.watcherId(), WatcherUpdate.builder().webhookUrl("").build());
client.v2.deleteWatcher(watcher.watcherId()); // soft delete, idempotent
listWatchers() and getWatcher(watcherId) read them back. updateWatcher requires at least one field and throws IllegalArgumentException otherwise. An explicit empty string for webhookUrl clears the webhook, while leaving it null means "no change". deleteWatcher is a soft delete: it returns the tombstoned watcher with status deleted, and a deleted watcher then reads as 404.
WatcherSnapshot.changes() is raw text lifted from the monitored page. Escape it before rendering it in a dashboard, an email, or a chat message.
PDF options#
PdfOptions is shared by convertUrlToPdf, convertWebsiteToPdf, convertDocument, convertToPdf, and PerceiveOptions.pdfOptions.
client.convertUrlToPdf("https://internal.example.com/report",
UrlToPdfOptions.builder()
.pdfOptions(PdfOptions.builder()
.pageSize("A4")
.orientation("landscape")
.margins(new PdfMargins(10.0, 10.0, 15.0, 15.0))
.scale(0.9)
.header(new PdfHeaderFooter("Quarterly Report", 15.0))
.footer(new PdfHeaderFooter("Confidential", 12.0))
.build())
.auth(new HttpBasicAuth("user", "pass"))
.cookies(List.of(BrowserCookie.builder("session", "abc123").domain("internal.example.com").build()))
.headers(Map.of("X-Tenant", "acme"))
.saveTo("report.pdf")
.build());
| Field | Type | Description |
|---|---|---|
pageSize |
String |
"A4", "A3", "Letter", "Legal", and similar. |
pageWidth, pageHeight |
double |
Custom dimensions. Set together, they override pageSize. |
orientation |
String |
"portrait" or "landscape". |
margins |
PdfMargins |
Record of top, bottom, left, right. Any null field is omitted. |
scale |
double |
Render scale, for example 0.9 for 90 percent. |
grayscale |
boolean |
Post-process the PDF to grayscale. |
header, footer |
PdfHeaderFooter |
Record of content (max 2000 characters) and height. |
BrowserCookie needs a name and value plus either domain or url; when domain is set without path, the API defaults path to /. Do not combine auth with an explicit Authorization header, because the API rejects the conflict.
Error handling#
Every SDK exception extends EnconvertException, which extends RuntimeException, so nothing forces a throws clause on your call sites. Catch the specific subclasses first.
try {
client.convertUrlToPdf("https://example.com");
} catch (AuthenticationException e) {
System.err.println("Invalid or missing API key");
} catch (QuotaException e) {
System.err.println("Request refused with 402: " + e.getMessage());
} catch (RateLimitException e) {
System.err.println("Too many requests, back off and retry");
} catch (ApiException e) {
System.err.println("API error [" + e.getStatusCode() + "]: " + e.getMessage());
}
| Class | Raised on | Status code |
|---|---|---|
AuthenticationException |
Missing, invalid, or non-permitted API key | 401, 403 |
QuotaException |
HTTP 402 | 402 |
RateLimitException |
Too many requests | 429 |
ApiException |
Any other 4xx or 5xx response | the actual code |
EnconvertException |
Base class, also raised on transport failure, an interrupted request, or an unreadable input file | none |
Client side validation (an unsupported conversion pair, a missing distill schema, an empty watcher update, the wrong number of outputs for perceiveDirect) throws IllegalArgumentException before any request is made. The message map for server responses is in the Error codes reference.
Timeout recovery#
Long URL renders and large document conversions can outlive the reverse proxy timeout even when the conversion itself eventually succeeds. The SDK handles that transparently:
- Before each single URL or file upload request, the SDK generates a UUID and sends it as
job_id. - If the request comes back 5xx, the SDK switches to polling
GET /v1/convert/status/{jobId}every 3 seconds. - On
successit returns the result. Onfailedit throwsApiExceptioncarrying the server's error message. - The polling deadline is 5 minutes. Past that it throws
ApiException(504, "Conversion timed out").
You write no code for this. If a successful response omits job_id, the SDK backfills the id it generated, so result.jobId() is always usable with getJobStatus.
convertWebsiteToPdf and convertWebsiteToScreenshot have no per-job row to poll, so a 5xx there means the submission itself failed and is surfaced directly. V2 endpoints do not use job polling either; their asynchronous flows go through getPerceiveBatch and getIngestJob.
Configuration#
Enconvert client = Enconvert.builder(System.getenv("ENCONVERT_API_KEY"))
.baseUrl("https://api.enconvert.com")
.timeout(Duration.ofSeconds(300))
.build();
Three constructors are available as shorthand: new Enconvert(apiKey), new Enconvert(apiKey, baseUrl), and new Enconvert(apiKey, baseUrl, timeout).
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
String |
required | Private API key. A null or empty value throws IllegalArgumentException. |
baseUrl |
String |
https://api.enconvert.com |
API base URL. Trailing slashes are stripped. |
timeout |
Duration |
300 seconds | Applied as both the connect timeout and the per-request timeout. |
The key travels in the X-API-Key header. Presigned download URLs are fetched without it, since they are already signed. Key types are covered in Authentication; create and manage keys in the dashboard.
Result shape#
Every single file and single URL conversion returns the same record:
public record ConversionResult(
String presignedUrl,
String objectKey,
String filename,
Long fileSize,
Double conversionTimeSeconds,
String jobId) {}
The presigned URL is time limited. Pass saveTo (or fetch the URL yourself) and store the bytes in your own bucket if you need them to outlive it.
The other response records you will touch most often:
| Record | Key accessors |
|---|---|
JobStatus |
status() (processing, success, failed), presignedUrl(), objectKey(), error() |
BatchStatus |
status(), total(), completed(), failed(), inProgress(), zipDownloadUrl(), items() |
PerceiveResult |
operationId(), renderQuality(), statusCode(), deductions(), outputs(), structured(), cacheHit(), warnings() |
V2OutputArtifact |
url(), objectKey(), sizeBytes(), contentType(), expiresIn() |
PerceiveDirectResult |
content(), contentType(), filename(), renderQuality(), contentHash() |
IngestJob |
jobId(), status(), pagesProcessed(), totalChunks(), outputUrl(), webhookDelivered() |
Watcher |
watcherId(), status(), frequencyMinutes(), checksCount(), nextCheckAt(), lastChangeAt() |
Fields whose shape is defined by your own request (structured, data, trackFields, snapshot changes) are exposed as Gson JsonObject and pass through untouched. String valued enumerations stay String rather than becoming Java enum constants, so a newer API value never breaks deserialization on an older SDK build. com.enconvert.model.v2.V2Enums holds every accepted value as a typo-proof constant.
Source and issues#
- Maven Central:
com.enconvert:enconvert-sdk:0.0.1 - GitHub: conversionapi/java-sdk
- License: MIT
- Other clients: all SDKs · endpoint reference · pricing
Frequently asked questions#
How do I convert files in Java with a Maven dependency?#
Add com.enconvert:enconvert-sdk:0.0.1 to your pom.xml or build.gradle, build a client with new Enconvert(System.getenv("ENCONVERT_API_KEY")), and call a typed method such as convertUrlToPdf, convertImage, convertDocument, or convertToPdf. Pass saveTo on the options builder to write the output straight to disk instead of handling the presigned URL yourself.
How do I convert a URL to PDF in Java?#
Call client.convertUrlToPdf(url, UrlToPdfOptions.builder().saveTo("page.pdf").build()). Set singlePage(false) to paginate using pdfOptions.pageSize instead of producing one continuous page, and pass auth, cookies, or headers for a page behind a login.
How do I convert DOCX to PDF in Java?#
Call client.convertDocument(Path.of("report.docx"), ConvertDocumentOptions.builder().saveTo("report.pdf").build()). The output format defaults to pdf, so you only set outputFormat when you want something else, for example yaml from a .json input. For formats without a dedicated pair, such as EPUB or RTF, use convertToPdf.
How do I convert HEIC to WebP in Java?#
Call client.convertImage(Path.of("photo.heic"), ConvertImageOptions.builder("webp").saveTo("photo.webp").build()). The input format comes from the file extension and the output format is the required builder argument. jpeg, png, svg, heic, and webp all convert to each other, and pdf rasterizes to jpeg. There is no compress-in-place method on this client.
How do I scrape a web page into clean Markdown from Java?#
Two paths. client.convertUrlToMarkdown(url, ...) returns GitHub Flavored Markdown with YAML frontmatter as a downloadable file. client.v2.perceive(url, PerceiveOptions.builder().outputs(List.of("markdown")).build()) returns the same content as an agent-ready artifact with a renderQuality score, extraction options, and cache control. Use perceive when a bad read must be detectable rather than silent.
What is renderQuality and why does every read have one?#
renderQuality is a score from 0.0 to 1.0 attached to every V2 render. A high score means the page rendered cleanly; a low score means something got in the way, such as a bot challenge, a cookie wall, a login screen, an HTTP error page, or an empty SPA shell. The content is still returned, with warnings() and deductions() populated, so your pipeline can drop or retry the read instead of feeding a challenge page into a model as if it were the article.
How do I turn a documentation site into RAG-ready chunks in Java?#
Call client.v2.ingest(IngestOptions.builder().mode("sitemap").url("https://docs.example.com").maxPages(100).chunk(new IngestChunkOptions(512, 1)).build()). The job is asynchronous, so either poll getIngestJob(jobId) until the status is completed and read outputUrl() for the JSONL, or set webhookUrl and verify the HMAC signature with the secret from getWebhookSecret(). For local documents rather than a site, use ingestFiles.
How does the SDK handle conversions that outlive the proxy timeout?#
Before each single URL or file upload request it generates a UUID and sends it as job_id. If the request returns 5xx, it polls GET /v1/convert/status/{jobId} every 3 seconds until the job reports success or failed, with a 5 minute deadline after which it throws ApiException(504, "Conversion timed out"). Whole site batch submissions are excluded, because they have no per-job row to poll.
Which Java version does the SDK require, and what does it pull in?#
Java 17 or newer. HTTP goes through the JDK's java.net.http.HttpClient, and Gson is the only third party artifact on the classpath. Responses are Java records, so a modern switch or pattern match over them works as expected. The client is thread safe: hold one instance and share it.