REST API
Create a subscription (webhook and/or people list)
Subscribe to events and deliver the matches to a webhook endpoint and/or a people list.
POST
/v1/subscriptionsParameters
namestringrequiredtriggeranyrequiredendpoint_idstringoptionalshorthand for sinks: [{kind:‘webhook’, endpoint_id}]
sinksany[]optionalwhere matches go: a webhook endpoint and/or a people list (at least one sink, or endpoint_id) (up to 4 items)
clientstringoptionalscopeanyoptionalpredicateobjectoptionalactionsany[]optional(up to 6 items)
payloadobjectoptionaldedupeobjectoptionallimitsobjectoptionaldeliveryobjectoptionalbackfill_daysintegeroptionaldeliver events already recorded this many days back (≤ plan); IGNORED for scope.kind ‘monitor’ — a monitor-scoped subscription starts at its oldest monitor’s creation, so nothing collected before it is missed (min 0, max 30)
What your agent reads
The tool description exactly as an MCP client receives it in tools/list — it doubles as the full behaviour of the endpoint.
Subscribe to events and deliver the matches to a webhook endpoint and/or a people list. Creating costs nothing; each DELIVERED payload is charged like the rows it contains (1 company_row when company fields are present, 1 person_row per person from actions; a monitor's person 40 / 80 once), the event itself is free. Validated on the spot: unknown event types, attributes, paths and filters are errors (describe_events has every name); event types of unreleased data layers are refused. Client binding as lists: every sink and every list or monitor in scope must be the subscription's client's or organization-wide. Use preview_subscription first to size it (matches and records per day over the last 7 days). `trigger`: {kind:'events', event_types:[…], where:{attr: value | [values] | {eq|in|contains|not_contains|gte|lte|exists}}} (types and attrs from describe_events); `contains`/`not_contains` take a string or a list (any of); WATERFALL = where.any: [{…tier 1…}, {…tier 2…}] — a person matches when ANY tier matches, e.g. {any:[{function:['marketing'], seniority:['director','vp','cxo']}, {seniority:['cxo','owner']}, {title:{contains:['gtm engineer','revops']}}]}. Or {kind:'search_diff', search:{kind:'jobs'|'companies'|'people', request:{…the search body…}}, every:'24h', on:['entered','left']} — re-runs the search and emits search.entered / search.left (the first run only seeds). `scope`: {kind:'list', list_ids} | {kind:'domains', domains (≤5000)} | {kind:'monitor', monitor_ids} | {kind:'people_list', list_ids} | {kind:'all'}. `predicate`: the search DSL, verbatim — for job.* events search_jobs' query/title/status/filters/company_filters; for company.* and list.* events company_filters/company_exclude_filters/must_terms/exclude_terms/site_text; validated by a dry run through the same query builders (any ICP size: the predicate is evaluated per event, never materialised). `payload`: {preset: minimal|job_default|company_default|person_default|full, fields:[paths], format: nested|flat, max_people, excerpt_chars}. `actions`: find_people, find_email, verify_email, company, job — run before delivery, charged at their usual rates. `dedupe` (one match per company per window, reposts suppressed; per_person_window for person events), `limits.max_matches_per_day`, `limits.max_records_per_day` (a record budget — preview_subscription's estimated_records_per_day sizes it), `delivery` (single | batch | digest; wait_max for enrichment), `backfill_days` (events already recorded, ≤ plan; ignored for a monitor scope, which starts at its oldest monitor's creation). `sinks`: deliver to a webhook endpoint and/or a people list — [{kind:'webhook', endpoint_id}] and/or [{kind:'people_list', list_id}] (endpoint_id alone = one webhook sink; a people list alone needs no endpoint: create_people_list, then read it with list_people_list_items). MONITORS (LinkedIn post engagers): event_types engager.found / engager.engaged_again / post.published with scope {kind:'monitor', monitor_ids} (or 'all'); the persona is trigger.where on the engager attributes (seniority, function, title, company_country, company_headcount, headline contains …, kind reaction|comment, count_window) — several acceptable roles go in where.any tiers (see above); title/seniority/function come from a STRUCTURED position, headline is the raw text; no predicate. PRICE: collecting and resolving are our cost; you pay per person DELIVERED or LISTED — a commenter 40 records, a reactor 80 (once per person per monitor, whichever path shows them first), a watched profile's daily look 40, an e-mail found +2 (find_email). person.* events (champion tracking) are coming.
Errors follow one format.