EnConvert API Endpoints#
EnConvert has three groups of endpoints. Perceive reads one web page, Ingest crawls a whole site into chunks, and Convert turns files and URLs into other formats. They share a base URL, one API key and one monthly ops allowance, so the request contract further down applies to all of them.
Perceive#
POST /v2/perceive renders a URL once in a headless browser and returns everything you asked for from that single render: Markdown, cleaned or raw HTML, a screenshot, a PDF, the link and image inventory, and structured page data. Five routes in total, including a batch endpoint that takes a list of URLs under one set of options, capped by your plan's batch limit. See Perceive.
Ingest#
POST /v2/ingest crawls a site and writes every page into one JSONL file of RAG-ready chunks; POST /v2/ingest/files does the same for documents you upload. Ingest is always asynchronous: the POST answers 202 with a job_id, and you either poll it or take a signed webhook. Eight routes, including webhook secret rotation and manual redelivery. See Ingest.
Convert#
51 conversion endpoints, all shaped POST /v1/convert/<id>, grouped into four families: web pages, documents, data formats, and images. Each takes a file upload or a URL and writes the result to storage. See Convert.
In development#
Distill, Lookup, Watch and Discover are in private beta and are not covered here. They are described under Coming Soon, along with the phase each one belongs to.
Shared request parameters#
Everything in this section holds across the three groups. Anything specific to one conversion lives on that endpoint's own page.
Base URL#
https://api.enconvert.com
Every path on this page is relative to that host. The gateway holds a request open for at most 300 seconds; if a response has not begun by then you get 504 with {"error": "Request timeout"}.
Authentication#
Every request carries one of the two credential headers. The Bearer token is read first and the API key second. Send neither and the API answers 401 with Authentication required.
| Header | Required | Description |
|---|---|---|
X-API-Key |
One of these two | Your API key. Private keys begin with sk_, public keys with pk_. |
Authorization |
One of these two | Bearer <token>, where the token is a JWT minted from a public key at POST /v1/auth/token. Access tokens last one hour. |
Content-Type |
Yes | application/json for JSON bodies, multipart/form-data for file uploads. |
X-Parent-Origin |
Widgets only | The parent domain embedding the widget, required for public key token exchange. |
Origin header while presenting an sk_ key is rejected with 403 Private API keys cannot be used from browsers. In client-side code, exchange a public key for a JWT instead.
The full key model, including domain allowlists and per-key endpoint scopes, is on Authentication.
Content types#
There are two request shapes.
JSON body (application/json)
- Every
/v2endpoint exceptPOST /v2/ingest/files. - The five web page conversion endpoints. Their
urlfield takes one URL string or an array of URL strings.
Multipart form (multipart/form-data)
- The 46 file conversion endpoints, which read the upload from a
filefield. POST /v2/ingest/files, which reads a list of uploads from afilesfield.
Uploads are checked by filename extension and by a magic-byte sniff of the first bytes. A high-confidence mismatch, such as a file named .pdf whose bytes are a PNG, returns 400. Text formats such as JSON, CSV, XML, YAML, TOML, Markdown, HTML and SVG carry no byte signature, so they pass the sniff and fail later in the converter if the content is malformed.
Common parameters#
| Parameter | Applies to | What it does |
|---|---|---|
output_filename |
V1 convert endpoints | Names the output file. A UTC timestamp is always appended: {output_filename}_{YYYYMMDD_HHMMSSmmm}.{ext}. If you include the target extension it is stripped first, so you never get a doubled extension. |
direct_download |
All V1 convert endpoints, POST /v2/perceive |
Returns the artifact bytes as the response body instead of a JSON envelope. The default differs by endpoint: true on file uploads, false on the URL endpoints with a private key. See Signed URLs. |
async_mode, callback_url, notification_email |
V1 URL endpoints | Queue the work instead of waiting for it, and get told when it finishes. See Sync and Async Jobs and Webhooks. |
pdf_options |
Endpoints that produce PDF | Page size, margins, orientation, scale, header and footer, grayscale. The field list sits on each PDF endpoint's page. |
Default output names when you pass no output_filename:
- File uploads: derived from the input filename, so
report.docxbecomesreport_20260405_123456789.pdf. - URL conversions: derived from the domain name, so
example_20260405_123456789.pdf. - Fallback:
output_20260405_123456789.{ext}.
Response envelope#
A synchronous V1 conversion answers 200 with the location of the file rather than the file itself:
{
"presigned_url": "https://spaces.example.com/...signed...",
"object_key": "live/files/4127/url-to-pdf/example_20260405_123456789.pdf",
"filename": "example_20260405_123456789.pdf",
"file_size": 48213,
"conversion_time_seconds": 2.41
}
Conversion responses repeat that metadata in headers:
| Header | Description |
|---|---|
Content-Disposition |
inline; filename="{filename}" |
X-Object-Key |
Storage path of the converted file |
X-File-Size |
Size of the converted file in bytes |
X-Conversion-Time |
Time taken for the conversion, in seconds |
X-Filename |
Generated filename |
V2 endpoints return their own JSON envelopes, documented on their pages, but every stored artifact inside those envelopes uses one shape:
{
"url": "https://spaces.example.com/...signed...",
"object_key": "live/files/4127/v2-perceive/per_3f9a..._markdown.md",
"size_bytes": 8421,
"content_type": "text/markdown; charset=utf-8",
"expires_in": 900
}
More on expiry, reuse and retention: Signed URLs.
Errors#
Failures come back as a JSON object with a detail field:
{
"detail": "Monthly operations limit reached (500/500). Upgrade your plan to continue."
}
413 Payload Too Large is the exception: its detail is an object carrying error, file_size, max_size, tier and key_type. Status codes and the messages behind them are on Errors. The plan limits that trigger 402, 413 and 429 are on Rate Limits and Quotas.
Service endpoints#
| Endpoint | Method | Description |
|---|---|---|
/health |
GET |
Health check. Returns 200 when the database, storage and browser all answer, 503 when one of them does not. No authentication. |
/v1/whoami |
GET |
Returns {"project_id": ..., "plan_slug": ...} for the private key you present. A public key or a JWT gets 403. |
Token minting, refresh and verification live under /v1/auth/ and are covered on Authentication. The widget config and token routes live under /v1/widget/ and are covered on Integrations.
Frequently asked questions#
Which endpoints accept file uploads?#
The 46 file conversion endpoints and POST /v2/ingest/files. They read multipart/form-data. Everything else takes a JSON body, including the five web page conversion endpoints, which accept a url string or an array of URLs.
Do the V2 endpoints use the same API key as the conversion endpoints?#
Yes. One key, one project, one monthly allowance. Every unit of work costs one op, whether it is a file conversion, a URL perceived, or a page ingested. There are no per-endpoint counters and no credit multipliers.
How do I check whether the API is up?#
Call GET /health. It returns 200 when the database, storage and browser all respond and 503 when any of them does not, and it needs no authentication.
How long do the download URLs stay valid?#
15 minutes. A URL can be used more than once before it expires, and re-polling a job's status endpoint returns a freshly signed URL for the same file.
Why does a conversion return a URL instead of the file?#
Two reasons. A large conversion can run for 60 to 120 seconds, which is long enough for a reverse proxy in front of your code to give up on a streaming response, and the same result often has to be fetched more than once. So the bytes go to storage and you get a signed URL to them. Where one round trip suits you better, direct_download returns the bytes inline.