Signals
Capture inbound intent events about people and companies, rank who to contact today, and keep signal tables refilling on a cadence.
A signal is one inbound intent event about a person or company: someone visited your site, viewed your profile, reacted to your post, or followed you. oxygen signals leads-today ranks them into a contact list.
The type registry
Four types are registered, all captured end to end, each with an intent weight that drives ranking:
| Type | Weight | Captured by |
|---|---|---|
website_visit | 100 | RB2B de-anonymization webhook |
profile_view | 60 | LinkedIn profile-viewer import |
post_reaction | 40 | LinkedIn engagement harvest |
new_follower | 20 | LinkedIn follower import |
oxygen signals registry --jsonSignals span three source families in the product model — pulse (external market events), engagement (inbound interaction), product (in-app usage). Only engagement is registered today: signals registry returns families: ["engagement"].
Three limits apply to every row above:
signals recordand the RB2B webhook only land if the matching signal-touch automation —crm-website-visitor,crm-profile-viewer,crm-post-engager,crm-follower— is armed in the workspace. Otherwise the event dispatches with zero deliveries and nothing reaches the timeline.- The LinkedIn imports raise signals only on people who already have a CRM record: the bridge that drains them never creates one.
- There is no
post_commenttype. A comment on a post is stored aspost_reactionwithengagement_type=comment.
Where you see them
There is no /signals page. Signals reach the browser in two places:
- CRM. Open a person from CRM in the sidebar (
/crm), then use the Signals filter on the record's Activity tab —/crm/people/<rowId>?tab=activity. Each event renders as a timeline entry. - Tables (
/tables). Every import and harvest fills a workspace table you can enrich, filter, and enroll from.
The ranked queue is CLI/MCP only:
oxygen signals leads-today --within-days 7 --limit 25 --json
oxygen signals list --types website_visit,post_reaction --since 14 --jsonleads-today ranks by weight, then recency. Do-not-contact filtering is best-effort: suppression keys on LinkedIn provider ids, so an email-only person (an RB2B reveal) comes back with suppressionResolvable: false — surfaced unchecked, not cleared.
Getting signals in
One command arms standing capture of profile viewers, followers, and connections:
oxygen linkedin intent setup --account <sender> --json
oxygen linkedin intent status --jsonAdd --post <social_id> to also watch one post's reactions and comments. Nothing is sent and no credits are charged.
The one-off imports fill the same tables and are also free:
oxygen viewers import --json
oxygen followers import --json
oxygen connections import --jsonEach drips in the background under a conservative read budget; poll viewers status, followers status, connections status. LinkedIn hides anonymous viewers, so the viewers table is always a partial sample.
Harvesting one post's engagers spends credits when it runs cookieless (a public post URL through the managed scraper), so --max-credits is required:
oxygen engagement harvest --post <social_id_or_url> --max-credits 200 --json
oxygen engagement status --jsonWriting a signal yourself is free and idempotent on --external-event-id:
oxygen signals record --event website_visit --email ada@acme.com \
--company-domain acme.com --page-url https://you.com/pricing --jsonEvents are website_visit, profile_view, post_engager, new_follower — post_engager stores as the registered post_reaction type. Identity comes from --email or --linkedin-url; --company-name, --first-name, --last-name, --session-id, and --payload-json fill the rest.
Feeds
A feed is a scheduled provider harvest bound to a table, so the table keeps filling itself. linkedin intent setup creates them; you operate them like any other.
oxygen feeds list --json
oxygen feeds get <feed> --json
oxygen feeds pause <feed>
oxygen feeds resume <feed>
oxygen feeds unbind <feed>Pause is free and total: the cadence stays armed but every cycle is refused, so nothing is fetched and nothing is billed. unbind deletes the feed and keeps every row it wrote. In the browser a bound table carries a Live, Paused, Error, or Exhausted chip beside its name on /tables/<id>, a pause switch next to the chip, and View deliveries in the chip's menu.
oxygen feeds run <feed> defaults to --mode dry_run, which renders the exact next provider request — cursor already injected — for free. A live cycle spends credits:
oxygen feeds run <feed> --mode dry_run --json
oxygen feeds run <feed> --mode live --max-credits 50 --approvedLinkedIn capture feeds refuse feeds run with feed_not_scheduled: they cycle on the worker's claim loop, not a cron, so there is no scheduled cycle to fire by hand.
oxygen feeds bind <table> arms a new one from a plan route's --kind, --tool-id, and --request-json. Adding a cadence on a paid provider is a standing spend grant and needs --every, --max-credits, and --approved together; without --every the feed is stored disarmed.
Market signal search
Plan first. This is free and calls no provider:
oxygen signals search plan --family hiring --prompt "companies hiring SDRs" \
--countries US --last-days 30 --estimate --jsonFamilies are hiring, tech, funding, acquisition, news, job_change; scope is market or watch_list with --domains. The plan returns the provider chain, which filters were dropped and why, a credit estimate, and the table blueprint.
Then run. Dry run is the default; live needs both gates:
oxygen signals search run --plan-json plan.json --json
oxygen signals search run --plan-json plan.json --mode live --max-credits 200 --approvedAdd --bind-feed --every weekly@mon9 --max-credits-per-cycle 50 to the live call to bind the route as a feed at the same time. Cadences are hourly, every_6h, daily@<hour>, or weekly@<day><hour>, in UTC.
The same routes back the browser path: New table on /tables, then the Signals group — Hiring, Technology adoption, Funding, Acquisitions, Company news.
A signal-search table is table data, not stream state. The registry holds no hiring, funding, or tech type, so those rows emit no signal and no workflow trigger sees them. signals list and leads-today cover the four engagement types only.
MCP
Every oxygen_signals_* and oxygen_feeds_* tool sits outside the core set a fresh client is advertised. Connect with ?toolset=signals (or ?toolset=full); the pack's full contents are in the MCP reference.