# Convert a file

> POST /v1/convert takes one document as multipart/form-data and returns its Markdown as JSON or as plain text/markdown.

`POST /v1/convert` converts one document to Markdown. The format is detected from the file’s bytes, never from its name or `Content-Type`.

```sh
curl -X POST https://api.md.tlelabs.com/v1/convert \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -F "file=@report.pdf" \
  -F "ocr=auto" \
  -F "ocr_model=auto"
```

## Request

`Content-Type: multipart/form-data` with these fields:

| Field             | Required | Value                                                                                                                         |
| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `file`            | yes      | The document. Up to 50 MB.                                                                                                    |
| `ocr`             | no       | `auto` (default), `force` or `off`. See [OCR modes](#ocr-modes).                                                              |
| `ocr_model`       | no       | `auto` (default) or a model id such as `openparser/mistral-ocr-4`. See [OCR models](https://md.tlelabs.com/docs/ocr-models/). |
| `preserve_images` | no       | `true` (default) or `false`. See [Picture placeholders](#picture-placeholders).                                               |

Supported formats: PDF (.pdf), DOCX (.docx), PPTX (.pptx), XLSX (.xlsx), HTML (.html, .htm). Anything else gets `415 unsupported_format`. A field with an invalid value gets `400 invalid_request` with `error.details.field` naming it; for `ocr_model` the allowed ids are in `error.details.allowed`.

Headers: `Authorization` (required) and `Accept` — `application/json` (default) or `text/markdown`.

## OCR modes

| `ocr`   | Behavior                                                                                                                                                                                                                  |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auto`  | OCR only what the file cannot give as text: pictures inside DOCX, PPTX, XLSX and HTML (`data:` pictures), scanned PDF pages, PDF pages whose text layer is missing or garbled, and large pictures on PDF pages with text. |
| `force` | PDF only: every non-blank page is read by the OCR model even when it has a text layer (useful for handwriting on typed pages). Other formats behave like `auto`.                                                          |
| `off`   | No OCR. Pictures keep only their placeholder. A PDF with no text at all fails with `422 parse_failed`.                                                                                                                    |

Content the converter reads from the file itself — text, tables, charts, SmartArt, text inside SVG and EMF/WMF pictures — is always included and costs 1 credit per page, even with `ocr=off`. See [Formats, pages and pricing](https://md.tlelabs.com/docs/formats-and-pricing/).

An OCR failure does not fail the request: the affected picture or page is left without OCR text, you are charged a text page for it, and `warnings` says what happened. Only a document that ends up with no text at all because OCR failed gets `502 ocr_provider_error`, and nothing is charged.

## Picture placeholders

With `preserve_images=true` each picture leaves a placeholder such as `![image](image-1.png)` where it was, followed by its OCR text as a quote:

```markdown
Revenue grew ![Scanned chart](image-1.png) this quarter.

> Q1 120 · Q2 134 · Q3 151
```

With `preserve_images=false` only the text remains. Picture bytes are never stored or returned: the placeholder only marks the position.

## Response: JSON

`200 OK`, `Content-Type: application/json`:

```json
{
  "markdown": "# Quarterly report\n\n…",
  "format": "pdf",
  "usage": {
    "pages": 10,
    "ocr_pages": 3,
    "credits_charged": 16,
    "credits_remaining": 9984,
    "ocr_page_credits": 3
  },
  "metadata": {
    "byte_size": 524288,
    "duration_ms": 1842,
    "ocr_model": "auto",
    "ocr_models_used": ["openparser/paddleocr-vl-1.6"]
  },
  "warnings": []
}
```

| Field                      | Meaning                                                                                                         |
| -------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `usage.pages`              | Billed pages ([how pages are counted](https://md.tlelabs.com/docs/formats-and-pricing/#pages)).                 |
| `usage.ocr_pages`          | Pages charged at the OCR price (OCR succeeded on them).                                                         |
| `usage.credits_charged`    | `(pages − ocr_pages) × 1 + ocr_pages × ocr_page_credits`.                                                       |
| `usage.credits_remaining`  | Wallet balance after this charge.                                                                               |
| `usage.ocr_page_credits`   | Price of one OCR page with the chosen model.                                                                    |
| `metadata.ocr_model`       | The `ocr_model` you sent, `auto` when omitted.                                                                  |
| `metadata.ocr_models_used` | Models that returned at least one OCR result, in order of first use (shows `auto` fallbacks); `[]` without OCR. |
| `warnings`                 | Non-fatal problems, for example a picture whose OCR timed out.                                                  |

## Response: Markdown

With `Accept: text/markdown` the body is the Markdown itself (`Content-Type: text/markdown; charset=utf-8`) and the usage comes in headers:

```http
HTTP/1.1 200 OK
Content-Type: text/markdown; charset=utf-8
X-Pages: 10
X-OCR-Pages: 3
X-Credits-Charged: 16
X-Credits-Remaining: 9984
X-Format: pdf
X-Duration-Ms: 1842
X-OCR-Model: auto
X-OCR-Models-Used: openparser/paddleocr-vl-1.6
X-OCR-Page-Credits: 3
```

Warnings are only in the JSON response.

## Charging

Before converting, the most the file can cost is reserved from your available credits (`402 insufficient_credits` when there are not enough). After the conversion you are charged only for the pages actually converted and the rest of the reservation is released. Any error releases the whole reservation. See [Errors](https://md.tlelabs.com/docs/errors/) for the one exception (`503` with a `request_id`).

Limits per request — file size, pages, slides, sheets and pictures to OCR — are listed in [Formats, pages and pricing](https://md.tlelabs.com/docs/formats-and-pricing/#limits). Base URL: `https://api.md.tlelabs.com`; files up to 50 MB.
