Todo lo que puede hacer un asistente una vez conectado. Si algo no está en esta lista, no lo puede hacer.
Este listado se genera del servidor en cada release, así que no puede quedar atrasado. El texto queda en inglés porque es lo que lee tu asistente.
The people who follow a LinkedIn company page YOUR seat administers — the second opted-in audience after a newsletter, and often the bigger one. Address the page with target ({ type, value }: company URL, universal name, or provider id); anything but an id spends one company.fetch to resolve first. 50 followers per page, one page per metered action; page with cursor. LinkedIn shows this list to the page’s admins only — a page this seat does not run is refused, and the fix is to connect the account that runs it.
A page of a COMPANY page's own posts — what a competitor, a partner or a target account publishes under its own name, not what its people post. Address it with target — { type, value } (company URL, universal name, or provider id) — and page with cursor; one page is one metered action, and a target that is not already a provider id spends one company.fetch to resolve first. For a PERSON's feed use profile_posts; to reach the people who engaged with one of these posts use post_interactions. Transient — nothing here is persisted.
Forbid contact with 1..1000 people or companies. This is not only about the future: everything already lined up for them stops. Their campaign runs end, messages already drafted or scheduled for them are dropped, and they are removed from every list holding them. Removing the entry later does NOT put them back in those lists. Set facet, then send items. A PERSON needs a LinkedIn profile URL, or a complete name (fullName, or firstName + lastName) TOGETHER with a company (companyName, companyUrl or companyDomain) — a name with no company is refused. A COMPANY needs at least one of companyName, linkedinUrl or domain. Every item is judged on its own and comes back added, duplicate or invalid with a reason: one bad item never fails the others.
Who this organization must never contact — the org-wide suppression list, in two facets: people (named individuals) and companies (the company itself AND everyone who works there). Returns both, newest first, unless you name a facet. Read it before building a list or launching a campaign, and read it FIRST when asked why somebody is missing from a list or why a campaign skipped them: a person can be suppressed by their employer's entry, not only by their own. Each entry carries the id exclusion_remove takes.
Stop suppressing one person or one company, by the entry id exclusion_list returned (exper_… on people, excom_… on companies). It lifts the ban going FORWARD only: it does not put the person or the company back into any list they were swept out of, and it does not revive a campaign run that ended — add them to a list again if that is what you want. It removes ONE entry, so somebody suppressed by their employer's company entry stays suppressed until that entry goes too. An id of the wrong kind is refused; an id belonging to another organization is simply not found.
Resolve a human-readable Sales Navigator filter value (location, industry, function) to a LinkedIn id, echoing the canonical label so a wrong match is visible. Cached globally; pass the returned urn to search_sales_navigator, or let it resolve names for you.
The members of a LinkedIn group, from the group’s own member list — people who joined a community around a topic, which is a targeting fact no search filter can express. Pass the group URL or id. Works on ANY connected seat for a public group: no Sales Navigator, no need to have joined. Every page reports total; 100 members per page, one page per metered action. LinkedIn serves at most the 10,000 most recent joiners of a group, newest first — for a bigger group that is the slice you get, and the walk stops there. Each member carries networkDistance (1, 2 or 3) so you know who can be messaged and who must be invited first. A private group is readable only by a member seat.
Create an ICP — a target thesis spanning the companies to reach and the people inside them — at version 1. Criteria are optional: a name plus an empty definition is a valid coarse ICP you can sharpen later with icp_update. **Read the icp-definition skill first**: it carries the check rules and the filter defaults, and an ICP written without them disqualifies everyone while looking right.
Soft-delete an ICP. Returns referencedByCampaigns — how many campaigns referenced it as their enrollment gate (any status, draft included) and no longer have one. The delete always proceeds, so check that number and tell the operator if it is not zero: any of those campaigns that is running just widened its audience.
Read one ICP and its latest (active) version by icpId.
List this org's ICPs, each with its latest (active) version's criteria and conditions.
No recibe parámetros.
Qualify one LinkedIn person or company against a saved ICP version and PERSIST the verdict: **you** judge the AI checks, Kairon judges the structured filters and owns the row. Call icp_get first — its conditions are the checks to answer, and their ids are what results[].criterionId must carry. Answer EVERY check of the sides judged or the call is refused, never scored: a missing answer reads as a rejection. rationale is required. Pace a large audit against the returned headroom.
Leer la descripción completa
**Filters run FIRST and outrank your answers.** When rejectedByFilter is set, a title/location/headcount rule from the ICP's Sales Navigator criteria decided the verdict and your check answers were discarded unread (checksNotRead). That is the ICP being narrow, not the person failing: if your reading says they fit, show the operator requirement vs actual and offer to loosen that rule with icp_update, then qualify again. Different answers cannot overturn a filter.
Revise an ICP: mints a new immutable version, which becomes the active one. This **replaces** the definition wholesale, so a field you leave out is cleared, not carried forward — call icp_get first and send back the complete definition, changing only what you mean to change. Past versions stay readable, so earlier verdicts remain explainable. **Read the icp-definition skill** before you change the AI checks — it decides whether the new version still qualifies anyone.
Bring an earlier version back by minting a NEW version equal to it, which becomes the one in use straight away — unlike the campaign equivalent, there is no draft to review first, so confirm with the operator before calling. Nothing is destroyed: versions are append-only, every earlier one stays, and a restore is itself undone by restoring again. Read icp_versions first to pick the right one.
Every version of this ICP, newest first, with the date and the check counts for each — and which one is in use. An ICP is versioned on EVERY edit and none is ever rewritten, so this is the whole history. Pass version to get that one in full: its thesis, both filter sets, both check lists, as they were saved. This is how you recover a definition an icp_update cleared by leaving fields out.
The connection requests waiting in your LinkedIn inbox — people who asked to connect with YOU and have not been answered. The one audience that came looking for the operator, and until now nothing read it. Each hit is one request: person (the inviter), note (what they wrote when they asked, verbatim — often the reason they want in) and receivedAt. Page with cursor; 100 per page, one page per metered action. exhaust walks every page into the list, and like every other walk it returns the counts rather than the rows — read a page at a time when you want to READ the notes. Read-only: this does not accept or decline anything.
Is this company hiring? A published vacancy PROVES a need, dated and linked — the strongest buying signal for anyone selling staffing, recruitment or talent. Sweep a market with region + what you are looking for (this DISCOVERS companies, it does not filter yours), or check one with company, where zero postings is a real answer and not an error. region is required for a sweep: without it LinkedIn silently returns your own country only. Prefer roles over keywords — a job-title id matches the ROLE while a keyword matches anywhere in the ad, and on the same sweep that was 17 postings against 1241. Seniority is seniority, NEVER the word "Senior" in keywords: LinkedIn ORs keywords, so that widens the search (1241 → 2480) while looking like it narrows it. Hits are NOT deduped — one company often posts one need many times.
Fetch a LinkedIn person OR company through the caller's connected account — whichever the target addresses ({ type, value }: url, public_identifier, or provider_id). The kind is detected from the target; pass kind: 'company' for one addressed by universal name or numeric id. Returns complete data, metered and cached (refresh: true forces a live read), plus headroom — how many MORE fetches you may make (own account plus Kairon's shared capacity, combined). Budget a large audit against the smallest number in it, and keep going until nextSlotAt is set: only that field means wait. A counter at 0 does not — the next call comes from shared capacity. For a person, withRecommendations: true also returns who vouched for them — each recommender named and addressable, the strongest warm-intro path LinkedIn publishes. When the person is a 1st-degree connection of this seat, relative.emails and relative.phones carry their contact panel — visible via the connection, never stored.
Add members to a list ({ asset, id, items }, up to 500). An item is { memberId } (already in the org), { url } (a pasted LinkedIn URL — instant and free, no provider call), or { snapshot } (a hit from search_people / search_sales_navigator / signal_search, passed as-is with its providerId, which is what makes the saved list show the person rather than a bare id). Re-adding a current member converges. This is how you assemble an audience after a search.
Create a named list — the campaign targeting unit. asset: 'leads' for people, asset: 'companies' for companies (build the company set first, then find people inside it by passing the list's id to search_sales_navigator as companyLists). Names are unique per org and asset, case-insensitive; a taken name fails with the existing list's id, never a silent merge. Build membership with list_add.
Soft-delete a list by asset + id: it disappears from list_list with its memberships, the member leads/companies are untouched (they stay in the org pool, in their other lists, with their qualification state intact), and the name is immediately free for reuse.
One list by asset + id: its name, live member count, created date, and its ordered columns (the list's own custom columns — free-text fields you attach to its members). Out-of-org or deleted ids are not found. To read who is IN it, use list_members, whose rows carry each member's values for those columns. Custom list columns reach a message two ways: {{@columnName}} sends that column's value word for word, and a normal AI slot that mentions a column can use it as a fact, written in the writer's own words.
Read this org's lists of one asset, keyset-paginated, newest first, each with its live member count. q matches the list name (case-insensitive). Page with the returned cursor. Deleted lists never appear.
One keyset page of a list's members. Row: { id, title, subtitle, location, linkedinUrl, target, company, companyId, companyRecordId, email, values, tags, owner, qualification, employeeCount, currentTitle, crmStage } — the id removes it with list_remove or writes with list_set. Leads carry currentTitle (the job; subtitle is the headline) and crmStage (what your team DID); companies carry employeeCount. Pass target to linkedin_fetch AS-IS; linkedinUrl is null for Sales Nav members.
Leer la descripción completa
NARROW HERE — every filter below runs in SQL; never page a list and sift it yourself.
qualification is only the verdict. includeQualification: true (both assets) adds qualificationDetail per row — criteria, rationales, evidence — which answers 'why were these disqualified' in ONE call.
A filter or sort the asset lacks is REFUSED, never ignored. employeeCount is null when nobody has learned it, and null matches NEITHER size bound, so the halves never add up. Page with cursor.
Judge a list's members against an ICP and return the run that started. Omit subjectIds to cover every live member; name them to qualify a subset. This SPENDS — each member Kairon has to judge costs a model call and, for anyone not loaded yet, a LinkedIn read.
Leer la descripción completa
Members already carrying a verdict against this ICP's ACTIVE version are skipped for free, so re-qualifying a list you have judged before costs only what is new. The run is NOT instant: it returns immediately with status: "pending" and a total, and you read what it did with list_qualify_runs. Ids that are no longer members are dropped silently and total counts what survived.
Stop a run that is still going. Every verdict it already reached is KEPT — cancelling costs nothing and undoes nothing, it only stops the spending from here on. The members it never reached keep no verdict and stay unevaluated. Refused on a run that already finished, because answering "fine" to a brake that stopped nothing would report a saving that never happened.
How a qualification run is going, or how it ended. Without runId, this list's most recent run; with one, that run. Returns null when the list has never been qualified, which is an answer and not a failure.
Leer la descripción completa
Read the counts as four different things: judged is what it paid to decide (split into qualified and disqualified), alreadyCurrent was skipped free because a verdict against this ICP version already stood, and failed could not be READ — those members keep no verdict and stay unevaluated, so they are worth retrying later. A completed run with stopReason: "allowance_exhausted" stopped short: start another over the same members to resume, and it will be nearly free for whatever the first run reached.
Remove members from a list ({ asset, id, items }). Each item names ONE member however you hold it: { memberId }, { url }, { providerId }, or { snapshot } (a hit passed back unedited — only its providerId is read). Deletes only the membership; the leads/companies stay in the org pool and their other lists. Per item: removed, not_member, or not_found.
Write custom-column values on members ALREADY in a list ({ asset, id, items }, ≤500). Each item names one member — { memberId }, { url }, { providerId } or { snapshot } — plus values, keyed by COLUMN NAME.
Leer la descripción completa
Merges: names you send are written, names you omit keep what they had, the empty string clears. Create columns first with list_update; a name this list lacks is skipped and echoed in ignored. Per item: set, not_member, not_found. Read them back with list_members.
When a value is a fact you looked up rather than one the operator told you, put the source in the cell — the quote or number, the URL you read it on, and the date. Values the operator owns (a stage, a note, your own summary) need none of that. Custom list columns reach a message two ways: {{@columnName}} sends that column's value word for word, and a normal AI slot that mentions a column can use it as a fact, written in the writer's own words.
Rename a list and/or replace its custom columns ({ asset, id, name, columns? }). A name another live list of that asset holds fails with that list's id.
Leer la descripción completa
Custom columns are free-text fields on the members of THIS list, addressed by NAME. columns replaces the WHOLE ordered set: omit it to leave them alone, [] removes all. { name, renameFrom } renames one and keeps its values; without renameFrom the old column and everything in it is destroyed. Max 20, names ≤40 chars. Custom list columns reach a message two ways: {{@columnName}} sends that column's value word for word, and a normal AI slot that mentions a column can use it as a fact, written in the writer's own words.
The people who subscribed to YOUR OWN LinkedIn newsletter — the warmest audience LinkedIn holds, since each one read your writing and chose to keep receiving it. Pass the newsletter URL or its id. Every page reports total, the size of the whole audience, so you can decide whether to walk it before spending the actions: 100 subscribers per page, one page per metered action. Each subscriber comes back with networkDistance: 1 when they are ALREADY a connection (message them) and null when they are not (invite first). Works only for newsletters the connected seat OWNS — LinkedIn serves this list to the author and nobody else, so there is no way to read a competitor’s.
Make one teammate responsible for the named leads or companies. Name them by email (what you usually have) or userId; pass owner: null to leave them unassigned. The owner must be a current member of the organization. This only labels — it grants no permission and does not decide which seat sends. Never creates a lead: an unknown url comes back not_found.
Where every one of this org's people stands. Returns a count for each stage — new, invite_sent, connected, contacted, replied, interested, meeting_booked, won, on_hold, lost, discarded — and, when you name a stage, that column's people with their role, their owner, and when they were last contacted. EVERY lead is on this board, so new is where the ones nobody has acted on yet collect; the early columns then separate an invitation nobody accepted (invite_sent) from an accepted one nobody followed (connected) from a message that got no reply (contacted). Use lastContacted to ask about a window ("who at interested have we not touched in 30 days"). A column comes back one page at a time: hasMore says whether more remain, and passing the returned cursor back reads the next page. Reads only: it never moves anyone.
Record where people now stand: new, invite_sent, connected, contacted, replied, interested, meeting_booked, won, on_hold, lost, or discarded. Name them by url (what you usually have), memberId, or providerId. Moving someone backward is allowed. Kairon advances the early stages on its own from what actually happens — an invitation going out, them accepting it, a message going out, them writing back — so use this for the judgment calls it cannot make: a meeting booked, a deal won or lost, someone parked. Two stages are not rungs on the ladder: on_hold keeps someone and does nothing for now — Kairon also puts people there itself when a campaign runs out of things to say and they never replied — and discarded means never contact them again — it also adds them to the exclusion list, which ends any campaign they are in and drops messages already scheduled for them. Never creates a lead: an unknown url comes back not_found.
Everyone who commented on or reacted to ONE named post, by its LinkedIn URL — the audience a post already earned, each person carrying HOW they engaged (their comment text, or which reaction), which is the warmest opening line outreach gets. Use this when you have the post; use signal_search with engaged_with_profile when you want whoever keeps engaging with a profile. A shortened lnkd.in link carries no post id — expand it first. Comments are walked to exhaustion, then reactions; pass intoList with exhaust to sweep the whole post into a list in one call, and expect several metered reads.
A page of a profile's recent posts (transient — not persisted). Address the person with target — { type, value } (url, public_identifier, or provider_id). Use cursor to page; one page is one metered action.
WHY one lead or company carries the verdict it does ({ asset, id }) — the stored scorecard. Ids come from list_members. Reads rows we hold: spends nothing, changes nothing.
Leer la descripción completa
Returns status, the tallies total / met / notMet / notChecked, and criteria — one { criterion, result } per criterion the ICP version defines — a filter's dimension / mode / values and the member's actual, an AI check's description and the judge's rationale + evidence. result: null is NOT CHECKED — the walk stopped earlier — never a failure, though it keeps them out of qualified.
On a lead, manualQualification (with manualQualificationBy) means a PERSON set status by hand; the tallies still describe what the criteria found, which may disagree. Null means Kairon decided.
icpVersionId is the version pinned when it was judged, not today's active one. An unevaluated member answers status: 'unevaluated' with no criteria — an answer, not a failure; next is list_qualify.
Say whether these people are worth reaching out to, in your own judgement, instead of waiting for Kairon to decide. qualified lets someone through even if your ideal-customer profile would reject them; disqualified keeps someone out even if it would accept them. Pass null to take your decision back. Name them by url, memberId, or providerId.
Leer la descripción completa
Your decision is what the lists show, what the filters match, and what the campaigns obey, for as long as it stands. Kairon keeps judging underneath it — qualification_get still shows what the criteria found, though its status now reads your decision — so taking your decision back hands the person straight back to what it had decided, with nothing to re-run and nothing to pay for.
Use it for the calls Kairon cannot make: someone you know personally, someone whose profile does not say what you know about them, or a good lead you need in a campaign that leaves today. Never creates a lead: an unknown url comes back not_found.
Classic LinkedIn people search — the door for ONE kind of ask: something measured from the operator themselves. networkDistance, connectionsOf and followersOf are measured from the operator here, and shared capacity REFUSES them — so a network-scoped ask that search_sales_navigator has refused belongs here, and **nothing else does**. **capabilities.viewerRelativeFilters: false is NOT a reason to come here**: Sales Navigator search runs on every seat and with no seat at all, so every company and people search that is not network-scoped still belongs there — coming here instead trades the role / seniority filters for a noisier keyword answer. Takes keywords, id-resolved filters, network distance and language; or paste a search url to run verbatim. Pass intoList to append the page.
Search LinkedIn POSTS — what is being said, by whom, when. keywords is required (boolean OR/AND/NOT allowed); everything else NARROWS. mentioningCompany / mentioningMember are @-mentions resolved by NAME to a LinkedIn id — the way to watch what the market says about a competitor, a partner, or you. fromCompany / fromMember restrict the AUTHOR instead; authorCompany / authorIndustry / authorKeywords describe who wrote it. A name that resolves to nothing REFUSES the search rather than silently widening it. One page is one metered action; page with cursor. Want the PEOPLE who engaged with these posts instead? signal_search with engaged_with_keyword.
Search Sales Navigator over people OR companies — **the door for EVERY people and company search, on every seat and with none**. No licence to check, nothing to ask the operator for. A connection degree is a structured filter like any other, so "my 1st-degree X" belongs here too (networkDistance), keeping the role / seniority precision classic search cannot express. ONE exception announces itself: the viewer-measured filters (networkDistance, connectionsOf, saved lists) are sometimes refused mid-call — that refusal, never a flag read beforehand, is what sends a degree-scoped ask to search_people. Prefer a structured filter over keywords: filters are precise where keywords are noisy. Pass human-readable names ({ type, query }); the server resolves ids. Same-dimension OR, different dimensions AND. **Read the sales-nav-search skill first.** One page is one metered action. **One search at a time** — a concurrent call is refused (CHANNEL_ACTION_SEARCH_IN_PROGRESS).
Find people by what they DID, not by what their profile says — the behavioural counterpart to search_people and search_sales_navigator. Pick a signal (who engaged with a profile or a keyword, who posted one, the seat's newest connections, who viewed it); each takes its own config. Results are RAW — real people who really did this, with no ICP screening, so screen them yourself. Pass intoList to append the page. The list-building skill covers which signal answers which question.
Detach the source from a lead list. The list KEEPS every member the source already found — only the feed stops, so the list becomes an ordinary static list rather than losing anything. Frees the one-source-per-list slot, so a new source can be attached afterwards with source_set. To pause a source temporarily instead, source_set it to a paused status.
The source attached to a lead list — the recurring feed that makes it a SIGNAL-BASED list, growing itself over time. Returns null when the list has none, which means it is an ordinary static list, not that anything failed. The result carries the ICP the source targets and every signal on it, each with its own id — those ids are what source_set echoes back to keep a signal's place in its feed. Read this before changing a source: source_set replaces the whole signal set.
Run the source now instead of waiting for its daily run, and return the run that started. This SPENDS — it is a real provider search on the seat the source was created on, and it is the only tool here that costs anything. Attaching a source does not run it, so this is how a newly attached list first fills. Refused while the source is paused, or while one of its runs is already in flight; a run is not instant, so read what it did with source_runs rather than expecting members back from this call.
What the source actually did. Without runId, one page of run history newest-first — when each run happened and how many people it appended. With runId, that run's CANDIDATES instead: one row per person it considered and the outcome that decided them, which is where "why is this person in my list, and why is that one not?" is answered. Page with the returned cursor; a run's candidates come back in the order the run touched them, so the page order IS the funnel order. Reads only — it never starts a run and spends nothing.
Make a lead list SIGNAL-BASED, or change the one it already is: give it an ICP to target and the signals that should feed it. This REPLACES the whole signal set — any signal you do not list is removed — so call source_get first and send back every signal you want to keep, each with the id it gave you. A kept signal holds its place in its feed; one sent without its id is treated as new and restarts from the top, which costs a re-walk. Attaching does NOT run the source: nothing is searched or spent until source_run. Swapping to a different ICP restarts every signal, because the audience changed. Refusals are total — a bad config or an unknown signal id leaves the source exactly as it was.
Removes the tag from the organization AND from every lead and company wearing it, in one act. Reports how many of each lost it. The name becomes free to use again immediately. This does not delete any lead or company.
Every tag this organization has, with its color. A tag is the operator's own label on a person or a company (warm, gatekeeper, met-at-event) and it follows them everywhere — unlike a list's custom column, which belongs to that one list. Read this before tag_set, which names tags by name and refuses one that does not exist.
No recibe parámetros.
Without id, creates a tag. With id, renames and/or recolors that one — and a rename carries it across every lead and company already wearing it, in one act. Names are unique per organization, compared case-insensitively. Colors are palette names: stone, clay, moss, sky, plum, amber, rust, slate.
Replaces each named subject's WHOLE tag set — so send every tag it should end up with, not just the new one, and send [] to clear it. Name a subject by memberId, url, or providerId, exactly as list_remove does. Tags are named by NAME and must already exist (tag_save first); an unknown name fails the call rather than creating one. This never creates a lead or a company: an unknown url comes back not_found.
Search, in plain words, for a third-party data endpoint Kairon can run for you — data OUTSIDE LinkedIn: tweets by handle, a Google Maps listing, a company's reviews, a person's record at a data broker, and hundreds more. Say what you want ("tweets by a handle", "amazon reviews for a product"), not a provider name. Each match carries a provider, an endpoint, a relevance score and the provider's price per call or per result. Free. Then webdata_inspect the match you like to learn its inputs.
The full card for one endpoint webdata_find returned, by its provider and endpoint: what it does, the exact input schema webdata_run expects (path, query and body parameters), the provider's price, a link to the provider's own docs, and any notes. Free. Read this BEFORE quoting an endpoint — the schema is what you must send, and the input you send decides the cost.
What THIS organization will pay for one run of an endpoint with exactly this input — the provider's price plus Kairon's margin — and the quoteId that webdata_run requires. Per-call endpoints are quoted exactly. Per-result endpoints are quoted per result; pass expectedResults (the limit you set in input, or your honest estimate) to get an estimatedUsd. THAT TOTAL IS AN ESTIMATE, NOT A PRICE: say it to the operator as approximate ('about $0.60'), never as the exact amount, because the provider bills what actually comes back — more or fewer results than you expected. An endpoint that states no price comes back with quoteId: null and CANNOT be run — say so and pick another. Free. YOU MUST show the operator the quoted price in plain words and get their yes BEFORE calling webdata_run — the run refuses without a quote, and a quote is only valid for 30 minutes and for this exact input.
Fetch the outcome of a webdata_run that came back with status other than COMPLETED, FAILED, BLOCKED, STOPPED or TIMED_OUT — a run the provider was still working on when the 60-second wait ended. Pass the runId it returned. One read, no waiting: if it is still going, wait a few seconds and call again. Reading is free; the run itself is billed once, when it completes, at the quoted rate — billedUsd says how much.
Run one endpoint and get its data back. REQUIRES the quoteId from webdata_quote for this exact provider, endpoint and input, and you must have shown the operator that price and received their yes — a run with no quote, a stale quote or a quote for different input is refused. THIS SPENDS the organization's monthly allowance, at the quoted rate, on what the provider actually returns. status: COMPLETED means the endpoint was called and answered; check providerStatus for whether it answered well (a 404 there is "not found", not a failure of this tool). billedUsd says what the run cost the organization. An output over 64 KB is cut and flagged truncated: true: a list keeps its shape and loses its tail (omittedItems says how many went), anything else is left out entirely and only outputPreview plus outputBytes come back. Narrow the input rather than re-running — a re-run is a second charge.
THIS STARTS SENDING MESSAGES TO REAL PEOPLE ON LINKEDIN, and a message already sent cannot be un-sent. Activates a draft or paused campaign; startNow (default true) enrolls today's first batch instead of waiting for the next window. Refused with CAMPAIGN_ACTIVATION_BLOCKED (409) unless all four preconditions hold — params.unmet names each one. **Call campaign_stats first and show the operator what activating will do**: under an ICP gate it returns who the ICP accepted, rejected and never judged, plus when the last person gets in — and no dates while anyone is unjudged, because the gate freezes whoever it rejects. Above zero unjudgedCount, say so and offer list_qualify on the bound lists (campaign_get → leadListIds); a current verdict is a free cache hit. unreadableCount is NOT work to offer: those profiles cannot be read, and they never withhold the dates. Activating an active campaign is a no-op, safe to retry. See the campaign-building skill.
Per-lead slot coverage. A {{@slot}} in a send body (either draft mode — an ai_template seed fills it before the writer runs) is filled by each lead's LIST COLUMN of the same name (case-insensitive) — write values with list_set, never a separate copy call. Returns coverage: { slots, ready, missing } over the whole audience plus a page of leads; missingOnly keeps just the ones still short — "what is left to write". campaign_runs cannot answer this: an unfilled lead never enrolls, so it has no run, which is why such a campaign shows a large audienceSize, queuedCount: 0, and sends nothing. 400 if the published graph declares no slots.
Create a draft campaign: a name and the sending seat (channelAccountId, a connected LinkedIn account in this org — seat_list returns the ones you can pick), and the ICP to gate enrollment on (icpId). There is no default and nothing is selected for you: omitting icpId creates an UNGATED campaign that contacts everyone on its lists. ASK the operator first, every time — show them their ICPs (icp_list) and say in one line what each answer means: gated, and only people who qualify are contacted while everyone else on their lists is skipped and never messaged; ungated, and everyone is contacted. "No gate" is a fine answer, but it has to be one they gave. Returns a campaign in draft with no audience and no sequence — bind lists with campaign_update, then campaign_set_graph, then campaign_publish. The campaign-building skill has the full order and the defaults.
Read ONE campaign completely enough to REBUILD it. Returns its configuration, the bound lead lists, and BOTH graphs: published (what the engine is sending now) and draft (what campaign_set_graph would overwrite), each in exactly the shape that tool accepts. To copy a campaign, replay what this returns — never retype a sequence someone described, because node configs carry slots, draft modes and limits invisible in prose. The campaign-building skill has the copy recipe.
Enroll one person from the campaign's audience now, instead of waiting for tomorrow's batch. It skips the DAILY PACING, never the gate: the same ICP and enrollment-filter checks the morning tick runs still judge the lead. So the outcome is data, not a promise — enrolled, skipped (with the reason, e.g. they do not match the ICP), excluded, or failed. A lead who does not fit is marked Skipped rather than messaged.
List this org's campaigns, keyset-paginated — { q?, status?, sort?, direction?, cursor?, limit? }. The default order is the one the app shows: the campaigns that are RUNNING first — active, then draft, then paused, then finished, then archived — and the most recent first inside each. q matches the campaign name; status filters to one of draft | active | paused | archived; sort reorders by name | status | sender | updated; page with the returned cursor (null on the last page). Each item is the campaign’s configuration — the same shape campaign_create returns — WITHOUT its sequence graph; read one campaign in full with campaign_get. This is how you find a campaign the operator named but whose id you were not given.
Pause an active campaign. Nothing new fires; runs already in flight wait at their current step and resume there if the campaign is reactivated. Nothing already sent is recalled. Idempotent — pausing a paused campaign returns it unchanged. Refused (400) if the campaign is draft or archived: only an active one can be paused.
READ THE MESSAGE BEFORE YOU ACTIVATE. Drafts one saved step for up to 3 real audience leads through the SAME pipeline a real send uses, and sends nothing. Name the step by its nodeKey from campaign_get; omit leadIds to sample the audience. Per lead you get the body that would fire, its reasoning and evidence, an error where no message could be produced, and the send-verifier's verification verdict (advisory here — a preview never blocks). The reply names which graph it read. **SLOW: expect up to ~2 minutes**, because the LinkedIn profile reads behind the drafts are paced one at a time per seat — do not retry a call that has not returned.
Validate and publish the campaign's draft graph. Publish can FAIL validation, and it fails as DATA, not as an exception: published: false with EVERY error in errors (each naming its code and node), so one pass fixes all of them. On success version carries the freshly published graph. migrateInFlight additionally repoints in-flight runs onto it by node key, canceling any run parked on a node the new version removed. The campaign-building skill lists the error codes.
The operator's Tasks page, as one read: every send booked to fire (autopilot), every manual run waiting for approval (review), every AI draft that errored (failed), and everything already acted on today (done), plus each seat's daily budget. Filter with kind and campaignId. This is where the runId and nodeRunId that every per-run tool takes come from — a review row's parked send has no other index. Each row's body holds the words that will go out — an invite's note, a message, a comment — told apart by actionType, so "what are we about to send" is one call. It is a live WINDOW: limit applies per kind so one busy kind cannot hide another, and windowCapped: true means total is a floor. To read one campaign exhaustively use campaign_runs, which pages with a cursor; for one row's reasoning, citations and timeline, open it with campaign_run_get.
THIS SENDS THE MESSAGE. On a manual campaign every send waits for a human; this releases one. Read the draft first with campaign_run_get — approving without reading is how an agent sends words nobody checked. If the send was held by the pre-send check, approving is an OVERRIDE and is recorded against your name on the message itself. Refused (409) if the run is not awaiting approval, or if its campaign is paused — resume it first. Edit the words first with campaign_send_edit.
Stop up to 100 enrolled leads: each run ends, anything drafted or booked for them is dropped, and their parked workflow is torn down so no timer fires later. Nothing already SENT is recalled. This is final for that campaign — a canceled person moves to skipped and it will not pick them up again (re-adding them via another bound list is the only way back). Ids are DEDUPED, so read canceled rather than counting what you sent. Every distinct id comes back with its own outcome: canceled, not_found, not_cancelable once a run has already finished, or failed — that one alone is unknown, so re-send it. A wrong-KIND id never gets that far: ids are checked for the srun_ prefix at the door, so a feed row’s nodeRunId is refused outright rather than counted as a retryable failure. Idempotent on an already-canceled run.
Read ONE sequence run in full, by an id from campaign_runs. Returns the lead, the sending seat, the run's position, and the node-by-node timeline with each step's status and timing. When a send is drafted or already out, body carries the actual text, with its reasoning and evidence for an AI draft. This is how you answer "what did we actually say to this person, and what happened next" — campaign_runs gives you the page, this gives you the story.
Cut short the delay step a run is parked on so the sequence carries straight on to its next step. Refused on a run that is NOT on a delay: a booked send is campaign_send_now, and a wait_connection_accepted cannot be skipped at all — the skip would not match the wait it is parked on, and no amount of skipping makes someone accept an invite. DO NOT RETRY a refusal: once the wait is skipped the run has moved past it, so a second call reports CAMPAIGN_RUN_NOT_ACTIONABLE for work that already succeeded.
Read the campaign's sequence runs — one per enrolled lead — keyset-paginated; page with the returned cursor. Each run carries the lead, where it stands, when it acts next, its outcome once terminal, and any error or skip detail. bucket filters to one activity tab (waiting, didnt_accept, didnt_answer, replied, interested, skipped, error); omit for every run. stage takes the same words as a SET; q, dateFrom/dateTo and listId narrow further, and filters stack. nodeKey + dateFrom/dateTo asks **who completed THAT step on those days** — the only way to tell a first message from a follow-up when a sequence has several, since campaign_stats counts them as one number. Without nodeKey the day window holds runs whose LAST event or next BOOKED action fell in it. campaign_stats says which bucket is worth reading. includeQualification: true attaches each lead's STORED scorecard — qualification_get's body — which is big, so pair it with a small limit.
Replace the words a drafted or scheduled send will fire with — the edited text is exactly what goes out, never re-written afterwards. Addressed by nodeRunId (from campaign_review_feed), NOT the run id. Refused (409) once the send is out; there is no editing a sent message. On a send held by the pre-send check, editing CLEARS the warning: it described a sentence that no longer exists, and your own words are yours. An InMail may carry a new subject; every other send type refuses one.
THIS SENDS THE MESSAGE, sooner than anything else would have: it fires this run's next booked send NOW, outside the send window and the usual pacing. It is a HAND-OFF — the run is armed and the sender fires it a moment later, so campaign_review_feed shows expediteNextSend: true until it goes. DO NOT RETRY: a second call is refused with CAMPAIGN_RUN_NOT_ACTIONABLE because the run is already armed, which means the send is on its way, not that it failed. Only a run parked on a send with a reviewed draft can be expedited; one sitting on a delay is refused (use campaign_run_skip_wait).
Replace the campaign's editable DRAFT sequence graph IN FULL — this overwrites, it is not a patch. Nodes are { key, type, config } with a stable key you choose; edges are { fromKey, toKey, condition } routing one node's outcome to the next. Layout is automatic. Which conditions a type emits, and which types it may connect to, are fixed rules — the edges field carries the table, and a violation is refused (400). The response is the saved graph, in full, in exactly the shape this tool accepts — read the echo back rather than assume your input was stored as sent. **The campaign-building skill has the order to build a campaign in, and how to write the copy.** Saving is not publishing.
How a campaign is performing, in one read — and **the read before campaign_activate**. summary is the live snapshot: status, audience, what is queued, today's usage, the send window, the current version (null until campaign_publish has run), and copy coverage, which is why queuedCount can sit under audienceSize. Under an ICP gate it also carries the forecast: qualifiedCount / disqualifiedCount / unjudgedCount / unreadableCount over the unenrolled audience, enrollmentEndsOn (when the last person gets in) and finishesOn. They are null when there is no split — no ICP gate, or nobody left to enrol. **Both dates are null while unjudgedCount is above zero** — the gate freezes whoever it rejects, so a date then counts people it will drop; qualifyEtaMinutes is that check's cost. unreadableCount never withholds a date: re-checking it cannot help. funnel runs enrolled → invited → accepted → messaged → replied → interested. buckets counts where everyone stands.
Edit a campaign's configuration — any subset of its fields; omitted ones are unchanged. This is also how an audience is bound, via leadListIds, and that binding is **additive**: a bound list you leave out is NOT unbound, because unbinding would cancel every in-flight run drawn from it. It is also how a campaign is pointed at the bet it serves, via initiativeId (null detaches) — do that for every campaign you build under an initiative, or the bet reports an empty funnel forever. sendWindow is required before campaign_activate will run. autonomy is 'autopilot' (sends fire on schedule) or 'manual' (each send waits for approval, except a note-less invite).
Copy an earlier published version back into the campaign's editable DRAFT, steps and routing and layout intact — the way to undo an edit that replaced copy someone wrote. It does NOT publish: leads keep getting the live version until someone publishes the draft, and in-flight runs are untouched either way. It REPLACES any unpublished draft, so read campaign_versions first if one exists. Publishing afterwards mints the next version number; the old one stays where it is.
Every version this campaign has published, newest first, plus its unpublished draft — with a line per version saying what it changed (steps added/removed/edited, routing). Pass versionNumber to get that version's whole graph back, message bodies and all, in exactly the shape campaign_set_graph accepts — so you can hand it straight back instead of retyping copy. History is publish-boundary: edits between two publishes overwrote one draft and were never stored.
Remove one checkpoint from a bet’s history. Deleting the newest one moves the initiative’s health back to whatever the one before it said — or to unknown when there is none. Use it for an entry published against the wrong bet or by accident, never to tidy away a verdict somebody did not like.
Fix what one checkpoint says, or the verdict it froze — for a typo or a verdict typed by mistake. The entry keeps the date it was made and its author, and every surface marks it as edited. A checkpoint that has gone out of date is NOT fixed here: publish a newer one instead, so the history shows the change of mind. A rewritten body carries @mentions the same way checkpoint_publish does.
Record a dated verdict on an initiative: what happened, the one thing to do next, and a health of on_track, at_risk or off_track. The verdict is FROZEN — publishing a newer one never rewrites an older one, which is what makes the history evidence. Read the gtm-checkpoint skill first; it carries the rule about judging volume before rates.
Remove one initiative from the board. A soft delete: the campaigns serving it detach and keep running, and its checkpoints survive. Use it for a bet created by mistake — most often a duplicate left behind by an initiative_set that omitted initiativeId. Never use it to retire a bet that ran: set its status to completed or canceled with initiative_set, so the board keeps what was learned.
One initiative whole: the thinking in its body, the campaigns serving it as the campaigns list knows them, and every checkpoint newest-first with the verdict each one froze. Read this before publishing a checkpoint — the last one says what was recommended, and whether it was done is the question a checkpoint has to answer.
Every initiative in the organization: what is being bet on, its status, the health of its latest checkpoint and how many days old that verdict is, plus the summed funnel of the campaigns serving it. Read this before proposing work — a campaign that serves no bet is how fifteen open fronts happen.
Write down a bet: a name, the thinking in markdown, and who is accountable for it. Omit initiativeId to create, pass one to edit. Call initiative_list FIRST and reuse the id of any bet that already covers this — a second call that omits the id creates a duplicate rather than updating what you just wrote. Health is NOT a field here and never will be — an initiative reports the verdict of its newest checkpoint, so the way to move it is checkpoint_publish. Read the gtm-initiative skill before creating one.
The org-wide outreach funnel over a date window — invites sent → accepted → messages → replied → interested — with the daily activity series and a per-seat breakdown, each seat carrying its campaigns and their acceptance, reply and interested RATES. This is the account-level question campaign_stats cannot answer: that one needs a campaign named first, and this spans every campaign and every seat. Narrow to one seat with userId (from the breakdown). Days are bucketed in your own stored timezone, not UTC.
What the operator has told Kairon: their competitor and source URLs, and their organization's value proposition, lead magnet and learnings log. Read this FIRST when deciding what they should work on next. It holds no writing rules: for the rules any draft is held to, theirs and Kairon's own, call writing_check_list. Name sections to read only some.
Replace one or more sections of what the operator has told Kairon: competitorUrls, sourceUrls, valueProposition, leadMagnet, learnings. Each section you name is replaced WHOLE; each one you omit is untouched. When the operator gives you a whole section, send it straight here. Only when ADDING to what is already stored (one more item, another learnings entry) call config_get first and send the merged text. An empty array or string clears a section. It writes no writing rules: writing_check_set is the only door to those, in every section.
One product doc in full, as markdown, by a slug from doc_list. Ground what you tell the operator in what it says — do not invent features, steps, limits or settings that are not in it, and when the doc states a constraint, report the constraint rather than working around it. Pair with skill_get when the question is not "what does Kairon do" but "how do I do this well".
Kairon's product reference — how each part behaves and where it stops. doc_get returns one in full, by a slug listed here. Call this BEFORE answering how Kairon works, what it can do, or why it is doing something — troubleshooting included ("nothing is sending", "is this a bug"). You do not know this product: it has documented rules that cause exactly those symptoms on purpose, and they are not guessable from other outreach tools. A guess tells an operator their working product is broken.
No recibe parámetros.
Order a sending domain and its mailboxes. **KAIRON pays, not the operator** — never quote them a price as something they will be charged; email_domain_quote's figures are Kairon's cost, and agreedTotal only makes the order refuse if that cost moved. What DOES need their explicit yes: the order cannot be undone, and the domain is registered and kept by Kairon's sending provider, so if they leave Kairon they cannot take it or its email history with them. Four endings, and only placed ordered anything — rejected means the name was refused, so pick another; payment_failed and checkout_required are both Kairon's to fix. The operator is charged nothing in any of them. Say which happened rather than assuming success. Mailboxes appear a few minutes after a successful order; then call email_warmup_start, because a mailbox that is not warming builds no reputation.
Ask Instantly whether this domain's MX, SPF, DKIM and DMARC records are set up correctly. Use it after the customer edits their DNS: records take minutes to hours to spread, so calling this again IS the retry — a failing answer now is not a permanent one. The domain must be one this workspace already sends from.
Price a sending domain and its mailboxes. The figures come from Instantly simulating the real order, so they are exact — and nothing is bought, no card is touched. Show the operator dueNow and pass that same number back as agreedTotal if they say yes; the purchase re-prices and refuses if it moved. One to five mailboxes.
Check whether Kairon can buy this domain to send from, and what is available instead when it is taken. Only .com and .org can be bought, and a name carrying a well-known trademark is refused outright. Free, and buys nothing. Use it for a domain the customer does NOT own yet — a domain they already run needs email_mailbox_add instead.
Add a mailbox the customer ALREADY owns — on their own domain, in their own Google Workspace or Microsoft account — so Kairon can send from it. Needs an APP PASSWORD, not the account password: for Google that is an app password created in their Google account, and 2-step verification must be on. Ask the operator for it directly; never guess one. Adding the same address twice is safe and creates nothing. The reply says whether the domain's DNS records (MX, SPF, DKIM, DMARC) pass yet — they often do not immediately, which is normal and does not undo the mailbox. Then call email_warmup_start.
Ask Kairon to set this workspace up for email sending (plan), or to add enrichment credits (credits). Owner or admin only. Neither is charged automatically: a person at Kairon completes the purchase, and you will see it done when the workspace appears or the credit balance rises — so tell the operator it is being set up, not that it is ready. Asking twice for the same thing is refused; ask email_setup_get first to see what is already open.
How this workspace sends email: whether it is on Kairon's sending plan, which sending domains it has and what each mailbox is doing (still being created, warming up, sending, or in trouble), how much of the plan is used, how many enrichment credits are left, and anything already requested from Kairon. Read this FIRST before adding a domain or a mailbox — it says whether the shop is even open for this org.
No recibe parámetros.
Start warming up every mailbox on this sending domain. Warmup is what builds the reputation that keeps mail out of spam, and it takes about three weeks — Kairon attaches a mailbox to campaigns by itself once it is ready, so nothing else is needed after this. Safe to call twice: a mailbox already warming is left exactly as it is. If the mailboxes do not exist yet the reply says pending, which means a domain order is still being provisioned — wait and call again.
Tell Kairon that something went wrong for this user. Call it the FIRST time a Kairon tool errors, returns something that clearly is not what was asked for, or refuses in a way you cannot explain, and also with no error at all when the user says Kairon is broken or asks again for something a Kairon tool has already answered. Kairon cannot see your conversation, so a problem you do not report here is a problem nobody at Kairon will ever learn about. It is free, spends no action and needs no seat. Do not report the same problem twice in a row; a second, different problem in the same conversation IS worth a second call. Never send credentials. Quote the user only where their own words explain the problem better than your summary does, and only the sentence that does it.
One axiom in full, as markdown, by a slug from framework_list. It is the argument and what to do about it, in the author's own words. Use it to ground advice and to explain WHY a playbook step exists; quote its reasoning to the operator in plain language, never the slug. Pair with skill_get for the how and doc_get for what Kairon itself does.
Kairon's go-to-market theory: the standalone truths every playbook rests on, each a slug, an area (belief-system, targeting, outreach, content, loop) and a title that says exactly what is inside. framework_get returns one in full. Read one when you need the WHY behind a step a playbook prescribes, when the operator questions the approach, or when you are about to advise on strategy rather than run a job. Titles only — cheap to list once per session.
No recibe parámetros.
One playbook in full, as markdown, by a name from skill_list — call that first; an invented name is a 404. Read it BEFORE doing the job it covers, never after drafting: it encodes what Kairon measured to work, which is usually NOT what a general model would choose, so a draft written first is already wrong in the ways the playbook exists to prevent. Pair a writing skill with writing_check_list for the rules the draft is held to, and config_get for the operator's offer. Every playbook says WHAT to do; talking-to-the-operator says how to say it back to a founder who is not an engineer — fetch that one too if you have not already.
Kairon's playbooks — how to do each job well: onboarding, Sales Navigator, list building, campaigns, writing messages and posts, what to do next. Each row is a TEASER, not the craft: it hints at what a playbook covers so you can choose, and the instructions live only in the body. Never act on a row alone — including its own "pair with config_get" advice, which applies AFTER you skill_get that name, not instead of it. The catalogue grows without a release; never assume you know it.
No recibe parámetros.
Change the organization's own display name, exactly as it appears across the Kairon app. Owner/admin only — a caller on any other role is refused. Separate from config_set because it carries its own, stricter permission rather than the open one every config section shares.
Run one draft past the SAME judge a real send goes through, and get a pass/fail plus a reason for every rule that governs it. Nothing is sent, drafted or saved. Use it to try a rule the operator just wrote before they rely on it, and to show them WHY a draft fails — a rule that reads well and cannot be satisfied is the failure this exists to catch. Pass context (the post being answered, the thread) or rules about their words cannot be judged at all. Costs one model call.
The rules Kairon holds every draft to, in four sections: general (everything it writes), post, comment, dm (one-to-one messages — campaign sends, inbox replies, auto-replies). Each returns Kairon's own shared rules, with the key you switch them by and whether they are on, then the Author's own rules with their ids. recentFailures counts drafts each rule stopped in the last 7 days — a high one is a rule nothing can satisfy. Read this before drafting anything.
Replace the Author's OWN rules for ONE section, whole. Kairon's shared rules are untouched — switch those with writing_check_shared_set. Send the COMPLETE list for that section: keep a rule by including it with its id from writing_check_list (an id we do not recognise is replaced with a fresh one, which resets what that rule has stopped), add one by omitting the id, delete one by leaving it out. Other sections are untouched. Phrase a rule so a draft either passes it or does not.
Switch one of Kairon's SHARED rules on or off for this Author, by a key from writing_check_list. Kairon's rules are on by default and are not editable — an Author only chooses whether one applies — so this is the only thing to do to one. Use it when the operator's own style genuinely conflicts with a built-in rule, not to quiet a rule that keeps stopping drafts: that one is usually telling the truth about the writing.