# Build a waterfall (/docs/concepts/build-a-waterfall)



## 1. Start from a template or from scratch [#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 [#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](/docs/concepts/routing#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 [#3-speed-stop-rules-and-budget]

| Choice | Options                                                                                                                               |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| Speed  | `fastest` (race, hedge 750 ms), `balanced` (one at a time, hedge at 3 s), `cheapest` (sequential, cheapest first), `custom`           |
| Stop   | `first_hit`, `first_verified`, `until_fields_filled`, `all_then_merge`, `condition`, combined with `stop_mode` `any` or `all`         |
| Budget | `max_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 [#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](/docs/concepts/merge-rules).

## 5. Test [#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 [#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:

| Mode          | Call                                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Sync          | `POST /v1/waterfalls/{id}/run` `{"input":{…}}`, up to 50 s                                                                                 |
| Async         | add `"async": true` or `Prefer: respond-async`, then poll `GET /v1/waterfalls/{id}/runs/{run_id}`, stream `/events`, or pass `webhook_url` |
| Batch         | `POST /v1/waterfalls/batch-run` with up to 1,000 rows or a CSV, then `GET /v1/lists/{id}`                                                  |
| Your endpoint | `POST /v1/x/{workspace}/{name}`, or the MCP tool `x_run`                                                                                   |

```ts
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);
```

