mdfi.ai Docs

API

The HTTP API behind the connector lets you upload documents, read their pages and tables, and delete them.

The base URL is https://api.mdfi.ai, and the server's own OpenAPI reference, with every field, is at https://api.mdfi.ai/docs. The examples below use curl, with your key in $MDFI_API_KEY.

Authentication

Every request sends your API key, which you get on the dashboard's Connect page, as a bearer token:

Authorization: Bearer mdfi_…

Without a valid key the answer is HTTP 401: API key required (no header), Invalid API key format (the key does not start with mdfi_) or Invalid or revoked API key. A valid key whose account has no active plan gets HTTP 402: Subscription required. Choose a plan at …/dashboard/billing.

Documents belong to the account that uploaded them. Another account's document id answers 404, as if it did not exist.

Upload a document

POST /v1/documents takes a form with these fields:

Field Required What it is
sha256 Yes The file's SHA-256: 64 hex characters, in any case.
file No The file itself. Leave it out to ask whether your account already has the document.
max_tier No The highest tier a re-read may use, 1 to 3. Default 3. max_tier=2 keeps every page away from the vision model.

Send the hash first, without the file:

SHA=$(shasum -a 256 report.pdf | cut -d' ' -f1)
curl -s -H "Authorization: Bearer $MDFI_API_KEY" -F sha256=$SHA https://api.mdfi.ai/v1/documents
  • 200: your account already has this document. The answer is its current status, with its manifest while it converts or once it is ready (see Read a document). Nothing is charged, and max_tier is ignored.
  • 404 upload required: your account does not have it. Send the same request with the file:
curl -s -H "Authorization: Bearer $MDFI_API_KEY" -F sha256=$SHA -F file=@report.pdf https://api.mdfi.ai/v1/documents
  • 202: the conversion has started. The answer is {"id": "…", "status": "converting", "source": "report.pdf", "error": null, "manifest": null}. Use the id with the requests below.

Before converting, mdfi checks the upload. A failed check converts nothing and charges nothing:

Status Message When
400 sha256 must be exactly 64 hex characters (SHA-256) sha256 is not a SHA-256.
400 sha256 does not match the uploaded file The file's hash is not sha256.
400 Unsupported file type: <extension> mdfi does not convert this type. It converts the types the Connector reads, and also other plain-text and code files such as .csv, .json, .yaml, .py and .log, which the connector does not send.
400 Could not read this PDF — the file may be damaged., Password-protected PDF — can't read it., Could not read this file — it may be damaged. mdfi could not open the file to count its pages.
400 Too large: limit is 1,000 pages / 100 MB per document. The file has more than 1,000 pages.
413 Too large: limit is 1,000 pages / 100 MB per document. The file is over 100 MB.
429 Not enough pages left this month: needed N, M left. followed by when the allowance resets The pages left this month do not cover the file (Pricing).

A conversion that fails is not kept: sending its hash again answers 404 upload required, and uploading the file converts it again. The document is charged once, when its conversion completes.

Read a document

GET /v1/documents/{id} returns the document's status:

curl -s -H "Authorization: Bearer $MDFI_API_KEY" https://api.mdfi.ai/v1/documents/$ID
Field What it holds
id The document id.
status converting, ready or failed.
source The uploaded file's name.
error Why the conversion failed; null otherwise. The Connector page lists the reasons.
manifest What the agent needs to choose pages. null when the document failed, and while it converts until its outline or first pages exist.

The manifest:

Field What it holds
page_count The number of pages. While converting, the count known before conversion where it is known.
weighted_pages What the document is charged, in weighted pages. While converting, the weight of the work done so far; nothing is charged before the document is ready.
approx_tokens The characters of all pages divided by 4. A rough size, never used for billing.
pages One entry per page: number, state (ready or converting), score (0 to 100), charged_tier, kept_tier and flags. score and both tiers are null for a page not converted yet.
outline Entries with title, level (1 to 6), start_page and end_page.
warnings Messages about the conversion as a whole, such as pages a tier could not re-read.
tables One entry per table: id, start_page, end_page, rows (data rows, the header not counted), cols, uncertain and notes.
first_text The text of the ready pages: all of it up to about 10,000 tokens, otherwise whole pages from the first, up to about 3,000 tokens. Each page starts with a --- page N --- line.
first_text_pages The pages in first_text.
estimated_seconds_left While converting, an estimate of the seconds until the document is ready, from the pace of the pages converted so far. It is an estimate, not a promise. null before the first page is converted, when only re-reads are left, when the conversion has run past its estimate, and once the document is ready.
complete true when first_text is the whole document. Always false while converting; use status to know when the document is ready.

How it works explains scores and flags, tiers and tables. For example, a ready 3-page report whose third page was re-read by the vision model (a few fields shortened):

{
  "id": "1b4e28ba-2fa1-41d2-883f-0016d3cca427",
  "status": "ready",
  "source": "report.pdf",
  "error": null,
  "manifest": {
    "page_count": 3,
    "weighted_pages": 7,
    "approx_tokens": 49,
    "pages": [
      {"number": 1, "score": 100.0, "charged_tier": 1, "kept_tier": 1, "flags": [], "state": "ready"},
      {"number": 2, "score": 96.0, "charged_tier": 1, "kept_tier": 1, "flags": [], "state": "ready"},
      {"number": 3, "score": 88.0, "charged_tier": 3, "kept_tier": 3, "flags": ["ocr", "enhanced_tier3"], "state": "ready"}
    ],
    "outline": [
      {"title": "Summary", "level": 1, "start_page": 1, "end_page": 1},
      {"title": "Results", "level": 1, "start_page": 2, "end_page": 3}
    ],
    "warnings": [],
    "tables": [
      {"id": "p2-t1", "start_page": 2, "end_page": 2, "rows": 4, "cols": 3, "uncertain": false, "notes": []}
    ],
    "first_text": "--- page 1 ---\n## Summary\nRevenue grew in every region except the north.\n\n--- page 2 ---\n…",
    "first_text_pages": [1, 2, 3],
    "estimated_seconds_left": null,
    "complete": true
  }
}

While a document converts, PDF, image and PowerPoint pages become ready before the whole document is. The pages of other formats stay converting until it is ready (How it works).

A document mdfi does not have for your account answers 404 Document not found or expired — upload it again with POST /v1/documents: the id is wrong, the document was deleted, or it is older than 7 days.

Read pages

GET /v1/documents/{id}/pages returns the text of some pages. Pass exactly one of:

  • range: page numbers and ranges, such as 3,7,9-11;
  • section: a title from the outline. A title several entries share is chosen as Parent > Title; the Connector page explains the matching.
curl -s -H "Authorization: Bearer $MDFI_API_KEY" "https://api.mdfi.ai/v1/documents/$ID/pages?range=2-3"
curl -s -G -H "Authorization: Bearer $MDFI_API_KEY" --data-urlencode "section=Results" https://api.mdfi.ai/v1/documents/$ID/pages

The answer lists the pages whose text it holds and the requested pages that are not ready yet (text shortened):

{
  "id": "1b4e28ba-2fa1-41d2-883f-0016d3cca427",
  "pages": [2, 3],
  "text": "--- page 2 ---\n## Results\n[table p2-t1 — 4 rows × 3 cols]\nRegion\t2024\t2025\n…\n\n--- page 3 ---\nFigures are unaudited.",
  "not_ready": []
}

While the document converts, text holds the requested pages that are ready, and not_ready the rest; ask for those again later. While only the outline exists, every requested page is in not_ready. Reading pages never charges.

Status Message When
400 Pass exactly one of range or section Both or neither were given.
400 range/section must be at most 200 characters The selector is too long.
400 Invalid page range '<range>': use a form like 3,7,9-11 range is not a list of numbers and ranges.
400 Invalid page range '<part>': pages start at 1 and ranges run low to high A page is 0, or a range runs backwards.
400 Page N is out of range: the document has M pages A page is past the last page.
404 Section not found in outline: <section> No outline entry has this title.
409 Section '<section>' matches N outline entries; pass one as "Parent > Title": … Several entries share the title; the message lists them.
409 Document is converting Nothing of the document is available yet.
409 Document is failed: <reason> The conversion failed.

Export a table

GET /v1/documents/{id}/tables/{table_id} returns one table, with the id from the manifest's tables or from the table's label in the page text:

curl -s -H "Authorization: Bearer $MDFI_API_KEY" https://api.mdfi.ai/v1/documents/$ID/tables/p2-t1
{
  "id": "p2-t1",
  "start_page": 2,
  "end_page": 2,
  "headers": ["Region", "2024", "2025"],
  "rows": [["North", "12", "11"], ["South", "20", "24"], ["East", "9", "10"], ["West", "15", "17"]],
  "uncertain": false,
  "notes": []
}

A table is returned only once the document is ready, because a page still being converted or re-read can extend it. Exporting never charges.

Status Message When
409 Document is converting Nothing of the document is available yet.
409 table <id> is still being enhanced (R of N pages ready, about M minutes left); tables can be exported once the document is ready The document is converting and the pages converted so far have this table. The time left appears only when there is an estimate.
409 table <id> is not on the pages converted so far; table ids come from labels such as [table p13-t1 — …] in the page text, so read the page again once the document is ready The document is converting and no converted page has this table.
409 Document is failed: <reason> The conversion failed.
404 Table <id> not found in this document The document is ready and has no table with this id.

Delete a document

DELETE /v1/documents/{id} deletes a document at once and answers 204:

curl -s -X DELETE -H "Authorization: Bearer $MDFI_API_KEY" https://api.mdfi.ai/v1/documents/$ID

It removes the document's pages, outline and tables, and the record that recognises the file. Afterwards the id answers 404, and uploading the same file converts and charges it again. An unknown id answers 404 Document not found.

A document deleted while it is still converting is charged for the work already done, and its result is not kept. Data and privacy says what mdfi keeps and for how long.

Error codes

Status Meaning
200 Success. For POST /v1/documents: your account already has the document.
202 The upload was accepted and the conversion has started.
204 The document was deleted.
400 The request or the file is not valid; the message says why.
401 No API key, or one mdfi does not accept.
402 No active plan. The message links to the Billing page.
404 upload required, or the document, section or table does not exist for your account.
409 The document is still converting or failed, or a section title matches several entries.
413 The file is over 100 MB.
422 A field is missing or out of range, such as no sha256 or max_tier=4. The answer lists the fields.
429 Not enough pages left this month.
500 An error inside mdfi. Try again; if it persists, contact us.
503 A service mdfi depends on is briefly unavailable: Usage service unavailable, try again, Storage unavailable, try again, Could not register the upload; retry or Conversion service unavailable, try again. Nothing was converted or charged; try again in a minute.

Error answers carry their message in a JSON detail field (for 422, a list of the fields in error). An unexpected 500 may be plain text.

Older endpoints

The server also has the endpoints of mdfi's first API. They convert a whole file, or a web page from a URL, to a single markdown text, without the per-page scores, outline and table ids of /v1/documents:

  • POST /convert: converts and answers in the same request.
  • POST /jobs, GET /jobs/{id}, GET /jobs, DELETE /jobs/{id}: background conversions, with an optional webhook_url that receives the result.
  • POST /batches, GET /batches/{id}, GET /batches, DELETE /batches/{id}: up to 50 files and URLs at once.

On mdfi they read at Tier 1 only, and each conversion counts as one weighted page whatever its size. A request is refused with HTTP 429 when no page is left this month; a batch needs one page per item. Uploads are limited to 100 MB: a larger file answers HTTP 413, File too large (NMB). Maximum is 100MB. (in a batch, File '<name>' too large (NMB). Maximum is 100MB.).

POST /convert waits up to 30 seconds for a free conversion slot. If none frees up, it answers HTTP 503 Server busy converting other documents — use POST /jobs or /v1/documents and poll.

The schema option of these endpoints takes the name of a built-in template and adds the fields it extracts to the result. To do that, it sends the whole converted text to a language model; Data and privacy names the provider. What these endpoints keep, and for how long, is under Older endpoints on the same page.

Use /v1/documents for new work: it is the API the connector uses, and the only one that re-reads poor pages.

Templates (coming soon)

A /v1/templates API is coming soon. Until then, extraction templates are kept on the dashboard (Templates).