---
seo_title: Supported File Formats and Extensions | EnConvert
meta_desc: Every file format the EnConvert API accepts as input and every format it produces, grouped by conversion family, plus how extension and magic-byte checks work.
keywords: supported file formats api, accepted file extensions conversion api, anything to pdf supported formats, anything to markdown supported formats, image conversion formats api, magic byte file validation, unsupported format 400 error
---

# Supported Formats

Every extension the API accepts and every format it hands back, one table per converter family. The lists here are the ones the code checks against, so an extension that is missing is genuinely rejected rather than merely undocumented.

If you want the pairing instead (this input, that output, and the endpoint path that joins them), the [conversion matrix](/docs/endpoints/convert/matrix.md) has all 51 endpoints in one scannable table.

---

## Web pages

Five endpoints that take a URL in a JSON body rather than a file upload.

| Endpoint | Input | Output |
|----------|-------|--------|
| `POST /v1/convert/url-to-pdf` | One URL, or an array of URLs | PDF (`application/pdf`) |
| `POST /v1/convert/url-to-screenshot` | One URL, or an array of URLs | PNG (`image/png`) |
| `POST /v1/convert/url-to-markdown` | One URL, or an array of URLs | Markdown (`text/markdown; charset=utf-8`) |
| `POST /v1/convert/website-to-pdf` | One site URL, crawled or read from `sitemap.xml` | ZIP of PDFs |
| `POST /v1/convert/website-to-screenshot` | One site URL, crawled or read from `sitemap.xml` | ZIP of PNGs |

Screenshots are PNG. There is no JPEG or WebP screenshot option: the capture call is hard-coded to PNG. The two `website-to-*` endpoints are async only and always answer `202` with a `batch_id` and an `output_format` of `zip`.

---

## Documents to PDF

Eleven single-target endpoints, each a `multipart/form-data` upload with the document in the `file` field. Output is always a single PDF. The catch-all `anything-to-pdf` is the twelfth and has its own section below.

| Endpoint | Accepted input | Engine |
|----------|----------------|--------|
| `POST /v1/convert/html-to-pdf` | `.html`, `.htm` | WeasyPrint |
| `POST /v1/convert/markdown-to-pdf` | `.md`, `.markdown` | WeasyPrint |
| `POST /v1/convert/doc-to-pdf` | `.doc`, `.docx` | LibreOffice |
| `POST /v1/convert/excel-to-pdf` | `.xls`, `.xlsx` | LibreOffice |
| `POST /v1/convert/ppt-to-pdf` | `.ppt`, `.pptx` | LibreOffice |
| `POST /v1/convert/odt-to-pdf` | `.odt` | LibreOffice |
| `POST /v1/convert/ods-to-pdf` | `.ods` | LibreOffice |
| `POST /v1/convert/odp-to-pdf` | `.odp` | LibreOffice |
| `POST /v1/convert/ots-to-pdf` | `.ots` | LibreOffice |
| `POST /v1/convert/pages-to-pdf` | `.pages` | LibreOffice |
| `POST /v1/convert/numbers-to-pdf` | `.numbers` | LibreOffice |

The engine decides which `pdf_options` are legal. HTML and Markdown go through WeasyPrint and accept page size, orientation, margins, scale, header and footer. The nine LibreOffice-backed endpoints take page geometry from the source document, so they honour `grayscale` only and return `400` when a geometry option is set explicitly.

---

## anything-to-pdf

`POST /v1/convert/anything-to-pdf` accepts 36 extensions and always returns a PDF. It is file upload only: it takes no URLs.

```text
.bmp   .csv   .doc   .docx  .epub  .gif
.heic  .heif  .htm   .html  .jpeg  .jpg
.markdown     .md    .mdown .mkd   .numbers
.odp   .ods   .odt   .ots   .pages .pdf
.png   .ppt   .pptx  .rtf   .svg   .text
.tif   .tiff  .txt   .webp  .xhtml .xls
.xlsx
```

The extension picks the engine:

| Input group | Extensions | Engine |
|-------------|------------|--------|
| Office, OpenDocument, iWork, RTF, CSV | `.doc` `.docx` `.xls` `.xlsx` `.ppt` `.pptx` `.odt` `.ods` `.odp` `.ots` `.pages` `.numbers` `.rtf` `.csv` | LibreOffice headless |
| HTML | `.html` `.htm` `.xhtml` | WeasyPrint |
| Markdown | `.md` `.markdown` `.mdown` `.mkd` | WeasyPrint |
| Plain text | `.txt` `.text` | Wrapped in a monospace block, then WeasyPrint |
| EPUB | `.epub` | EPUB to Markdown, then WeasyPrint |
| Raster images | `.png` `.jpg` `.jpeg` `.gif` `.bmp` `.tiff` `.tif` `.webp` `.heic` `.heif` | Pillow |
| SVG | `.svg` | CairoSVG |
| PDF | `.pdf` | Validated passthrough |

Two consequences of that routing:

- Page geometry (`page_size`, `page_width`, `page_height`, `orientation`, margins, `scale`, `header`, `footer`) is honoured for HTML, Markdown, plain text, EPUB, image and SVG input only. Set one explicitly on office or PDF input and the request returns `400`. `grayscale` works for every input.
- A PDF upload is accepted and passed straight through, after a check that the bytes start with `%PDF-`. Combined with `grayscale`, that makes this endpoint the way to turn an existing PDF grayscale.

---

## anything-to-markdown

`POST /v1/convert/anything-to-markdown` accepts 22 extensions and always returns one UTF-8 `.md` file.

```text
.csv   .doc   .docx  .epub  .htm   .html
.markdown     .md    .mdown .mkd   .odp
.ods   .odt   .pdf   .ppt   .pptx  .rtf
.text  .txt   .xhtml .xls   .xlsx
```

Grouped by how each one is read:

| Input group | Extensions | Path |
|-------------|------------|------|
| Text and Markdown | `.txt` `.text` `.md` `.markdown` `.mdown` `.mkd` | Read directly |
| HTML | `.html` `.htm` `.xhtml` | HTML to Markdown |
| Legacy and OpenDocument | `.doc` `.ppt` `.xls` `.odt` `.ods` `.odp` `.rtf` | LibreOffice to HTML, then to Markdown |
| Native extractors | `.pdf` `.docx` `.pptx` `.xlsx` `.csv` `.epub` | Format-specific extractor |

Three things to plan around:

- **Images are rejected.** `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp`, `.tiff` and `.tif` return `400` with `Image OCR ('<ext>') is not yet supported on this endpoint.` There is no OCR on this endpoint, so a scanned page in an image file has no text path today.
- **`.ots`, `.pages` and `.numbers` are not accepted here**, even though `anything-to-pdf` takes all three. Send them through `anything-to-pdf` first, since `.pdf` is on the list above.
- Output is normalised before it is returned: CRLF becomes LF, runs of three or more blank lines collapse, and the file ends with a newline.

Extraction runs under resource caps that protect against hostile uploads. ZIP-based inputs (EPUB, DOCX, XLSX, PPTX) are rejected above 400 MB declared uncompressed or 10,000 entries. Emitted tables truncate cells at 500 characters and stop at 5,000 body rows. The PDF extractor caps at 2,000 pages and 20,000 words per page.

---

## Data formats

Eleven endpoints, all synchronous uploads. The extension of the file has to match the endpoint you send it to.

| Endpoint | Accepted input | Output |
|----------|----------------|--------|
| `POST /v1/convert/json-to-xml` | `.json` | `.xml` |
| `POST /v1/convert/xml-to-json` | `.xml` | `.json` |
| `POST /v1/convert/json-to-yaml` | `.json` | `.yaml` |
| `POST /v1/convert/yaml-to-json` | `.yaml`, `.yml` | `.json` |
| `POST /v1/convert/json-to-csv` | `.json` | `.csv` |
| `POST /v1/convert/csv-to-json` | `.csv` | `.json` |
| `POST /v1/convert/json-to-toml` | `.json` | `.toml` |
| `POST /v1/convert/toml-to-json` | `.toml` | `.json` |
| `POST /v1/convert/csv-to-xml` | `.csv` | `.xml` |
| `POST /v1/convert/xml-to-csv` | `.xml` | `.csv` |
| `POST /v1/convert/markdown-to-html` | `.md`, `.markdown` | `.html` |

---

## Images

Twenty-two endpoints: twenty conversions between JPEG, PNG, SVG, HEIC and WebP (every ordered pair), plus `pdf-to-jpeg` and `compress-image`. Read this table as "what can I turn this into".

| Input | Available outputs |
|-------|-------------------|
| JPEG (`.jpeg`, `.jpg`) | PNG, SVG, HEIC, WebP |
| PNG (`.png`) | JPEG, SVG, HEIC, WebP |
| SVG (`.svg`) | JPEG, PNG, HEIC, WebP |
| HEIC (`.heic`, `.heif`) | JPEG, PNG, SVG, WebP |
| WebP (`.webp`) | JPEG, PNG, SVG, HEIC |
| PDF (`.pdf`) | JPEG, via `pdf-to-jpeg` |
| PNG, JPEG, WebP | The same format, smaller, via `compress-image` |

Endpoint names follow the pair: `jpeg-to-png`, `svg-to-webp`, `heic-to-jpeg`, and so on. Note the JPEG endpoints are spelled `jpeg-`, not `jpg-`, though both `.jpeg` and `.jpg` files are accepted.

`compress-image` keeps the input format: PNG in, PNG out. Its optional `target_size_kb` must be at least `1`, and a smaller value returns `400` before any op is spent.

`pdf-to-jpeg` returns a raw JPEG for a single-page PDF and a ZIP of `page_1.jpeg` through `page_N.jpeg` for a multi-page one. Any converter output that begins with a ZIP signature is delivered with a `.zip` extension, which is why the same endpoint can hand you two different content types.

### Pixel ceilings

These are checked separately from your plan's file-size limit, because a small file can decode into an enormous bitmap.

| Limit | Value | Applies to |
|-------|-------|------------|
| Decoded pixels | 40,000,000 | Every image endpoint, checked right after the image is opened |
| SVG output dimension | 10,000 px per side | SVG rasterisation (`svg-to-*`) |
| SVG output pixels | 25,000,000 total | SVG rasterisation (`svg-to-*`) |
| Render size | 25,000,000 px per page | `pdf-to-jpeg`, which scales the render down to fit |
| Page count | 500 pages | `pdf-to-jpeg` |

SVG requests that break a dimension ceiling return `400` before any op is counted. Passing only `width` or only `height` derives the other from the SVG's intrinsic ratio; when that ratio cannot be read, the request fails with a `400` asking for both.

---

## Output media types

Direct downloads carry the media type mapped from the output extension.

| Extension | Content type |
|-----------|--------------|
| `.pdf` | `application/pdf` |
| `.png` | `image/png` |
| `.jpeg`, `.jpg` | `image/jpeg` |
| `.heic` | `image/heic` |
| `.webp` | `image/webp` |
| `.svg` | `image/svg+xml` |
| `.json` | `application/json` |
| `.xml` | `application/xml` |
| `.yaml`, `.yml` | `application/x-yaml` |
| `.csv` | `text/csv` |
| `.toml` | `application/toml` |
| `.html` | `text/html` |
| `.md` | `text/markdown; charset=utf-8` |
| `.zip` | `application/zip` |

Anything not in that list is served as `application/octet-stream`.

---

## V2 inputs and outputs

The web intelligence endpoints work in outputs rather than file formats.

[`POST /v2/perceive`](/docs/endpoints/perceive.md) takes a URL and returns any of a closed set of outputs: `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. The default is `["markdown", "structured"]`. Everything except `structured` is written to storage and returned as a signed download URL; `structured` comes back inline in the response body.

[`POST /v2/ingest/files`](/docs/endpoints/ingest.md) reuses the `anything-to-markdown` list above verbatim, so the same 22 extensions are accepted and images are rejected the same way. A single job takes up to 200 files, each filename up to 255 characters, and the job's output is one JSONL file of chunks.

---

## How format detection works

Uploads are checked twice, and neither check looks at the request's `Content-Type`. There is no MIME allowlist for uploads anywhere in the API.

**First, the filename extension.** If the endpoint declares an accepted list and your extension is not on it, the request fails immediately:

```json
{
    "detail": "Invalid file format '.txt' for json-to-xml. Allowed: .json"
}
```

**Then, the bytes.** The API sniffs the first bytes of the upload and compares what it finds against what the extension claimed. It recognises PNG, JPEG, GIF, WebP, HEIC and HEIF, PDF, and the two office container types (the ZIP container behind `.docx`, `.xlsx`, `.pptx`, ODF, iWork and EPUB, and the older OLE2 container behind `.doc`, `.xls`, `.ppt`).

Text formats skip the sniff entirely. JSON, CSV, XML, YAML, TOML, Markdown, HTML, SVG and plain text have no reliable signature, so their extension is taken at face value and the converter reports the problem instead.

The sniff is deliberately conservative. It rejects only a high-confidence mismatch, meaning a signature it recognises that belongs to a different group than the extension claimed. A `.png` upload whose bytes are a JPEG is rejected. Bytes it does not recognise pass through. A mismatch returns `400`:

```json
{
    "detail": "File content does not match the 'jpeg-to-png' input type."
}
```

<div class="alert alert-info">
<strong>Renaming a file does not convert it.</strong> Calling <code>photo.webp</code> <code>photo.png</code> gets past the extension gate and then fails the byte check with the <code>400</code> above. Send the real extension and pick the endpoint that matches it.
</div>

Filenames are rewritten, not rejected, on the way to storage: only the basename survives, `..` and the characters `<>:"|?*` are stripped, and spaces become underscores. See [file ingestion](/docs/guides/file-ingestion.md) for the resulting object key.

---

## Related pages

- [Conversion matrix](/docs/endpoints/convert/matrix.md) pairs every input with its output and endpoint path.
- [File ingestion](/docs/guides/file-ingestion.md) covers how to send the bytes: multipart upload, a URL the API fetches, and the size ceilings that apply.
- [Errors](/docs/reference/errors.md) has the full message list for `400`, `413` and `415`.
- [Rate limits and quotas](/docs/reference/rate-limits.md) has the per-plan upload ceilings.
