# Quote before converting

> POST /v1/quote prices several files at once without converting them, calling OCR, reserving credits or charging anything.

`POST /v1/quote` tells you what converting each file would cost. It is a dry run: nothing is converted, no OCR model is called, no credits are reserved or charged, and it does not count toward the [concurrent conversion limit](https://md.tlelabs.com/docs/rate-limits/). Authentication is the same as for `/v1/convert`.

```sh
curl -X POST https://api.md.tlelabs.com/v1/quote \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -F "file=@a.pdf" -F "file=@b.docx" \
  -F "ocr_model=openparser/mistral-ocr-4"
```

## Request

`Content-Type: multipart/form-data`:

| Field       | Required | Value                                                                                                                              |
| ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `file`      | yes      | 1 to 10 files (repeat the field), priced in the order sent.                                                                        |
| `ocr`       | no       | `auto` (default), `force` or `off`, as for [`/v1/convert`](https://md.tlelabs.com/docs/convert/#ocr-modes); applies to every file. |
| `ocr_model` | no       | `auto` (default) or a model id, as for `/v1/convert`; applies to every file.                                                       |

The whole request may be up to 50 MB (`413 file_too_large`). No file, more than 10 files (`details: {"field": "file", "max": 10}`) or an invalid `ocr` / `ocr_model` gets `400 invalid_request`.

## Response

`200 OK`. Example: `a.pdf` has 5 pages, 4 with a text layer and 1 scan; the second file is not a supported format.

```json
{
  "ocr": "auto",
  "ocr_model": "auto",
  "ocr_page_credits": 3,
  "files": [
    {
      "index": 0, "filename": "a.pdf", "size_bytes": 81234, "status": "ok", "format": "pdf",
      "pages": 5, "text_pages": 4, "ocr_pages": 1,
      "details": {
        "estimate": "exact_if_ocr_succeeds",
        "pdf_pages": { "blank": 0, "text": 4, "scan": 1, "text_render": 0, "render": 0 },
        "images_to_ocr": 1
      },
      "ocr_models": { "image": "novita/deepseek-ocr-2", "pdf": "openparser/paddleocr-vl-1.6" },
      "credits": { "estimated": 7, "max": 15 },
      "warnings": []
    },
    {
      "index": 1, "filename": "x.zip", "size_bytes": 120, "status": "error",
      "error": { "code": "unsupported_format", "message": "File format not recognized. Supported: HTML, DOCX, PDF, PPTX, XLSX." }
    }
  ],
  "total": { "files": 2, "ok": 1, "estimated_credits": 7, "max_credits": 15 }
}
```

| Field                              | Meaning                                                                                                                                                                               |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pages`, `text_pages`, `ocr_pages` | Billed pages ([how pages are counted](https://md.tlelabs.com/docs/formats-and-pricing/#pages)); `text_pages = pages − ocr_pages`.                                                     |
| `credits.estimated`                | `text_pages × 1 + ocr_pages × ocr_page_credits`: the price if every expected OCR succeeds.                                                                                            |
| `credits.max`                      | Exactly what `/v1/convert` would reserve for this file; you need this much available.                                                                                                 |
| `details.estimate`                 | `exact_if_ocr_succeeds`: `/v1/convert` charges `estimated` when every OCR succeeds (less when some fail). `upper_bound`: the file could not be analyzed in detail, `estimated = max`. |
| `details.pdf_pages`                | PDF: pages by kind — `blank`, `text`, `scan`, `text_render` (text layer plus a rendered read), `render`; `null` when not analyzed.                                                    |
| `details.images_to_ocr`            | Pictures expected to be sent to OCR.                                                                                                                                                  |
| `details.basis`                    | HTML: `"bytes"` — priced by file size, like the reservation.                                                                                                                          |
| `ocr_models`                       | The model tried first for pictures and for PDF pages; `null` when there is none, with `ocr=off`, and `pdf` for non-PDF files.                                                         |
| `total`                            | Number of files, files `ok`, and the sums of `estimated` and `max` over the `ok` files.                                                                                               |

## How each format is estimated

- **PDF up to 20 MB** (in a request up to 20 MB): pages are classified with the same logic as `/v1/convert`, so the estimate is exact when OCR succeeds.
- **PDF up to 20 MB in a request over 20 MB**, with OCR on: not analyzed (`upper_bound`, `estimated = max`, warning `page analysis skipped: request larger than 20 MB`); quote that file alone for a detailed price. With `ocr=off` it is still counted exactly.
- **PDF over 20 MB**: too large for OCR, so it is priced from its text layer (`ocr_pages` 0). With `ocr=auto` a page without text, and with `ocr=force` any file, gets `file_too_large`, like `/v1/convert`; with `ocr=off` it is priced from the text layer whatever it contains.
- **DOCX, PPTX, XLSX**: `ocr_pages` counts the chunks, slides or sheets with a picture to OCR (`estimated = max` when an image OCR model is available). Without one, `ocr_pages` is 0, `estimated` can be lower than `max` and a warning says pictures will not be read.
- **HTML**: `estimated = max` (`upper_bound`, `basis: "bytes"`).

## Errors per file

A file that cannot be priced gets `status: "error"` with the same code and message `/v1/convert` would return — `unsupported_format`, `file_too_large`, `page_limit_exceeded`, `image_limit_exceeded`, `parse_failed` — or `internal_error`. It does not fail the request (still `200`) and is not counted in `total`.
