ScrapeNest API

One bearer token. Multi-engine web search, RAG answers with citations, Google Maps reviews, audio transcripts. Clean JSON in, clean JSON out. Failures don’t bill.

Base URL. https://scrapenest.dev. Every /v1/* endpoint takes JSON and returns JSON. Responses include credits_charged and (where relevant) cache_hit so you can predict cost before you scale.
Reading this with an AI agent? Skip the HTML and point it at https://scrapenest.dev/llms.txt. It is the entire API (endpoints, schemas, credit prices, MCP setup) in one plain-text file built for LLMs. Paste that URL into ChatGPT, Claude, or your coding agent and it can write working ScrapeNest calls on the first try.

Quickstart

Sign up at /signup, mint a key on /dashboard/keys (shown once, so copy it), then make your first request:

curl -X POST https://scrapenest.dev/v1/search \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"openai gpt-5 release notes","num_results":5}'

That’s the full ergonomics: one Bearer header, one JSON body, one JSON response. The same shape works for every endpoint below.

Authentication

Every /v1/* endpoint uses a Bearer token in the Authorization header:

Authorization: Bearer sn_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys are minted at /dashboard/keys. The plaintext is shown once; we only store a salted hash and the displayable prefix. Lost the plaintext? Mint a new key and revoke the old one. Treat keys like passwords: never embed them in browser bundles or mobile apps.

Sanity-check a key with the free GET /v1/me endpoint (below). If you get 401, the key is wrong, revoked, or you forgot the Bearer prefix. If you get 402, your credit balance is exhausted, so top up at /dashboard/billing.

Errors

All errors are JSON with a consistent shape:

{
  "error": {
    "code": "<short_code>",
    "message": "<human-readable explanation>",
    "details": []
  }
}

Every failure (validation, auth, rate-limit, upstream) uses this envelope. details is an array that’s only present on validation errors and lists the offending fields. Rate-limit errors additionally include source, scope, limit, window_seconds, retry_after, and reset_at alongside code/message, and set the Retry-After response header.

Three endpoints stream their response — /v1/scrape/screenshot, /v1/scrape/pdf, and the synchronous /v1/scrape/google-maps/reviews. Because the connection is held open with keep-alive whitespace before the body is known, a handler error on these three arrives inside an HTTP 200 body as {"error": {...}, "http_status": <N>} (after leading whitespace), not as a real 5xx status line. Parse the body and branch on http_status. Auth, validation, and rate-limit errors still fire before streaming begins, so those keep their real 4xx status line.

HTTPCodeMeaningWhat to do
400unsafe_urlURL points at a private/internal/loopback host.Send a public http(s) URL.
400redirect_to_unsafe_urlTarget redirected the fetch to a private/internal address.Use a different URL; we won't follow the redirect.
400cookies_invalidOn /v1/audio/transcript only: the cookies field isn’t valid Netscape cookies.txt content.Re-export and resend full file contents, including the # Netscape HTTP Cookie File header.
400invalid_run_idrun_id is not a UUID.Use the value from the 202 response.
400place_id_formatOn Google Maps endpoints: the place URL / ID isn’t a recognised format.Pass a valid Maps place URL or CID.
401unauthorizedMissing or invalid bearer token.Check the key in /dashboard/keys.
401login_requiredOn /v1/audio/transcript: the source (typically a YouTube Short or gated upload) requires a logged-in session.Pass Netscape-format cookies from a burner account (see YouTube cookies).
402insufficient_creditsCredit balance is exhausted.Top up or upgrade at /dashboard/billing.
402overage_cap_reachedAccount hit the negative-balance floor (5,000 credits).Top up at /dashboard/billing or wait for the monthly credit reset.
402bandwidth_cap_exceededFree-tier daily bandwidth budget exhausted.Upgrade to any paid plan to remove the cap.
403no_ownerAPI key has no owning account (key revoked or detached).Generate a new key in /dashboard/keys.
403email_not_verifiedAccount’s email isn’t verified.Click the verification link or hit /verify/resend.
403account_suspendedAccount suspended.Contact [email protected].
402team_seat_blockedThe team is over its plan’s seat cap.Upgrade the plan or remove members.
404run_not_foundAsync run doesn’t belong to this customer, or has expired.Confirm the key + 7-day expiry.
404not_foundUnknown route, or the requested place/resource doesn’t exist.Verify the path, method, and inputs.
405method_not_allowedWrong HTTP method on a valid /v1 route (e.g. GET on a POST-only endpoint).Use the method documented for the endpoint; check the Allow response header.
422validation_errorRequest body has missing or invalid fields.The response’s details array names each offending field. Fix and retry.
429rate_limitedPer-minute or per-day rate limit exceeded.Honour Retry-After and back off.
429abuse_throttledAccount temporarily throttled by abuse-detection (e.g. excessive refund rate).Wait the indicated retry_after; contact support if persistent.
500server_errorUnexpected server error.Retry with backoff; contact [email protected] if it persists.
502upstream_failedTarget site returned an error or could not be fetched.Retry once with backoff. Credits are not consumed on errors.
200 (in-body)pdf_failedTarget could not be rendered to PDF. Streamed: arrives in-body with "http_status": 502 (or 504 on render timeout) inside a 200.Branch on the in-body http_status, then retry once or try a different URL.
200 (in-body)screenshot_failedTarget could not be screenshotted. Streamed: arrives in-body with "http_status": 502 (or 504 on render timeout) inside a 200.Branch on the in-body http_status, then retry once or try a different URL.
502no_sources/v1/search/answer couldn’t find usable sources.Rephrase the query.
502rpc_parse_failedGoogle Maps returned a malformed response.Retry once; if persistent, file a bug.
502rpc_unexpected_bodyGoogle Maps returned an unexpected (non-JSON) response.Retry once; often a transient challenge.
503upstream_not_configuredAn upstream we depend on isn’t configured server-side.Contact [email protected].
504Request exceeded the gateway timeout.Use the /async variant for long-running scrapes.

In-body failures on /v1/scrape/url. A fetch that reaches our infrastructure but doesn’t yield usable content returns HTTP 200 with success: false and a machine-readable error_code — not a 5xx status line. These are never charged. Branch on success, then on error_code:

error_codeMeaningWhat to do
anti_bot_blockThe target’s anti-bot refused the request.Already retried once in stealth (unless auto_escalate: false). Retry later, or leave auto-escalation on.
timeoutThe fetch exceeded the time budget.Raise timeout_seconds for slow targets, or retry.
not_foundThe target returned 404/410.Verify the URL. Delivered not-founds bill the flat 1-credit floor.
empty_responseThe page returned no usable content.Try render: "browser" for JS-rendered pages, or a different URL.
upstream_failedA transient fetch-infrastructure error.Retry once with backoff.

Endpoint-specific codes. Some endpoints raise their own descriptive code values beyond the table above: audio availability/geo/DRM (geo_restricted, audio_not_available, audio_private, drm_protected), Maps place resolution (place_id_unresolvable, parse_failed), translate language validation (unsupported_target_language, translation_rejected), and answer (llm_not_configured, llm_upstream_failed). They follow the same envelope shape.

Retry policy. On 5xx, retry with exponential backoff (1s, 3s, 9s) up to 3 attempts. On 429, honour Retry-After. On 4xx, don’t retry: fix the request first.

Strict bool fields. On /v1/scrape/url and /v1/scrape/batch, boolean fields are strictly typed: they accept true/false only. Truthy strings ("yes", "1", "true") and numeric 0/1 are rejected with 422. Always send a JSON boolean.

Rate limits

Per-key sliding-window limits, enforced server-side. Your plan sets the ceiling:

PlanReq / minuteReq / day
Free30500
Starter6010,000
Growth12050,000
Pro300200,000
Scale600500,000

Both windows are checked on every call; the more restrictive one wins. Polling GET /v1/scrape/runs/{run_id} counts toward your rate limit too, so don’t poll faster than every 2–10 seconds. The live values for the calling key are in GET /v1/me.

Need higher ceilings than your plan allows? Pro and Scale customers can have per-key limits raised. Email [email protected] with your customer ID.

Caching

Responses are cached on a hash of the full normalized request (URL, schema, render mode, proxy flag, etc). An identical request inside the TTL window:

Most endpoints accept an optional cache_ttl_seconds override (translate and batch are never cached; screenshot and PDF cache only when you set a non-zero TTL):

Requests carrying YouTube cookies are never cached, so cookie-protected results can’t leak to a different caller.

Credits & pricing

Every request consumes credits based on the infrastructure it used. The cost is in the response (credits_charged) and aggregated on /dashboard/usage.

Generic operations

OperationCreditsPAYG $Used by
Plain HTTP fetch (datacenter, no JS)1$0.0002Static pages, JSON APIs, sitemaps.
Datacenter + JS render5$0.0010SPAs on soft targets.
Residential proxy, no JS10$0.0020Geo-blocked / soft anti-bot.
Residential + JS40$0.0080Hostile sites, full render.
Stealth (anti-bot bypass)40$0.0080/v1/scrape/url with render="stealth". Residential exit + a hardened real browser. Bypasses modern anti-bot protections.
Screenshot, viewport15$0.0030/v1/scrape/screenshot on cooperative sites.
Screenshot, full page25$0.0050/v1/scrape/screenshot with full_page=true.
Screenshot, selector-targeted15$0.0030/v1/scrape/screenshot with selector.
Screenshot, stealth fallback35$0.0070/v1/scrape/screenshot auto-escalated when the target blocks the standard browser engine.
PDF, standard paper25$0.0050/v1/scrape/pdf, Letter / Legal / A3-A6.
PDF, landscape or large paper35$0.0070/v1/scrape/pdf with landscape=true or paper format Tabloid / Ledger / A0-A2.
Cache hit (any tier)1$0.0002Repeat inside the TTL window.
Batch fetch (per URL)1–40$0.0002–$0.0080/v1/scrape/batch bills each URL at its own resolved tier; the total is the sum. Batch responses are never cached.
AI extraction surcharge+5+$0.0010Added on top of the page tier when ai_query is set on /v1/scrape/url.
Delivered 404 / 410 (not found)1$0.0002A page that loads but returns 404/410 bills a flat 1-credit floor. Undeliverable fetches (timeouts, blocks, 5xx) bill 0.

Image sources

When include_images=true on /v1/search, results are merged from one or more sources. Default is Bing only (fast). Add Brave for wider coverage (slower, browser-rendered). Image costs stack on top of the standard 5-credit /v1/search base charged whenever results are delivered.

SourceAdd-on costLatencyNotes
"bing" (default)+5~1 sBing Image Search. Returns direct image URLs when available.
"brave"+40~8 sBrave Search Images. Wider sources beyond Bing's index. Higher latency.

Totals include the search base. Bing only bills 5 (search) + 5 (bing) = 10; image_sources: ["bing", "brave"] bills 5 (search) + 5 (bing) + 40 (brave) = 50 and merges deduped results.

Search & AI

EndpointCredit formula
/v1/search5 per call; 0 when nothing is delivered (zero results and zero images).
/v1/search/deep5 + 5 × pages_fetched (pages that returned text).
/v1/search/answer5 + 5 × sources_fetched + 5 (answer overhead). The +5 answer step is waived when the model can’t ground an answer in the sources.
/v1/translate1 per call; 0 when the source equals (or auto-detects to) the target.

Pre-built scrapers

EndpointCredits
/v1/audio/transcript20 per 2 minutes of audio (ceiling-rounded; minimum 20).
/v1/scrape/google-maps/reviews100 per 50 reviews returned (rounded up), with a 40-credit floor when 50 or fewer come back. Hard cap 10,000, newest-first.
/v1/scrape/gas-prices10 per call. Returns up to 50 stations near a US ZIP or City, ST.

See pricing for monthly plan inclusions ($29 Starter through $599 Scale) and pay-as-you-go ($0.0002 / credit, no commit).

Endpoints

GET /v1/me Free

Returns metadata for the API key making the request. Use it to sanity-check auth and read your live rate limits.

curl https://scrapenest.dev/v1/me \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY"

Response

{
  "prefix": "sn_AbCdEfGhi",
  "label": "production",
  "is_active": true,
  "is_internal": false,
  "rate_limit_per_minute": 60,
  "rate_limit_per_day": 10000,
  "credit_balance": 48230,
  "created_at": "2026-05-01T12:00:00Z",
  "last_used_at": "2026-05-14T15:24:11Z"
}
POST /v1/scrape/url 1–40 credits

Fetches a single URL and returns clean JSON. Use this when no dedicated endpoint exists for the source.

Request body

FieldTypeDefaultNotes
urlstring (http/https)requiredAbsolute URL. Private/loopback hosts are blocked.
render"auto" | "direct" | "browser" | "stealth""auto"direct = plain HTTP through a proxy (cheap, no JS); browser = headless browser; stealth = residential exit + hardened browser for anti-bot targets (40 credits); auto picks per-target.
force_proxyboolfalseForce a higher-tier proxy even when a direct fetch would work.
auto_escalatebooltrueWhen a non-stealth fetch is refused by the target’s anti-bot, automatically retry once in stealth mode. You are only charged the stealth rate if that retry succeeds; a request that stays blocked is free. Set false to keep the original render tier and receive the block as-is.
timeout_seconds5–120~100Hard time budget for the whole request, including any auto-escalation retry. Lower it (e.g. 20) to fail fast on hard targets; raise it for slow stealth solves. On timeout the response has success: false and error_code: "timeout".
block_assetsbooltrueWhen rendering with a browser, block images/fonts/media to save bandwidth.
extractbooltrueRun readability + JSON-LD extraction. Set false to get raw HTML in text.
ai_querystringnullNatural-language extraction prompt (e.g. "return the product name, price, and rating"). Populates ai_extract in the response. Adds 5 premium credits.
ai_extract_schemaJSON Schema objectnullConstrains ai_extract to a typed object. Used together with ai_query.
include_usageboolfalseInclude a usage block in the response with credit totals.
include_htmlboolfalseInclude the raw page html in the response alongside the cleaned text. No extra credits.
return_markdownboolfalseConvert the page to Markdown and return it in a markdown field. No extra credits.
extract_rulesobject <string, string | object> (up to 50)nullCSS-based extraction. Each value is a selector string (returns first match's text) or {"selector": "...", "attr": "href", "all": true} for finer control. Result returned in extracted. No extra credits.
wait_ms0–10000nullExtra wait in ms after load before reading the DOM. Browser render only.
wait_for_selectorstringnullCSS selector to wait for before returning. Browser render only.
wait_for_timeout_ms100–300005000Max ms to wait for wait_for_selector.
window_width320–38401280Browser viewport width. Browser render only. viewport_width accepted as an alias.
window_height240–2160720Browser viewport height. Browser render only. viewport_height accepted as an alias.
countryISO 3166-1 alpha-2 (2 chars)nullRoute through a residential exit in this country (e.g. "us", "de", "fr"). Forces a higher-tier proxy. Residential exits are available for us, be, de, fr, it, sg; other codes have no exit and fail upstream.
device"desktop" | "mobile""desktop"mobile sets a mobile User-Agent + 390x844 viewport. Browser render only.
block_adsboolfalseBlock known ad and tracker domains during the fetch. Speeds up renders on ad-heavy sites. Browser render only.
simulate_behaviorboolfalseSprinkle mouse-move + scroll motion before extraction to fool behavioral bot detectors (PerimeterX, DataDome, Akamai BMP). Adds ~200–600 ms. Browser path only.
session_idstring (1–64 chars, [A-Za-z0-9_.-])nullSticky session id. Repeated requests reuse the same residential exit IP for ~10 min, ideal for multi-step login flows or carts. Forces a higher-tier proxy and disables caching.
cookiesobject <string, string> (up to 50)nullCookies injected before navigation. Object only ({name: value}) on this endpoint.
headersobject <string, string> (up to 30)nullExtra request headers. Set User-Agent, Referer, etc. Host, Content-Length, Connection, Transfer-Encoding are stripped.
cache_ttl_seconds0–864009000 bypasses cache.
curl -X POST https://scrapenest.dev/v1/scrape/url \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/article",
    "render": "auto",
    "extract": true
  }'

Response

{
  "success": true,
  "url": "https://example.com/article",
  "final_url": "https://example.com/article",
  "status_code": 200,
  "method": "fetch",
  "proxy_used": "datacenter",
  "elapsed_ms": 312,
  "title": "Example Article",
  "description": "Lead summary...",
  "text": "Cleaned article body...",
  "structured": { "@type": "Article", "headline": "..." },
  "cache_hit": false,
  "escalated": false,
  "credits_charged": 1,
  "error": null,
  "error_code": null
}
Always branch on success, not the HTTP status. This endpoint returns 200 even when the target blocks the fetch or serves no content. success: false means the content fields are null and error_code is populated — check it before reading text/html.

Credit cost depends on the resolved tier: 1 direct, 5 browser-only, 10 residential-only, 40 residential+browser, 1 on a cache hit. Blocked and empty fetches are free.

Auto-escalation. When a non-stealth fetch is refused by the target’s anti-bot, we automatically retry once in stealth mode — you don’t need to resend anything. If that retry succeeds, escalated is true and credits_charged reflects the stealth rate; if it stays blocked, the request is free. Disable with auto_escalate: false.

When success is false, error_code is one of: anti_bot_block (anti-bot refused — already retried in stealth unless you disabled it), timeout (exceeded the time budget — raise timeout_seconds), not_found (target returned 404/410), empty_response (no usable content), or upstream_failed (transient fetch error — retry).

POST /v1/scrape/batch per URL

Fetches up to 10 URLs in a single call. Each URL is fetched independently and concurrently, so one URL failing never fails the rest. The render options apply to every URL in the batch.

Billing is per URL. Each URL bills at its own resolved tier (140), and credits_charged is the sum. A delivered 404/410 bills the flat 1-credit not-found floor; undeliverable fetches (timeouts, blocks, 5xx) bill 0. Batch responses are never cached, so there is no cache_ttl_seconds field and items never report cache_hit.

Request body

FieldTypeDefaultNotes
urlsstring[] (1–10, http/https)requiredAbsolute URLs. Each is fetched independently and concurrently. Private/loopback hosts are blocked per URL.
render"auto" | "direct" | "browser" | "stealth""auto"Render mode applied to every URL. Same semantics as /v1/scrape/url.
force_proxyboolfalseForce every fetch through a higher-tier proxy.
block_assetsbooltrueOn browser renders, block images/fonts/media to save bandwidth.
extractbooltrueRun readability extraction to return cleaned title/description/text per URL.
return_markdownboolfalseAdd a markdown field per URL. Implies include_html. No extra credits.
include_htmlboolfalseInclude the raw html per URL alongside the cleaned text. No extra credits.
block_adsboolfalseOn browser renders, block known ad/tracker domains.
countryISO 3166-1 alpha-2 (2 chars)nullRoute every fetch through a residential proxy in this country (e.g. "us"). Forces a higher-tier proxy.
include_usageboolfalseInclude a usage block with the total credits and breakdown.
curl -X POST https://scrapenest.dev/v1/scrape/batch \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com/a",
      "https://example.com/b",
      "https://example.com/c"
    ],
    "render": "auto",
    "extract": true
  }'

Response (abbreviated)

{
  "results": [
    {
      "url": "https://example.com/a",
      "final_url": "https://example.com/a",
      "status_code": 200,
      "method": "fetch",
      "proxy_used": "datacenter",
      "elapsed_ms": 284,
      "title": "Page A",
      "text": "Cleaned body...",
      "structured": null,
      "ok": true,
      "credits_charged": 1,
      "error": null,
      "error_code": null
    },
    {
      "url": "https://example.com/blocked",
      "status_code": 403,
      "ok": false,
      "credits_charged": 0,
      "error": "anti-bot protection refused the request",
      "error_code": "anti_bot_block"
    }
  ],
  "requested": 3,
  "succeeded": 2,
  "credits_charged": 3
}

Each item carries its own ok, credits_charged, error, and error_code. error_code is a machine-readable failure category; anti_bot_block means the target’s anti-bot protection refused the request — retry that URL with render: "stealth". succeeded counts the items that returned a usable page; credits_charged at the top level is the sum across all items. When include_usage=true, the usage.breakdown reports the batch total under a single fetch key.

POST /v1/search/deep 5 + 5×fetched credits

Search and fetch the top N pages in one round trip. Same SERP as /v1/search, plus cleaned text for each fetched page.

Request body

All fields from /v1/search (including depth), plus:

FieldTypeDefault
fetch_top1–105
render"auto" | "direct" | "browser""auto"
depth"fast" | "balanced" | "thorough" | "stealth""balanced"

Here depth also controls how hard we fetch each page. fast and balanced (default) fetch every page directly in parallel — lowest latency, and genuinely bot-walled pages simply fall back to their SERP snippet. thorough and stealth escalate bot-walled pages through a real-browser bypass with progressively larger per-page budgets; stealth has the best coverage on the hardest sites. Easy pages still return in a couple seconds regardless; the larger budget only applies to pages that actually need the bypass.

curl -X POST https://scrapenest.dev/v1/search/deep \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "how does retrieval-augmented generation work",
    "fetch_top": 3
  }'

Response (abbreviated; results carries the same SERP shape as /v1/search)

{
  "query": "how does retrieval-augmented generation work",
  "results": [],
  "pages": [
    {
      "url": "https://...",
      "final_url": "https://...",
      "status": 200,
      "method": "browser",
      "elapsed_ms": 640,
      "title": "...",
      "text": "..."
    }
  ],
  "took_ms": 2110,
  "fetched_count": 3,
  "cache_hit": false,
  "credits_charged": 20
}
POST /v1/search/answer 5 + 5×sources + 5 credits

RAG-style: searches the web, fetches the top sources, then asks an LLM to write a cited answer. Returns the answer plus [1]-style citations mapping to source URLs.

Request body

Accepts the same search fields as /v1/search (including depth) plus render ("auto" | "direct" | "browser", default "auto"). Instead of fetch_top, the number of sources to read is set by:

FieldTypeDefault
max_sources1–105
Image flags are rejected here. include_images, image_sources, and include_image_descriptions are not supported on /v1/search/answer. Sending any of them returns 422. Use /v1/search when you need image results.
curl -X POST https://scrapenest.dev/v1/search/answer \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"is the new gpt-5 multimodal","max_sources":4}'

Response (abbreviated)

{
  "query": "is the new gpt-5 multimodal",
  "answer": "Yes. GPT-5 supports text, image, audio... [1][2]",
  "citations": [
    { "index": 1, "url": "https://openai.com/...", "title": "...", "snippet": "..." }
  ],
  "took_ms": 4200,
  "cache_hit": false,
  "credits_charged": 30
}
POST /v1/translate 1 credit

Translate text between languages.

Request body

FieldTypeDefault
textstring, 1–5000 charsrequired
targetIETF BCP 47 ("es", "ja", "pt-BR", "zh-Hant", ...)required
sourceIETF BCP 47 or "auto""auto"
include_usageboolfalse

BCP-47 region aliases are normalised server-side: pt-BR, zh-Hant, zh-CN, en-GB, and underscore forms like en_US all map to the right target code. Pass whatever your stack hands you; no need to canonicalise.

curl -X POST https://scrapenest.dev/v1/translate \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Hello world","target":"ja"}'

Response

{
  "translated": "こんにちは世界",
  "detected_source": "en",
  "target": "ja",
  "credits_charged": 1
}

Response fields

  • translated: the translated text.
  • detected_source: the language we detected the source as (BCP-47).
  • target: echoes the requested target language.
  • note: only present when we adjusted behaviour (source equals target so text was returned unchanged, an alias was applied, or auto-detection fell back to a heuristic). Omitted from normal paid translations.
  • credits_charged: 1 per call, or 0 when source equals (or auto-detects to) target.
POST /v1/audio/transcript 20 / 2 min

Timestamped transcript of any audio source. Supports podcast episodes (Apple Podcasts, Overcast, RSS), YouTube (captions when available, else audio transcription), X Spaces, SoundCloud, Facebook video, TikTok, and direct MP3/M4A/WAV/OGG links.

Request body

FieldTypeDefaultNotes
urlstringrequiredAudio source URL.
languageISO 639-1auto-detectHint for the transcriber, e.g. "en", "es".
cookiesstring (Netscape cookies.txt)nullOptional. See YouTube cookies below.
cache_ttl_seconds0–25920002592000Default 30 days. Forced to 0 when cookies is set.
include_usageboolfalseInclude a usage block in the response with credit totals.
curl -X POST https://scrapenest.dev/v1/audio/transcript \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://podcasts.apple.com/us/podcast/.../id..."}'

Response (abbreviated)

{
  "url": "...",
  "audio_url": "https://traffic.megaphone.fm/....mp3",
  "canonical_url": "https://podcasts.apple.com/us/podcast/.../id...?i=...",
  "title": "Episode title",
  "channel": "Show name",
  "thumbnail": "https://is1-ssl.mzstatic.com/.../artwork.jpg",
  "duration_seconds": 3618.2,
  "language": "en",
  "source": "apple_podcasts",
  "chunks": [
    { "start": 0.0, "end": 4.2, "text": "Welcome back to the show..." }
  ],
  "text": "Welcome back to the show... [full transcript]",
  "took_ms": 41200,
  "cache_hit": false,
  "credits_charged": 620
}

chunks is omitted when the upstream source doesn’t provide timestamps (some podcast feeds and short clips); the text field always contains the full transcript regardless. canonical_url and thumbnail are populated when the resolver can pin them; treat absence as “not available”, not an error.

YouTube cookies (optional, for the long tail)

~95% of YouTube URLs work out of the box via the captions API. The remaining ~5% (YouTube Shorts that fall through the captions path, videos with auto-captions disabled, geo-restricted content, members-only, age-gated) require a logged-in YouTube session to download the audio. For those, pass a Netscape-format cookies.txt in the cookies field.

How to obtain cookies:

  1. Use a burner Google account. Not your personal one, since every transcript request using these cookies adds to that account’s watch history.
  2. Install a cookies-export browser extension (Chrome: Get cookies.txt LOCALLY; Firefox: cookies.txt).
  3. Log into youtube.com in that browser, click the extension on any YouTube tab, choose Export. You’ll get a file starting with # Netscape HTTP Cookie File.
  4. Send the entire file contents as the cookies field. We accept up to 128 KB; only the youtube.com lines actually matter.
Security model. Cookies are written to a per-request tempfile (0600 perms), used by the audio resolver, and deleted before the response returns. Requests carrying cookies are never cached. Audit logs scrub the cookies key entirely.

Omit cookies for the typical case. Add it only when you’ve previously seen login_required on a video you specifically need.

POST /v1/audio/transcript/async same as sync

Identical request shape to the sync endpoint. Returns immediately with a run_id (202 Accepted); poll GET /v1/scrape/runs/{run_id} for the completed transcript. Recommended for long-form audio (multi-hour podcasts, full conference talks) so you’re not holding an HTTP connection open for the duration of the transcription. Credit cost is identical and is charged when the job completes. Failed runs are not billed.

Initial response (202)

{
  "run_id": "f0e0c0a0-1234-5678-9abc-def012345678",
  "status": "queued",
  "status_url": "/v1/scrape/runs/f0e0c0a0-1234-5678-9abc-def012345678",
  "created_at": "2026-05-14T16:00:00Z",
  "expires_at": "2026-05-21T16:00:00Z"
}
POST /v1/scrape/google-maps/reviews from 40

Pulls Google Maps reviews for any place. Returns up to ~10,000 unique reviews, deduplicated and sorted newest-first.

Pricing. 100 credits per 50 reviews returned (rounded up to the next 50), with a 40-credit floor when 50 or fewer come back — so you only pay for what’s available. 200 reviews bills ~400 credits; the 10,000-review cap bills ~20,000.

Why is the cap 10,000 reviews per place when some places have more?

Google itself only exposes a limited slice of any place’s reviews, regardless of how many the place actually has. On places with >20K total reviews the practical ceiling lands around 10,000 unique. Tested against Six Flags Fiesta TX (37,429 Google-reported reviews): 10,000 returned in ~4 minutes. We cap the request at 10,000 because asking for more is misleading, since Google won’t serve it.

How some services claim 100K reviews per place. That’s a hard limit on what Maps itself will surface. Marketing numbers like “up to 100K” aren’t tested against Eiffel-tier places (486K reviews); they’re aspirational ceilings, not measured deliveries. DataForSEO publishes its real cap at 4,490. SerpAPI documented in their own bug tracker that pagination stops where Google’s UI scroll stops.

Realistic delivery by place size

Place review count (Google)Typical delivery
< 1,000~100% of available
1,000–10,00060–100% (subject to Google’s rate-limit state)
10,000–100,000~10,000 (the per-request cap)
100,000+ (Eiffel Tower, Times Square)~10,000 (Google’s indexed pool, not our cap, is the binding constraint)

The note field in the response is your authoritative count of what came back and why. The review_count field reflects what Google publishes for the place, which is often much higher than what’s actually accessible through any documented endpoint. This reflects Google’s spam-filter and pagination behavior, not a limitation of this API.

For pulls above ~700 reviews, use the async variant. The synchronous endpoint is bound by a request time limit. Submit to /v1/scrape/google-maps/reviews/async, then poll /v1/scrape/runs/{run_id}. Failed runs are not billed.

Request body

FieldTypeDefaultNotes
place_idstringrequiredFull Google Maps URL of the place or a FID hex (0x...:0x...). URLs are parsed server-side, so you can paste the link straight from the Maps address bar. FID hex skips an internal lookup so it’s slightly faster.
place_urlstringnoneOptional canonical Maps URL. Pass when the place’s FID has been rotated and the legacy short URL redirects to a generic location.
max_reviews1–10000200Hard cap (request ceiling). Actual delivery is bounded by what Google makes available (see the table above).
sortnewest | most_relevant | highest_rating | lowest_rating"newest"Accepted values: newest, most_relevant, highest_rating, lowest_rating. Reviews are returned newest-first regardless of this value. Kept for backward compatibility.
languageIETF BCP 47 ("en", "pt-BR", "zh-Hans")"en"Affects place name/address language only; review text is whatever the reviewer wrote.
cache_ttl_seconds0–864003600
include_usageboolfalseInclude a usage block in the response with credit totals.

Finding a FID

Open the place on Google Maps. The URL contains a segment like !1s0x47e6…:0x8ddc…!8m2!…. The FID is the 0x…:0x… hex pair immediately after !1s. Copy it whole, separator included.

curl -X POST https://scrapenest.dev/v1/scrape/google-maps/reviews \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "place_id": "0x47e66e2964e34e2d:0x8ddca9ee380ef7e0",
    "max_reviews": 5000
  }'

Response (abbreviated)

{
  "place_id": "0x47e66e2964e34e2d:0x8ddca9ee380ef7e0",
  "name": "London Eye",
  "address": "Riverside Building, County Hall, London SE1 7PB, UK",
  "rating": 4.5,
  "review_count": 89234,
  "google_maps_url": "https://www.google.com/maps/place/...",
  "reviews": [
    {
      "review_id": "ChdDSUhNMG9nS0VJQ0FnSURBNS0z...",
      "review_url": "https://www.google.com/maps/contrib/.../reviews?hl=en",
      "author_name": "Jane D.",
      "author_id": "1234567890",
      "author_profile_url": "https://www.google.com/maps/contrib/1234567890/reviews?hl=en",
      "author_avatar_url": "https://lh3.googleusercontent.com/...",
      "rating": 5,
      "text": "Amazing view, worth the wait.",
      "language": "en",
      "relative_time": "2 weeks ago",
      "posted_at": 1778630400,
      "posted_at_iso": "2026-05-13T00:00:00Z",
      "helpful_count": 4,
      "owner_reply": null
    }
  ],
  "star_histogram": [1820, 1140, 980, 1500, 4200],
  "note": "Returned 5000 unique reviews after deduplication.",
  "took_ms": 197000,
  "cache_hit": false,
  "credits_charged": 10000
}

Heads up: name, address, rating, review_count, and star_histogram can be null when the FID resolves but the metadata block isn’t served. The reviews array is always the source of truth for what you get.

star_histogram is a flat array of 5 ints ordered 1★ → 5★ (so [1820, 1140, 980, 1500, 4200] means 1820 one-star reviews, 4200 five-star reviews). photos is omitted from a review entry when the reviewer didn’t attach any; treat its absence as the empty case.

POST /v1/scrape/google-maps/reviews/async same as sync

Identical request body to the sync endpoint. Returns immediately with a run_id (202 Accepted); poll GET /v1/scrape/runs/{run_id} for the final result. Recommended for any large pull (it doesn’t hold a long-lived HTTP connection open, and you can fire-and-forget across many places). Credit cost is identical and is charged when the job completes. Failed runs are not billed.

Initial response (202)

{
  "run_id": "f0e0c0a0-1234-5678-9abc-def012345678",
  "status": "queued",
  "status_url": "/v1/scrape/runs/f0e0c0a0-1234-5678-9abc-def012345678",
  "created_at": "2026-05-14T15:00:00Z",
  "expires_at": "2026-05-21T15:00:00Z"
}
POST /v1/scrape/gas-prices 10 credits

Returns nearby retail gas-station prices for a US location. Accepts a 5-digit ZIP, City, ST, or City, State. Backed by public station-finder listings.

Upstream limitation. Some sources have progressively hidden per-station prices behind a login wall. On any given pull, individual price values may be null even when the station itself is listed. The region_stats block (regional lowest / average) stays populated. When prices are missing the response sets note so you can surface the caveat to your caller.

Request body

FieldTypeDefaultNotes
locationstring, 2–200 charsrequired5-digit US ZIP ("37122", "37122-1234"), "City, ST" ("Mount Juliet, TN"), or "City, State" ("Nashville, Tennessee"). Ambiguous queries may auto-resolve, so check location in the response.
grade"regular" | "midgrade" | "premium" | "diesel" | "e85""regular"Fuel grade.
limit1–5020Hard cap on returned stations. Listings show ~13 by default.
cache_ttl_seconds0–3600per-endpoint defaultMax 1 hour (prices change slowly).
include_usageboolfalseInclude a usage block in the response.
curl -X POST https://scrapenest.dev/v1/scrape/gas-prices \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "location": "37122",
    "grade": "regular",
    "limit": 20
  }'

Response (abbreviated)

{
  "location": {
    "query": "37122",
    "zip": "37122",
    "city": "Mount Juliet",
    "state": "TN"
  },
  "grade": "regular",
  "stations": [
    {
      "brand": "Kroger",
      "sub_brand": "Kroger Fuel Center",
      "station_id": "21847",
      "address": "401 S Mt Juliet Rd, Mount Juliet, TN",
      "city": "Mount Juliet",
      "state": "TN",
      "price": 2.789,
      "currency": "USD",
      "unit": "USD/gallon",
      "reporter": "anon_18234",
      "updated_relative": "12 minutes ago"
    }
  ],
  "region_stats": {
    "state": "TN",
    "region": "Tennessee",
    "region_type": "state",
    "lowest_price": 2.699,
    "average_price": 2.952
  },
  "source_url": "https://scrapenest.dev",
  "fetched_at": "2026-05-19T17:42:00Z",
  "took_ms": 1820,
  "cache_hit": false,
  "credits_charged": 10,
  "note": null
}

Field rules:

  • location.zip, location.city, location.state are populated when the resolver can identify them; location.query always echoes the verbatim input.
  • region_stats is omitted when the source doesn’t render the regional summary block (typically rural ZIPs).
  • region_stats.region_type is "state" when region is a US state name and state holds the USPS code; it’s "city" when the source returned a metro-level summary (state is then null).
  • note is set when stations were returned without per-station prices, when an ambiguous query was auto-resolved, or when other caveats apply.
GET /v1/scrape/runs/{run_id} Polling free

Poll the status of any async run. Returns one of queued, running, completed, failed, cancelled. When completed, result holds the same body shape as the synchronous endpoint.

Polling does not consume credits, but it does count against your rate limit. Runs expire after 7 days.

The response also includes an optional progress object on long-running scrapes (currently large Maps reviews pulls). Shape: {"current": <int>, "target": <int>, "detail": "<string>"}. Use it to display a progress bar to your users; absent or null means the worker hasn't emitted a milestone yet.

To stop an in-flight run, hit POST /v1/scrape/runs/{run_id}/cancel. The worker stops on its next checkpoint (cooperative cancellation). No refund: cancellation is best-effort and any work already performed is billed.

Suggested cadence. 2–5 seconds is fine for most scrapes; 10–15 seconds is more polite for Maps runs targeting 5K reviews.
RUN=$(curl -s -X POST https://scrapenest.dev/v1/scrape/google-maps/reviews/async \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"place_id":"0x47e6...:0x8ddc...","max_reviews":5000}' \
  | jq -r .run_id)

while true; do
  STATUS=$(curl -s https://scrapenest.dev/v1/scrape/runs/$RUN \
    -H "Authorization: Bearer $SCRAPENEST_API_KEY")
  STATE=$(echo "$STATUS" | jq -r .status)
  [ "$STATE" = "completed" ] || [ "$STATE" = "failed" ] && { echo "$STATUS" | jq; break; }
  sleep 10
done

Response (result holds the same body as the synchronous endpoint)

{
  "run_id": "f0e0c0a0-1234-5678-9abc-def012345678",
  "endpoint": "POST /v1/scrape/google-maps/reviews",
  "status": "completed",
  "created_at": "2026-05-14T15:00:00Z",
  "started_at": "2026-05-14T15:00:02Z",
  "completed_at": "2026-05-14T15:03:15Z",
  "cancelled_at": null,
  "expires_at": "2026-05-21T15:00:00Z",
  "credits_charged": 10000,
  "error_code": null,
  "error_message": null,
  "progress": null,
  "result": {}
}
POST /v1/scrape/screenshot 15–35 credits

Captures a PNG or JPEG screenshot of any public URL. Returns base64-encoded image bytes inline. Auto-falls-back to a stealth backend when the target site blocks the normal headless browser — same endpoint, same response shape. If you already know the target is hostile, skip the failed first attempt with render: "stealth".

Request body

FieldTypeDefaultNotes
urlstring (http/https)requiredAbsolute URL. Private/loopback hosts are blocked.
viewport_width320–38401280Browser viewport width in CSS pixels.
viewport_height240–2160720Browser viewport height in CSS pixels.
full_pageboolfalseWhen true, captures the entire scrollable height. Auto-detected up to the rendered page height.
selectorstring (CSS, up to 300 chars)nullCaptures the first element matching selector; if the selector doesn't match, the capture falls back to the full viewport. Returns 422 selector_not_found only when nothing can be rendered. Overrides full_page (e.g. "#hero", ".product-card:first-child").
render"auto" | "browser" | "stealth""auto"auto lets us pick the right backend per target (including stealth fallback for hostile sites); browser forces the standard headless browser; stealth forces the anti-bot capture backend up front (35 credits).
format"png" | "jpeg""png"JPEG is typically ~half the byte size at quality 75–85.
jpeg_quality1–10085Ignored when format is png.
wait_extra_ms0–100000Extra wait after page load for late-loading content (SPAs, lazy images, charts).
cookiesobject <string, string> or [{name, value}, …] (up to 50)nullCookies injected before navigation. Accepts either a {name: value} map or a list of {name, value} objects; the list form is normalised server-side.
headersobject <string, string> (up to 30)nullExtra HTTP headers (User-Agent, Referer, etc.). Host/Content-Length are stripped.
session_idstring (1–64 chars, [A-Za-z0-9_.-])nullSticky exit IP (10-minute window). Pair with the matching /v1/scrape/url session to capture an authenticated page.
include_usageboolfalseInclude a usage block in the response.
cache_ttl_seconds0–864000 (off)Set non-zero to dedupe repeat captures of the same URL+viewport+format.
curl -X POST https://scrapenest.dev/v1/scrape/screenshot \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://news.ycombinator.com",
    "full_page": true,
    "format": "png"
  }' \
  | jq -r .image_base64 | base64 -d > screenshot.png

Response

{
  "url": "https://news.ycombinator.com",
  "final_url": "https://news.ycombinator.com/",
  "status_code": 200,
  "method": "browser",
  "proxy_used": null,
  "elapsed_ms": 2470,
  "width": 1280,
  "height": 1179,
  "bytes": 253264,
  "format": "png",
  "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
  "cache_hit": false,
  "credits_charged": 25
}

The method field tells you which engine handled the capture: "browser" is the normal headless path (~2–3 s, 15–25 credits), "stealth" is the anti-bot path for hostile sites (~8–12 s, 35 credits). You don't have to choose — we route per target — but render: "stealth" forces it when you already know the site blocks normal browsers.

POST /v1/scrape/pdf 25–35 credits

Renders any URL to PDF. Returns base64-encoded bytes. Useful for archiving articles, generating invoice/receipt copies, or capturing legal evidence.

Request body

FieldTypeDefaultNotes
urlstring (http/https)requiredAbsolute URL.
format"Letter" | "Legal" | "Tabloid" | "Ledger" | "A0"–"A6""Letter"Paper size.
landscapeboolfalseOrientation.
viewport_width320–38401280Render viewport width.
viewport_height240–2160720Render viewport height.
wait_extra_ms0–100000Extra wait before capture for late-loading content.
cookiesobject <string, string> or [{name, value}, …]nullCookies injected before navigation (for paywalled/logged-in pages). Accepts either a {name: value} map or a list of {name, value} objects.
headersobject <string, string>nullExtra request headers.
session_idstringnullReuse a sticky exit IP (see /v1/scrape/url).
include_usageboolfalseInclude a usage block in the response.
cache_ttl_seconds0–864000 (off)Set non-zero to dedupe repeats.
curl -X POST https://scrapenest.dev/v1/scrape/pdf \
  -H "Authorization: Bearer $SCRAPENEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/article",
    "format": "A4"
  }' | jq -r .pdf_base64 | base64 -d > article.pdf

Response

{
  "url": "https://example.com/article",
  "final_url": "https://example.com/article",
  "status_code": 200,
  "proxy_used": null,
  "elapsed_ms": 2120,
  "format": "A4",
  "landscape": false,
  "bytes": 20925,
  "pdf_base64": "JVBERi0xLjQKJdPr6eEKMSAwIG9iago...",
  "cache_hit": false,
  "credits_charged": 25
}

Common workflows

Reviews → sentiment summary for a place

  1. POST /v1/scrape/google-maps/reviews/async with max_reviews=5000.
  2. Poll /v1/scrape/runs/{run_id} every 10s until status=completed.
  3. Batch result.reviews[*].text in chunks of 50, feed each chunk to your LLM with "Summarize themes and rate sentiment 1–5 per review." Map back via review_id.

Topic research with citations

  1. POST /v1/search/answer with a clear question and max_sources=6.
  2. The response gives you a written answer plus citations[].
  3. For deeper backing, take each citations[*].url, fan out to /v1/scrape/url with extract=true, and feed the cleaned text back into your own LLM prompt.

Podcast → show notes

  1. POST /v1/audio/transcript with the episode URL.
  2. Take chunks[] (start/end/text) and ask your LLM: "Group these into 5–10 topic chapters, each with timestamps from the chunks." Works because chunks are already time-anchored.

Common mistakes

MCP server — connect Claude, Cursor & ChatGPT

ScrapeNest runs a hosted Model Context Protocol server so any MCP-capable AI assistant can use live web search, scraping, deep research and translation as built-in tools. It is the same API documented above — billing, rate limits and failure handling are identical — exposed over MCP’s streamable-HTTP transport.

Server URL. https://scrapenest.dev/mcp. Authenticate with your API key as Authorization: Bearer sn_..., or append ?apiKey=sn_... to the URL for clients that have no custom-header field (Claude.ai web, ChatGPT). No separate key — any key from /dashboard/keys works.

Add it to your client:

claude mcp add scrapenest --transport http \
  --header "Authorization: Bearer $SCRAPENEST_API_KEY" \
  https://scrapenest.dev/mcp

The default profile exposes six tools; each is a thin wrapper over the endpoint shown, billed at the same rate (tool output ends with the credits charged):

ToolDoesEndpointCredits
web_searchMulti-engine web search, merged & ranked/v1/search5
ask_webCited answer synthesized from live pages/v1/search/answer5 + 5×sources
deep_searchSearch + full text of the top results/v1/search/deep5 + 5×pages
scrape_urlFetch one URL (handles JS + anti-bot)/v1/scrape/url1–40
translate_textTranslate text, auto-detect source/v1/translate1
scrapenest_accountKey status, credit balance, limits/v1/mefree
Long calls. Individual tool calls are capped at ~90s so they return cleanly within the edge timeout; a call that would exceed it is cancelled and not billed. For heavy stealth scrapes with a larger time budget, call the /v1 API directly with a higher timeout_seconds.

SDKs & libraries

Official clients are published for Python and TypeScript/JavaScript (both version 0.5.0, MIT licensed). They wrap every endpoint, return typed responses, and raise typed exceptions for 401, 402, 403, 404, and 429. The API is also a thin HTTP layer, so it works cleanly with any client (curl, httpx, requests, fetch, axios, got, Go’s net/http, Rust’s reqwest) if you prefer raw REST.

Python

Install from PyPI (requires Python 3.9+):

pip install scrapenest
from scrapenest import Client

client = Client("sn_your_key_here")

result = client.search("python web scraping", num_results=5)
for r in result.results:
    print(r.title, r.url)

deep = client.search_deep("what is rust lang", fetch_top=3)
for page in deep.pages:
    print(page.url, len(page.text or ""))

answer = client.search_answer("who wrote the odyssey", max_sources=3)
print(answer.answer)
for c in answer.citations:
    print(c.index, c.url)

page = client.scrape_url("https://example.com", render="auto", return_markdown=True)
print(page.title)

data = client.scrape_url("https://example.com/product", ai_query="Return the product name and price")
print(data.ai_extract)

batch = client.scrape_batch(
    ["https://example.com/a", "https://example.com/b", "https://example.com/c"],
    render="auto",
)
print(batch.succeeded, "/", batch.requested, "-", batch.credits_charged, "credits")

gas = client.gas_prices("Los Angeles, CA", grade="regular", limit=10)
for s in gas.stations:
    print(s.brand, s.address, s.price)

tr = client.translate("Hello, how are you?", "es")
print(tr.translated)

run = client.maps_reviews_async("0x6b12ae665e892fdd:0x3133f8d75a1ac251", max_reviews=2000)
final = client.wait_for_run(run.run_id)
print(len(final.result["reviews"]), "reviews")

Every method returns a typed dataclass. Pass include_usage=True (where supported) to get a usage block with the credits charged. An AsyncClient with the same methods is available for asyncio:

import asyncio
from scrapenest import AsyncClient


async def main():
    async with AsyncClient("sn_your_key_here") as client:
        result = await client.search("python web scraping")
        print(result.results[0].title)


asyncio.run(main())

Errors map to typed exceptions you can catch by class:

from scrapenest import RateLimitError, AuthenticationError, PaymentRequiredError

try:
    result = client.search("test")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except PaymentRequiredError:
    print("Out of credits")
except AuthenticationError:
    print("Invalid API key")

TypeScript / JavaScript

Install from npm (requires Node.js 18+; ships ESM + CJS builds and full type definitions):

npm install scrapenest
import { ScrapeNest } from "scrapenest";

const client = new ScrapeNest({ apiKey: "sn_your_key_here" });

const result = await client.search({ query: "python web scraping", num_results: 5 });
for (const r of result.results) {
  console.log(r.title, r.url);
}

const deep = await client.searchDeep({ query: "what is rust lang", fetch_top: 3 });
for (const page of deep.pages) console.log(page.url, (page.text ?? "").length);

const answer = await client.searchAnswer({ query: "who wrote the odyssey", max_sources: 3 });
console.log(answer.answer);

const page = await client.scrapeUrl({ url: "https://example.com", render: "auto", return_markdown: true });
console.log(page.title);

const batch = await client.scrapeBatch({
  urls: ["https://example.com/a", "https://example.com/b", "https://example.com/c"],
  render: "auto",
});
console.log(`${batch.succeeded}/${batch.requested} - ${batch.credits_charged} credits`);

const gas = await client.gasPrices({ location: "Los Angeles, CA", grade: "regular", limit: 10 });
for (const s of gas.stations) console.log(s.brand, s.address, s.price);

const tr = await client.translate({ text: "Hello, how are you?", target: "es" });
console.log(tr.translated);

const run = await client.mapsReviewsAsync({ place_id: "0x6b12ae665e892fdd:0x3133f8d75a1ac251", max_reviews: 2000 });
const final = await client.waitForRun(run.run_id);
console.log(final.status, final.credits_charged);

Every method takes a single options object and returns a typed response. Errors map to typed classes you can catch with instanceof:

import { ScrapeNest, RateLimitError, AuthenticationError, PaymentRequiredError } from "scrapenest";

try {
  await client.search({ query: "test" });
} catch (e) {
  if (e instanceof RateLimitError) console.log(`Rate limited. Retry after ${e.retryAfter}s`);
  else if (e instanceof PaymentRequiredError) console.log("Out of credits");
  else if (e instanceof AuthenticationError) console.log("Invalid API key");
}

Machine-readable: /openapi.json · interactive: /reference · for AI agents: /llms.txt. Stuck? Email [email protected].