Guides
Contributing recipes
How public recipes are written, checked and reviewed.
A recipe teaches Wield one task on one website: "search train connections on bahn.de", "list my Doctolib appointments". It is a JSON file that describes the HTTP calls the site's own pages make. It contains no code. Wield runs it inside a tab of that site in the user's own browser, with the user's own session, and sends requests only to the origins the recipe lists.
Every recipe runs on other people's machines with their logins. So every recipe passes automated checks and a human review before it is public. This page lists both.
Three ways to make a recipe
- Record it (easiest). In the Wield extension, click Record, do the task once, click Stop. The extension removes cookies, tokens, personal data and the values you typed on your device, and the builder drafts the recipe. Your own browser then runs the draft's test plan. Hosted builds are part of the Pro plan.
- From a HAR file (free, local). Record the task in Chrome DevTools (Network → Preserve log → Save all as HAR). Keep HAR files out of any repository: they contain your cookies. Then: ``
bash node packages/builder/dist/cli.js trace flow.har --task "search used bikes" --input "Suchbegriff=fahrrad" -o flow.trace.json node packages/builder/dist/cli.js derive flow.trace.json --id example.de/search-bikes -o draft.json # needs ANTHROPIC_API_KEY, or --llm file:<dir>`trace` sanitizes on your machine; only the trace goes to the model. - By hand. Study the site's calls in DevTools and write the JSON against the recipe format. Then run
wield-build lintandwield-dev run.
Whichever way you choose, the result must pass the checks below.
Before you submit
- [ ]
wield-build lint recipes/<site>/<slug>.jsonreports no errors. Warnings have been read, and fixed or explained inprovenance.notes. - [ ] The recipe ran live and passed its
verify.checks: in your browser (extension test plan, orwield-dev runfor public reads), with the date and how it was checked in your submission. - [ ] The task is something people would ask an agent to do, and the output answers it: typed values (numbers, booleans, ISO dates, absolute links), no internal ids the user can't use, no fields "just in case".
- [ ] Ads, sponsored and promoted results are dropped by default (a parameter may bring them back, marked).
- [ ]
taskis a short user-facing headline.descriptionsays what comes back and what is filtered.keywordscover the site's language and English. - [ ]
provenancesays how each call was found. Every host outside the site's own domain is named and explained. - [ ]
tos.notessays what the site's terms say about automated access, if known, and how the recipe stays polite.
Automated checks
These run on every submission (wield-build lint runs them all). Errors block publishing.
Schema and lint
The JSON Schema (contracts/recipe.schema.json) and the format lint: template references exist, JMESPath compiles, ids are unique, step URLs stay on site.origins, no Cookie/Host headers, output never references vars. It also warns about two JMESPath traps: params.x inside [? … ], and !a.b.
Safety rules
| Code | Severity | Rule | Why | How to fix |
|---|---|---|---|---|
| S1 origin-unused | error | Every origin in site.origins is called by some step | Each listed origin widens what the executor permits | Remove the unused origin |
| S2 origin-unjustified | error | A host outside the site's registrable domain (also a sister domain such as .com for a .de site) is named and explained in description or provenance.notes | Requests to other sites must be deliberate and reviewable | Name the host and say what it is ("the site's own search API on api.example-cdn.com") |
| S3 dynamic-host | warning (error for start_path starting with //) | The destination is not decided at run time from a template | The executor still blocks unlisted origins, but reviewers should see where calls go | Use a fixed origin and a templated path |
| S4 secret-to-third-party | error, warning when the host is justified | Values derived from the session (vars.*: cookies, tokens) are not sent to another site | That is how a recipe would exfiltrate a session | Keep tokens on the site's own origins |
| S5 data-to-third-party | error for login recipes, else warning | Data read from the site is not passed to another site | Logged-in pages hold personal data | Justify the host, or don't send the data |
| S6 credentials-third-party | warning | Cross-site calls use "credentials": "omit" unless they need cookies | Don't attach the user's cookies for another site without need | Set omit |
| S7 write-unmarked | error | PUT, PATCH, DELETE, GraphQL mutations and POSTs to booking/ordering/sending endpoints are "write": true | Writes must go through the Allow window | Mark the step, and set effect: "write" |
| S7 post-read | warning | A read-only POST (search APIs) says so in the step description | Reviewers need to know why a POST doesn't change anything | "search API, read-only POST" |
| S8 write-loop | error | A write step never loops (foreach.max must be 1) | One Allow must cover exactly what is sent | Split into one write |
| S9 write-no-confirm | error | Write recipes have confirm.summary, with template values (warning if static) | The user must see what will be sent, to whom, when | "Book {{params.slot}} with {{steps.practitioner.name}}" |
| S10 write-no-login | warning | Writes normally require login | Anonymous writes are unusual | Double-check |
| S11 secret-literal | error | No token, API key or session value is written into the recipe | Literals leak and expire; tokens must come from the user's session at run time | Use auth.derive (meta tag, inline script, cookie) or extract from an earlier step. A public key that the site ships to every visitor may stay if provenance.notes says so and names it (then it is only a warning). Persisted GraphQL query hashes are fine |
| S12 personal-literal | error | No email, phone number, IBAN or card number anywhere, including verify.example_params | Recipes are public | Use neutral examples |
| S13 output-vars | error | output.value never reads vars | Vars can hold tokens; output goes to the agent | Return only page data |
| S14 login-check-missing | error | requires_login recipes have a login_check: a cheap GET that passes only when logged in | Wield must tell "not logged in" apart from "broken" | Add one, with expect.login_required_if |
| S15 request-budget | warning above 15, error above 40 requests per run | Runs stay small | Sites allow about 30 requests per hour per user | Fewer steps, smaller loops |
| S16 pacing | warning | Loops of more than 5 requests have delay_ms | Bursts trip bot protection and burden the site | Add a delay, or shrink the loop |
| S17 account-data | warning | Calls to account-looking paths (/account, /orders, /my) set requires_login | A recipe that needs a session must say so | Set requires_login and add a login check |
| S18 provenance / tos | warning | provenance and tos.notes are filled in | Reviewers and users need to know where the recipe came from | Fill them in |
Live verification
Lint can't tell whether a recipe works. Every recipe is run live before it is public:
- Public reads: the test plan (example params, your recorded values, a variant, a zero-results search) passes in a browser, or with
wield-dev runwhere the site answers servers. - Login reads: the author runs it logged in; the login check passes, and fails when logged out.
- Writes: the preview stops at the Allow window and shows the right summary; then the author runs it once for real, with their own data, only if they want the effect. Until that has happened the recipe stays a draft.
After publishing, health checks run every public read recipe daily from our servers where the site allows bots (robots.txt) and answers them. Login, write and browser-only recipes are judged by opt-in run telemetry. A recipe that fails twice in a row is marked degraded on its page and in find_recipe, and the builder tries a repair from a fresh public sample. Repairs come back as new versions for review.
What we accept, and what we don't
We accept recipes for tasks people do on a site with their own account or in public view: searching, comparing, checking their own bookings, orders and messages, and, with the user's Allow, booking, ordering and messaging.
We don't accept recipes that:
- sign in, store or ask for passwords, or capture credentials in any way. Users log in themselves in their browser;
- solve, skip or work around CAPTCHAs, bot checks, rate limits or paywalls. If a site blocks automated requests, the recipe is not for us;
- collect other people's personal data (profiles, contact details), or download whole catalogues;
- send requests anywhere other than the site and the services it uses itself;
- contain code, obfuscated values, or requests the description does not explain.
Review checklist (maintainers)
- Destinations. Every origin belongs to the site, or is the site's own service and is explained. No templated hosts without reason.
- What is sent. Read every step: URL, query, headers, body. Nothing from the session goes anywhere but the site. Only needed headers.
- Reads really read. No POST that changes state without
write: true. Check GraphQL operation names. - Writes.
confirm.summarynames exactly what happens (who, what, when, how much). One write per Allow. Login check present. - Output. Only what the task needs. No tokens, no internal ids that leak, no other people's personal data beyond what the user is looking at (for example a practice's public address is fine; other customers' names are not).
- Politeness. Request count per run, loops, delays. One run should look like one person using the site.
- Verification evidence. Date, method (browser,
wield-dev), the checks that passed. For writes, who ran the live test. - Terms.
tos.notesis honest; sites that forbid automated access in their terms, or actively block it, are declined. - Naming and format.
idis<site>/<task>,taskis a user-facing headline, params have clear names, descriptions and examples.
Versions
Never change a published version in place. A fix is a new version: the old file stays as recipes/<site>/<slug>.v<N>.json (pinned), and <slug>.json holds the latest one with a higher version. Keep id, params and output shape stable unless the change is unavoidable, because agents depend on them. Say what changed in provenance.notes.
Reporting problems
- A recipe that doesn't work: use "Report" on its page, or
request_recipefrom your agent with the site and what failed. - A security issue (a recipe that sends data where it shouldn't, a way around the Allow window): write privately to the contact on the privacy page. Please don't open a public issue.