curl -X POST https://seonio.com/api/v1/scraper \ -H "Content-Type: application/json" \ -H "X-API-Key: $SEONIO_API_KEY" \ -d '{"format": "md", "max_age_days": 7, "url": "https://moz.com/blog"}'
{
"url": "https://moz.com/blog",
"title": "The Moz Blog | SEO Insights, Tips & Best Practices",
"meta_description": "Read the latest SEO news, tips and research from the Moz team.",
"length": 18342,
"markdown": "# The Moz Blog\n\nThe latest SEO news, tips and research from the Moz team ...",
"html": null,
"scraped_at": "2026-07-05T12:00:00"
}What to send and what comes back
Send a supported URL and get available page content back as clean markdown (or raw HTML), with available title and meta description. Results are cached and served from storage while fresh, so repeat lookups are fast. Ideal for content research, briefs and feeding pages into LLM pipelines. Bot-protected sites are retried through an unblocking service and can take up to 90 seconds; the price is the same. You get whatever the site served, including its own error pages (404, 410, soft 404), charged like any page; max_age_days 0 refreshes a stored copy. A domain that does not exist (no DNS record), a page over 10 MB, a PDF over 50 MB, a link serving no page or document (an image, an archive), or a URL that is an IP address, a local host or contains credentials is a 422, not charged.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | required | A full URL, e.g. "https://moz.com/blog". |
max_age_days |
integer | optional | Cache freshness window in days (default 7). Cached pages younger than this are served from storage; older pages are re-scraped. Use 0 to force a fresh scrape. |
format |
string | optional | Output format: "md" (markdown, the default), "html" (raw HTML) or "both". Unknown values fall back to "md". |
Response
| Field | Type | Description |
|---|---|---|
url |
string | The URL that was scraped. |
title |
string | null | Page title, or null if the page has none. |
meta_description |
string | null | Meta description, or null if unavailable. |
length |
integer | Character length of the page's markdown content. A page that could not be scraped is a 503 (not charged). |
markdown |
string | null | The page content as markdown. Present for format "md" and "both"; null otherwise. |
html |
string | null | The page as HTML. Present for format "html" and "both"; null otherwise. |
scraped_at |
string | Timestamp of the stored scrape (an older copy is served when a fresh scrape fails). |
Copy, paste, add your key
The same request in cURL, Python and Node.js.
curl -X POST https://seonio.com/api/v1/scraper \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"format": "md", "max_age_days": 7, "url": "https://moz.com/blog"}'
import requests
resp = requests.post(
"https://seonio.com/api/v1/scraper",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"format": "md", "max_age_days": 7, "url": "https://moz.com/blog"},
)
print(resp.json())
const resp = await fetch("https://seonio.com/api/v1/scraper", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "YOUR_API_KEY",
},
body: JSON.stringify({"format": "md", "max_age_days": 7, "url": "https://moz.com/blog"}),
});
console.log(await resp.json());
Run a real request
Send this request from your browser with your API key, or leave the key empty to see the sample response.
A successful run costs $0.002 / request. With no key, you see the sample response and nothing is charged.
{
"html": null,
"length": 18342,
"markdown": "# The Moz Blog\n\nThe latest SEO news, tips and research from the Moz team ...",
"meta_description": "Read the latest SEO news, tips and research from the Moz team.",
"scraped_at": "2026-07-05T12:00:00",
"title": "The Moz Blog | SEO Insights, Tips \u0026 Best Practices",
"url": "https://moz.com/blog"
}
What developers build with it
- Feeding page content into LLM and analysis pipelines as markdown.
- Building content briefs from competitor pages.
- Extracting article text without running your own scraper.
The same contract on every endpoint
Error responses are JSON with a "detail" field. It is a message string unless the row above says otherwise.
| Code | When |
|---|---|
200 | Success. The lookup completed and was charged, even if the response is empty. /keywords/global charges only the keywords it looked up. |
401 | Missing or unknown API key. |
402 | Not enough balance. "detail" is an object with "error", "message", "balance_required" and "balance_available". Requests still running hold their price until they finish, so a retry can succeed once they do. |
403 | The account is not verified yet, or it is locked. |
422 | Invalid request (wrong shape or types, a keyword over 200 characters, a URL the page checker cannot audit, a domain that does not exist, or a bare public suffix such as co.uk that groups many websites) — never charged. "detail" is usually a list of validation errors; an answer that retrying cannot change (such as a domain without a DNS record) is a message string with an X-Outcome header naming it: missing, unsupported, too_large or refused_input. |
429 | Rate limit exceeded, never charged. Requests per second per account by plan: 5 on a free account, 10 on Starter, 15 on Pro and 20 on Business; retry after the Retry-After header. Also the page checker's provider limit. |
503 | A data provider failed or timed out (X-Outcome: temporary). Nothing was charged; retry after the seconds in the Retry-After header. |
Questions, answered
Simple answers for developers using the API.
Every endpoint has a published USD price or formula. Requests draw from the same testing, included and Extra Usage balance as website and MCP actions: testing first, then included usage, then Extra Usage expiring soonest.
API actions require a verified account with a free $1 balance or an active subscription balance. Create and manage API keys from your account, then send the key in the X-API-Key header.
Yes. Your account shows API usage and remaining balance, so you can track costs while building and running automated workflows.
REST API and MCP requests per second depend on your plan: 5 on a free account, 10 on Starter, 15 on Pro and 20 on Business. Website data requests from the same account count toward the same second. Requests above the limit return HTTP 429 with a Retry-After header and are not charged; wait a second and retry.
Turn search insights into measurable growth
Start your SEO research in seconds.