shinnslist

v1 recipes LIVE

Composite signal queries as deterministic, billable plans. A recipe names the geography, the signals, and the per-signal params; the compiler resolves it to an execution plan before any data is touched — and the estimate binds the charge. Everything below is verified against the deployed functions code (functions/lib/v1-recipes.js, functions/v1/recipes/*). Planned surfaces stay DRAFT-badged.

the plan is the price. calls = parcels × signals, deterministic, computed by compileRecipe before a single adapter runs. The estimate binds the charge: calls_estimated must equal calls_consumed for the same recipe version + parcel set, or the difference is credited — only completed evaluations consume quota, so an estimate above actual is automatically credited. Save and estimate are unbilled; only runs bill, and only for completed evaluations. Every cost figure carries "modeled": true — the $0.003/call price is the plan's pricing math (M), not a price anyone has been charged. No real charges exist today.

the three endpoints

endpointwhat it doesbilling
POST /v1/recipessave a recipe — the unit of reuse. Body must be { recipe, name? }.unbilled
POST /v1/recipes/estimatepre-execution cost estimate. Body must be { recipe } or { recipe_id }. Idempotency-Key not required.unbilled
POST /v1/recipes/{id}/runexecute a saved recipe. Idempotency-Key header required (400 idempotency_key_required without it).billed N×P (signals × parcels), completed evaluations only

Save returns 201 with recipe_id (r_ + 24 hex chars), recipe_version (today: 1), name (your name, sliced to 120 chars, or the recipe's own name), signals, parcels_in_scope, and stored_via (d1 when the binding is present, memory otherwise).

the recipe object — exact validated shape

{
  "geography": {
    "type": "parcel_list",          // county_list | radius | parcel_list | bounding_box
    "parcel_keys": [                // required for parcel_list
      { "state_fips": "51", "county_fips": "087", "apn_normalized": "E-123-4" }
    ]
  },
  "signals": [                      // non-empty array; each entry needs a signal name
    { "signal": "absentee_high_equity", "params": { } }
  ],
  "logic": {
    "exclusions": [                 // evaluated AND billed like any signal
      { "signal": "recent_buyer", "params": { } }
    ]
  }
}

compile-time errors

codestatuswhen
bad_params400recipe not an object; recipe.geography missing or not an object; geography type not one of the four; signals missing, not an array, or empty; a signal entry without a string signal name; logic.exclusions present but not an array
unknown_signal400a signal name not in the 228-signal catalog
signal_not_built404a catalog signal with no working v1 adapter yet
invalid_parcel400a parcel_key failing the FIPS/APN shape above

geography: exact vs streaming LIVE

MVP geography support is honest about what resolves:

geography typeestimaterun
parcel_listexact: parcels_in_scope is the count, calls_estimated is parcels × signals, cost_estimated_usd is computed at the $0.003/call modeled priceexecutes
county_list / radius / bounding_boxstreaming: parcels_in_scope: "streaming", calls_estimated: null, cost_estimated_usd: null — no parcel-inventory database exists, and inventing a parcel count would be inventing coverage. The parcel set resolves at run time via paged execution (1,000 parcels/page); per-page billing is reported as a running total. The charge is never open-ended.400 bad_params with a geography_not_supported: message — run executes over parcel_list geography only. Re-save the recipe with parcel_list.

The estimate carries its own hint: "note": "Streaming geography: pass parcel_list geography for an exact pre-run figure." Streaming run execution is planned, not shipped (see DRAFT below).

the estimate binds the charge

Every estimate response carries: api_version (v1), estimate_id (est_ + 24 hex chars), recipe_hash (sha256 over the recursively key-sorted recipe JSON — stable regardless of key insertion order), recipe_version, parcels_in_scope, signals, calls_estimated, cost_estimated_usd, price_per_call_usd (0.003, modeled), plan_coverage, quota, and the idempotency note "Re-running within 24h with the same recipe version costs $0 additional (idempotency)."

execution: pages, quota, and what bills

planned: streaming run execution DRAFT

county_list, radius, and bounding_box recipes estimate honestly today but cannot run — the parcel-inventory database does not exist, so POST /v1/recipes/{id}/run answers 400 geography_not_supported. When the inventory ships, these geographies execute with the same paged, per-page-billed, estimate-bound contract. The estimate's streaming shape (calls_estimated: null) will be replaced by a real pre-run figure — not final.

planned: bound charges for streaming geography DRAFT

Today the charge can only be bound for parcel_list recipes, because only they have an exact parcel count. Until the inventory ships, streaming-geography charges cannot be pre-bound — the per-page running-total reporting in estimate responses is the contract, and no exact total is printed because none is knowable. Exact binding rules for streaming geographies are not final.

paths you won't find. There is no GET /v1/recipes list endpoint, no recipe update endpoint, and no delete endpoint anywhere in the codebase — recipes are saved, estimated, and run, and that is the full surface. Any list/update/delete path mentioned elsewhere would be invented, which is why none are printed here.

next: usage & billing · api keys · api reference · docs hub