Hybrid keyword + semantic · 200ms cached · 114 languages

Search API for
AI agents & apps

Search the web, read a page in every format at once, map a site, crawl it and get back only what changed. Built for agents, not browsers.

$
/api/v1/extract

Paste a URL and press Run

# Get a free API key (one command)
curl -X POST https://searchx.dev/api/v1/signup \
  -d '{"email":"you@example.com"}'
# → {"api_key": "sk-sx-...", "daily_limit": 3000}

# Search the web
curl https://searchx.dev/api/v1/search \
  -H "Authorization: Bearer sk-sx-YOUR_KEY" \
  -G -d "q=kubernetes deployment" -d "mode=hybrid"

# Read a page in four shapes, one fetch
curl https://searchx.dev/api/v1/extract \
  -G -d "url=https://react.dev" \
     -d "formats=markdown,links,html,screenshot"
# → every key you asked for. One that could not be produced
#   comes back null with the reason in "warnings".

# Re-crawl a site, get back only what changed
curl -X POST https://searchx.dev/api/v1/crawl \
  -H "Authorization: Bearer sk-sx-YOUR_KEY" \
  -d '{"url":"https://docs.example.com","track_changes":true,"only_changed":true}'

The parts other scraping APIs leave out

Most of an agent's time on the web is spent re-reading pages that did not change and reconciling a response against the request that asked for it. These exist so it does not have to.

#Crawl diff mode

Re-crawl a site and get back only what moved. Every page carries new, same, changed or removed with a git-style diff; the job carries a tally of all four. The tally counts every page the crawl saw, so an empty result is distinguishable from a crawl that fetched nothing.

docs →

#Near-duplicate collapsing

One flag folds pages that repeat something already in the results into the page they repeat — the same article under three paths, the same list paginated. You get back what was collapsed and into what, so a deduplicated crawl is never mistaken for a short one.

docs →

#A format is never dropped silently

Ask for ten shapes of a page and you get ten keys. One that could not be produced stays in the response as null with the reason beside it in warnings. You never have to diff your request against your response to find out what went missing.

docs →

#It tells you what it actually did

Every extraction reports which engine ran, whether a browser was spent, which egress tier actually applied and where the request left from. Ask for a proxy tier we cannot serve and you get a warning saying the request went out directly — not a pretence that it didn't.

docs →

#Watch one field, not one page

Hand change tracking a JSON Schema and it diffs the values rather than the prose: changed_fields comes back as dotted paths with the before and after. An unchanged page is never re-extracted, so watching a price costs a fetch, not a model call.

docs →

#Map a site without crawling it

Two sources merged in parallel — the pages we have already crawled, which arrive with titles and descriptions, and the site's own sitemaps followed through indexes and .gz files. Bounded by a timeout, so a slow sitemap never holds up the answer.

docs →
1B+
Indexed pages
200ms
Cached latency
114
Languages
$0
3K queries/day free

Why switch to SearchX

SearchXBing APIFirecrawlSerpAPI
Free tier3K/day (90K/mo)1K total500/mo100/mo
Search + ExtractBoth includedSearch onlyExtract onlySearch only
JS renderingIncludedNo$19+No
Semantic searchIncludedNoNoNo
AI answerBuilt-inNoNoNo
Image searchIncluded$5 add-onNo$50/mo
MCP protocolNativeNoYesNo

Clean JSON that admits what it could not do

Every requested format is a key. A key that could not be filled stays, set to null, with the reason beside it — and the request is still a success.

{
  "url": "https://example.com",
  "title": "Example Domain",
  "markdown": "# Example Domain\n\nThis domain is for use in...",
  "links": ["https://iana.org/domains/example"],
  "summary": null,
  "fetch": {
    "engine": "static",
    "browser_used": false,
    "proxy_tier": "direct"
  },
  "warnings": ["summary: the language model is unavailable right now"]
}