API Platform
On-Page API
Check one page the way Google and a browser see it — Lighthouse scores and Core Web Vitals, an on-page SEO audit, or its main content as text and Markdown — at a flat price per request.
Check a single page. One endpoint serves three reports, selected with the type parameter: a full Lighthouse run (performance, accessibility, best practices and SEO scores, Core Web Vitals and every failed check), a fast on-page SEO audit (title, description, headings, content, links, size, load time and the problems found), and the page's main content as structured sections and Markdown.
Requests longer than 60 seconds
This endpoint always runs requests as tasks. A request that runs longer than 60 seconds — a Lighthouse run on a slow page can — returns 202 with a task_id; collect the result from GET /v1/tasks/{task_id}. Every response, including a normal 200, carries the task id in the X-Task-Id header. Use ?wait= to change how long the request waits, and an Idempotency-Key header to retry safely.
Authentication
| Header | Type | Description |
|---|---|---|
SEMUST-API-USER | string | Your API user's username |
SEMUST-API-PASSWORD | string | Your API user's password |
Accept-Language | string | Optional. Send tr to receive error messages in Turkish |
Create API credentials from the API Access page.
SEMUST-API-USER: your_username
SEMUST-API-PASSWORD: your_password
Content-Type: application/jsonReport Types
type | Returns | Typical time | Price |
|---|---|---|---|
lighthouse (default) | Lighthouse category scores, Core Web Vitals and the failed checks, mobile or desktop | 10–30 s | $0.0055 |
audit | On-page SEO audit: title, description, headings, word count, links, size, load time and the problems found | ~1 s (with JavaScript ~3 s) | $0.000165 (with JavaScript $0.00165) |
content | The page's main content as sections (heading + text), contact details found, and optionally the whole page as Markdown | ~1 s | $0.000165 |
The price is per request and does not depend on the device, the categories or Markdown. A page that cannot be checked — blocked, missing or unreachable — returns an error and is not charged (see Errors).
{
"type": "lighthouse",
"url": "https://www.python.org/",
"device": "mobile"
}{
"type": "audit",
"url": "https://www.python.org/",
"javascript": true
}{
"type": "content",
"url": "https://www.python.org/",
"markdown": true
}Request Parameters
A parameter that does not apply to the chosen type is rejected rather than ignored, and a misspelled one is named in the error with a suggestion. A JSON null is treated as "not sent".
Body
typestringdefault: lighthouselighthouse, audit or content.
urlstringRequired. The page to check: an http or https address on a domain name, up to 2048 characters. Without a scheme, https:// is assumed. localhost, IP addresses and other schemes are rejected (INVALID_URL). Redirects are followed and the final address is returned.
devicestringdefault: mobilelighthouse only. mobile (a mid-range phone on a throttled connection, as PageSpeed Insights tests) or desktop.
categoriesstring[]lighthouse only. Any of performance, accessibility, best_practices, seo; all four by default. The price is the same; asking for fewer only shortens the response. metrics is returned only with performance.
languagestringdefault: enlighthouse only. The language of audit titles, descriptions and display values, e.g. tr. GET /v1/on-page/languages lists the supported languages.
javascriptbooleandefault: falseaudit only. Render the page in a browser with JavaScript before auditing it, for pages that build their content client-side. Ten times the price.
markdownbooleandefault: falsecontent only. Also return the whole page as Markdown in data.markdown. Same price.
idempotency_keystringOptional, up to 128 characters. Repeating a request with the same key returns the same task — its 202 while it runs, its stored result once it finishes — and it is charged once. The key is reserved until 72 hours after the task finishes. Same as the Idempotency-Key header; if you send both, this field wins.
Query
waitintegerdefault: 60Seconds to wait for the result, 0–80. If the request is still running after that, the response is 202 with a task_id. 0 answers 202 at once.
Headers
Idempotency-KeystringOptional. Works like idempotency_key above; if you send both, the body field wins.
GET /v1/on-page/languages lists the languages Lighthouse can write its results in. It is free.
A request still running after 60 seconds returns 202. The call() helper on Tasks & Long-Running Requests polls until the result is ready.
curl -X POST https://data.semust.com/v1/on-page \
-H "SEMUST-API-USER: your_username" \
-H "SEMUST-API-PASSWORD: your_password" \
-H "Content-Type: application/json" \
-d '{"type": "lighthouse", "url": "https://www.python.org/", "device": "mobile"}'import requests
response = requests.post(
"https://data.semust.com/v1/on-page",
headers={
"SEMUST-API-USER": "your_username",
"SEMUST-API-PASSWORD": "your_password",
},
json={"type": "lighthouse", "url": "https://www.python.org/"},
timeout=90, # longer than ?wait= (60 seconds by default)
)
data = response.json()
if response.status_code == 202:
# Still running. Collect it later from data["poll_url"],
# or use the call() helper from the Tasks page.
raise SystemExit(f"Task {data['task_id']} is still running")
if response.status_code != 200:
# PAGE_BLOCKED / PAGE_UNAVAILABLE: the page could not be checked (not charged)
raise SystemExit(f"{data['code']}: {data['error']} {data.get('hint', '')}")
print(data["data"]["scores"])
for audit in data["data"]["failed_audits"]:
print(audit["category"], audit["title"], audit["display_value"])Response
Every report returns the request as it ran — type, the normalized url and the options that apply to the type — and the result in one data object.
lighthouse scores are 0–100, as PageSpeed Insights shows them; a category you did not ask for is null. metrics are lab measurements from this run. failed_audits lists every scored check that did not pass, grouped by category in report order and, within a category, the biggest estimated saving first. Titles and descriptions are in language. Lab results vary a little from run to run, as in any Lighthouse tool.
audit returns what the page itself says about its SEO, and issues names the problems found (see the list below); an empty list means none.
content returns the page's main content in sections and everything outside it — navigation, sidebars, related blocks — in secondary_sections.
The examples below are real responses (the Lighthouse one was run with "language": "tr"; descriptions are shortened).
{
"success": true,
"type": "lighthouse",
"url": "https://www.python.org/",
"device": "desktop",
"categories": ["performance", "accessibility", "best_practices", "seo"],
"language": "tr",
"data": {
"final_url": "https://www.python.org/",
"lighthouse_version": "13.4.0",
"fetched_at": "2026-11-07T03:08:11Z",
"scores": { "performance": 94, "accessibility": 79, "best_practices": 100, "seo": 92 },
"metrics": {
"first_contentful_paint_ms": 641,
"largest_contentful_paint_ms": 1085,
"total_blocking_time_ms": 0,
"cumulative_layout_shift": 0.122,
"speed_index_ms": 641,
"time_to_interactive_ms": 1085
},
"failed_audits": [
{
"id": "render-blocking-insight",
"category": "performance",
"title": "Oluşturma engelleme istekleri",
"description": "İstekler sayfanın ilk oluşturma işlemini engelliyor…",
"score": 0,
"display_value": "Tahmini tasarruf: 370 ms.",
"savings_ms": 350,
"savings_bytes": null
},
{
"id": "unused-css-rules",
"category": "performance",
"title": "Kullanılmayan CSS'yi azaltın",
"description": "Ağ etkinliğinin kullandığı bayt sayısını azaltmak üzere…",
"score": 0,
"display_value": "Tahmini tasarruf: 125 KiB",
"savings_ms": 120,
"savings_bytes": 128464
},
{
"id": "color-contrast",
"category": "accessibility",
"title": "Arka plan ve ön plan renkleri yeterli kontrast oranına sahip değil.",
"description": "Birçok kullanıcı, düşük kontrastlı metni okumakta zorlanır…",
"score": 0,
"display_value": null,
"savings_ms": null,
"savings_bytes": null
}
],
"passed_count": 53,
"failed_count": 18,
"warnings": []
},
"cost": 0.0055,
"cached": false,
"request_id": "0b7e6f1a-3c2d-4e5f-9a8b-1c2d3e4f5a6b"
}{
"success": true,
"type": "audit",
"url": "https://www.python.org/",
"javascript": true,
"data": {
"final_url": "https://www.python.org/",
"status_code": 200,
"score": 98.17,
"title": "Welcome to Python.org",
"description": "The official home of the Python Programming Language",
"canonical": null,
"h1": ["Functions Defined", "All the Flow You’d Expect", "Compound Data Types"],
"headings": { "h1": 5, "h2": 9, "h3": 0, "h4": 0, "h5": 0, "h6": 0 },
"word_count": 537,
"readability": 56.6,
"links": { "internal": 66, "external": 67 },
"images": 1,
"scripts": 7,
"stylesheets": 0,
"size_bytes": 53822,
"transfer_bytes": 11718,
"load_time_ms": 173,
"timing": {
"connect_ms": 20,
"waiting_ms": 11,
"download_ms": 1,
"dom_complete_ms": 172,
"time_to_interactive_ms": 156
},
"issues": ["title_too_short", "render_blocking_resources", "low_content_rate", "no_image_title"],
"fetched_at": "2026-11-07T03:07:49Z"
},
"cost": 0.00165,
"cached": false,
"request_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a"
}{
"success": true,
"type": "content",
"url": "https://www.python.org/",
"markdown": true,
"data": {
"title": "Functions Defined",
"language": "en",
"sections": [
{
"level": 1,
"heading": "Functions Defined",
"text": "The core of extensible programming is defining functions.\nPython allows mandatory and optional arguments, keyword arguments,\nand even arbitrary argument lists. More about defining functions"
}
],
"secondary_sections": [
{ "level": 2, "heading": "Latest News", "text": "More news" }
],
"word_count": 286,
"contacts": { "emails": [], "phones": [] },
"markdown": "# Functions Defined\n## >>> Python Software Foundation\n\n…"
},
"cost": 0.000165,
"cached": false,
"request_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d"
}Response Fields Reference
Envelope
| Field | Type | Description |
|---|---|---|
url | string | The address as checked: scheme and host lowercased, the host in ASCII (punycode) form, no #fragment |
device, categories, language | string, string[], string | lighthouse only. The options the run used, defaults filled in |
javascript | boolean | audit only. Whether the page was rendered with JavaScript; decides the price |
markdown | boolean | content only. Whether Markdown was requested |
lighthouse → data
| Field | Type | Description |
|---|---|---|
final_url | string | Where the page ended up after redirects |
lighthouse_version, fetched_at | string, string | null | Lighthouse version, and when the page was loaded (ISO 8601, UTC) |
scores | object | performance, accessibility, best_practices, seo: 0–100, or null when not requested |
metrics | object | null | Lab values in milliseconds — first_contentful_paint_ms, largest_contentful_paint_ms, total_blocking_time_ms, speed_index_ms, time_to_interactive_ms — and the unitless cumulative_layout_shift. null unless performance was requested |
failed_audits | object[] | id (Lighthouse's audit id), category, title, description (Markdown, with a "learn more" link), score 0–100, display_value, savings_ms and savings_bytes (estimated savings, null when not applicable) |
passed_count, failed_count | integer | Scored checks that passed and failed in the requested categories |
warnings | string[] | Lighthouse's own notes about the run, e.g. a redirect or a page that loaded slowly |
audit → data
| Field | Type | Description |
|---|---|---|
final_url, status_code | string, integer | The address that answered after redirects, and its HTTP status |
score | number | null | On-page score, 0–100: how many of the checks the page passes, weighted |
title, description, canonical | string | null | From the page's <title>, meta description and canonical link |
h1, headings | string[], object | The H1 texts, and the number of headings at each level h1…h6 |
word_count, readability | integer | null, number | null | Words of visible text, and the Flesch reading-ease score (0–100, higher is easier; calibrated for English) |
links | object | internal and external link counts |
images, scripts, stylesheets | integer | null | Counts on the page |
size_bytes, transfer_bytes | integer | null | HTML size, and bytes transferred (compressed) |
load_time_ms, timing | integer | null, object | Total load time, and its parts: connect_ms, waiting_ms (time to first byte), download_ms, dom_complete_ms, time_to_interactive_ms |
issues | string[] | The problems found, from the list below |
fetched_at | string | null | When the page was fetched (ISO 8601, UTC) |
issues values: no_https, https_to_http_links, no_title, title_too_long, title_too_short, multiple_title_tags, irrelevant_title, no_description, irrelevant_description, irrelevant_meta_keywords, no_h1_tag, duplicate_meta_tags, not_canonical (the canonical link points to another page), seo_unfriendly_url, no_doctype, no_encoding_meta_tag, charset_mismatch, meta_refresh_redirect, render_blocking_resources, high_loading_time, high_waiting_time, no_content_encoding, page_over_3mb, large_page_size, small_page_size, low_content_rate, high_content_rate, low_character_count, high_character_count, low_readability_rate, no_image_alt, no_image_title, no_favicon, structured_data_errors, deprecated_html_tags, uses_flash, uses_frames, lorem_ipsum, broken_links, broken_resources.
content → data
| Field | Type | Description |
|---|---|---|
title | string | null | The page's main heading as the content reads |
language | string | null | The content's language, e.g. tr |
sections, secondary_sections | object[] | level (heading level), heading and text (paragraphs separated by newlines). sections is the main content; secondary_sections is what surrounds it |
word_count | integer | Words in sections |
contacts | object | emails and phones found on the page |
markdown | string | null | The whole page as Markdown when "markdown": true, otherwise null |
Errors
Every error carries a stable code, a readable error, and retryable; where it helps also field, hint and allowed. Send Accept-Language: tr for Turkish messages. You are never charged for an error. The full list, shared with the other APIs, is on Error Codes. Specific to this endpoint:
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 202 | — | Not an error: still running after wait seconds | Poll GET /v1/tasks/{task_id}; see Tasks |
| 400 | URL_REQUIRED | No url | Send the page to check |
| 400 | INVALID_URL | Not a public http/https address: another scheme, localhost, an IP address, a name without a dot | Send a URL on a domain name |
| 400 | INVALID_PARAMETER | e.g. an unknown device, categories value or language; field names it and allowed lists the values | Fix the field |
| 422 | PAGE_BLOCKED | The site refused the request (HTTP 401, 403 or 429), usually bot protection such as Cloudflare. The status is in the message. Not charged | Allow automated checks for the page, or check another page |
| 422 | PAGE_UNAVAILABLE | The page could not be loaded: it answered with an error status (in the hint, e.g. HTTP 404), the host does not resolve or did not answer, or the page has no readable content. Not charged | Check the address; retryable is true when trying again can help |
Why a blocked page is an error and not a result: Lighthouse scores whatever page it receives. Behind bot protection that is the challenge page, and a "SEO 40" for it says nothing about your site. Sites that block headless browsers often still answer a plain fetch, so audit without javascript may work where lighthouse does not.
{
"error": "The site refused the request (HTTP 403)",
"code": "PAGE_BLOCKED",
"field": "url",
"hint": "Bot protection (such as Cloudflare) often blocks automated checks. Allow it for this check or try another page. You were not charged",
"retryable": false,
"request_id": "3f2b1c4d-0000-4000-8000-000000000001"
}{
"error": "The page could not be loaded",
"code": "PAGE_UNAVAILABLE",
"field": "url",
"hint": "The page answered with HTTP 404. You were not charged",
"retryable": false,
"request_id": "3f2b1c4d-0000-4000-8000-000000000002"
}Credits & Limits
| Request | Cost |
|---|---|
lighthouse | $0.0055 |
audit | $0.000165 |
audit with "javascript": true | $0.00165 |
content (with or without Markdown) | $0.000165 |
A page that cannot be checked (PAGE_BLOCKED, PAGE_UNAVAILABLE) is not charged.
Results are cached for 15 minutes, so that a page you have just fixed is checked again soon. A cached answer is marked "cached": true and billed at the same price as a live one.
Per-minute and per-day limits are set on your account (429 RATE_LIMIT_EXCEEDED). At most 5 requests per API user run at the same time; more get 429 TOO_MANY_CONCURRENT_REQUESTS with Retry-After. A request that has returned 202 keeps its place until its task finishes. At most 100 tasks per API user can be unfinished at once (429 TOO_MANY_PENDING_TASKS). Polling has its own limit and does not count against any of these; see Tasks.
