> ## Documentation Index
> Fetch the complete documentation index at: https://80x.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Cheapest-first enrichment waterfall

> Enrich a new company through ordered providers, stop on the first hit, and write into dedicated source fields so you never pay twice.

Enrich a new company through ordered providers, stop on the first hit, and write into dedicated source fields so you never pay twice. To enrich means to fill in missing details about a company, such as its industry or headcount, by calling an outside data provider. A waterfall calls those providers in order, cheapest first, and stops as soon as one returns the data. This recipe rebuilds Clay's stop-on-first-hit waterfall economics natively in Attio.

Here is the flow. A [Record created](/reference/triggers/record-created) trigger fires on the Companies object. A free [Filter](/reference/conditions/filter) skips records that already hold good data. A [Send HTTP request](/reference/utilities/send-http-request) block calls your cheapest provider first. A [Parse JSON](/reference/utilities/parse-json) block pulls out the fields you need. An [If](/reference/conditions/if) condition stops the run the moment the cheap provider hits. An [Update record](/reference/records/update-record) block writes the result into dedicated `Enriched ...` attributes.

Only when the first provider misses does the run fall through to a second, pricier provider. So you pay for the expensive lookup only on the records the cheap one could not fill.

| | |
| - | - |
| **Difficulty** | Advanced |
| **Time to build** | About 30 minutes |
| **Trigger** | [Record created](/reference/triggers/record-created) on Companies, or a [Record command](/reference/triggers/record-command) |
| **Blocks used** | [Filter](/reference/conditions/filter), [Send HTTP request](/reference/utilities/send-http-request), [Parse JSON](/reference/utilities/parse-json), [If](/reference/conditions/if), [Update record](/reference/records/update-record) |
| **Credit cost per run** | 1 to 3 credits, depending on how far down the waterfall the run falls |
| **Plan required** | Pro recommended. See [Credits & pricing explained](/reference/credits-and-pricing) |

## Prerequisites

* A workspace plan that includes the new Workflows engine. Pro is recommended for the [Send HTTP request](/reference/utilities/send-http-request) volume a waterfall generates. Plan allowances live on [Credits & pricing explained](/reference/credits-and-pricing).
* Two enrichment providers with REST APIs, ordered cheapest first. For example, a domain-cheap provider as provider A and a richer provider as provider B. You need an API key for each, stored as a workspace secret.
* The Companies object with a populated `domain` attribute to use as the lookup key.
* Dedicated source attributes that no human edits directly: `Enriched industry`, `Enriched employee count`, and `Enriched source`. The last is a text or select attribute that records which provider filled the row.
* An optional `Industry verified` checkbox, or any human-owned attribute, so the [Filter](/reference/conditions/filter) can skip records a teammate has already confirmed.

## How it works

The workflow runs from the cheapest provider to the priciest. It stops the moment it has the data. It writes results only into fields humans never touch.

The [Filter](/reference/conditions/filter) is free, so gating costs nothing. Gating means checking a record and deciding whether the run should continue. You pay one credit per HTTP call you actually make, plus one credit for the single write at the end. A record that the cheap provider fills costs 2 credits and never touches provider B.

1. [Record created](/reference/triggers/record-created) on Companies fires for each new company.
2. [Filter](/reference/conditions/filter) continues only when `Enriched industry` is empty and `Industry verified` is unchecked.
3. [Send HTTP request](/reference/utilities/send-http-request) calls provider A with the company `domain`.
4. [Parse JSON](/reference/utilities/parse-json) extracts `industry` and `employee_count` from provider A's response.
5. [If](/reference/conditions/if) checks whether provider A returned an industry. When it did, the run jumps straight to the write.
6. [Send HTTP request](/reference/utilities/send-http-request) calls provider B with the same `domain`, on the miss branch only.
7. [Parse JSON](/reference/utilities/parse-json) extracts the same two fields from provider B's response.
8. [Update record](/reference/records/update-record) writes the winning values into `Enriched industry`, `Enriched employee count`, and `Enriched source`.

## Build steps

<Steps>
  <Step title="Add the Record created trigger">
    Create a new workflow and add the [Record created](/reference/triggers/record-created) trigger. Set its object to Companies so the workflow fires once per new company. You can also enrich on demand from a record's actions menu. To do that, swap in a [Record command](/reference/triggers/record-command) trigger. The rest of the build is identical.

    | Field | Value |
    | - | - |
    | Trigger | Record created |
    | Object | Companies |
    | Run as | A workspace member with edit access to Companies |
  </Step>

  <Step title="Gate on the free Filter">
    Add a [Filter](/reference/conditions/filter) block immediately after the trigger. Set it to continue only for records worth enriching. That means the target field is empty and no human has verified it. The Filter is free, so this gate costs nothing and protects every paid step after it.

    | Field | Value |
    | - | - |
    | Condition 1 | `Enriched industry` is empty |
    | Condition 2 | `Industry verified` is unchecked |
    | Logic | Match all conditions |
    | When no match | Stop the run |
  </Step>

  <Step title="Call provider A, the cheapest">
    Add a [Send HTTP request](/reference/utilities/send-http-request) block. Point it at your cheapest provider and pass the company `domain` from the trigger. Pick a provider endpoint that responds within the 2-minute HTTP timeout. Respect that provider's rate limits too, so a burst of new companies does not trip a `429`.

    | Field | Value |
    | - | - |
    | Method | `GET` |
    | URL | `https://provider-a.example.com/v1/enrich?domain={{trigger.record.domain}}` |
    | Header `Authorization` | `Bearer {{secrets.PROVIDER_A_KEY}}` |
  </Step>

  <Step title="Parse provider A's response">
    Add a [Parse JSON](/reference/utilities/parse-json) block and feed it the response body from the provider A call. Parsing JSON reads the provider's raw response and turns it into named fields. Define a schema for the two fields you care about so later blocks can reference them by name.

    | Field | Value |
    | - | - |
    | Input | `{{step_3.response.body}}` |
    | Schema field `industry` | string, optional |
    | Schema field `employee_count` | number, optional |
  </Step>

  <Step title="Stop on the first hit">
    Add an [If](/reference/conditions/if) condition. When provider A returned an industry, route to the write step and skip provider B entirely. When the industry is empty, fall through to the next provider. The condition itself is free. Stopping early is what makes this a waterfall rather than two full lookups every time.

    | Field | Value |
    | - | - |
    | Condition | `{{step_4.industry}}` is not empty |
    | If true | Go to the Update record step |
    | If false | Continue to the provider B call |
  </Step>

  <Step title="Call provider B, richer, on the miss branch">
    On the false branch only, add a second [Send HTTP request](/reference/utilities/send-http-request) block. Point it at your richer, pricier provider, and again pass `domain`. This call sits behind the [If](/reference/conditions/if) condition. So you pay provider B, and spend the credit, only on records provider A could not fill.

    | Field | Value |
    | - | - |
    | Method | `GET` |
    | URL | `https://provider-b.example.com/lookup?domain={{trigger.record.domain}}` |
    | Header `X-API-Key` | `{{secrets.PROVIDER_B_KEY}}` |
  </Step>

  <Step title="Parse provider B's response">
    Add a [Parse JSON](/reference/utilities/parse-json) block for provider B's body and map its shape to the same two fields. Provider B may name them differently. In the schema, map `sector` to `industry` and `headcount` to `employee_count`.

    | Field | Value |
    | - | - |
    | Input | `{{step_6.response.body}}` |
    | Schema field `industry` | string, optional, from `sector` |
    | Schema field `employee_count` | number, optional, from `headcount` |
  </Step>

  <Step title="Write only to source fields">
    Add an [Update record](/reference/records/update-record) block targeting the triggering company. Write the winning values into your dedicated `Enriched ...` attributes only. Never write into the human-owned `Industry` or `Employees` attributes. Set `Enriched source` to the provider that won, so you can audit coverage later.

    | Field | Value |
    | - | - |
    | Record | `{{trigger.record}}` |
    | `Enriched industry` | `{{step_4.industry}}` or `{{step_7.industry}}`, the branch that ran |
    | `Enriched employee count` | `{{step_4.employee_count}}` or `{{step_7.employee_count}}` |
    | `Enriched source` | `Provider A` or `Provider B` |
  </Step>
</Steps>

<Warning>
  Write enriched values only into the dedicated `Enriched ...` attributes. If the [Update record](/reference/records/update-record) block points at the human-owned `Industry` or `Employees` attribute, it silently overwrites a teammate's manual entry on every run.
</Warning>

## Variable mapping

| Variable | Source block | Field | Example value |
| - | - | - | - |
| `{{trigger.record.domain}}` | Record created | `domain` | `acme.com` |
| `{{step_3.response.body}}` | Send HTTP request (provider A) | Response body | `{"industry":"Software","employee_count":240}` |
| `{{step_4.industry}}` | Parse JSON (provider A) | `industry` | `Software` |
| `{{step_4.employee_count}}` | Parse JSON (provider A) | `employee_count` | `240` |
| `{{step_6.response.body}}` | Send HTTP request (provider B) | Response body | `{"sector":"SaaS","headcount":255}` |
| `{{step_7.industry}}` | Parse JSON (provider B) | `industry`, from `sector` | `SaaS` |
| `{{step_8.record}}` | Update record | Updated company | `Acme Inc.` |

## Credit cost worked example

The [Filter](/reference/conditions/filter), [Parse JSON](/reference/utilities/parse-json), and [If](/reference/conditions/if) blocks are free logic and lookups, so they never consume credits. You pay 1 credit per [Send HTTP request](/reference/utilities/send-http-request) you actually fire, plus 1 credit for the single [Update record](/reference/records/update-record) write. The whole point of the waterfall is that most runs never reach provider B.

Three paths cost three different amounts. A run where the cheap provider hits fires one HTTP call to provider A plus one write, so it costs 2 credits. A run where provider A misses and provider B hits fires two HTTP calls plus one write, so it costs 3 credits. A run that the Filter stops at the gate fires nothing and costs 0 credits.

Now work an example. Assume 800 new companies a month, a 70% hit rate on provider A, and a 10% Filter skip rate. That breaks down into three groups:

* 80 skipped runs at 0 credits.
* 504 runs that stop on provider A at 2 credits each, for 1,008 credits.
* 216 runs that fall through to provider B at 3 credits each, for 648 credits.

The total is roughly **1,656 Attio credits a month**, plus whatever each provider bills you per call. Compare that against your plan's monthly allowance on [Credits & pricing explained](/reference/credits-and-pricing).

You can also tag enrichment quality with an AI block such as [Classify record](/reference/ai/classify-record). Gate it behind the same Filter. Its cost is variable and token-based, so do not assume a fixed number. Run a test batch of about 10 records and read the per-run AI cost in the run viewer.

## Test plan

1. In the workflow editor, open the test panel. Pick a real Companies record that has a `domain` but an empty `Enriched industry`, for example `acme.com`. Fire a test run.
2. Confirm the [Filter](/reference/conditions/filter) shows **continue** in run history. It should pass because both conditions are true.
3. Confirm the provider A [Send HTTP request](/reference/utilities/send-http-request) returns a `200` and a body, and that [Parse JSON](/reference/utilities/parse-json) populates `{{step_4.industry}}` with `Software`.
4. Confirm the [If](/reference/conditions/if) routed straight to [Update record](/reference/records/update-record) and that provider B never ran. Open the company and verify `Enriched industry`, `Enriched employee count`, and `Enriched source` are set, while the human `Industry` attribute is untouched.
5. Test the miss branch. Pick a company whose domain provider A does not cover, such as a stealth or very new company. Confirm the run falls through to provider B and the write still lands.
6. **Run a deliberately failing input.** Fire the workflow on a company whose `Industry verified` checkbox is already ticked. The Filter stops the run at the gate, no HTTP call fires, no credits are spent, and run history shows the run ending at the Filter. This proves the waterfall never overwrites verified data.
7. Read each block's output in the run viewer under run history. Confirm credits were charged only for the steps that actually executed.

## Failure modes

| Symptom | Cause | Fix |
| - | - | - |
| HTTP request times out | The provider endpoint did not respond within the 2-minute engine limit | Switch to a faster endpoint, or move that provider lower in the waterfall so it runs less often. |
| `429 Too Many Requests` on a burst of new companies | You are exceeding the provider's rate limit | Add a [Delay](/reference/delays/delay) before the call, or batch enrichment with a [Record command](/reference/triggers/record-command) instead of firing per create. |
| Parse JSON fails on some responses | The provider returns a different shape on misses or errors | Mark schema fields optional, and add an [If](/reference/conditions/if) that checks for a non-`200` status before parsing. |
| Provider B runs on every record | The [If](/reference/conditions/if) condition references the wrong field or is inverted | Point the condition at the provider A `industry` output and confirm the true branch jumps to the write. |
| Human-entered industry gets clobbered | The [Update record](/reference/records/update-record) block writes to `Industry` instead of `Enriched industry` | Re-point every mapping to the dedicated `Enriched ...` attributes, and never write to human-owned fields. |
| Records re-enriched on every edit | You used [Attribute value changed](/reference/triggers/attribute-value-changed) without a tight filter | Keep the trigger on Record created, or filter strictly so the run continues only when the source field is still empty. |

## Variations

### Three-tier waterfall

Add a third provider for the records the first two both miss. Duplicate the [If](/reference/conditions/if), [Send HTTP request](/reference/utilities/send-http-request), and [Parse JSON](/reference/utilities/parse-json) trio on the second miss branch, ordered cheapest to priciest. The economics still hold. Each tier runs only on what the tier above could not fill. A record that the first provider covers still costs 2 credits.

### Command trigger instead of create

Swap the [Record created](/reference/triggers/record-created) trigger for a [Record command](/reference/triggers/record-command). Now a rep enriches a single company on demand from its actions menu. This avoids spending credits on bulk imports you do not care about. It also lets you re-run the waterfall after a company changes domains. Everything from the Filter down stays the same.

## See also

* [Webhook lead intake with safe upsert](/guides/capture-and-qualify/webhook-lead-intake)
* [Variables & data passing](/explanation/variables)
* [Send HTTP request](/reference/utilities/send-http-request)
* [Parse JSON](/reference/utilities/parse-json)
* [Credits & pricing explained](/reference/credits-and-pricing)

***

*Source: [Attio Workflows block library](https://attio.com/help/reference/automations/workflows/workflows-block-library). Last reviewed against the Attio Help Center, June 2026.*


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.