Skip to content
GridRouterhome

Search

Search providers, capabilities and pages

Docs
Concepts

Build a waterfall

Start from a template or from scratch, choose steps and speed, set stop rules and per-step limits, test on the sandbox, publish and run.

Before you start

1. Start from a template or from scratch

Templates cover the common jobs (work email, verification, mobile, person and company enrichment, full lead enrichment). Find one with GET /v1/waterfalls/templates?runnable=true&q=email or in the dashboard under Waterfalls → Templates, where each card shows whether your vendor keys cover it. Use template opens it in the builder. From scratch, New waterfall starts with one stage for a capability and its first three supported endpoints.

2. Steps

A stage runs one capability. Its steps are tried by the speed profile until the stop rule holds:

  • A routed step is a pool: the capability's endpoints filtered by provider preferences (order, only, ignore, allow_fallbacks), up to max_endpoints. Templates use routed steps only, so they stay vendor-neutral.
  • An endpoint step pins one vendor endpoint, such as hunter/email.find.

To pin vendors on a template, pass pins when creating it: {"pins":{"email":["hunter"]}} puts those vendors first in that stage's pool, and "pins_only": true turns off the fallback.

Each step can set timeout_ms, retries, max_cost_micro, required fields, a minimum confidence, verified-only, what a vendor 4xx does, and a when guard ({"field":"email","op":"exists"}, combined with all, any and not). A stage can also have a when: a stage that's skipped doesn't count against the outcome.

3. Speed, stop rules and budget

ChoiceOptions
Speedfastest (race, hedge 750 ms), balanced (one at a time, hedge at 3 s), cheapest (sequential, cheapest first), custom
Stopfirst_hit, first_verified, until_fields_filled, all_then_merge, condition, combined with stop_mode any or all
Budgetmax_cost_micro on the definition and on each run; the run stops before an attempt that would pass it and returns the partial record

POST /v1/waterfalls/estimate prices a definition or a template without calling any vendor: worst-case and expected cost, expected time and fill, plus every step's quote.

4. Inputs between stages

A stage's input is the run input plus anything earlier stages found. Capability inputs fill themselves from the golden record by name, so a company stage gets the domain that a person stage returned. Map one explicitly with input_map: {"domain":"stages.employer.domain"} or {"email":"input.work_email"}. Mapped values are normalized like run inputs; a website URL becomes a bare domain, for example. Set fields on a stage to keep only those outputs. See merge rules.

5. Test

The builder's test runner runs the draft ("version":"draft"). On a grid_test_ key, the sandbox-* templates and vendors are deterministic and need no vendor key (their calls still draw on your credit at list price). A scenario input scripts each sandbox vendor (swift, deep, sure) with hit, miss, unverified, lowconf, timeout, slow:<ms>, 500, 400 or 429:<s>. For example, swift=miss,deep=slow:2000,sure=hit lets you check fallbacks and stop rules without spending anything.

6. Publish and run

Publishing freezes a semver version (POST /v1/waterfalls/{id}/versions {"bump":"minor"}; from-template publishes 1.0.0). Then run it:

ModeCall
SyncPOST /v1/waterfalls/{id}/run {"input":{…}}, up to 50 s
Asyncadd "async": true or Prefer: respond-async, then poll GET /v1/waterfalls/{id}/runs/{run_id}, stream /events, or pass webhook_url
BatchPOST /v1/waterfalls/batch-run with up to 1,000 rows or a CSV, then GET /v1/lists/{id}
Your endpointPOST /v1/x/{workspace}/{name}, or the MCP tool x_run
import { createGrid } from "@relaygrid/sdk";

const grid = createGrid({ apiKey: process.env.GRID_API_KEY! });
const wf = await grid.waterfalls.fromTemplate("full-lead-enrichment", {
  pins: { email: ["hunter"] },
  max_cost_micro: 200_000,
});
const run = await grid.waterfalls.run(wf.id, { email: "ada@example.com" }, { async: true });
const done = await grid.waterfalls.waitFor(wf.id, run.id);
console.log(done.outcome, done.data, done._provenance);