URL to Markdown API
Convert URLs to clean Markdown programmatically from any language. The API is the same Apify URL to Markdown Actor that powers the web tool - same content extraction, same boilerplate stripping.
When to use the API
- You want to automate it. Cron jobs, ingestion pipelines, RAG indexing.
- You're converting more than a handful of URLs. The web tool is rate-limited per browser; the API has a generous monthly quota.
- You need JavaScript rendering. Switch
scrapingTooltobrowser-playwrightfor JS-heavy pages, or keep the fast, cheapraw-httpdefault. - You want control over proxies, concurrency, or which elements get stripped.
Get an API token
- Sign up free at console.apify.com. The free plan includes $5 of usage every month - no credit card needed.
- Open Account → Integrations and copy your personal API token.
- Use it in any of the snippets below.
Quick start
JavaScript (apify-client)
Install the client: npm i apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_API_TOKEN });
const input = {
url: 'https://docs.apify.com/academy/scraping-basics-javascript',
scrapingTool: 'raw-http',
proxyConfiguration: { useApifyProxy: true },
};
const run = await client.actor('apify/url-to-markdown').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].markdown);
Python (apify-client)
Install with pip install apify-client
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_API_TOKEN"])
run_input = {
"url": "https://docs.apify.com/academy/scraping-basics-javascript",
"scrapingTool": "raw-http",
"proxyConfiguration": {"useApifyProxy": True},
}
run = client.actor("apify/url-to-markdown").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["markdown"])
curl (synchronous, single URL)
curl -X POST \
"https://api.apify.com/v2/acts/apify~url-to-markdown/run-sync-get-dataset-items?token=$APIFY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://docs.apify.com/academy/scraping-basics-javascript",
"scrapingTool": "raw-http",
"proxyConfiguration": { "useApifyProxy": true }
}'
fetch (async start + poll)
For long-running jobs, start the run and poll for status - exactly what this site does under the hood.
// 1. Start the run
const start = await fetch(
`https://api.apify.com/v2/acts/apify~url-to-markdown/runs?token=${TOKEN}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://example.com',
scrapingTool: 'raw-http',
}),
},
);
const { data: run } = await start.json();
// 2. Poll until SUCCEEDED
async function poll() {
const r = await fetch(
`https://api.apify.com/v2/actor-runs/${run.id}?token=${TOKEN}`
);
const { data } = await r.json();
if (data.status === 'SUCCEEDED') return data;
if (['FAILED', 'ABORTED', 'TIMED-OUT'].includes(data.status))
throw new Error(data.status);
await new Promise((res) => setTimeout(res, 3000));
return poll();
}
await poll();
// 3. Read the dataset
const ds = await fetch(
`https://api.apify.com/v2/datasets/${run.defaultDatasetId}/items?token=${TOKEN}&clean=true`
);
const items = await ds.json();
console.log(items[0].markdown);
Recommended input
The full input schema, with the defaults this site relies on:
{
"url": "https://example.com",
"scrapingTool": "raw-http",
"proxyConfiguration": { "useApifyProxy": true },
"removeElementsCssSelector": "nav, footer, script, style, noscript, svg, img[src^='data:'], [role=\"alert\"], [role=\"banner\"], [role=\"dialog\"], [role=\"alertdialog\"], [role=\"region\"][aria-label*=\"skip\" i], [aria-modal=\"true\"]",
"desiredConcurrency": 1,
"debugMode": false
}
Useful tweaks
scrapingTool:"raw-http"(default) is fastest and cheapest but can't run JavaScript;"browser-playwright"renders JS-heavy pages but costs more.removeElementsCssSelector: CSS selector for elements stripped before conversion. Defaults to removing nav, header, footer, modals, scripts, and inline images. Set it to a non-existent selector likedummy_keep_everythingto keep everything.desiredConcurrency: number of pages fetched in parallel.0lets the Actor pick automatically based on available memory.proxyConfiguration: use Apify Proxy for the target site.debugMode:truestores extra debugging info under adebugfield in the dataset.
This Actor converts one page per URL - it doesn't crawl or follow links. To convert a whole site, use the Website Content Crawler instead.
Output schema
Each dataset item looks like this:
{
"crawl": {
"httpStatusCode": 200,
"httpStatusMessage": "OK",
"loadedAt": "2026-06-11T09:00:12.010Z",
"uniqueKey": "I0mexdHttr",
"requestStatus": "handled"
},
"metadata": {
"title": "Article title",
"description": "Meta description",
"languageCode": "en",
"url": "https://example.com/article",
"redirectedUrl": "https://example.com/article"
},
"query": "https://example.com/article",
"markdown": "# Article title\n\nFirst paragraph..."
}
Pricing & limits
The URL to Markdown Actor is pay-per-result, from $1.50 per 1,000 converted pages. The Apify free plan includes $5 of platform usage every month (no credit card required), which covers well over a thousand pages to start. The exact rate depends on your plan and scraping mode:
| Apify plan | Raw HTTP (per 1,000) | Browser (per 1,000) |
|---|---|---|
| Free | $3 | $6 |
| Starter | $2 | $5 |
| Scale | $1.70 | $4 |
| Business | $1.50 | $3 |
See the Actor's pricing details for current rates.
Next steps
- Skip the SDK and use the MCP server directly from Claude.
- See a complete walkthrough in the how-to guide.
- See how this stacks up against other URL-to-Markdown converters.