HTML to Markdown
Feed in raw HTML — a scraped article, an old CMS export, an email newsletter — and get back clean Markdown that keeps the structure and drops the clutter: no inline styles, no tracking scripts, no fifteen layers of nested divs left over from a page builder. It's the reverse trip of turning formatting-heavy markup back into something a human, or a language model, can actually read.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
HTML in the wild is rarely clean
Real-world HTML — from a scraped webpage, a decade-old CMS, an exported email template — is full of noise that has nothing to do with the actual content: tracking pixels, inline style attributes, empty spans left by a WYSIWYG editor, nested tables used for layout instead of data. Converting that directly into Markdown without a cleanup pass just produces Markdown that's equally noisy, full of stray backslash-escaped characters and broken formatting, which defeats the entire point of moving to a lighter format.
What the endpoint strips and what it keeps
POST /data/html-to-markdown parses the HTML, discards script and style tags, tracking attributes, comments and presentational markup that has no Markdown equivalent, and maps the rest to clean syntax: headings map to hash marks at the correct level, tables become pipe-delimited Markdown tables, ordered and unordered lists nest correctly, and links and images keep their href and alt attributes intact rather than getting flattened into plain text.
Why this direction is harder than it looks
Converting Markdown to HTML is close to deterministic — there's one obvious HTML shape for a given piece of Markdown. Going the other way is not: HTML can express the same visual result in a dozen different tag structures, and deciding which nested div-and-span soup actually represents a heading, a blockquote or a plain paragraph requires real structural analysis, not a simple tag-for-syntax swap. That's the core problem this endpoint solves, so you don't have to hand-tune a scraper's output every time a source site changes its markup.
What to expect in the result
The output Markdown is CommonMark-compatible, so it opens correctly in any editor, static site generator or Markdown renderer without further adjustment, and content that has no clean Markdown equivalent — a complex multi-column layout, a video embed — is either simplified to its closest readable form or preserved as a raw HTML block, depending on what the element is, so you never lose the underlying information silently.
Common places this fits in a pipeline
Pair it with our Markdown-to-HTML endpoint to normalize content moving between two CMSs, or run it ahead of a text-generation or summarization step so a language model receives clean prose instead of chasing meaning through nested markup, which noticeably improves how well downstream text processing performs on scraped or migrated content.
What you can do with it
Content migration between CMSs
Convert an old platform's HTML export into Markdown so it imports cleanly into a modern, Markdown-native CMS or static site generator.
Web scraping and archiving
Turn scraped article HTML into readable Markdown for a research archive or a read-it-later tool, stripping ads and navigation chrome in the process.
Feeding content to language models
Clean up HTML before passing it to a summarization or extraction task, since Markdown carries structure with far less token overhead than raw tags.
Email newsletter archiving
Convert HTML newsletter templates into Markdown for a searchable, version-controlled archive instead of storing bulky raw HTML.
FAQ
How do I convert HTML to Markdown with the API?
POST your HTML to /data/html-to-markdown; you get a task_id and the cleaned Markdown arrives via webhook or a signed link.
Does it remove scripts and tracking code?
Yes, script tags, style blocks, comments and tracking attributes are stripped as part of the conversion, leaving only content-relevant markup.
What happens to elements with no Markdown equivalent, like embedded videos?
They're either simplified to the closest readable representation or kept as a raw HTML block, so the underlying information isn't silently dropped.
Is there a free tier for the HTML-to-Markdown converter?
The tool above runs free in your browser. The API is paid — each call draws from your prepaid ForHosting KIT balance: top up from $10.00 (it never expires), pay each request's published price, and a call with no balance returns HTTP 402. No subscription, no tokens, and a failed task is never charged.
How much does each conversion cost?
$0.002 per request regardless of document size, and a failed conversion after retries is never billed.
Does it handle tables correctly?
Yes, HTML tables convert into pipe-delimited Markdown tables, preserving rows, columns and header cells.
Is the resulting Markdown standard CommonMark?
Yes, the output is CommonMark-compatible and opens correctly in any standard Markdown editor or renderer without adjustment.
Can I convert a large batch of HTML pages at once?
Send each page as its own request; since every call is async, you can run many conversions in parallel and collect results as each webhook arrives.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/data/html-to-markdown \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/data/html-to-markdown", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/data/html-to-markdown",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/data/html-to-markdown", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/data/html-to-markdown", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "data.html_to_markdown",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |