HTML Parser v1.0 1 credit / call
Parse an HTML document into structured content — metadata, Open Graph, headings, links, images and readable text.
- Base path
- /api/v1/html-parser
- Methods
- GET or POST — parameters are read identically from the query string or a JSON body
- Auth
- X-Api-Key header (never Authorization: Bearer)
- Price
- 1 credit per call · about $0.0010 at 1,000 credits/USD
- Free tier
- 50 credits, refilled every month — drawn down before your paid balance
- Test mode
- Supported — ksty_test_ keys call this API free of charge
- Rate limit
- 60 calls/minute per key by default, raisable per key
HTML Parser API
Turns a raw HTML document into clean, structured JSON. Give it the HTML directly with html, or hand it a url and it fetches the page for you, and it returns the pieces you normally have to write a scraper for — the title and meta description, Open Graph and Twitter card tags, the canonical URL and favicon, every heading, link and image, and the readable plain-text body with a word count and reading-time estimate.
Parsing runs locally against a real HTML parser, so malformed markup, unclosed tags and messy real-world pages are handled the same way a browser would tolerate them — you get structure back, not an error.
Two ways to call it
Supply the markup directly:
POST /api/v1/html-parser
X-Api-Key: ksty_test_...
Content-Type: application/json
{"html": "<html><head><title>Hello</title></head><body><h1>Hi</h1><p>World</p></body></html>"}
…or let the API fetch the page (relative links are resolved against the fetched URL automatically):
{"url": "https://example.com"}
{
"title": "Hello",
"lang": "",
"description": "",
"canonical": "",
"favicon": "",
"meta": {},
"opengraph": {},
"twitter": {},
"headings": { "h1": ["Hi"] },
"links": [],
"images": [],
"text": "Hi World",
"word_count": 2,
"reading_time_min": 1
}
Endpoints
The root endpoint returns everything. Three focused endpoints return only one section each, so you pay for and transfer just the part you need:
html-parser— the full parse (all sections above).html-parser.metadata— title, language, description, canonical, favicon, and the rawmeta/opengraph/twittermaps. Ideal for building link previews.html-parser.links— every<a href>as{url, text}, plus every<img>as{src, alt}, with relative URLs resolved to absolute.html-parser.text— the readable plain-text body only, withword_countandreading_time_min.
Parameters
html— the HTML document to parse. Either this orurlis required; if both are sent,htmlwins.url— anhttp(s)URL to fetch and parse. The fetch is a single GET with an 8-second timeout and a 3 MB size cap; URLs that resolve to private or reserved IP addresses are refused (SSRF protection).base_url— a base used to resolve relative links and images to absolute URLs. Defaults tourlwhen you fetch, or the document's own<base href>when present.
Link resolution
Relative links (/about, ../pricing, logo.png) are turned into absolute URLs whenever a base is available — from base_url, the fetched url, or a <base href> in the document, in that order. When no base can be determined the original relative value is returned unchanged.
Readable text
text is the visible body text with <script>, <style> and <noscript> content removed and whitespace collapsed to single spaces — the same text a reader would see, not the raw markup. word_count counts whitespace-separated tokens and reading_time_min is ceil(word_count / 200).
Limits
- No JavaScript. The parser reads the HTML as delivered; content injected by client-side scripts after load is not present. For script-rendered pages, fetch the rendered HTML yourself and pass it as
html. - Fetch is best-effort. A URL that times out, is too large, or resolves to a blocked address returns a
fetch_failederror and is not billed. - One document per call. There is no crawling or link-following; each call parses exactly the document you provide.
When to use it
- Building link previews / unfurls from a URL (title, description, OG image).
- Extracting the readable article text from a page for indexing or summarisation.
- Auditing a page's links and images, or its SEO metadata, without writing a parser per site.
Endpoints
Parse an HTML document into all structured sections
The root endpoint runs every extractor and merges the results, so one call gives you metadata, headings, links, images and readable text together. If you only need one of those, the dedicated endpoints below do the same work for the same price but return a much smaller payload — worth using when you are parsing at volume.
curl -X POST "https://ksty.ch/api/v1/html-parser" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<html><head><title>Hello</title></head><body><h1>Hi</h1><p>Hi World</p></body></html>"}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/html-parser');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "html": "<html><head><title>Hello</title></head><body><h1>Hi</h1><p>Hi World</p></body></html>"}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/html-parser', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"html": "<html><head><title>Hello</title></head><body><h1>Hi</h1><p>Hi World</p></body></html>"
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/html-parser',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"html": "<html><head><title>Hello</title></head><body><h1>Hi</h1><p>Hi World</p></body></html>"
},
)
data = response.json()
{
"title": "Hello",
"lang": "en",
"description": "",
"canonical": "",
"favicon": "",
"meta": [],
"opengraph": [],
"twitter": [],
"headings": {
"h1": [
"Hi"
]
},
"links": [],
"images": [],
"text": "Hi World",
"word_count": 2,
"reading_time_min": 1
}
Extract page metadata only — title, description, canonical, favicon, Open Graph and Twitter tags
curl -X POST "https://ksty.ch/api/v1/html-parser.metadata" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<html><head><title>Hello</title><meta property=\"og:image\" content=\"/card.png\"></head></html>"}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/html-parser.metadata');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "html": "<html><head><title>Hello</title><meta property=\"og:image\" content=\"/card.png\"></head></html>"}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/html-parser.metadata', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"html": "<html><head><title>Hello</title><meta property=\"og:image\" content=\"/card.png\"></head></html>"
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/html-parser.metadata',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"html": "<html><head><title>Hello</title><meta property=\"og:image\" content=\"/card.png\"></head></html>"
},
)
data = response.json()
{
"title": "Hello",
"lang": "",
"description": "",
"canonical": "",
"favicon": "",
"meta": [],
"opengraph": {
"image": "/card.png"
},
"twitter": []
}
Extract every link and image, with relative URLs resolved to absolute
curl -X POST "https://ksty.ch/api/v1/html-parser.links" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<a href=\"/about\">About</a><img src=\"/logo.png\" alt=\"Logo\">","base_url":"https://example.com"}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/html-parser.links');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "html": "<a href=\"/about\">About</a><img src=\"/logo.png\" alt=\"Logo\">", "base_url": "https://example.com"}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/html-parser.links', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"html": "<a href=\"/about\">About</a><img src=\"/logo.png\" alt=\"Logo\">",
"base_url": "https://example.com"
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/html-parser.links',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"html": "<a href=\"/about\">About</a><img src=\"/logo.png\" alt=\"Logo\">",
"base_url": "https://example.com"
},
)
data = response.json()
{
"links": [
{
"url": "https://example.com/about",
"text": "About"
}
],
"images": [
{
"src": "https://example.com/logo.png",
"alt": "Logo"
}
]
}
Extract the readable plain-text body with word count and reading time
curl -X POST "https://ksty.ch/api/v1/html-parser.text" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<body><script>ignored()</script><p>Hi World</p></body>"}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/html-parser.text');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{ "html": "<body><script>ignored()</script><p>Hi World</p></body>"}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/html-parser.text', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"html": "<body><script>ignored()</script><p>Hi World</p></body>"
})
});
const data = await response.json();
import requests
response = requests.post(
'https://ksty.ch/api/v1/html-parser.text',
headers={'X-Api-Key': 'YOUR_API_KEY'},
json={
"html": "<body><script>ignored()</script><p>Hi World</p></body>"
},
)
data = response.json()
{
"text": "Hi World",
"word_count": 2,
"reading_time_min": 1
}
Errors
Errors are JSON with a stable error.code — branch on the code, never on
the message. Everything below carries the X-Request-Id of the failed call.
Requests rejected by this API are not charged, and a 502 after a
charge is refunded automatically.
Response headers
Every successful call reports its own cost, so you never have to guess what a request spent or reconcile it later.