Skip to main content

API Scraping

Some platforms expose a JSON API that returns the article content directly — cleaner than scraping the rendered HTML and immune to bot detection. When scrape.api is configured, the scraper fetches each URL as JSON and extracts the HTML body, file path, title, and any extra frontmatter fields using dot-notation paths you define.

Playwright and playwright_config are ignored in API mode.

When to use

  • The site has a public or authenticated REST API (e.g. Zendesk Help Center, Confluence, Notion)
  • You want cleaner HTML than what the rendered page produces
  • The site blocks headless browsers and you can't get a working session

Setup

Add an api block inside scrape in config.json:

{
"scrape": {
"api": {
"content": "article.body",
"filepath": "article.html_url",
"title": "article.title",
"fm": ["article.created_at", "article.updated_at"],
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}

Then put API endpoint URLs in your urls.json (or urls_file):

[
"https://support.example.com/api/v2/help_center/en-us/articles/123.json",
"https://support.example.com/api/v2/help_center/en-us/articles/456.json"
]

Options

OptionTypeRequiredDescription
contentstringYesDot-notation path to the HTML body in the JSON response
filepathstringYesDot-notation path to the page URL — used to determine the output file path and permalink frontmatter
titlestringNoDot-notation path to the page title — used as frontmatter title
fmstring[]NoAdditional dot-notation paths — each becomes a frontmatter field, keyed by the last segment of the path
headersobjectNoHTTP headers sent with every request — use for API authentication tokens

Dot-notation paths

All field paths are dot-notation strings that resolve against the JSON response object. For example, given this response:

{
"article": {
"title": "Getting Started",
"html_url": "https://support.example.com/hc/en-us/articles/123-Getting-Started",
"body": "<p>Welcome...</p>",
"created_at": "2024-01-15T10:00:00Z"
}
}

The path "article.title" resolves to "Getting Started", "article.body" resolves to the HTML string, and so on. Deeply nested paths like "data.attributes.content" are also supported.

Frontmatter output

Given the config above, the output MDX frontmatter will look like:

---
title: "Getting Started"
created_at: "2024-01-15T10:00:00Z"
updated_at: "2024-06-01T08:30:00Z"
permalink: "https://support.example.com/hc/en-us/articles/123-Getting-Started"
---

The fm paths use the last segment of the dot path as the frontmatter key: "article.created_at"created_at.

File path mapping

The value at filepath is treated as a full URL and mapped to an output path the same way regular scraping works:

https://support.example.com/hc/en-us/articles/123-Getting-Started
→ output/hc/en-us/articles/123-Getting-Started.mdx

This means your API URLs and your page URLs can be completely different — the API endpoint is only used to fetch the data.

Authenticated APIs

Pass credentials via the headers field:

"api": {
"content": "article.body",
"filepath": "article.html_url",
"title": "article.title",
"headers": {
"Authorization": "Bearer eyJhbGci..."
}
}

For Zendesk, you can use Basic auth with an API token:

"headers": {
"Authorization": "Basic base64(email/token:TOKEN)"
}

Zendesk Help Center example

Zendesk exposes articles at:

https://{subdomain}.zendesk.com/api/v2/help_center/{locale}/articles/{id}.json

Config:

{
"scrape": {
"urls_file": "urls.json",
"output": "output",
"images": { "strategy": "download_by_page", "folder": "images" },
"html_preserve_elements": ["table", "iframe"],
"api": {
"content": "article.body",
"filepath": "article.html_url",
"title": "article.title",
"fm": ["article.created_at", "article.updated_at"]
}
}
}

The full HTML conversion pipeline still applies — tables are preserved and spaced correctly, inline styles are converted to React format, void elements are self-closed, and all component mappings run as configured.