Your coding agent uses three mdfi-connect tools, described here with every message they can return and what to do about it.

## Configuration

Install the connector as the [Quickstart](index.md#install-the-connector) shows. It reads two environment variables and nothing else:

| Variable | Required | What it holds |
| --- | --- | --- |
| `MDFI_API_KEY` | Yes | Your API key, from the dashboard's Connect page. It starts with `mdfi_`. |
| `MDFI_API_URL` | No | The mdfi API to call. When it is not set, the connector uses `https://api.mdfi.ai`. |

The connector sends requests only to `MDFI_API_URL`, and it never logs document text. Every tool returns its answer, error messages included, as text: a tool never fails inside your agent's session.

Paths are read relative to the connector's working directory, the folder your agent starts it in. With Claude Code, that is the folder you start Claude Code in.

## `read_document`

`read_document(path)` opens a local file.

- `path`: the file, absolute or relative to the working directory. `~` is expanded. [Which files it reads](#which-files-it-reads) lists what is accepted.

What it does:

1. It checks the path, then computes the file's SHA-256 and asks mdfi whether your account already has that file.
2. If not, it uploads the file: its contents and its name, not the folder it is in.
3. It waits up to 20 seconds, checking after 1 second and then every 2 seconds, and returns as soon as the document is ready or failed, a page is readable, or the document has an outline.

It returns a header (the file name, `document_id`, pages ready, status, quality, charge, and the ⚠ list of what is uncertain), the outline, and the text of the ready pages: all of them when they come to about 10,000 tokens or less, otherwise the first pages, up to about 3,000 tokens. The Quickstart shows [a full example](index.md#what-your-agent-sees), and [How it works](how-it-works.md#flags-and-warnings) explains each flag.

While the document converts, the last line starts with `STILL CONVERTING — this is partial.` It says which pages to read now and, when there is one, gives an estimate of the time left. Before mdfi has anything to report on the document, the answer is:

```text
mdfi is preparing report.pdf
document_id: 1b4e28ba-2fa1-41d2-883f-0016d3cca427
status: converting
Nothing is readable yet — call read_document again with the same path shortly (no new upload, no extra charge).
```

A conversion that failed returns `mdfi could not read <file>: <reason>`, then the document id, `status: failed` and `Nothing was charged. Do not rely on this file's content.` [Conversion failures](#conversion-failures) lists the reasons.

**Charges:** a document is charged once, when its conversion completes. Calling `read_document` again on the same file sends only its SHA-256 and charges nothing, for as long as mdfi keeps the document (7 days). An edited file has a new hash, so it is a new document.

## `read_pages`

`read_pages(document_id, pages, section)` returns the text of some pages of a document that `read_document` opened. Pass exactly one of `pages` and `section`.

- `document_id`: the id `read_document` printed, a UUID such as `1b4e28ba-2fa1-41d2-883f-0016d3cca427`.
- `pages`: page numbers and ranges, such as `"3,7,9-11"`. Pages start at 1, and a range runs low to high.
- `section`: a title from the outline; [Choosing a section](#choosing-a-section) explains how it is matched.

`pages` and `section` are each at most 200 characters.

Each ready page comes under a `--- page N ---` line. A page with a concern gets a `⚠` line under that marker, and a page with an informational table note gets a plain `note:` line. Pages that are not ready yet are listed at the end, as `still converting (about 5 minutes left): 8–12`, or `still converting — ask again shortly: 8–12` when there is no estimate. [How it works](how-it-works.md#outline-first-then-pages) says when each format's pages become readable.

After the text, the connector loads the document's page flags. If that request fails, the text still comes back, under a first line saying so: `⚠ page quality flags could not be loaded (<reason>) — call read_pages again before relying on these pages`.

`read_pages` never charges.

## Choosing a section

`section` is matched against the titles in the outline: exactly first, then ignoring case. Runs of spaces count as one space. The section's pages are the pages of that outline entry.

When several entries have the title, mdfi lists them instead of guessing. For example, with an "Introduction" in two chapters:

```text
Section 'Introduction' matches 2 outline entries; pass one as "Parent > Title": Chapter 1 Filing Information > Introduction (pages 3–4); Chapter 2 Filing Status > Introduction (pages 12–13)
```

Pass the one you want as it is listed, without the pages in parentheses: `section="Chapter 2 Filing Status > Introduction"`. The parts are separated by ` > `, with a space on each side. You can name more of the entry's parents, outermost first: `"Part I > Chapter 2 > Introduction"`. Entries with no parent, or with the same titles at every level above them, cannot be told apart this way; read them by page number.

A title that is not in the outline answers `Section not found in outline: <section>`.

## `export_table`

`export_table(document_id, table, path, overwrite)` writes one table of a ready document to a CSV file.

- `document_id`: as for `read_pages`.
- `table`: the table's id, such as `p13-t1`, taken from its label in the page text: `[table p13-t1 — 42 rows × 6 cols]`. [Tables](how-it-works.md#tables) explains the ids.
- `path` (optional): where to write the CSV, absolute or relative to the working directory. The default is `mdfi-tables/<file-stem>-<table-id>.csv`, for example `mdfi-tables/report-p13-t1.csv`. Missing folders are created.
- `overwrite` (optional, default `false`): replace files that already exist.

The CSV is UTF-8 with a byte-order mark, so Excel reads accented and other non-ASCII characters correctly. It has a header row and standard CSV quoting.

A cell that is not a number and starts with `=`, `+`, `-`, `@`, a tab or a carriage return is written with a leading `'`, so a spreadsheet does not run it as a formula. Numbers such as `-12` and cells made only of dashes are written as they are.

When the table has notes, they are written to a notes file beside the CSV, named `<name>.csv.notes.txt`. The notes are the table's own notes, plus how many cells got a leading `'` and which rows have a different number of cells from the header. With `overwrite=true`, a replaced file keeps its permissions (on macOS and Linux), and an old notes file is removed when the new table has no notes.

The reply says what was written:

```text
Uncertain table — check it against the source before relying on it: "Total" row 3 does not add up in column "2025"
Wrote /home/you/project/mdfi-tables/report-p5-t1.csv (3 rows × 3 columns).
Notes: continues on page 6; 1 cell began with =, +, -, @, tab or carriage return and was prefixed with ' so spreadsheets do not run it as a formula
Notes saved to /home/you/project/mdfi-tables/report-p5-t1.csv.notes.txt.
```

The `Uncertain table` line comes first when the table has a problem; informational notes are on the `Notes:` line. A table can be exported only once the document is ready, because a page that is still being re-read can extend a table. `export_table` never charges.

## Which files it reads

`read_document` reads only the path you pass, and only these file types (the extension may be in any case):

| Type | Extensions |
| --- | --- |
| PDF | `.pdf` |
| Word | `.docx` |
| PowerPoint | `.pptx` |
| Excel | `.xlsx` |
| HTML | `.html`, `.htm` |
| Email | `.eml` |
| Image | `.png`, `.jpg`, `.jpeg`, `.tif`, `.tiff`, `.bmp`, `.webp` |
| Text | `.txt`, `.md` |

- A symbolic link is followed first, and the checks apply to the file it points to.
- A path with a hidden file or folder in it (a name starting with `.`) is refused, so `.env`, `.git/config` and `~/.ssh/…` are never read. For a file inside the working directory, only the part of the path below that directory is checked, so a project that itself sits in a hidden folder still works.
- Files outside the working directory can be read.
- A file can be up to 100 MB and 1,000 pages ([Limits](pricing.md#limits)).

The connector uploads nothing but that file. [Data and privacy](data.md#what-the-connector-uploads) says what it sends.

## Where CSVs are written

`export_table` writes a file only if all of these hold:

- the path ends in `.csv` (any case);
- it is inside the working directory;
- no file or folder in it below the working directory is hidden;
- neither the CSV nor its notes file is a symbolic link;
- neither file exists yet, unless `overwrite=true`.

Both files are first written to temporary files beside their targets and then renamed into place, the notes first. A write that fails part way, for example on a full disk, never leaves a half-written CSV, and an existing CSV is not lost.

## Errors

Values in angle brackets, such as `<path>`, and the letters N, M and R stand for what the message fills in. Only a completed conversion is charged; an error answer never costs pages.

### Setup and connection

| Message | Meaning | What to do |
| --- | --- | --- |
| `Set MDFI_API_KEY — get one on the Connect page of your mdfi dashboard.` | `MDFI_API_KEY` is not set, or is empty, in the connector's configuration. | Add your key to the configuration, as in the [Quickstart](index.md#install-the-connector), and start a new agent session. |
| `MDFI_API_KEY was rejected — create a new key on the Connect page.` | mdfi does not accept the key (HTTP 401): it is mistyped, does not start with `mdfi_`, or was revoked or deleted. | Check the key, or create a new one on the Connect page and put it in the configuration. |
| `mdfi API unreachable at <url> — check MDFI_API_URL and your network.` | The request did not reach mdfi, or got no answer in time: 120 seconds for an upload, 30 seconds for anything else. | Check `MDFI_API_URL` (leave it unset for the default) and your network, then call the tool again. If the file was already uploaded, its conversion carries on, and calling `read_document` again neither uploads nor charges it twice. |
| `mdfi API error (status N) — try again in a minute.` | mdfi answered with a server error (HTTP 5xx), for example because a service it depends on was briefly unavailable. The API's own messages for these are under [Error codes](api.md#error-codes). | Wait a minute and call the tool again. If it keeps happening, [contact us](https://mdfi.ai/#contact). |
| `mdfi API returned an unexpected response — try again in a minute.` | The answer reported success but was not the JSON the API sends, for example because `MDFI_API_URL` points at a web page. | Try again. If it persists, check that `MDFI_API_URL` points at the mdfi API. |
| `mdfi API error (status N)` | The API refused the request without a message the connector can show, often because `MDFI_API_URL` points at something that is not the mdfi API. | Check `MDFI_API_URL`. If it is right, [contact us](https://mdfi.ai/#contact) with the status. |
| `mdfi-connect error: <type> — please report this` | A bug in the connector, or an answer it could not read. | [Contact us](https://mdfi.ai/#contact) with the error type shown. |

### Plan and allowance

| Message | Meaning | What to do |
| --- | --- | --- |
| `Subscription required. Choose a plan at <dashboard>/dashboard/billing` | Your account has no active plan (HTTP 402): none was chosen, it was cancelled, or a payment is past due. | Choose or renew a plan on the dashboard's Billing page. Your key keeps working once the plan is active. |
| `Not enough pages left this month: needed N, M left. Upgrade to Pro or wait until <month> 1.` | The file needs more pages than your Starter plan has left this month (HTTP 429). Nothing was converted or charged. | Upgrade to Pro, wait until the 1st, or split the file. [Pricing](pricing.md#when-the-month-runs-out) explains the check. |
| `Not enough pages left this month: needed N, M left. Your allowance resets on <month> 1.` | The same, on Pro. | Wait until the 1st, or split the file. |

### Reading a file

These come from `read_document`.

| Message | Meaning | What to do |
| --- | --- | --- |
| `Refusing to read hidden file or folder: <path>` | The path has a part whose name starts with `.` ([Which files it reads](#which-files-it-reads)). The connector refuses these on purpose. | Do not work around it. Tell the user; if they want the file read, they can move it out of the hidden folder themselves. |
| `Unsupported file type: <extension> — mdfi reads PDF, Word, PowerPoint, Excel, HTML, email, images and text files` | The connector does not read this extension. | Save or export the file in one of the [listed types](#which-files-it-reads). |
| `Unsupported file type: <extension>` | The file reached mdfi, but mdfi does not convert this type (HTTP 400). | Save or export the file in one of the [listed types](#which-files-it-reads). |
| `File not found: <path>` | Nothing exists at the path. | Check the path. |
| `Not a file: <path>` | The path is a folder. | Pass a file inside it. |
| `Cannot read <path>: permission denied` | The connector may not read the file. | Tell the user; they can give themselves read access to the file. |
| `Cannot read <path>: <reason>` | The operating system could not read the file, for the reason given. | Fix the cause, then call `read_document` again. |
| `Could not read this PDF — the file may be damaged.` | mdfi could not open the PDF to count its pages (HTTP 400). | Check that the PDF opens elsewhere; if it does, save a new copy and read that. |
| `Password-protected PDF — can't read it.` | The PDF needs a password to open (HTTP 400). | Save a copy without the password and read that. |
| `Could not read this file — it may be damaged.` | mdfi could not open the file to count its pages, slides or image frames (HTTP 400). | Check that the file opens elsewhere, then save a new copy. |
| `Too large: limit is 1,000 pages / 100 MB per document.` | The file is over 100 MB (HTTP 413) or has more than 1,000 pages (HTTP 400). | Split the file and read the parts. |
| `sha256 does not match the uploaded file` | The file changed between hashing and uploading (HTTP 400). | Call `read_document` again once the file is saved. |

When the path you passed is not the full path the connector read, the `File not found`, `Not a file` and `Cannot read` messages end with `(you gave: <path>)`.

### Reading pages and sections

These come from `read_pages`. The `document_id` and expiry messages also come from `export_table`.

| Message | Meaning | What to do |
| --- | --- | --- |
| `Invalid document_id: '<id>' — pass the document_id that read_document printed (a UUID such as 1b4e28ba-2fa1-41d2-883f-0016d3cca427).` | The id is not a document id. | Pass the `document_id` line from `read_document`. |
| `Pass exactly one of pages (e.g. "3,7,9-11") or section (a heading from the outline).` | Both or neither of `pages` and `section` were given. | Pass one of them. |
| `Invalid page range '<pages>': use a form like 3,7,9-11` | `pages` is not a list of numbers and ranges. | Use the form shown. |
| `Invalid page range '<part>': pages start at 1 and ranges run low to high` | A page is 0, or a range runs backwards, such as `5-3`. | Use `3-5`. |
| `Page N is out of range: the document has M pages` | A page number is past the last page. | Use pages 1 to M. |
| `range/section must be at most 200 characters` | `pages` or `section` is too long. | Ask for fewer pages at a time, or use a shorter section title. |
| `Section not found in outline: <section>` | No outline entry has this title. | Use a title from the outline that `read_document` returned. |
| `Section '<section>' matches N outline entries; pass one as "Parent > Title": …` | Several entries share the title ([Choosing a section](#choosing-a-section)). | Pass one of the listed `Parent > Title` forms, or read by page number. |
| `Document is converting` | mdfi has nothing of the document to show yet. For Word, Excel, HTML, email and text files this lasts until their text is converted. | Wait, then call `read_document` again with the same path. |
| `Document is failed: <reason>` | The conversion failed ([Conversion failures](#conversion-failures)). | Follow the advice for the reason. |
| `Document expired — call read_document again (it will be re-converted and charged).` | Your account has no document with this id (HTTP 404): it was older than 7 days, it was deleted, or the id is wrong. | Call `read_document` on the file again. If the document expired or was deleted, the file is converted and charged again. |

### Exporting a table

These come from `export_table`.

| Message | Meaning | What to do |
| --- | --- | --- |
| `Invalid table id: '<table>' — use the id from the table's label in the page text, e.g. p13-t1 from [table p13-t1 — …].` | The id is not of the form `p<page>-t<n>`. | Copy the id from the table's label in the page text. |
| `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 still converting (HTTP 409). The part about the time left appears only when there is an estimate. | Call `export_table` again once `read_document` shows `status: ready`. |
| `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 still converting, and no converted page has a table with this id (HTTP 409). | Once the document is ready, read the page and take the id from its label. |
| `Table <id> not found in this document` | The document is ready and has no table with this id (HTTP 404). | Take the id from a table label in the page text. A table that is only text has no id and cannot be exported. |
| `Document is converting` | mdfi has nothing of the document to show yet. | Wait until the document is ready, then export. |
| `Document is failed: <reason>` | The conversion failed. | See [Conversion failures](#conversion-failures). |
| `export_table writes .csv files only: <path>` | `path` does not end in `.csv`. | Use a path ending in `.csv`. |
| `Refusing to write outside the working directory: <path>` | `path` leads outside the working directory. | Use a path inside it. |
| `Refusing to write into hidden file or folder: <path>` | A part of the path below the working directory starts with `.`. | Use a path without hidden parts. |
| `Refusing to write through a symlink: <path>` | The CSV or its notes file is a symbolic link. | Use another path. |
| `<path> is a directory` | A folder exists at the CSV's path. | Use another path. |
| `<path> exists — pass overwrite=true to replace it` | The CSV or its notes file already exists. | Pass `overwrite=true`, or use another path. |
| `Could not write <path>: <reason>` | The operating system could not write the file, for example because the disk is full. Nothing was left half-written. | Fix the cause and call `export_table` again. |

### Conversion failures

When a conversion fails, `read_document` returns `mdfi could not read <file>: <reason>`, and `read_pages` and `export_table` answer `Document is failed: <reason>`. A failed conversion is not charged and not kept, so calling `read_document` on the file again converts it again.

| Reason | Meaning | What to do |
| --- | --- | --- |
| `Too large: limit is 1,000 pages / 100 MB per document.` | A Word, Excel, HTML, email or text file had more than 1,000 pages once converted. | Split the file and read the parts. |
| `Not enough pages left this month: needed N, M left.` | A Word, Excel, HTML, email or text file turned out to need more pages than you had left. Its page count is only estimated before conversion ([Pricing](pricing.md#what-counts-as-a-page)). | Upgrade, wait until the 1st, or split the file. |
| `The upload was lost when the server restarted; nothing was charged — upload it again.` | mdfi restarted before converting the file and lost the upload. | Call `read_document` again. |
| `Conversion failed: the server restarted twice while converting this document. Nothing was charged; upload it again to retry.` | mdfi restarted twice while converting the file. | Call `read_document` again. If it fails the same way again, [contact us](https://mdfi.ai/#contact). |
| `Conversion failed: <type>` | An error inside mdfi. | Try once more. If it fails again, [contact us](https://mdfi.ai/#contact) with the type shown. |
| `No content extracted from <file>` | A Word, Excel, HTML, email or text file holds no text that mdfi could extract. The name in the message is a temporary file name on the server, not your file's name. | Check that the file has text in it. |
| Any other text | No page of the document could be converted; the text is the parser's reason. | Check that the file opens elsewhere, then try once more. |
