Recipes (beta)¶
A recipe is a ready-made scraper for a well-known site. You send a small input - a search query, a repository name - and get back clean JSON with a stable schema, instead of raw HTML to parse yourself. ScrapeNest maintains the scraper: when the site changes, we fix the recipe; your integration does not change.
Beta
Recipes are in beta and included on every plan. Recipes that run on the
Stealth engine (google-serp, amazon-search, amazon-product) need a
plan that includes Stealth (Pro and up); every other recipe runs on the
Light engine.
How it works¶
Every recipe publishes one or more contracts. A contract is the stable
part: the JSON Schema of the input you send and of the output you receive.
Behind a contract, ScrapeNest runs an implementation (impl) - the actual
scraper. Implementations change whenever the site changes; contracts do not.
A new contract is only published for a breaking output change, and an old
contract keeps working until its announced sunset date.
Every output is validated against its contract's output schema before it is
delivered. If a site change breaks a recipe, you get a clear outcome
(layout_changed) and no charge - never silently wrong data.
Catalog¶
All recipes are in beta. In the console they are grouped by category, with
search; GET /v1/recipes returns each recipe's category.
Search engines
| Recipe | What you get | Billed per |
|---|---|---|
google-serp |
Organic Google results for a query, language and country: title, URL, snippet, position | run |
google-autocomplete |
The suggestions Google offers for a partial query, language and country | run |
Video
| Recipe | What you get | Billed per |
|---|---|---|
youtube-search |
Videos YouTube returns for a query: title, URL, channel, views, duration, published | run |
youtube-video |
One video's title, channel, duration, views, keywords, description, caption languages | run |
youtube-subtitles |
A video's existing captions as timed segments and full text, in a chosen language or translated | run |
youtube-channel-feed |
The latest ~15 uploads of a channel with dates and view counts | run |
E-commerce
| Recipe | What you get | Billed per |
|---|---|---|
amazon-search |
amazon.com search results: ASIN, title, price, rating, reviews, sponsored flag | run |
amazon-product |
One amazon.com product: price, list price, rating, availability, features, categories | run |
App stores & browser extensions
| Recipe | What you get | Billed per |
|---|---|---|
app-store-app |
One Apple App Store app (iPhone, iPad, Mac, Safari extensions): price, rating, version, release notes, size, genres, age rating, screenshots | run |
app-store-search |
Apps the App Store returns for a search term in a country, for iPhone, iPad or Mac | run |
app-store-reviews |
An App Store app's customer reviews in a country, most recent or most helpful: rating, title, text, version | run (50 reviews) |
google-play-app |
One Google Play app: rating, installs, price, version, last update, description, what's new, screenshots | run |
google-play-search |
Apps Google Play returns for a search term in a language and country, plus the featured top result | run |
google-play-reviews |
A Google Play app's reviews, newest, most relevant or by rating: rating, text, thumbs-up, version, developer reply; a token continues | run (up to 200 reviews) |
chrome-extension |
One Chrome Web Store extension or theme: users, rating, version, last update, size, category | run |
edge-addon |
One Microsoft Edge Add-ons extension: active installs, rating, version, last update, category | run |
firefox-addon |
One Firefox add-on: daily users, rating, version, license, permissions, compatibility | run |
firefox-addon-search |
Firefox extensions, themes or dictionaries matching a search, by relevance, users, rating or date | page of 50 add-ons |
firefox-addon-reviews |
A Firefox add-on's reviews, newest first: rating, text, version, developer reply | page of 50 reviews |
Safari extensions are sold on the App Store, so the app-store-* recipes
cover them. Review recipes never return the reviewer's name or profile.
Social & communities (public content only; no logged-in data)
| Recipe | What you get | Billed per |
|---|---|---|
bluesky-posts |
Recent public posts of a Bluesky account with likes, reposts, replies, images | page of 50 posts |
bluesky-profile |
A Bluesky account's bio, avatar, follower / following / post counts, pinned post | run |
bluesky-thread |
One Bluesky post (from its URL) and its direct replies, with engagement counts | run |
mastodon-hashtag |
Recent public Mastodon posts with a hashtag, as seen from mastodon.social | page of 40 posts |
mastodon-trends |
The posts trending on mastodon.social right now, in a chosen language | page of 40 posts |
lemmy-community |
Posts of a Lemmy community (hot, new, top...): title, link, text, score, votes, comments | page of 50 posts |
telegram-channel |
The ~20 latest posts of a public Telegram channel: text, links, views, date, forwards; plus title and subscribers | run |
hackernews-search |
Hacker News stories, Ask HN, Show HN or the front page: points, comments, links | run |
Social recipes return what anyone can see without an account. Networks that require a login to read (X, Instagram, Facebook, LinkedIn, TikTok, Threads) are not in the catalog.
Code hosting
| Recipe | What you get | Billed per |
|---|---|---|
github-issues |
Issues of a public GitHub repository (open, closed or all) with labels, author, dates | page of 100 issues |
github-repo |
A repository's stars, forks, topics, license, language and activity dates | run |
github-search-repos |
Repositories matching a GitHub search query (qualifiers included) | run |
github-releases |
A repository's releases: tag, date, pre-release flag, notes, assets | page of 30 releases |
Developer tools
| Recipe | What you get | Billed per |
|---|---|---|
npm-package |
Latest version, license, repository, dependencies and size of an npm package | run |
pypi-package |
Latest version, license, Python requirement, dependencies, links, known vulnerabilities | run |
stackoverflow-search |
Stack Overflow questions for a query and tags: score, answers, views, accepted answer | run |
News (publishers' own RSS feeds: headline, link, summary, author, date, image - never article text)
| Recipe | What you get | Billed per |
|---|---|---|
lemonde-headlines |
Le Monde headlines for a section (international, politique, économie, ...) | run |
lefigaro-headlines |
Le Figaro headlines for a section (actualités, politique, international, ...) | run |
humanite-headlines |
L'Humanité's latest headlines | run |
nypost-headlines |
New York Post headlines for a section (news, politics, sports, ...) | run |
bbc-news-headlines |
BBC News headlines for a section (world, UK, business, technology, ...) | run |
guardian-headlines |
The Guardian headlines for a section (world, politics, environment, ...) | run |
Knowledge & reference
| Recipe | What you get | Billed per |
|---|---|---|
wikipedia-summary |
An English Wikipedia article's short description, lead paragraph, image, Wikidata id | run |
Wikipedia and Stack Overflow content is CC BY-SA: attribute it if you republish it.
The console's Recipes page and GET /v1/recipes list what your account
can run, with each recipe's status (beta / ga) and health:
- Operational - our continuous test runs (every 15 minutes) pass.
- Degraded - test runs are failing; we are on it. Runs still work when the site allows it, and failed runs are not billed.
- No data yet - a new recipe without test results yet.
Run a recipe¶
curl -X POST https://api.scrapenest.com/v1/recipes/github-issues/runs \
-H "X-API-Key: $SCRAPENEST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": {"owner": "cli", "repo": "cli", "state": "open"}}'
# -> 201 {"job_id": "...", "status": "queued", "recipe": {"slug": "github-issues", "contract": 1, "impl": 3}}
curl https://api.scrapenest.com/v1/jobs/<job_id>/result -H "X-API-Key: $SCRAPENEST_API_KEY"
# client.recipes is available from the SDK release that follows 1.2.0.
from scrapenest import ScrapeNestClient
client = ScrapeNestClient(api_key="sn_live_...")
result = client.recipes.run_and_wait("github-issues", {"owner": "cli", "repo": "cli"})
if result.outcome == "ok":
for issue in result.output["issues"]:
print(issue["number"], issue["title"])
Open Recipes, pick a recipe, fill in the form (it is generated from the input schema and pre-filled with an example) and press Run. The result appears as a table with a JSON tab, and stays on the job's page.
The MCP server exposes list_recipes,
get_recipe, run_recipe and get_recipe_result, so an agent can pick a
recipe, read its input schema and get structured results directly.
GET /v1/recipes/{slug} returns each contract's input_schema,
output_schema, an example input and output, the engine it runs on and its
credits_per_unit. Pass contract in the run request to pin a contract;
otherwise the newest one runs. Pass an idempotency_token to make retries
safe: the same token returns the original job instead of a new one.
Getting the result¶
A run is an ordinary job: it appears in your job history, and you can follow it the same way.
- Poll:
GET /v1/jobs/{job_id}/resultreturns409 result_not_readywhile the job runs, then the structured result. - Webhook: the
job.completedevent of a recipe job carries adata.recipeblock with the outcome and, when it is at most 256 KB, theresultitself (otherwise aresult_url).
{
"job_id": "7473a7e8-39bd-4fca-ab9b-e1e5293d6ed3",
"status": "succeeded",
"recipe": {"slug": "github-issues", "contract": 1, "impl": 3, "outcome": "ok"},
"captured_at": "2026-10-10T08:00:05+00:00",
"output": {"issues": [{"number": 11873, "title": "...", "state": "open", "labels": ["bug"], "...": "..."}], "pages_fetched": 1, "captured_at": "..."}
}
Outcomes and billing¶
You pay only when the recipe gives a definitive answer:
| Outcome | Meaning | Billed |
|---|---|---|
ok |
Results found; output is set |
Yes |
ok_empty |
The site has no results for this input (e.g. a search with no matches); output is set |
Yes |
not_found |
The thing you asked for does not exist (e.g. an unknown repository) | Yes |
blocked |
The site refused the request (anti-bot wall) | No |
rate_limited |
The site's quota was exhausted | No |
upstream_error |
The site returned an error | No |
layout_changed |
The site changed; the recipe is being fixed | No |
schema_invalid |
The output failed validation; the recipe is being fixed | No |
timeout / error |
The run did not complete | No |
The price of one unit is the engine's credit weight times the recipe's
multiplier, rounded up (credits_per_unit in GET /v1/recipes/{slug}).
Recipes billed per page (such as github-issues) reserve credits for the
maximum pages you ask for (max_pages) and charge only for the pages actually
fetched; the rest is released when the job finishes. Your plan caps the pages
a single run may fetch:
| Plan | Max pages per run |
|---|---|
| Free | 3 |
| Starter | 25 |
| Pro | 100 |
| Business | 250 |
| Enterprise | 500 |
Inside a recipe¶
Every recipe is written in a small declarative language: no custom code per
site. Here is most of hackernews-search:
slug: hackernews-search
contract: 1
tier: light
steps:
- request:
method: GET
url: "https://hn.algolia.com/api/v1/search?query={{ input.q | urlencode }}&tags={{ input.type | urlencode }}"
assertions:
page_ok: [{ status: 200, jsonpath_exists: "$.hits" }]
empty: [{ status: 200, body_matches: '"nbHits":\s*0\b' }]
rate_limited: [{ status: 429 }]
extract:
items:
type: records
container_jsonpath: ["$.hits[*]"]
fields:
title: { jsonpaths: ["$.title"], required: true, transform: [html_text] }
points: { jsonpaths: ["$.points"], default: 0 }
output:
results: { from: items }
captured_at: { meta: captured_at }
stepsfetch the data. The Light engine runs an HTTPrequest, optionally followed bypaginateorfollow. The browser engines runnavigate,click,fill,scroll,wait_for_selector,dismiss_consentand similar steps.assertionsdecide what the response means: a real answer, an empty result, not found, blocked or rate-limited. That decision is the outcome you are billed on.extractreads fields with CSS, XPath or JSONPath. Each locator is a fallback chain.outputmaps the extracted data onto the contract's output schema.
Inputs are templated only into URLs, request bodies and form values, never into selectors, scripts or hosts. Each recipe also ships with golden test cases, which are replayed offline against captured pages before every release and run against the live site every 15 minutes as canaries.
Coming soon: your own recipes
You will soon be able to write recipes in this same language for the sites you care about, run them under your own organization and plan, and share them. Want a site in the catalog today? Contact support and tell us which one.
Errors¶
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_payload |
The body is not {"input": {...}} |
| 403 | recipe_not_in_plan |
Your plan does not include recipes |
| 404 | recipe_not_found |
Unknown recipe or contract |
| 409 | recipe_disabled |
The recipe is temporarily disabled |
| 410 | recipe_contract_sunset |
The contract passed its sunset date; move to a newer one |
| 422 | invalid_recipe_input |
input does not match the contract; details lists the fields |
| 429 | plan_limit_* |
Monthly credits or concurrent jobs exhausted, or the recipe's engine is not in your plan (plan_limit_job_type_not_allowed) |
| 503 | recipe_unavailable |
Temporarily unavailable; retry later |