---
seo_title: All API Endpoints: Perceive, Ingest, Convert | EnConvert
meta_desc: Every EnConvert endpoint in one place: Perceive for reading a page, Ingest for crawling a site, and 51 Convert routes, plus the shared request contract.
keywords: enconvert api endpoints, api endpoint list, x-api-key header, api base url, multipart form data upload, presigned url response, api request parameters, api response envelope, health check endpoint
---

# 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](/docs/endpoints/perceive.md).

## 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](/docs/endpoints/ingest.md).

## 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](/docs/endpoints/convert.md).

## In development

Distill, Lookup, Watch and Discover are in private beta and are not covered here. They are described under [Coming Soon](/docs/coming-soon.md), 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. |

<div class="alert alert-warning">
<strong>Private keys are server-side only.</strong> Any request that carries an <code>Origin</code> header while presenting an <code>sk_</code> key is rejected with <code>403 Private API keys cannot be used from browsers</code>. In client-side code, exchange a public key for a JWT instead.
</div>

The full key model, including domain allowlists and per-key endpoint scopes, is on [Authentication](/docs/authentication.md).

### Content types

There are two request shapes.

**JSON body (`application/json`)**

- Every `/v2` endpoint except `POST /v2/ingest/files`.
- The five web page conversion endpoints. Their `url` field 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 `file` field.
- `POST /v2/ingest/files`, which reads a list of uploads from a `files` field.

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](/docs/concepts/signed-urls.md). |
| `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](/docs/concepts/sync-and-async.md) and [Webhooks](/docs/guides/webhooks.md). |
| `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.docx` becomes `report_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:

```json
{
    "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:

```json
{
    "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
}
```

<div class="alert alert-info">
<strong>Signed URLs live for 15 minutes.</strong> They work more than once inside that window, and polling a job's status endpoint again mints a fresh URL over the same object. Download the file or copy it into your own storage promptly.
</div>

More on expiry, reuse and retention: [Signed URLs](/docs/concepts/signed-urls.md).

### Errors

Failures come back as a JSON object with a `detail` field:

```json
{
    "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](/docs/reference/errors.md). The plan limits that trigger `402`, `413` and `429` are on [Rate Limits and Quotas](/docs/reference/rate-limits.md).

---

## 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](/docs/authentication.md). The widget config and token routes live under `/v1/widget/` and are covered on [Integrations](/docs/guides/integrations.md).

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