Skip to main content
Use this page to read a recipe correctly and to write one that passes validation.

Overview

A recipe is one JSON object. Its kind decides which fields apply: The JSON Schema at schema/recipe.schema.json in the public registry repository is the formal definition. A field that is not listed for a kind is rejected. The one exception is an optional $schema pointer for editor tooling, which must be exactly ../../schema/recipe.schema.json.

Fields every recipe shares

string
required
Lowercase, matching ^[a-z0-9][a-z0-9.-]*$. A merchant or signup uses its primary host. A platform uses its platform slug.
string
required
verified, partial, unverified or dead-end. See Recipe statuses.
string
A date in YYYY-MM-DD form. Required when status is verified.
string
How the verification was proven. Required when status is verified. Up to 2,000 characters.
string[]
Things that will catch out the next agent. One sentence each, describing something you actually observed. Up to 50 entries of up to 1,000 characters.
string[]
Gates the next agent can clear before checkout starts. For checkout recipes: delivery_location, region_select or account_required. No repeats. Omit the field rather than sending an empty array. Unlike deadEnd, a precondition never makes a store unusable.
string
Free text for judgement that does not fit a field, for example whether a blocker is the platform’s or the store’s choice. Up to 2,000 characters.

Platform recipe

Describes how a whole platform’s checkout works. Platform recipes are shared by every store on that platform, so a maintainer reviews each change.
string
required
The literal "platform".
string
required
A human-readable name, for example "WooCommerce + Stripe". Up to 200 characters.
object
required
How to recognise this platform. Include at least one of the four signals.
string
required
Where the card fields live. One of stripe-payment-element, stripe-split, shopify-card-fields, direct-inputs or varies.
string
required
Where the security code goes, as a selector of up to 300 characters. Named on its own because the card fill order depends on it.
object
required
One instruction per checkout phase, up to 1,000 characters each.
object
Named selectors, up to 20. Each name is up to 100 characters. Each value is a selector or an array of up to 10 fallbacks tried in order. Each selector is up to 300 characters.
The platform id must be one of the platforms the SDK can execute. See Supported platforms.

Example

Merchant recipe

Pins down one store.
string
required
The literal "merchant".
string[]
required
Every host this store serves from. At least one, up to 50, each up to 253 characters. No *. wildcards. A host matches itself and its subdomains. A host may belong to only one merchant recipe, and www. and a trailing dot are ignored when comparing.
string
required
A platform recipe id, or "custom" for a store on no known platform.
string
Overrides the platform’s value when this store differs.
string
Overrides the platform’s value when this store differs. Up to 300 characters.
object
Selector overrides applied on top of the platform recipe’s selectors, keyed the same way. Up to 20. Each name is up to 100 characters. Unlike a platform’s selectors, each value is exactly one non-empty selector string of up to 300 characters: an array of fallbacks is rejected with invalid_recipe. Include only the ones you actually used.
object
Present if, and only if, status is "dead-end".
string
An http:// or https:// product URL a later run can use to test again. Up to 2,048 characters.
object[]
The ordered steps from the entry URL to the payment page, 1 to 30 of them. Omit the field rather than sending an empty array. A route is replayed read only, so no step types anything. See Route steps.
object
How a checkout with no guest path signs in. Allowed only when preconditions includes account_required, and rejected otherwise. Signing in comes first: the checkout route is walked afterwards, in the same browser. A recipe with account_required and no signIn is still valid.

Route steps

Each step is one object with a host and exactly one of these:
  • The first step is always a url step: the entry URL.
  • host is the one concrete host the step happens on. It must be covered by the recipe’s hosts (the host itself or a subdomain of one), so a checkout that hands off to another host lists that host in hosts too. In signIn.route, signIn.hosts count as well. IP addresses and private network names such as localhost or .internal are rejected.
  • url is http:// or https://, on the step’s own host, up to 2,048 characters. Origin and path only: no query string, no fragment and no credentials. A step that needs search parameters reaches them by a click.
  • selector is up to 300 characters. A url step and a manual step take no selector.
  • note is required on a manual step, up to 500 characters.
  • Every value is a single line, with no control characters.

Example: a working store

Example: a dead end

Signup recipes

Signup recipes describe a site’s account signup form. They follow the same submission flow as checkout recipes, with their own vocabulary. Their statuses claim an account creation rather than a purchase; see Statuses on a signup recipe.
A signup recipe describes a form. It is not permission to open an account. Creating a login still goes through a signup request the account holder approves. See Agent signup.
  • Field names for fields and overrides: email, username, password, confirmPassword, fullName, firstName, lastName, dateOfBirth, phone, gender, street1, city, state, zip, country and submit. No other keys. The address keys are named after the address token components, so each one says which address token fills it.
  • ownerSteps lists, in order, what the account owner completes after submit: email_code, sms_code, email_link or captcha. Up to 6 entries. Omit the field rather than sending an empty array. Repeats are real: a site that asks for a code, then a CAPTCHA, then a code again is ["email_code", "captcha", "email_code"].
  • Signup preconditions: region_select, consent_gate or mode_toggle. The checkout preconditions are rejected here.
  • Signup dead end types: turnstile, captcha, automation-blocked, sso-only, invite-only, phone-required, waitlist or payment-required.
  • Signup platform ids: auth0, workos, okta, cognito, shopify-customer-accounts, shopify-classic-accounts, google-oauth or apple-oauth.

Selector conventions

  • Prefer a stable id or class over a text match.
  • Use an array as a fallback chain where a platform has changed its markup across versions. Entries are tried in order. Only a platform’s selectors and a signup platform’s fields take an array. overrides, on a store or a signup, take one selector per field.
  • Target a payment iframe by a stable CSS selector. Never rely on enumerating frames: list_frames is refused once a token has been filled.

What a recipe may not contain

Screening rejects a submission that contains any of these. Full rules and rejection reasons: Contribute a recipe.

Validation rules

The schema checks each recipe on its own. These registry-wide rules are checked as well:
  • id is unique within its family (checkout or signup).
  • A host is claimed by at most one recipe in its family.
  • A merchant’s platform names an existing platform recipe, or "custom".
  • A signup’s platform names an existing signup platform recipe, or "custom".
  • verified requires evidence and lastVerifiedAt.
  • deadEnd and status: "dead-end" always appear together.
  • signIn appears only when preconditions includes account_required.
  • Every route and signIn.route step’s host is covered by the recipe’s hosts (plus signIn.hosts for a sign-in), and the first step is a url step.
To check a file locally, clone the public registry repository and run:
An editor-only $schema pointer, and any nested $comment, are removed when the API parses a submission. Leaving them in your file is fine.