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 Tables in the sidebar, then New → Table in the top bar (/tables/new). The picker groups its cards under Companies (Company Search, Scrape Google Maps Businesses), People (Find people, Find decision makers at companies), Professional Network, Imports (Import a CSV to upload your list, Blank table to start with an empty grid, Webhook), CRM (Import from HubSpot and Import from Pipedrive, into a new table or into Records, and Import from Records) and Signals (Job postings). Paid cards price the work before anything runs. Three Professional Network cards read LinkedIn through accounts you have already connected rather than buying data: Professional Network Connections & Followers, Professional Network Open Connection Requests, and Professional Network Profile Viewers, which collects the people who viewed your own profiles — you tick which connected accounts to read, they fill one table in the background at your accounts' safe daily read budget, and they are free. The same account list, with each account's status, sits behind the LinkedIn chip on that table, where unticking an account pauses it and keeps its rows. LinkedIn hides anonymous and private-mode viewers, so the viewers table is always a partial sample of real viewers. Connections & Followers merges your connections and followers into one table, and Open Connection Requests lists every pending request, sent and received, marking one accepted once the person becomes a connection. From the terminal, oxygen linkedin network setup, oxygen linkedin invitations setup and oxygen viewers import switch the three on (--account <ref> for one account; every account when you leave it out), and oxygen linkedin network pause, oxygen linkedin invitations pause and oxygen viewers pause switch them off and keep the rows. Professional Network Post Search finds public LinkedIn posts by keyword and fills a table with them for free, unless you add an AI filter, whose reasons cost credits per kept post (the preview quotes them); the table can refresh every 6 hours, daily or weekly. From the terminal, start with oxygen signals search preview --family linkedin_posts --keywords <terms>. Professional Network Post Engagers is not free: it collects the people who engage with posts by any profile you name — your team, partners or competitors — from a data supplier, priced per watched profile in its setup step. Professional Network Company Followers (ScrapeLi) reads no account of yours either. Paste a company page — a competitor's, say — and a follower count, and the free Check this page quotes the exact order (per follower, 100 minimum) before you buy. Our data provider fulfils the order on its side, so the table is created at once and fills when the followers arrive; there is no published delivery time. The same order runs from the terminal as oxygen tables followers check --company <page URL> --records <n> and then oxygen tables followers order --approved with the quoted --max-credits. From the terminal, oxygen tables sources lists the same catalog. The CRM imports are covered in Track deals in the CRM (HubSpot and Pipedrive) and Work a Records view in a table.
Under People, the Find decision makers at companies card sources everyone who works at the companies you already have. Pick the table holding your companies, then the column holding each company's LinkedIn URL, narrow by job level, job function, connections, country, continent and sales region, and OXYGEN shows a free sample — one page of people across your first 50 companies — so you can look before buying the rest. Pages per company in the footer sets how deep each company goes — 50 people per page, 1 credit per page.
Some sources are not in the picker right now: lookalikes, web search, import from a link, and most signal feeds (technology, funding, acquisitions, news, job changes). Use the CLI for those: --source-intent lookalike or --source-intent web on the company search below, oxygen tables import --url <https-link> for a public file or a Google Sheet, and the Signals guide for signal feeds.
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. Leave it out and the run takes every match one query reaches, up to a tenth of your spendable credits — target_count_default says which bound applied.
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, Google Maps listings on cid, and people rows on linkedin_url. Re-running the same search updates existing rows instead of duplicating them.
Local businesses on Google Maps
Use the native Google Maps source to save listings with their name, website, phone, Google listing URL and place ID:
oxygen companies search run --source google_maps \
--source-filters-json '{"q":"dentists in Austin, Texas"}' \
--target-count 25 --max-pages 2 --max-credits 1 --jsonThis defaults to dry_run: it plans and prices the import without contacting the supplier or writing rows. Review the page quote, then add --mode live --approved to run it within your credit ceiling. The import handles pagination and saves google_maps_url, place_id, cid and raw_source. A listing may have no website or phone.
The import upserts on the listing's cid, so offices sharing a website stay separate. The target bounds fetched rows; overlapping pages can leave fewer distinct listings. Read the completed ingestion run and saved table to check the result.
Everyone at the companies you already have
When the account list is already a table, point people search at it instead of describing the companies again. --from-table names the table, --linkedin-url-column names the column holding each company's LinkedIn URL, and OXYGEN runs Blitz Employee Finder once per company: 1 credit per company per page, 50 people per page. --prompt is optional here — the table is the request — but pass one to steer the persona further.
oxygen people search plan \
--from-table accounts --linkedin-url-column company_linkedin_url \
--job-functions Sales,Marketing --seniorities VP,Director \
--countries US --sales-regions EMEA --min-connections 200 --jsonThe plan's source.companies_planned is the real company count the run will spend against. Buy the first company first, then source the rest into the same table:
oxygen people search run --plan-json ./plan.json --route-id employee-roster-from-table \
--company-limit 1 --mode live --approved --max-credits 10 --json
oxygen people search run --plan-json ./plan.json --route-id employee-roster-from-table \
--table <table-from-the-first-run> --company-offset 1 \
--pages-per-company 2 --mode live --approved --max-credits 1000 --json--max-pages means pages per company on a --from-table run; --pages-per-company is the same ceiling spelled the way it reads. The people table's default columns are First Name, Last Name, LinkedIn URL and a Company link back to the row the person was found at, so every person stays attached to their account.
From a file you already have
Free — no provider call, no credits.
In the app: open the table, click Tools in the top bar and choose Import rows on its Export tab, or pick Import a CSV in the source picker. 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 when it is creating the table (it lands in the folder you are browsing), 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 equals your plan's rows-per-Table limit (oxygen limits show). The file-size ceiling is 500 MiB on free, 1 GiB on $49–$99, 2 GiB on $199–$499 and 5 GiB on $999 and up (table and signal limits); an import into a non-empty Table must also fit its remaining capacity. JSON and XLSX are read whole, so they stop at 100 MiB on every plan: save a larger file as CSV or JSONL. A CSV or JSONL file over 64 MiB uploads straight from disk to file storage and always loads in the background.
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.