Waterfalls
Ordered provider attempts that stop at the first usable result.
A waterfall tries providers in order until one returns a usable result.
Waterfalls find a value the row is missing. To check an address the row already has, see Email verification — a different chain with its own catch-all escalation.
Capabilities
--capability takes work_email, mobile_phone, or linkedin_url. Each writes the value, a match confidence, and the provider that produced it. mobile_phone adds --verify-phone (line type and carrier); linkedin_url gates every hit through name-and-company identity validation.
Which provider each chain tries first and what it falls back to is listed under Waterfall defaults. Override the order for one run with --provider-order.
Previewing an enrichment
oxygen enrich-column preview <contacts-table> \
--capability work_email \
--linkedin-url-column linkedin_url \
--company-domain-column domain \
--limit 10 \
--jsonPreview calls no provider and spends 0 credits. It returns the resolved provider profile, per-provider preflight, and the credit estimate that sizes --max-credits.
Per-cell provenance
An enrichment cell records the whole call trace — source is the tool that won:
{
"value": "ada@acme.com",
"source": "leadmagic.email_finder",
"confidence": "verified",
"attempts": [
{ "provider": "hunter", "operation": "email_finder", "status": "no_result", "latency_ms": 412 },
{ "provider": "leadmagic", "operation": "email_finder", "status": "success", "latency_ms": 380, "billing": { "credits_used": 1 } }
]
}Attempt status is success, no_result, skipped, error, or incomplete_async (the provider took the request async and is polled later — not a failure). A skipped attempt carries a reason such as missing_input or credit_ceiling_reached. Inspect a cell with oxygen cells inspect.
Cost behavior
Billing depends on the intent. Work-email and phone waterfalls charge only the lane that returns a value, so a row that resolves early costs less than one that exhausts the list. LinkedIn-URL and company-field waterfalls charge every managed lane they attempt, whether or not it finds anything — see tool costs. --max-credits is required on a live run and caps total spend either way.
Lanes priced above 500 credits per row are pre-skipped unless you pass --allow-premium-lanes, so an unattended run cannot bill-shock. They appear in the trace as skipped with reason premium_lane_not_opted_in — that, not an empty chain, is why an expensive mobile_phone provider never fires by default.
Running live
oxygen enrich-column run <contacts-table> \
--capability work_email \
--linkedin-url-column linkedin_url \
--company-domain-column domain \
--limit 100 \
--approved \
--max-credits 100 \
--jsonCompany field enrichment has its own helper. run defaults to live mode, so it needs --approved and --max-credits:
oxygen companies enrich preview <accounts-table> --missing-fields headcount,industry --limit 10 --json
oxygen companies enrich run <accounts-table> --missing-fields headcount,industry --limit 100 --approved --max-credits 100 --jsonRelated
- Provider reference — the default and fallback provider for every chain.
- Columns — enrichment columns and materialized outputs.
- Cells — inspecting per-attempt history.