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.
Start a table in the app
Open Data → Tables, then New table at the top of the table rail (/tables/new). Choose Import a CSV to upload your list, or Blank table to start with an empty grid.
The Total addressable market and Signals source groups are temporarily hidden while their setup is redesigned. To source companies, people, or signals, use the CLI paths below and the Signals guide.
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. The per-file row ceiling is 3,000,000 on every plan. The file-byte ceiling remains 100 MB on free and 250 MB on paid plans; an import into a non-empty Table must also fit its remaining capacity.
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.