---
title: "Conversion Files"
description: "Every source and output belongs to a conversion. Upload inline, reference an earlier file, or read its raw content."
url: https://markitdown.ai/docs/files
updated: 2026-09-28
source: markitdown.ai
---

# Conversion Files

> Every source and output belongs to a conversion. Upload inline, reference an earlier file, or read its raw content.

## Reference a file from an earlier conversion

To reuse a file already produced by an earlier conversion, send its `file_…` id as the `file` (or `files`) input on a later call instead of the file's bytes. It can be a source file from a previous conversion (matched by extension) or an output file (matched by `format`, e.g. a `markdown` output can feed `POST /v1/convert/markdown/html`). To submit a brand-new file too large for one multipart request, use `POST /v1/uploads` instead — see “Uploading a file larger than the multipart limit” below.

**Convert a stored PDF's Markdown output to HTML**

```bash
curl -X POST https://api.markitdown.ai/v1/convert/markdown/html \
  -H "x-api-key: mdai_…" \
  -H "Content-Type: application/json" \
  -d '{ "file": "file_b2…" }'
```

The referenced file must belong to your own account (otherwise `404 not_found`, so existence is never leaked), and its conversion must not have expired or been deleted (`410 file_expired`).

## Uploading a file larger than the multipart limit

A single `multipart/form-data` request tops out at 100 MB regardless of plan (Cloudflare's request-body limit on our zone). A file within your plan's per-file limit — Lite 100 MB, Pro 200 MB, Max unlimited (bounded by credits and storage instead) — but over that transport ceiling uploads directly to storage in parts instead, on the public API and the web app alike. This exists only to get the bytes past Cloudflare; it is unrelated to OCR or any parsing limit.

**POST** `/v1/uploads` — Start a chunked upload — returns a part plan and presigned PUT URLs

**1. Create the upload**

```bash
curl -X POST https://api.markitdown.ai/v1/uploads \
  -H "x-api-key: mdai_…" \
  -H "Content-Type: application/json" \
  -d '{ "name": "report.pdf", "bytes": 314572800 }'
```

**201 Created**

```json
{
  "id": "upl_3f7c…",
  "name": "report.pdf",
  "bytes": 314572800,
  "mime": "application/pdf",
  "part_size": 31457280,
  "part_count": 10,
  "parts": [ { "number": 1, "url": "https://…" }, … ],
  "expires_at": "2026-09-27T00:00:00.000Z"
}
```

2. PUT each byte range to its part's `url` (any order, in parallel if you like — each is valid for 24 hours) and keep the `ETag` response header from every part.

**POST** `/v1/uploads/{id}/complete` — Finish the upload once every part has been PUT

**3. Complete it**

```bash
curl -X POST https://api.markitdown.ai/v1/uploads/upl_3f7c…/complete \
  -H "x-api-key: mdai_…" \
  -H "Content-Type: application/json" \
  -d '{ "parts": [ { "number": 1, "etag": "\"…\"" }, … ] }'
```

The server HEADs the assembled object and rejects it (`413 upload_too_large`, deleting the object) if the real size does not match what you declared or exceeds your plan's limit.

4. Submit the conversion with the upload's id instead of raw bytes: `{ "upload": "upl_3f7c…" }` (or `"uploads": […]`, up to 1000, for several files) on `POST /v1/convert/{name}` — it behaves exactly like a multipart `file`.

If a part fails or your process is interrupted, `GET /v1/uploads/{id}` returns which parts already landed (`parts_uploaded`) and fresh presigned URLs for the ones still missing (`parts`), so you resume instead of starting over. `DELETE /v1/uploads/{id}` aborts it. An upload not completed within 24 hours is aborted automatically, and a completed-but-unused upload is deleted after the same 24 hours.

Submitting an upload id that is not yours, was never completed, or has expired returns `404 not_found`, `409 upload_not_completed`, or `410 file_expired` respectively.

## Read a file's content

**GET** `/v1/conversions/{id}/files/{file_id}` — Get one file's metadata

**GET** `/v1/conversions/{id}/files/{file_id}/content` — Get one file's raw bytes, by its own MIME type

There is no per-file delete — `DELETE /v1/conversions/{id}` removes the conversion and every source and output file it holds. A file cannot be deleted alone because output files exist only in relation to the source that produced them.

## When should I reference a file instead of uploading it again?

For a one-off conversion, send the file straight to `POST /v1/convert/{name}` as multipart. Reference an existing `file_…` id when you want to run the same source through a different `name` or options (a stored PDF converted again with `pages` set), or chain a parse output into a render call (a stored `markdown` output converted with `/v1/convert/markdown/html`) — either way the bytes are reused, not re-uploaded.

## Is there a presigned upload for the public API?

Yes — `POST /v1/uploads` (above) works the same way on the public API and the web app. Use it for a file over the multipart request-body limit; a smaller file can still go straight to `POST /v1/convert/{name}` as multipart bytes.

## What file types can I upload?

PDF; Word (`.doc`, `.docx`, `.odt`, `.rtf`); PowerPoint (`.ppt`, `.pptx`, `.odp`); spreadsheets (`.xls`, `.xlsx`, `.ods`, `.csv`); HTML; plain text and Markdown; and images (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`, `.bmp`, `.tif`, `.tiff`) — each behind its own `name` endpoint. `GET /v1/formats` returns the same list together with your plan's page and file-size limits, so a client can check a file before uploading it.

## Related

- [Convert](https://markitdown.ai/docs/convert)
- [Conversions](https://markitdown.ai/docs/conversions)
- [Batches](https://markitdown.ai/docs/batches)
- [Introduction](https://markitdown.ai/docs)
- [Document Conversion APIs](https://markitdown.ai/developers)
