# HTML to Markdown Converter for LLM and RAG (`ucchiii/html-to-markdown`) Actor

Convert web pages, raw HTML or dataset HTML to clean Markdown for docs, LLMs and RAG: main-content extraction, GFM tables, code languages, absolute links, front matter. Empty input = free demo. $1.00 per 1,000 pages. Failed pages are free.

- **URL**: https://apify.com/ucchiii/html-to-markdown.md
- **Developed by:** [Actor Factory](https://apify.com/ucchiii) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 page converteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## HTML to Markdown Converter for LLM and RAG

Turn web pages, raw HTML or a dataset of HTML into **clean Markdown**, one row per document. Built for people who feed
documents to an LLM, a RAG pipeline or a docs site and are tired of navigation menus, cookie banners, footers and
script junk in their text. No browser, no LLM, no API key.

**Try it free: leave input empty and press Start.** The demo converts two sample pages and is not charged (rows carry `_demo: true`).

| source | title | markdown (shortened) | wordCount | status |
|---|---|---|---|---|
| https://example.com/blog/hello-markdown | Hello Markdown (sample) | `# Hello Markdown` ... `\| Plan \| Price \|` | 24 | ok |
| https://example.com/docs/api | API reference (sample) | `# API reference` ... a `json` code block | 17 | ok |
| https://example.com/missing | | | 0 | error (`http_404`) |

### What you get

- **Main-content extraction**: nav, header, footer, aside, scripts, styles, forms and hidden elements are removed; if the page has `<main>` or `<article>`, only that is kept
- **Tables** become GitHub-flavored Markdown tables (or plain lines if you turn them off)
- **Code blocks keep their language** (` ```python `), read from `class="language-..."`, `lang-...`, `highlight-source-...` or `data-lang`
- **Absolute links and image URLs**, resolved against the page URL (`javascript:` links are removed)
- **Images**: dropped (default) or kept as `![alt](src)`
- **Front matter** with `title` and `url` (YAML) if you want it
- **Max characters** per document, cut at a paragraph boundary, with a `truncated` flag
- Three inputs, mixable in one run: URLs, raw HTML strings, or an Apify dataset (any field name)
- Works for English and Japanese; `wordCount` counts words, or characters for Japanese, Chinese and Korean text

### Price

**$1.00 per 1,000 documents** ($0.001 per document converted). 1,000 pages cost $1, 10,000 pages cost $10.
Only successful conversions are charged. Failed fetches (404, timeout, blocked, too large), empty pages and the demo are free.

### Input

```json
{
  "urls": ["https://example.com/docs/intro"],
  "htmlStrings": ["<main><h1>Hello</h1></main>"],
  "baseUrl": "https://example.com",
  "datasetId": "<dataset ID of a run that stored HTML>",
  "htmlField": "html",
  "mainContent": true,
  "keepTables": true,
  "absoluteLinks": true,
  "imageMode": "drop",
  "frontMatter": false,
  "maxChars": 0
}
```

Up to 10,000 inputs per run. Leave URLs, HTML and dataset empty for the free demo.

### Output (one row per input)

```json
{
  "source": "https://example.com/blog/hello-markdown",
  "url": "https://example.com/blog/hello-markdown",
  "title": "Hello Markdown (sample)",
  "markdown": "# Hello Markdown\n\nTurn **web pages** into clean Markdown ...",
  "wordCount": 24, "charCount": 312, "truncated": false,
  "status": "ok", "error": null
}
```

`status` is `ok`, `empty` (the page had no text) or `error`. `error` holds a short code such as `http_404`, `timeout`,
`dns_failure`, `page_too_large`, `blocked_private_address`, `unsupported_content_type: ...`, `invalid_url`, `empty_input`
or `missing_html_field: html`. If a page redirected, `finalUrl` is added. For datasets, `source` is the item's `url`
(or `dataset:<id>#<index>`). Counts of ok / empty / error are saved in the key-value store as `STATS`.

### Fetching rules

- Only `http://` and `https://` URLs. Redirects are followed (up to 10) and **every hop is checked**; localhost, private,
  link-local (cloud metadata) and other internal addresses are refused (`blocked_private_address`)
- Pages larger than **5 MB** are skipped (`page_too_large`); only HTML and text content types are converted
- Polite: at most 2 connections per host and at least 0.2 s between requests to the same host
- The Actor does not crawl, does not run JavaScript and does not log in. Pages that need JavaScript to show their content
  return what the server sent; for those, fetch the rendered HTML with a browser-based Actor and pass it in as a dataset

### Use cases

- Prepare documentation for an LLM or a vector database (pair it with a chunker)
- Archive articles as Markdown with front matter for a static site or knowledge base
- Clean HTML exported from a CMS or a scraper into readable text
- Cut every page to a fixed length for prompt budgets

### Notes

You are responsible for having the right to fetch and reuse the pages you convert, and for following each site's terms and
robots.txt. This Actor fetches only the URLs you list and adds no personal data.

### More tools from the same developer

- [RAG Chunker](https://apify.com/ucchiii/rag-chunker) — split scraped pages into chunks for RAG and vector stores
- [Sitemap URL Extractor](https://apify.com/ucchiii/sitemap-urls) — list every URL of a website from its sitemaps
- [PDF Extractor](https://apify.com/ucchiii/pdf-extractor) — PDF to text, tables and metadata from public PDF URLs, page by page
- [Bulk HTTP Request](https://apify.com/ucchiii/bulk-http-request) — send many HTTP requests (GET/POST/...) from a list and collect status, headers and body
- [Bulk URL Status Checker](https://apify.com/ucchiii/url-status-checker) — bulk HTTP status and redirect-chain checker for URL lists

# Actor input Schema

## `urls` (type: `array`):

Full page URLs starting with http:// or https://. Only these pages are fetched (no crawling); pages over 5 MB and internal or private addresses are refused. Up to 10,000 inputs per run in total. Leave URLs, HTML and dataset all empty for a free demo.

## `htmlStrings` (type: `array`):

Raw HTML documents to convert, one string per document (up to 5 MB each). Use this when you already have the HTML, for example from your own scraper.

## `baseUrl` (type: `string`):

Optional http(s) URL used to turn relative links and images in the raw HTML strings into absolute ones, and written to the front matter.

## `datasetId` (type: `string`):

Optional. Dataset ID (a finished run's 'defaultDatasetId', e.g. 'WkzbQMuFYuamGv3YF') whose items hold HTML in the field below. If an item has a 'url' it is used as the base URL for links.

## `htmlField` (type: `string`):

Name of the field in each dataset item that holds the HTML string.

## `mainContent` (type: `boolean`):

Remove nav, header, footer, aside and similar page chrome, and keep only the main or article element when the page has one. Scripts and styles are always removed.

## `keepTables` (type: `boolean`):

Convert HTML tables to GitHub-flavored Markdown tables. If off, each table row becomes a plain line of text.

## `absoluteLinks` (type: `boolean`):

Resolve relative links and image URLs against the page URL (or the base URL for raw HTML). javascript: links are always removed.

## `imageMode` (type: `string`):

Drop images (best for LLM text) or keep them as Markdown image links. Inline data: images are always dropped.

## `frontMatter` (type: `boolean`):

Start each document with a YAML front-matter block holding the title and the URL.

## `maxChars` (type: `integer`):

Cut each Markdown document at this length, at a paragraph boundary where possible (the row gets truncated: true). 0 means no limit.

## `timeoutSecs` (type: `integer`):

Timeout for each page request, in seconds. Allowed range 1 to 120 (default 20).

## `concurrency` (type: `integer`):

How many pages are fetched in parallel. Allowed range 1 to 10 (default 5). At most 2 connections per host are used at a time, with at least 0.2 s between requests to the same host.

## `userAgent` (type: `string`):

Optional custom User-Agent header for page requests. Default identifies this Actor.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "htmlField": "html",
  "mainContent": true,
  "keepTables": true,
  "absoluteLinks": true,
  "imageMode": "drop",
  "frontMatter": false,
  "maxChars": 0,
  "timeoutSecs": 20,
  "concurrency": 5
}
```

# Actor output Schema

## `results` (type: `string`):

One item per input: source, url, title, markdown, wordCount, charCount, truncated, status (ok, empty or error) and error.

## `stats` (type: `string`):

Record STATS: counts of converted, empty and failed inputs.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ucchiii/html-to-markdown").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": ["https://example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("ucchiii/html-to-markdown").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://example.com"
  ]
}' |
apify call ucchiii/html-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ucchiii/html-to-markdown"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/6WgVwF9FnzaXLrNOa/builds/zcnF2sEMrkRIRRc6E/openapi.json
