Core
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.
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.
https://api.markitdown.ai/v1/uploadsStart a chunked upload — returns a part plan and presigned PUT URLscurl -X POST https://api.markitdown.ai/v1/uploads \
-H "x-api-key: mdai_…" \
-H "Content-Type: application/json" \
-d '{ "name": "report.pdf", "bytes": 314572800 }'{
"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.
https://api.markitdown.ai/v1/uploads/{id}/completeFinish the upload once every part has been PUTcurl -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
https://api.markitdown.ai/v1/conversions/{id}/files/{file_id}Get one file's metadatahttps://api.markitdown.ai/v1/conversions/{id}/files/{file_id}/contentGet one file's raw bytes, by its own MIME typeThere 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.
Sources and specs: RFC 9110: HTTP Semantics · RFC 7763: The text/markdown Media Type