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.