# How to convert a PDF to Markdown with Python, Node.js, or curl

> Convert any text-layer PDF to clean Markdown with one HTTP request — no API key, no library install. Working examples in curl, Python, and Node.js.

Published: 2026-07-07 · Source: https://marklipi.com/blog/convert-pdf-to-markdown-python-node-curl


The fastest way to convert a PDF to Markdown programmatically is a single HTTP request: POST the file to `https://api.marklipi.com/convert` and you get JSON back with the Markdown inside. No API key, no signup, no library to install — the free tier allows 10 conversions a minute and 100 a day per IP.

This works for **text-layer PDFs**: bank statements, contracts, invoices, reports, papers — anything where you can select the text in a PDF viewer. Scanned or image-only PDFs need OCR instead ([here's how to tell the difference](/blog/pdf-text-layer-vs-ocr)).

## Convert a PDF to Markdown with curl

```bash
curl -s https://api.marklipi.com/convert -F "file=@statement.pdf"
```

Response:

```json
{
  "markdown": "# Account Statement\n\n| Date | Description | Amount |\n...",
  "filename": "statement.pdf",
  "characters": 18342
}
```

That's the whole integration. Save just the Markdown with `jq`:

```bash
curl -s https://api.marklipi.com/convert -F "file=@statement.pdf" | jq -r .markdown > statement.md
```

## Convert a PDF to Markdown in Python

No SDK needed — the standard `requests` library does it:

```python
import requests

with open("statement.pdf", "rb") as f:
    resp = requests.post(
        "https://api.marklipi.com/convert",
        files={"file": ("statement.pdf", f, "application/pdf")},
        timeout=60,
    )
resp.raise_for_status()
markdown = resp.json()["markdown"]

with open("statement.md", "w") as out:
    out.write(markdown)
```

Feeding the result to an LLM afterwards? Markdown typically costs 60–90% fewer tokens than pushing the raw PDF at a vision model — [the token math is here](/blog/markdown-vs-pdf-llm-token-costs).

## Convert a PDF to Markdown in Node.js

Modern Node (18+) has `fetch` and `FormData` built in:

```js
import { readFile } from "node:fs/promises";

const bytes = await readFile("statement.pdf");
const form = new FormData();
form.append("file", new Blob([bytes], { type: "application/pdf" }), "statement.pdf");

const resp = await fetch("https://api.marklipi.com/convert", {
  method: "POST",
  body: form,
});
if (!resp.ok) throw new Error(`convert failed: ${resp.status}`);

const { markdown } = await resp.json();
console.log(markdown);
```

## Handling errors

The API returns errors as JSON with a `detail` field:

| Status | Meaning | What to do |
| ------ | ------- | ---------- |
| `413` | File over 25 MB | Split the PDF or compress it |
| `422` | No text layer (scanned/image PDF) | Route to an OCR pipeline instead |
| `429` | Rate limit hit (10/min or 100/day per IP) | Back off and retry, or get a key with higher limits |

The `422` behavior is deliberate: marklipi extracts the existing text layer and never runs OCR, so a scan returns a clear error instead of hallucinated text.

## Why not just use a Python PDF library?

Libraries like pdfplumber, PyMuPDF, or Marker work well if you want local processing and are happy managing the dependency (and for Marker, a GPU). The trade-off is setup and maintenance versus one stateless HTTP call. Files sent to marklipi are processed in memory and discarded — nothing is stored. For a comparison of the options, see [the best PDF-to-Markdown tools compared](/best-pdf-to-markdown-tools).

## FAQ

### Do I need an API key?

No. Basic use is keyless — the free tier is 10 conversions/minute and 100/day per IP.

### What's the maximum file size?

25 MB per PDF.

### Does it keep tables?

Yes — tables in the text layer come out as Markdown tables, which is most of the point for statements and financial documents.

### Can I run this from a browser instead?

Yes — the [free in-browser converter](/pdf-to-markdown) uses the same API with a drag-and-drop UI.

