Recipes

A recipe is one file that turns one site into one command. Your agent asks for reddit/hot and gets structured JSON, instead of a browser handle and a scraping problem.

npm install -g anti-detect-browser

anti-detect-browser recipe update
anti-detect-browser recipe run reddit/hot --temporary --json
anti-detect-browser recipe run reddit/hot --profile shopper-01 --jq '.items[].title'

Recipes live in their own public repository, antibrow/recipes, and are shared by the Node and Python SDKs - the same recipe produces the same output from either. Adding a site is a pull request there, not a release of the SDK.

This is the whole format. No imports, no dependencies, no build step:

export const meta = {
  id: 'reddit/hot',
  summary: 'Hot posts from the front page or one subreddit.',
  domains: ['www.reddit.com'],      // every host it may reach
  entry: 'https://www.reddit.com/', // the page the runtime opens
  identity: 'any',                  // 'any' | 'logged-in' | 'anonymous'
  args: [{ name: 'limit', type: 'number', default: 25, max: 100 }],
}

export async function run(ctx, args) {
  const res = await ctx.fetchJson(`/hot.json?limit=${args.limit}`)
  return { items: res.data.children.map((c) => ({ id: c.data.id, title: c.data.title })) }
}

run() executes inside the page the runtime opened, so a relative fetch carries that profile's session for that site. A site with no JSON endpoint of its own can be read straight off the DOM instead.

entry may interpolate the recipe's own arguments - https://www.google.com/search?q={query} - for a page that only exists per query. Some sites render results only on a real navigation and answer a fetch for the same url with a redirect, so this is the difference between reaching them and not. The host is never interpolated: it is what the allowlist below is checked against.

Commands

Every command takes --json, and everything that returns data takes --jq so an agent can ask for two fields instead of reading a whole payload into its context: .items[].title, .items[0], .items[1:3], length, keys, joined with |.

recipe update Pull the registry and pin every recipe by SHA-256.
recipe list [--site <site>] What is published. Unreviewed recipes are marked.
recipe info <id> Arguments, types, defaults, declared hosts, identity requirement, review state.
recipe run <id> Run it. Takes --profile or --temporary, --args, --jq, --json, --headless, --timeout.
recipe fanout <id> --profiles <pattern> Run it on several profiles at once, with --concurrency.
recipe test <id> Run your local working copy on a temporary profile.
recipe scaffold <site>/<command> Write a skeleton at the path the registry expects.
recipe guide Print the authoring guide, for you or for your agent.

The Python SDK has the same set: python -m antibrow recipe ….

Fanout: the same command on N identities

This is the part a tool that drives your own everyday browser cannot do. One command, four profiles, four personas, four cookie jars, four exit IPs:

anti-detect-browser recipe fanout reddit/hot \
  --profiles 'shopper-*' --concurrency 4 --json

Each profile is a separate identity, so a task that goes wrong costs you a profile rather than an account. Concurrency is capped by your plan's limit, and read before anything is queued - a fanout does not walk into a refusal halfway through and look like a broken recipe. One profile failing does not take the rest down; the result names which one and why.

What a recipe may do

A recipe is community code running in a browser profile that may hold live logins, so it is treated the way a browser extension would be:

meta.domains Enforced at the network layer, not by the helpers the recipe is asked to use. A request to a host it did not declare is blocked, so a Reddit recipe cannot reach your mail. Blocked hosts come back in the result, which is also how you find out what a new recipe still needs to declare.
SHA-256 pin Every recipe is pinned to the exact bytes this machine first saw. If the published file changes, the next recipe update refuses until you have read the diff and passed --accept-changes.
Review state Only reviewed recipes run by default. --allow-unreviewed opts in, and only on a --temporary profile - those are local-only, so nothing an unreviewed recipe collects travels to another machine.
No write actions Recipes read. Posting, voting, sending and buying are out of scope, and so are click-and-type sequences that drive a UI.

From an agent (MCP)

Three tools, not one per recipe - a tool list with a hundred entries in it is context an agent pays for on every turn. See the MCP server page for the rest.

list_recipes
site?, query?
What is published, with the arguments each one takes, the hosts it may reach and whether it has been reviewed.
run_recipe
id, args?, profile?, temporary?, jq?, allowUnreviewed?
Run one recipe and get its JSON. temporary: true for an anonymous run, profile for a persistent identity that stays signed in.
fanout_recipe
id, args?, profiles[], concurrency?, jq?
Run one recipe across several profiles at once. Concurrency is capped by your plan.

From code

Node

import { runRecipe, fanoutRecipe } from 'anti-detect-browser'

const { value, blockedHosts } = await runRecipe({
  id: 'reddit/hot',
  key: process.env.ANTI_DETECT_BROWSER_KEY,
  temporary: true,
  args: { limit: 5 },
})

const { results } = await fanoutRecipe({
  id: 'reddit/hot',
  key: process.env.ANTI_DETECT_BROWSER_KEY,
  profiles: ['shopper-01', 'shopper-02', 'shopper-03'],
  concurrency: 3,
})

Python

from antibrow import run_recipe, fanout_recipe

result = run_recipe("reddit/hot", temporary=True, args={"limit": 5})
print(result.value, result.blocked_hosts)

fanned = fanout_recipe("reddit/hot", ["shopper-01", "shopper-02"], concurrency=2)
for row in fanned.rows:
    print(row.profile, row.value if row.ok else row.error)

Writing one

Recipes are the kind of work an agent is good at: open the site, read the network log, copy the request that carried the data, drop the hardcoded token, done. Hand it the guide and let it work.

anti-detect-browser recipe guide
anti-detect-browser recipe scaffold mysite/list
anti-detect-browser recipe test mysite/list --args '{"limit":5}'

Explore the site from a throwaway profile rather than your own account - that is what --temporary is for, and it is the difference between losing a profile and losing an account. Then open a pull request against antibrow/recipes: one site per pull request, exact hosts in meta.domains, and the output your test run produced.