Async Document Lifecycle
Once a file has been submitted via POST /files/v1/uri, POST /files/v1/jobs, or POST /files/v1 (direct multipart upload), use these endpoints to poll its status, download its converted results, or delete it.
| Endpoint | Description |
|---|---|
| GET /files/v1/{file_id} | Poll processing status |
| GET /files/v1/{file_id}.{ext} | Download a converted result in the requested format |
| DELETE /files/v1/{file_id} | Permanently remove a file and its results |
GET /files/v1/{file_id}
GET api.mathpix.com/files/v1/{file_id}
Returns the file's status and processing progress.
Poll until status is "completed" (or "error").
Example
- cURL
- Python
- JavaScript / TypeScript
- Go
- Java
curl -H 'app_key: APP_KEY' \
https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d
import requests
r = requests.get(
"https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
headers={"app_key": "APP_KEY"},
)
body = r.json()
print(body["status"], body["percent_done"])
const response = await fetch(
"https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
{ headers: { app_key: "APP_KEY" } },
);
const file = await response.json();
console.log(file.status, file.percent_done);
req, _ := http.NewRequest("GET", "https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d", nil)
req.Header.Set("app_key", "APP_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
result, _ := io.ReadAll(resp.Body)
fmt.Println(string(result))
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d"))
.header("app_key", "APP_KEY")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
{
"percent_done": 100.0,
"formats": {
"md": "completed",
"docx": "completed"
},
"custom_id": "contract-001",
"num_pages": 30,
"destination_uri": null,
"destination_basename": null,
"filename": "b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.pdf",
"format_primary": "mmd",
"file_id": "b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
"num_pages_completed": 30,
"status": "completed"
}
Response body
file_id The file's identifier.
status Overall processing status; one of the status values below.
percent_done Progress from 0.0 to 100.0.
num_pages Total pages detected in the document. 0 until the page split runs.
num_pages_completed Pages that have finished OCR.
formats Per-requested-format conversion status; see Per-format conversion status below.
format_primary Always mmd.
filename The display name supplied at submit, or <file_id>.pdf when none was.
custom_id Echoed back when supplied at submit; null otherwise.
destination_uri The result destination supplied at submit; null when results stay in Mathpix storage.
destination_basename The output basename supplied at submit; null when defaulted.
Status values
| Status | Meaning |
|---|---|
pending | File registered, queued for processing. |
split | Pages extracted, OCR and conversion in progress (poll percent_done). |
completed | All processing finished; results available via download. |
error | Processing failed; see the error fields on the response. |
Per-format conversion status
The formats map carries one entry per format you requested via conversion_formats, each with its own conversion status (received / loaded / processing / completed / error). A requested format is absent from the map until its conversion starts; treat a missing entry the same as a not-yet-completed one.
Conversions complete independently of the top-level status and can lag behind it: a file can be completed overall while an individual format is still processing.
Poll formats.{ext} before downloading that extension; a download of a format that isn't yet completed returns 404 format_not_ready.
Error fields
When status is "error", the response carries the same error + error_info pair used by Files API request errors, alongside the usual fields:
{
"percent_done": 0.0,
"formats": {},
"error_info": {
"id": "data_source_not_found",
"message": "No data source registered for source"
},
"custom_id": "contract-001",
"num_pages": 0,
"destination_uri": null,
"destination_basename": null,
"filename": "b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.pdf",
"format_primary": "mmd",
"file_id": "b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
"num_pages_completed": 0,
"error": "data_source_not_found",
"status": "error"
}
error is a stable, machine-readable code (error_info.id duplicates it for v3-parser compatibility); see the error reference for the full list.
Remote-source fetching happens asynchronously, so source problems surface here, not on the original submit call. Common values: data_source_not_found (no data source registered for the bucket), data_source_access_denied (the bucket's grant isn't set up), and content_too_large.
GET /files/v1/{file_id}.{ext}
GET api.mathpix.com/files/v1/{file_id}.{ext}
Download a converted result.
The MMD format is always produced; other formats produce only when requested via conversion_formats on the original submission.
Supported extensions
mmd, md, md.zip, mmd.zip, docx, pptx, xlsx, html, html.zip, tex.zip, latex.pdf, pdf, lines.json, lines.mmd.json.
See Supported formats for the full list with descriptions.
Example
- cURL
- Python
- JavaScript / TypeScript
- Go
- Java
curl -H 'app_key: APP_KEY' \
-o output.docx \
https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.docx
import requests
r = requests.get(
"https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.docx",
headers={"app_key": "APP_KEY"},
)
if r.status_code == 200:
with open("output.docx", "wb") as f:
f.write(r.content)
elif r.status_code == 404 and r.json().get("error") == "format_not_ready":
print("Format still converting; retry later.")
import { writeFile } from "node:fs/promises";
const response = await fetch(
"https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.docx",
{ headers: { app_key: "APP_KEY" } },
);
if (response.ok) {
await writeFile("output.docx", Buffer.from(await response.arrayBuffer()));
}
req, _ := http.NewRequest("GET", "https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.docx", nil)
req.Header.Set("app_key", "APP_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := os.Create("output.docx")
defer out.Close()
io.Copy(out, resp.Body)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.docx"))
.header("app_key", "APP_KEY")
.GET()
.build();
client.send(request, HttpResponse.BodyHandlers.ofFile(Path.of("output.docx")));
Response headers
HTTP/1.1 200 OK
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Disposition: attachment; filename="b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d.docx"
The Content-Type matches the requested extension (for example text/plain for .mmd); the Content-Disposition filename is <basename>.<ext>.
Errors
| Code | HTTP | When it fires |
|---|---|---|
format_not_ready | 404 | Format is still converting (formats.{ext} is absent or not yet completed). The error body carries the file's current status. Retry after a short delay. |
unsupported_format | 415 | Extension wasn't requested via conversion_formats on the original submission, or isn't a supported output format. |
not_found | 404 | file_id doesn't exist (or was deleted). |
lines.json and lines.mmd.json are available once the primary mmd format completes.
DELETE /files/v1/{file_id}
DELETE api.mathpix.com/files/v1/{file_id}
Permanently remove a file and its results from Mathpix-owned storage.
Files are auto-deleted on a per-artifact schedule (source and page images after 30 days, text outputs after 90 days; see Data retention). Call this to remove sooner.
Example
- cURL
- Python
- JavaScript / TypeScript
- Go
- Java
curl -X DELETE -H 'app_key: APP_KEY' \
https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d
import requests
r = requests.delete(
"https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
headers={"app_key": "APP_KEY"},
)
print(r.json()) # {"file_id": "...", "status": "deleted"}
const response = await fetch(
"https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
{ method: "DELETE", headers: { app_key: "APP_KEY" } },
);
const { file_id, status } = await response.json();
req, _ := http.NewRequest("DELETE", "https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d", nil)
req.Header.Set("app_key", "APP_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
result, _ := io.ReadAll(resp.Body)
fmt.Println(string(result)) // {"file_id": "...", "status": "deleted"}
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.mathpix.com/files/v1/b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d"))
.header("app_key", "APP_KEY")
.DELETE()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
{
"file_id": "b1c9c3a8-55e4-4a09-b7d0-218ba5de4c4d",
"status": "deleted"
}
Response body
file_id The deleted file's identifier.
status Always "deleted" on success.
Behavior
- Only terminal files can be deleted. A file still being processed (
pending/split) cannot be deleted; DELETE returns409 conflict. Wait forcompletedorerror, then delete. - Idempotent. Calling DELETE on an already-deleted file returns the same
200 / status: deletedbody, not404. - Mathpix-owned storage only. Results delivered to a customer-owned bucket via
destination_uriare not affected; those live under your bucket's own lifecycle policy. Mathpix never deletes from customer-owned buckets. - Billing counters preserved. Per-month page and file counts that drive billing are never decremented. Deleting a file does not credit your account.
- Job counters preserved. A file's job remains intact;
file_count/files_completed/files_erroredon the parent job are not adjusted.
Errors
| Code | HTTP | When it fires |
|---|---|---|
not_found | 404 | file_id doesn't resolve to any row (and has never existed). |
conflict | 409 | file_id exists but is still processing (pending / split); not yet deletable. |
forbidden | 403 | file_id exists but is owned by a different group. |
See also
POST /files/v1/uri: submit a single file.POST /files/v1/jobs: submit a batch.- Data retention: automatic cleanup schedule per artifact type.
- Supported formats: full list of conversion outputs.