Writing a recipe

A recipe turns a booking job into a firm quote, a fare menu, or a paid ticket on one supplier's website. Here is the path from zero to an activated domain.

1 — Start from the contract

Clone recipe-spec. It contains the normative README, the SDK (sdk/index.mjs — zero dependencies), a complete toy recipe (example.com/recipe.mjs) that speaks every signal, the JSON Schemas, and the offline conformance suite:

git clone https://github.com/brij-digital/recipe-spec
cd recipe-spec && node test-sdk.mjs   # the corpus your recipe must satisfy

2 — The shape of a recipe

One ES module, driven entirely by environment variables, talking to the runtime only through stdout signals. Three tasks, selected by TASK:

TaskInput (env)Output signal
searchDCITY, ACITY, DDATE (+RDATE round trip)emitResult("search", {route, count, offers})
offer-detailsFLIGHT (the offer id)emitResult("offer-details", {base, fares}) — each fare: price + the supplier's own condition wording
bookFLIGHT, FARE_PRICE, PRICE_CAP, PAX_LIST, CONTACT_EMAIL, PURCHASE_MODEemitResult("book", {payClicked, paymentStatus, reference…})

The SDK stamps every signal with {v: 1, task} and refuses to emit a malformed one (exit 7). The runtime revalidates everything server-side — honesty is structural, not optional.

3 — The rules that matter

4 — The manifest

protocol_version: 1
domain: yoursupplier.com
recipe: recipe.mjs
flow: guest                 # guest · ephemeral-account · account
kind: browser               # or api (a connector: same signals, no browser)
oracle:
  type: email
  sender_domains: [yoursupplier.com]   # DKIM d= that proves a ticket
coverage:
  regions: [EU]
  cabins: [Y]
  trip_types: [ow, rt]
payment:
  currency: USD
  max_amount: 150
routing:
  partner: your-name

5 — Test locally

node test-sdk.mjs                                # contract conformance, offline
TASK=search DCITY=mad ACITY=bcn DDATE=20261215 node yourdomain/recipe.mjs
TASK=book … PURCHASE_MODE=dry node yourdomain/recipe.mjs   # dry: stops before Pay, no card env

6 — Submit (1 USDC)

POST /recipes/submissions — x402-paid. The paying wallet is your durable identity: payout, stake and reputation hang off it.

curl -X POST https://travel.brij.fi/recipes/submissions \
  -d '{"author_wallet":"…","domain":"yoursupplier.com",
       "manifest_yaml":"…","recipe_js":"…"}'
# → {submission_id, status, findings}; poll GET /recipes/submissions/{id}

The pipeline: static review → sandboxed dry-run against the live supplier (runner-collected evidence, synthetic passenger, a dry run that clicks Pay is refused unconditionally) → an AI judge comparing evidence, manifest and code. The response is 200 whether queued or rejected — the findings are what the fee buys. A pass goes to human activation (registry merge + pin), then probation under capped amounts.

7 — Live: watch yourself

GET /recipes shows your domain's settlement funnel and run telemetry publicly; GET /partner/runs gives you your own failures with diagnostics and replay ids. Infra failures are attributed to the platform, never to your recipe. The best recipe holds the domain; a displaced author keeps a residual finder's fee.

External recipes currently run in dry mode only: modes that touch card data remain first-party until the capability-isolation phase lands. Your dry-run conformance and stats build your standing in the meantime.

© 2026 Brij Digital · Terms · Privacy · [email protected]