HTML to MD API
POST /v2/convert/html/to/md turns an HTML page into Markdown text. The 3,041-byte sample came back as 1,854 bytes of Markdown with a pipe table, * bullets, **bold**, *italic* and inline links, and without its <head>, script or HTML comment.
- Endpoint
- POST /v2/
convert/ html/ to/ md - Accepts
- html
- Sample timing
- under 1 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/html/to/md \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@seed-swap.html \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/html/to/md",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("seed-swap.html", "rb"))],
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "seed-swap.html";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/html/to/md";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0,
"Files": [
{
"FileName": "seed-swap.md",
"FileExt": "md",
"FileSize": 1854,
"FileData": "IyBTcHJpbmcgc2VlZCBzd2Fw…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.
seed-swap.md, first 30 of 32 lines
# Spring seed swap 2027The **Maple Row Seed Library** is a fictional lending library for garden seeds. Bring seeds you saved, take seeds you want, and *return a packet* after harvest. Read the [borrowing rules](https://seeds.example/rules) before you come.Doors open at 10:00## How the swap works1. Check in at the front table and get a tally card.2. Label each packet with the variety and the year it was saved.3. Take up to **five packets** per household.## Packets on hand| Variety | Crop | Packets | Germination | Last tested || --------------- | --------- | ------- | ----------- | ----------- || Cherokee Purple | Tomato | 42 | 91% | 2027-01-14 || Dragon Tongue | Bush bean | 18 | 86% | 2027-01-21 || Lemon Queen | Sunflower | 27 | 78% | 2026-12-03 || Red Russian | Kale | 9 | 94% | 2027-02-02 |## What to bring* Seeds in paper envelopes * no plastic bags, they trap moisture* A pen for the tally card> Hybrid (F1) seeds are welcome, but mark them, because they don't grow true to type.
- Input
- seed-swap.html (3.0 KB)
- Output
- seed-swap.md (1.8 KB)
- Source
- A one-page web page for a fictional community seed library, written for this site: headings, a table, two lists, links, a quote, a style sheet, a script and an inlined picture. (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
FileNamestring | Base name of the .md file; the sample would be seed-swap.md without it. |
StoreFileboolean, default false | true swaps the inline text for a Files[].Url link that works for 3 hours. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
GithubFlavored | Only false is accepted, although tables already come out as GitHub-style pipe tables. |
Images | Only unchanged passes: images stay as , data: URLs included. Other values return 400. |
ListBulletChar | - is accepted, yet bullets are written with *. Any other character returns 400. |
RemoveComments | false passes and true returns 400; HTML comments are dropped either way. |
UnsupportedTags | Only PassThrough is accepted. |
PassThroughTags | Any value returns 400 "PassThroughTags is not supported yet for HTML → MD". |
What carries over, and what doesn't
Kept
- Headings as ATX
#and##lines - Tables that have header cells, as pipe tables with padded columns (the sample's 5 × 5 table)
- Ordered lists as
1., bullets as*, the nested bullet indented by four spaces **bold**,*italic*, inlinecode, and<pre><code class="language-python">as a fenced block taggedpython- Links inline,
mailto:included;<br>as a two-space hard line break;<hr>as* * * - Pictures as
: the sample's data: URL stays whole, a 663-character line - Characters Markdown would read as markup are escaped:
*stars*came out as\*stars\*
Lost or not yet supported
- The whole
<head>: the title and meta description are not in the output. - Tables with no
<th>cell. A test table of plain<td>cells came back as one paragraph per cell. - Underline, strikethrough, superscript, subscript and
<mark>: the words stay, the formatting goes. - A
<pre>without<code>inside is not fenced: its lines come out as plain text, which a Markdown viewer joins into one paragraph, indentation lost. - Scripts,
<style>and comments, all dropped; CSS has no effect on the result. - Definition lists and
<details>: terms, definitions and summaries become plain paragraphs. - A final line break: the sample's .md ends right after the last character.
Limits
- One HTML file per request, in UTF-8. Other encodings return 422 "the input file could not be read: the file is not UTF-8 text"; a UTF-8 byte order mark is fine.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 120 seconds per conversion, then 504.
Where teams use it
Web pages as input for language models and search
Markdown keeps headings, lists and tables without the markup around them: the sample shrank from 3,041 to 1,854 bytes, a third of it the base64 picture. Convert crawled or exported pages before chunking them for embeddings or a prompt, and strip <img> tags first if you don't want that data in the text.
Moving site content into a docs repository
Exported HTML from a CMS or a help center can be turned into .md files for a static site generator, with code samples kept as fenced blocks when they are written as <pre><code class="language-…">.
Storing editor output as Markdown
Rich text from a browser editor arrives as HTML. Convert it to Markdown when your database, diff view or review flow works better with plain text.
Questions
Why did my HTML table turn into separate lines?
Pipe tables are written only for tables that have header cells. A table with <th> cells, in a <thead> or not, became a pipe table; one made only of <td> cells came out as one paragraph per cell. Make the first row's cells <th> before converting.
Can I drop images or use `-` for bullets, as ConvertAPI's options allow?
Not yet. Images, ListBulletChar and the other Markdown options are refused with 400 unless set to ConvertAPI's defaults, and bullets are always *. Remove <img> tags from the HTML if you don't want pictures in the text.
What happens to images embedded as data: URLs?
They are kept as they are, so the base64 data lands in the Markdown. In the sample, the picture's line is 663 characters long, about a third of the 1,854-byte output.
Does the converter run scripts or load the page from a URL?
No. It reads the uploaded file only. The sample's script would have replaced "Doors open at 10:00"; the Markdown keeps the original text.
Convert your first HTML file in a minute.