Docs
Providers

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 to find a value the row is missing (verify_email grades one it already has). 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.

The default linkedin_url chain starts with OXYGEN's managed profile lookup (scraper.linkedin_profile_lookup): it finds the company from its domain or LinkedIn page, searches that company's people by name, and turns a member-id result into the public profile URL. Icypeas is the fallback.

A personal (non-work) address is an option of the work-email run: add --include-personal-email to --capability work_email. It uses its own single managed lane keyed on linkedin_url (the enrichment catalogue names it) and, like work_email, bills only on a hit; there is no catch-all escalation and no pattern pre-step. The winning address is graded by MillionVerifier and lands in the same cell's personal_email, never in the work address. --capability personal_email on its own still works and writes personal_email, personal_email_provider, personal_email_status, and personal_email_enriched_at — separate columns from work email.

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 \
  --json

Preview calls no provider and spends 0 credits. It returns the resolved provider profile, per-provider preflight, and the credit estimate that sizes --max-credits.

Own-key fees and retries

A lane that runs on your own key bills no managed price; your provider bills the call under its own terms. Each successful own-key call pays OXYGEN's 0.05-credit platform fee (billing), even when it finds nothing. Free-on-miss provider pricing does not waive that platform fee. provider_cost_breakdown[].byok_platform_fee_credits_per_call shows the fee for each lane, and the estimates include it. The run reserves it, and a row stops before an own-key lane that its remaining --max-credits cannot pay for.

--max-credits caps OXYGEN credits only: it never limits what your provider bills your account. Cap own-key calls with an enforced provider limit (oxygen limits provider set). A live find that dispatched an own-key lane lists it in own_key_spend, and its retry_safe is false: retry_safe is true only when a re-run spends nothing (no lane errored, charged OXYGEN credits, ran on your own key, or reported unknown billing), so repeating an own-key miss calls your provider again.

A free find preview names each finder and verification leg's configured credential_mode in estimate.legs, even before a key is connected. user_api_key means your provider account and any applicable OXYGEN platform fee; managed means OXYGEN's credential. An own-key lane without a connected key is skipped at execution. billing_rules_url in previews and results links back to these definitions. The vendor may charge failed or interrupted attempts: an absent OXYGEN fee is not evidence that retrying is free.

For a one-off company lookup, oxygen find company --domain <domain> --json (its default mode is dry_run) prints the same thing as a plan: would_attempt lists, per requested field, the lanes in the order a live run would walk them, each with its estimated_credits and, where the run would not attempt it, a pre_skip reason. It is a plan, not the answer — found stays null until you run it live. The order is a fixed preference per field, not cheapest-first; a free lane behind a priced one is reached only if the priced one finds nothing.

pre_skip reasons:

  • missing_identity_input — the lane keys on an identity the row does not hold yet (requires_identity names it, usually linkedin_url for a domain-only row). When another lane can resolve that identity from what the row holds, the plan adds a prerequisite: true step ahead of the field and plans the field as if it succeeds; a linkedin_url or domain field you request yourself does the same for the fields listed after it.
  • byok_only — the provider runs only on a key you connect (oxygen integrations connect <provider> --api-key <key>); OXYGEN holds no managed account for it. Its estimated_credits is 0 because nothing managed would be billed, not because it is free.
  • credit_ceiling_reached — the lane's estimate does not fit under the --max-credits you passed.
  • identity_prerequisite_unaffordable — a priced identity pre-step would eat the budget the requested field itself needs.
  • provider_not_selected — table previews only: the lane belongs to a provider you left out of --providers, so the run never reaches it. Its credits show what adding that provider would cost.
  • check_technologies_missing — technology_check was requested without --check-technologies.

To ask yes/no questions about a company's stack, request the technology_check field and name the technologies. It reads the detected stack, so the technology lookup runs and bills as usual:

oxygen find company --domain acme.com --fields technology_check --check-technologies hubspot,salesforce --json

Some company lanes cost nothing at all (a derived field reads a source field's payload for 0 credits). oxygen find company ... --mode live --max-credits 0 is therefore a real request, not a rejected one: it runs only the zero-credit lanes, skips every priced one as credit_ceiling_reached, and bills nothing. Use it to ask "answer this only if it is free". The person capabilities (find email, find phone, find linkedin, find personal-email, find person) have no free lane, so they still require a ceiling above 0.

One-off person lookups

oxygen find runs the same waterfalls for one person without a table. Every command previews for free (dry_run) until you pass --mode live with a --max-credits ceiling.

oxygen find person --linkedin-url https://www.linkedin.com/in/example --json
oxygen find personal-email --linkedin-url https://www.linkedin.com/in/example --json

find person returns one profile block from one managed lookup: name, current company and its LinkedIn URL, job title and start date, education, headline, bio, location, and follower count. Given an email, or a name with --company-domain or --company-name, it first resolves the LinkedIn URL through the LinkedIn-URL waterfall, priced in the preview as a prerequisite. A Sales Navigator link (--sales-navigator-url) resolves on your own HarvestAPI key. find personal-email runs the personal-email lane above and needs --linkedin-url.

Per-cell provenance

An enrichment cell records the whole call trace — source is the tool that won:

{
  "value": "[email protected]",
  "source": "leadmagic.email_finder",
  "confidence": "verified",
  "attempts": [
    { "provider": "icypeas", "operation": "email_discovery", "status": "no_result", "latency_ms": 412 },
    { "provider": "leadmagic", "operation": "email_finder", "status": "success", "latency_ms": 380, "billing": { "credits_used": 12.25 } }
  ]
}

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.

A company lane whose answer describes a different company is refused: the attempt is no_result with reason identity_mismatch (shown as Wrong company), or identity_unverifiable (Identity unverified) when a search-shaped lane returned no identity to check. Its identity_match holds the evidence, for example { "matched": false, "requested": "acme.com", "returned": "tata.com" }. The refused value is never written, the waterfall moves on to the next lane, and billing follows the lane's own rule unchanged: refusing an answer changes what is written, not what the provider charged. A per_attempt lane bills the attempt as always; an on_hit lane bills a refused answer the provider did return and releases only a provider miss (found: false). The cell detail in the grid and in the MCP cell widget shows the label with the requested and returned identity.

Cost behavior

Billing depends on the intent. Managed work-email and phone finder lanes charge only when they return a value; verification can still cost credits. Company-field waterfalls bill their Blitz lanes (domain ↔ LinkedIn URL and the company profile) only on a hit, and every other managed lane on each attempt, whether or not it finds anything. On the managed LinkedIn-URL waterfall a URL found that matches the person costs a flat 5 credits (9.2 while OXYGEN’s managed LinkedIn rail runs on HarvestAPI), whichever default lane finds it (a lane you add with --provider-order bills its own price, on a hit), and a row where no lane finds one costs nothing — see tool costs. --max-credits is required on a live run and caps OXYGEN credits, including own-key platform fees; vendor-account spend remains separate.

--allow-premium-lanes opts an enrich-column run into managed lanes priced above 50 credits per call on a capability that bills every attempt; without it such a lane is pre-skipped as premium_lane_not_opted_in, so an unattended run cannot bill-shock.

No enrich-column capability bills every attempt today. work_email, personal_email, mobile_phone, and linkedin_url all bill only on a hit, so a lane's list price is not spend — a miss costs nothing and at most one lane ever charges per row. Their expensive tail lanes therefore run by default, and passing --allow-premium-lanes changes nothing for them. Real spend on those intents is bounded by charge-on-hit plus --max-credits. Company-field enrichment has no premium gate: bound it with --max-credits and the preview's per-lane credits.

oxygen enrichment catalog --json lists every enrichment column kind and preset with a per-row credit estimate before you add anything — see it before picking a capability by hand.

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 \
  --json

Company 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 --json

The table preview carries the same would_attempt plan, for one representative row shaped like the identity columns that set the per-row ceiling (lane_plan_identity). Each lane adds credits, its price per attempt from the live price table (lane_pricing.priced_from), or null with price_status: "unavailable" when no honest price can be derived, and billed: per_attempt bills every attempt, hit or miss, while on_hit (the BlitzAPI company lanes) releases a miss. A lane of an own-key-only provider you did not select keeps pre_skip: "byok_only" with provider_not_selected: true. providers_without_lane names the selected providers that offer nothing for a field, so the run never asks them for it. estimated_credits_per_row and estimated_credits stay the ceiling to approve.

Company field groups as columns

A company preset adds one paid lookup column plus free columns for the fields it fills. Creating the columns costs 0 credits; they bill when you run them, by the company-field rules above.

oxygen columns add <accounts-table> --preset company_funding --json
PresetFills
company_fundingTotal raised, latest round, number of rounds, and investors
company_revenueAn annual revenue estimate — no source reports exact revenue for private companies
company_hiringOpen roles and how many there are
company_tech_stackTechnologies the company runs
company_lookalikesUp to 25 similar companies per seed company, kept as a separate list from the seed; reads the seed's domain column (--input company_domain=<column> if it is not detected)

oxygen tools search --for-table <accounts-table> --json prices each preset per row before you add it, and oxygen columns run <accounts-table> <column> --dry-run --json previews a run before you approve it.

  • Provider reference — the default and fallback provider for every chain.
  • Columns — enrichment columns and materialized outputs.
  • Cells — inspecting per-attempt history.

On this page