Source your first leads
Get real target companies or people into an Oxygen table — sourced from your ICP, or uploaded from a file you already have.
Rows land in a Table either way: Oxygen buys them from a provider, or you upload them.
From your ICP, in the app
Sidebar Data → Tables, then New table at the top of the table rail (/tables/new).
The picker is headed Create a table:
| Group | Cards |
|---|---|
| Imports | Import a CSV |
| Total addressable market | Companies — Blitz, Companies — Apollo |
| Signals | Hiring, Technology adoption, Funding, Acquisitions, Company news |
| Tables | Blank table |
Pick Companies — Blitz to build an account list from ICP filters. Describe the list in the field labelled What are you building — its placeholder is the shape to copy: Seed-stage B2B SaaS in the US hiring their first AE. Oxygen routes the request (the strip reads Routing…), fills a filter rail you can correct by hand, and prices the run.
Two priced buttons then appear, and their labels are the approval:
- Fetch first page · <n> credits — sits beside the prompt; buys one provider page so you can read real rows against your own filters before committing.
- Source up to <n> · up to <n> credits — the footer's commitment button. That number is the exact credit ceiling the run is sent with.
There is no second confirmation dialog. Clicking either button spends up to the number printed on it, and Enter in the prompt field fires the first-page fetch.
The footer also sets Lands in (the folder) and Sync: One-time fill, or Every hour / Every 6 hours / Daily at 09:00 UTC / Mondays at 09:00 UTC. A cadence re-spends its per-sync ceiling on every cycle until you pause it, so leave it on One-time fill for a first list.
The wizard has no people lane. Contact-level sourcing runs from the CLI only — oxygen people search, below.
From your ICP, from the CLI
Planning is free — the only provider call is the people planner's zero-credit count probe, which --no-estimate skips.
oxygen companies search plan \
--prompt "Seed-stage B2B SaaS in the US hiring their first AE" \
--target-count 500 --json
oxygen people search plan \
--prompt "Heads of Sales at US B2B SaaS companies" \
--titles "Head of Sales,VP Sales" --employees 20-200 --countries US --jsonThe plan returns ordered routes — each with estimated_credits — plus estimated_match_count, filter_application (which of your filters the provider applies, and which it drops), and the table_blueprint it would create. --target-count is capped at 50,000 per plan; larger asks return a clamp warning.
Not sure whether you want accounts or contacts? oxygen sourcing plan --prompt <file|text> and oxygen lead-sourcing plan --prompt <file|text> classify the request and recommend routes without executing any paid tool.
Dry-run next — still free, and --mode defaults to dry_run:
oxygen companies search run --prompt "Seed-stage B2B SaaS in the US" --target-count 500 --jsonThen live. This spends credits:
oxygen companies search run \
--prompt "Seed-stage B2B SaaS in the US" --target-count 500 \
--mode live --approved --max-credits 2500 --jsonBoth flags are mandatory. Without --approved the run fails approval_required (409); without --max-credits, spend_cap_required (400); with a ceiling under the route estimate, spend_cap_too_low (400), and the error names the estimated_max_credits you need.
Omit --table and Oxygen creates the table for you. Company rows dedupe on domain, people rows on linkedin_url, so re-running the same search updates rows instead of duplicating them.
From a file you already have
Free — no provider call, no credits.
In the app: open the table and click Import rows in the top bar (an icon button; the tooltip names it), or pick Import a CSV in the wizard. The dialog is Import file, or Create table from file when there is no table yet: Click to upload or drag and drop, CSV, JSON, JSONL, or XLSX. Set Table name and Folder when it is creating the table, press Preview to check the parsed rows — importing into an existing table then draws Column mapping — then Import.
oxygen tables import --create "Starter TAM" --file leads.csv --background --json
oxygen tables import <table> --file leads.csv --upsert-key domain --json--format is inferred from the extension (json, jsonl, csv, xlsx). Files over 500 rows load in the background and the command returns before the rows land — wait on the table-ingestions wait <id> it prints before you trust a row count. Per-file limit: 1,000,000 rows / 250 MB on a paid plan, 250,000 rows / 100 MB on free.
Watch it fill
A live search returns a durable ingestion run, not rows. The envelope carries ingestion_run and a web_url for /tables/<table-id>/ingestions/<run-id>, which shows Items, Rows, Inserted, Updated, and the Pending / Running / Completed / Failed breakdown.
oxygen table-ingestions get <run-id> --json
oxygen table-ingestions wait <run-id> --json # polls until terminal; 600s default
oxygen tables preview <table> --limit 25 --jsoncounts.rows at 0 while the status is still queued or running is normal — the envelope's message says so explicitly.
If the run ends completed_with_errors or failed, the table holds whatever landed first; oxygen table-ingestions items <run-id> --status failed --json names the items that broke.
If a live search returns search_ingestion_enqueue_failed (503), the table was created but nothing was queued into it. The error's next_step says whether Oxygen already archived the empty table or you must archive it before retrying.