Skip to content
SEO API

SERP Scraper

Run a Google search and get the top results scraped to clean markdown in one call.

POST /api/v1/serp/scraper$0.001 / request + $0.002 / scraped URLAuth: X-API-Key header
seonio / rest apiFrom the API docs
RequestPOST /api/v1/serp/scraper
curl -X POST https://seonio.com/api/v1/serp/scraper \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $SEONIO_API_KEY" \
  -d '{"count": 2, "country": "us", "keyword": "seo tools"}'
Response200 OK
{
  "keyword": "seo tools",
  "country": "us",
  "results": [
    {
      "url": "https://moz.com/free-seo-tools",
      "title": "Free SEO Tools by Moz | Try Premium SEO Checkers for Free",
      "meta_description": "Try the best free SEO tools for link building and analysis, keyword research ...",
      "length": 18342,
      "markdown": "# Free SEO Tools\n\nMoz's free SEO tools help you with link building, keyword research ..."
    },
    {
      "url": "https://www.semrush.com/seo/",
      "title": null,
      "meta_description": null,
      "length": null,
      "markdown": null
    }
  ],
  "scraped_count": 1
}
Contract

What to send and what comes back

Combines a SERP lookup with a content scrape: send a keyword and country and get back the top organic results rendered as markdown, with title, meta description and content length per page. One call replaces a SERP request plus a scraper pipeline — ideal for content research, briefs and competitive analysis.

Request body

Request body
FieldTypeRequiredDescription
keyword string required The search query to look up.
country string optional Two-letter country code for the search locale, e.g. "us" (the default).
50 supported countries
dzAlgeriaarArgentinaauAustraliaatAustriabdBangladeshbeBelgiumbrBrazilbgBulgariacaCanadaczCzechiadkDenmarkegEgyptfiFinlandfrFrancedeGermanyghGhanagrGreecehuHungaryinIndiaidIndonesiaieIrelanditItalyjpJapankeKenyamyMalaysiamxMexicomaMorocconlNetherlandsnzNew ZealandngNigerianoNorwaypkPakistanphPhilippinesplPolandptPortugalroRomaniasaSaudi ArabiarsSerbiasgSingaporezaSouth AfricaesSpainlkSri LankaseSwedenchSwitzerlandthThailandtrTurkiyeaeUnited Arab EmiratesgbUnited Kingdom (also uk, returned as gb)usUnited StatesvnVietnam
count integer optional How many top organic results to scrape (1–10, default 1). If the SERP returns fewer organic URLs, the available maximum is scraped. Billed per URL actually scraped (a page was fetched, even if its body is empty), plus the SERP lookup. If none of the URLs could be scraped the call is a 503 and is not charged.

Response

Response fields
FieldTypeDescription
keyword string The keyword that was searched.
country string The country locale used for the search.
results[] object[] One entry per organic result attempted, top to bottom. Only `url` is guaranteed; the other fields are null when no page could be fetched, and such an entry is not billed. A fetched page with an empty body has `markdown: ""` and is billed.
scraped_count integer How many results were scraped (a page was fetched); the call costs $0.001 + $0.002 × scraped_count.
results[].url string The organic result URL that was scraped.
results[].title string | null Page title, or null if no content was scraped.
results[].meta_description string | null Meta description, or null if no content was scraped.
results[].length integer | null Length of the markdown body in characters, or null if no content was scraped.
results[].markdown string | null The page content rendered as markdown, or null if no content was scraped.
Code

Copy, paste, add your key

The same request in cURL, Python and Node.js.

cURL
curl -X POST https://seonio.com/api/v1/serp/scraper \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"count": 2, "country": "us", "keyword": "seo tools"}'
Python
import requests

resp = requests.post(
    "https://seonio.com/api/v1/serp/scraper",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"count": 2, "country": "us", "keyword": "seo tools"},
)
print(resp.json())
Node.js
const resp = await fetch("https://seonio.com/api/v1/serp/scraper", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "YOUR_API_KEY",
  },
  body: JSON.stringify({"count": 2, "country": "us", "keyword": "seo tools"}),
});
console.log(await resp.json());
Playground

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.001 / request + $0.002 / scraped URL. With no key, you see the sample response and nothing is charged.

POST /api/v1/serp/scraperSample response
{
  "country": "us",
  "keyword": "seo tools",
  "results": [
    {
      "length": 18342,
      "markdown": "# Free SEO Tools\n\nMoz\u0027s free SEO tools help you with link building, keyword research ...",
      "meta_description": "Try the best free SEO tools for link building and analysis, keyword research ...",
      "title": "Free SEO Tools by Moz | Try Premium SEO Checkers for Free",
      "url": "https://moz.com/free-seo-tools"
    },
    {
      "length": null,
      "markdown": null,
      "meta_description": null,
      "title": null,
      "url": "https://www.semrush.com/seo/"
    }
  ],
  "scraped_count": 1
}
Use cases

What developers build with it

  • Content briefs built from what already ranks for a keyword.
  • Feeding top-ranking pages into LLM and analysis pipelines as markdown.
  • Competitive content audits without running your own scraper.
Status codes

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.

Status codes
CodeWhen
200Success. The lookup completed and was charged, even if the response is empty. /keywords/global charges only the keywords it looked up.
401Missing or unknown API key.
402Not 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.
403The account is not verified yet, or it is locked.
422Invalid 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.
429Rate 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.
503A data provider failed or timed out (X-Outcome: temporary). Nothing was charged; retry after the seconds in the Retry-After header.
FAQ

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.

Free $1 to test · No card required

Turn search insights into measurable growth

Start your SEO research in seconds.

SEO researchREST APIMCP