SnoopScan
Web scraping API

Sniff out any page.
Get clean data back.

SnoopScan is a web scraping API for AI agents. One call turns any public page into markdown, links or JSON in your own schema — including the pages that block everything else.

1,500 credits a month on the free plan. No card needed. Or try it right here:

Try it on a competitor pricing page, a product listing, a docs page for your RAG index or a company about page — or one of these:

Tools

Every tool. One key.

One key, plain REST and JSON. Pick a tool and the call appears beside it, ready to paste.

import requestsr = requests.post(    'https://api.snoopscan.com/v1/scrape',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'url': 'https://example.com/pricing',        'formats': [            'markdown',            'links'        ],        'onlyMainContent': True    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/scrape', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "url": "https://example.com/pricing",      "formats": [          "markdown",          "links"      ],      "onlyMainContent": true  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/scrape \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "url": "https://example.com/pricing",    "formats": [        "markdown",        "links"    ],    "onlyMainContent": true}'
200markdown · links1 credit, 0 from cache
import requestsr = requests.post(    'https://api.snoopscan.com/v1/crawl',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'url': 'https://example.com',        'limit': 100,        'maxDepth': 3,        'includePaths': [            '/blog/*'        ]    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/crawl', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "url": "https://example.com",      "limit": 100,      "maxDepth": 3,      "includePaths": [          "/blog/*"      ]  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/crawl \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "url": "https://example.com",    "limit": 100,    "maxDepth": 3,    "includePaths": [        "/blog/*"    ]}'
job idpoll or webhookfrom 1 credit a page
import requestsr = requests.post(    'https://api.snoopscan.com/v1/map',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'url': 'https://example.com',        'search': 'pricing'    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/map', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "url": "https://example.com",      "search": "pricing"  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/map \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "url": "https://example.com",    "search": "pricing"}'
url listsitemaps · links1 credit
import requestsr = requests.post(    'https://api.snoopscan.com/v1/search',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'query': 'best headless browsers',        'limit': 5,        'scrapeOptions': {            'formats': [                'markdown'            ]        }    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/search', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "query": "best headless browsers",      "limit": 5,      "scrapeOptions": {          "formats": [              "markdown"          ]      }  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/search \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "query": "best headless browsers",    "limit": 5,    "scrapeOptions": {        "formats": [            "markdown"        ]    }}'
resultseach page scraped2 credits
import requestsr = requests.post(    'https://api.snoopscan.com/v1/extract',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'urls': [            'https://example.com/pricing'        ],        'schema': {            'type': 'object',            'properties': {                'plan': {                    'type': 'string'                },                'price': {                    'type': 'number'                }            }        },        'prompt': 'The cheapest paid plan'    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/extract', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "urls": [          "https://example.com/pricing"      ],      "schema": {          "type": "object",          "properties": {              "plan": {                  "type": "string"              },              "price": {                  "type": "number"              }          }      },      "prompt": "The cheapest paid plan"  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/extract \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "urls": [        "https://example.com/pricing"    ],    "schema": {        "type": "object",        "properties": {            "plan": {                "type": "string"            },            "price": {                "type": "number"            }        }    },    "prompt": "The cheapest paid plan"}'
jsonin your schemavalidated
import requestsr = requests.post(    'https://api.snoopscan.com/v1/batch/scrape',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'urls': [            'https://example.com/a',            'https://example.com/b'        ],        'maxConcurrency': 10,        'scrapeOptions': {            'formats': [                'markdown'            ]        }    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/batch/scrape', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "urls": [          "https://example.com/a",          "https://example.com/b"      ],      "maxConcurrency": 10,      "scrapeOptions": {          "formats": [              "markdown"          ]      }  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/batch/scrape \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "urls": [        "https://example.com/a",        "https://example.com/b"    ],    "maxConcurrency": 10,    "scrapeOptions": {        "formats": [            "markdown"        ]    }}'
job idone jobfrom 1 credit a page
import requestsr = requests.post(    'https://api.snoopscan.com/v1/products',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'url': 'https://store.example.com',        'limit': 500    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/products', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "url": "https://store.example.com",      "limit": 500  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/products \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "url": "https://store.example.com",    "limit": 500}'
catalogueup to 250 a page1 credit a page
import requestsr = requests.post(    'https://api.snoopscan.com/v1/posts',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'url': 'https://blog.example.com',        'limit': 200    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/posts', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "url": "https://blog.example.com",      "limit": 200  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/posts \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "url": "https://blog.example.com",    "limit": 200}'
poststhe site’s own api1 credit a page
import requestsr = requests.post(    'https://api.snoopscan.com/v1/monitor',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'name': 'Pricing',        'urls': [            'https://example.com/pricing'        ],        'intervalMinutes': 60,        'webhook': 'https://hooks.example.com/snoop'    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/monitor', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "name": "Pricing",      "urls": [          "https://example.com/pricing"      ],      "intervalMinutes": 60,      "webhook": "https://hooks.example.com/snoop"  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/monitor \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "name": "Pricing",    "urls": [        "https://example.com/pricing"    ],    "intervalMinutes": 60,    "webhook": "https://hooks.example.com/snoop"}'
monitor idwebhook on changebilled as the scrapes
import requestsr = requests.post(    'https://api.snoopscan.com/v1/places/search',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'query': 'coffee shops',        'location': 'Austin, TX',        'limit': 20,        'enrich': True    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/places/search', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "query": "coffee shops",      "location": "Austin, TX",      "limit": 20,      "enrich": true  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/places/search \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "query": "coffee shops",    "location": "Austin, TX",    "limit": 20,    "enrich": true}'
placeswebsite · phone · contacts5 credits a search
import requestsr = requests.post(    'https://api.snoopscan.com/v1/domain',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'domain': 'https://example.com',        'backlinkLimit': 100    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/domain', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "domain": "https://example.com",      "backlinkLimit": 100  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/domain \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "domain": "https://example.com",    "backlinkLimit": 100}'
registrationdns · backlinks1 credit
import requestsr = requests.post(    'https://api.snoopscan.com/v1/company',    headers={'Authorization': 'Bearer sk_YOUR_KEY'},    json={        'url': 'https://example.com',        'contacts': True    },)print(r.json())
const r = await fetch('https://api.snoopscan.com/v1/company', {  method: 'POST',  headers: { Authorization: 'Bearer sk_YOUR_KEY', 'Content-Type': 'application/json' },  body: JSON.stringify({      "url": "https://example.com",      "contacts": true  }),});console.log(await r.json());
curl -X POST https://api.snoopscan.com/v1/company \  -H 'Authorization: Bearer sk_YOUR_KEY' \  -H 'Content-Type: application/json' \  -d '{    "url": "https://example.com",    "contacts": true}'
companycontacts · socials3 credits

Same shape for every tool. A URL or a list, a few options, and back comes data plus what it cost.

How it works

One URL in. Structured data out.

The engine follows the trail a page leaves — its sitemap, its links, its own JSON where it publishes any — and hands back markdown, links or JSON in the shape you asked for. When a page fights back, it climbs the tiers until something reads it.

In POST /v1/scrape
{  "url": "https://example.com/pricing",  "formats": ["markdown", "links"]}
Out 200 · 1 credit
{  "success": true,  "data": {    "markdown": "# Pricing\n\nStarter — $12 a month…",    "links": ["/signup", "/docs", "/enterprise"],    "metadata": { "title": "Pricing", "statusCode": 200 },    "cost": { "credits": 1, "cached": false }  }}
What it costs

Most pages cost one credit. You pay for what worked.

An easy page costs 1 credit. One that needs a proxy costs 2, and one that needs a real browser costs 5. You are billed for what the page actually took, and never for an attempt that failed.

A plain request A little more effort More again Whatever the page needs
Unblocking

Blocked pages come back. Nothing to configure.

Bot walls, pages that only exist once JavaScript has run, sites that want a particular country: one call, the same shape every time. What each kind of page costs is on the pricing page, and in your activity log after every call.

Plain pages

Most of the web. Fast, and 1 credit.

Walled pages

Sites that turn bots away still answer. Pick the country the request should come from; 12 to choose from.

Script-built pages

Pages that draw themselves with JavaScript are read once they have. Interact clicks, types and scrolls first when you ask.

Repeat pages

Asked for it lately? Your own comes straight back, free. You set how fresh is fresh enough.

Blocked yourself?

Open the page in the free web unblocker: read it as markdown or browse it in a frame, from the country you pick. No account, no scripts, no cookies.

Open the web unblocker ›
More tools

The rest of the job, already done.

Parse

PDFs and documents come back as text and tables, priced by the page.

Extract

A JSON schema and up to 100 URLs in, rows out. Reads the page's own structured markup first.

Interact

Click, type, scroll and wait in a real browser before the page is read.

Monitor

Watch a page on a schedule and see what changed, run by run.

Batch

Thousands of URLs as one job, with a webhook when it finishes.

Free re-reads

Ask again for a page you already fetched and it costs nothing. You set the window per request.

Use cases

The work people buy this for.

Each one is a few calls in a row. The list on the card is the order they run in, and every step is an endpoint you can call today.

Pages funnelling into a robot reading an open book
AI and RAG

Crawl a whole site into markdown that chunks cleanly, and keep the index fresh as the source moves.

Crawl · Batch · Monitor
Crawl the whole site
Main content only, as markdown
A webhook when it finishes
Re-crawl on a schedule
A price tag beside a grid of products
E-commerce and pricing

Read a product page for price, stock and variants, on one store or ten thousand, and know the hour a figure moves.

Products · Monitor · Batch
Price, stock and variants
Shopify and WooCommerce direct
Watch a page on a schedule
A diff of what changed
A search results list with a rank line rising beside it
SEO and search

Search the web and read every result in full instead of a snippet, then map a competitor site end to end.

Search · Map · Scrape
Search, results already read
Every URL on a site
Titles, meta and headings
1 credit for most pages
A business card above a funnel of data points
Lead generation

Map a company site, keep the pages that matter, and pull people, roles and contact details into your columns.

Map · Extract · Batch
Map every URL on the site
Keep the team and contact pages
Names, titles and emails
Batch a thousand sites
Scattered site thumbnails funnelling into a single chart
Market and competitor research

Track what a rival publishes, prices and promises, and get told the moment any of it changes.

Monitor · Crawl · Extract
Watch the pages that matter
See what moved, run by run
Extract the facts to a schema
Unchanged pages come from cache
A house outline above a grid of property listing cards
Listings and travel

Property, flights, fares and stock: pages built by JavaScript, read the same way as any other.

Scrape · Batch · Extract
Rendered pages, not shells
Numbers out as numbers
Thousands of URLs at once
Past the wall when it appears
Your stack

Plug it into the tools you already use.

Every tool is also an MCP server: one URL and your key, and your agent can read the web from inside its editor. Anything that speaks plain HTTP gets the same API.

Claude Code Cursor VS Code GitHub Copilot Windsurf Cline Roo Code Kilo Code Continue Zed JetBrains Claude Code Cursor VS Code GitHub Copilot Windsurf Cline Roo Code Kilo Code Continue Zed JetBrains

And any other MCP client: a URL and a header is all it takes.

Plain REST and JSON

Python, Node, cURL, or anything that can make an HTTP request. Nothing to install.

Webhooks

Crawls and batches call you back when they finish, so nothing polls.

Team keys

Invite people from Team. Everyone shares the credits, and every request shows who made it.

Activity log

Every call you made, what came back and what it cost.

Pricing

Priced by the page. From $0.45 a thousand.

One allowance across every tool. A failed request is never billed, and re-reading your own cached page costs nothing.

FAQ

The questions people ask first.

The basics
What is SnoopScan?+

A web scraping API for AI agents. Give it a URL and it hands back the page as clean markdown, HTML, links or JSON in your own schema. It handles the fetching, the proxies, the browser and the parsing, so you never build or babysit scraping infrastructure.

Who is it for?+

Developers and teams whose agents, research pipelines or products need pages from the web: agent builders, deep research, lead enrichment, price and stock monitoring, RAG and search indexes.

Which sites can it reach?+

Most public pages, including ones that only render with JavaScript and ones that block plain requests. It does not log in as you or keep cookies. Pages behind a login are out of scope for the API too.

Is web scraping legal?+

Scraping public pages is lawful in most places; what you do with the data is governed by the site's terms and by privacy law where you are. Robots rules are a per-request setting you control, and you are responsible for how you use what you collect.

How it works
What does a credit buy?+

One page. A plain fetch is 1 credit, a page that needed a proxy is 2, a page that needed a real browser is 5, and re-reading a page you already fetched is free. A page someone else fetched first comes from the shared index for a single credit. The rate card on the pricing page lists every unit, and your activity log shows what each call actually cost.

Am I charged for failed requests?+

No. A request that fails carries no cost and never counts against your balance. You only pay for pages that come back.

What is the difference between scrape and crawl?+

Scrape reads the pages you name. Crawl discovers pages by following links from a start URL, with depth and path filters, and reads each one. Map lists a site's URLs without reading them, which is the cheap way to decide what to crawl.

What formats come back?+

Markdown, HTML, raw HTML, links, a summary, a screenshot, or JSON shaped to a schema you give it. Ask for several in one call. PDFs and Word documents come back as text through Parse.

Does it respect robots.txt?+

Robots handling is a per-request setting, as is how hard it paces a host. Both are yours to set; neither is a promise made on your behalf.

The API
Does it work with my stack?+

It is plain REST and JSON. Python, Node, cURL, or anything that can make an HTTP request. Nothing to install.

Can I extract data with a prompt?+

Yes. Extract takes a JSON schema and, optionally, a prompt, and answers per page with a confidence score. For fixed shapes at volume, a schema alone is more consistent than a prompt.

Do I need an AI key to use Extract?+

No. Extract reads a page's own structured markup and fills your schema from that. For fields the markup cannot answer you can add your own Anthropic or OpenAI key in your account; it runs on your key and costs no credits.

How do long jobs report back?+

Crawls and batches return a job id. Poll it, or give a webhook URL and the job calls you when it finishes.

Can my team share an account?+

Yes. Invite people from Team. Everyone shares the credits and keys, and every request shows who made it.

Billing
Is there a free plan?+

Yes. 1,500 credits a month, refreshed on the day you signed up, no card needed. It is a real plan, not a trial that expires.

Do unused credits roll over?+

No. Your free credits refresh monthly on the day you signed up, and your dashboard shows the date. Paid plans refresh on their billing date.

What happens when I run out?+

Requests return a clear 402 with the reason, and nothing is charged. Move up a plan from Billing and the credits land the same day.

Can I switch plans later?+

Yes, from Billing, up or down. The new allowance applies from the switch.

Where does my data go?+

Fetched pages are cached briefly, then dropped. Re-reading your own cached page is free. Nothing you scrape is used for anything else.

What will the web unblocker not do?+

Log in as you, run scripts or keep cookies. It reads and browses public pages only. Sites that need those will not work there, and the API has Interact for that.

Try it on a page you actually need.

Sign up, paste a URL into the Playground, and copy the code it gives you. The first 1,500 credits are on us.

Start for free