The Pulse Recruit MCP server
Connect any assistant that speaks the Model Context Protocol and ask your own book questions in plain language. 51 tools, as the recruiter who connected, under the agency's rules. Nothing here sends.
What it is and is not
Four verbs, and the last one is the point.
Read
Your people, companies, roles and campaigns. What is due today and what replied. Roles advertised by companies you track.
Write
Notes and tasks, marked as written by an assistant. Add people to a campaign. Record what a reply meant. Propose facts you can cite.
Spend
Searching your book, drafting a message, refreshing a record, sourcing outside the book. Each one asks first past a threshold you set.
Never
Send a message, an invitation or an email. Create a client. Add a person it only read about. Overwrite a fact somebody entered.
Connecting
An admin turns it on for the agency in Settings, AI assistants. Then each person either signs Claude in, with no token at all, or mints a personal token there for a client set up by hand. A token is shown once.
Claude on the web, desktop or phone: Settings, Connectors, Add custom connector, then Connect and Allow
https://www.pulserecruit.ai/api/mcp
Claude Code, signing in: run this, then /mcp to sign in
claude mcp add --transport http pulse-recruit https://www.pulserecruit.ai/api/mcp
Claude Code, with a personal token
claude mcp add --transport http pulse-recruit https://www.pulserecruit.ai/api/mcp --header "Authorization: Bearer pmcp_..."
Claude Desktop on macOS or Linux, in its configuration file
{
"mcpServers": {
"pulse-recruit": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://www.pulserecruit.ai/api/mcp",
"--header",
"Authorization: Bearer pmcp_..."
]
}
}
}Claude Desktop on Windows, where npx must go through cmd
{
"mcpServers": {
"pulse-recruit": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"mcp-remote",
"https://www.pulserecruit.ai/api/mcp",
"--header",
"Authorization: Bearer pmcp_..."
]
}
}
}Anything else
POST https://www.pulserecruit.ai/api/mcp
Authorization: Bearer pmcp_...
Content-Type: application/json
Accept: application/json, text/event-stream
{"jsonrpc":"2.0","id":1,"method":"tools/list"}Streamable HTTP, stateless: every request carries the bearer and no session id. The token is pmcp_ and 32 random bytes, expires on the date the settings screen shows, and can be ended there at any time, with effect on the next call. Your records go to the company behind the assistant when you use it, and the agency is responsible for that; personal plans on some assistants can train on what you send, which the picker says beside each entry.
The tools
Every one, generated from the server's own registry. 51 live.
Finding people
search_the_book
Spend. Costs money, so it reserves first and asks past your threshold.Who the agency already knows for a role: ranked people from the ATS and from the recruiters' LinkedIn networks, each with their reasons and which pool they came from. The one answer nobody else can give. Name a job_id, or a posting_id for a role a company is advertising. The consent filter applies. Costs at most the title-widening call; a repeat on the same title is free.
| job_id? | id | A role the agency is working. |
| posting_id? | id | Or a role a company is advertising, from roles_matching_my_desk. |
| limit? | integer | Default 25. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
who_do_we_know
Read. Touches nothing.Colleagues already connected to people at a company, with the relationship and who holds it. The consent filter applies: a person whose consent has lapsed is not returned. Give a company_id, an outbound_company_id, or a company_name.
| company_id? | id | A company on the ATS. |
| outbound_company_id? | id | A prospect. |
| company_name? | string | Or just the name, if you have no id. |
Since 2026-09.
find_in_market
Spend. Costs money, so it reserves first and asks past your threshold.People NOT in the book, from the sourcing vendor, read and scored against the role, staged for a recruiter to accept in Pulse. Nobody becomes a person on the book until a recruiter accepts them. Always over the confirmation threshold: say the cost and get a yes first. Run search_the_book before this.
| job_id | id | The role to source for. Its title, location and description are the brief. |
| wanted? | integer | How many people to read in detail. Default 10. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
Looking things up
get_person
Read. Touches nothing.One person: who they are now, their history on the agency's roles and their interview scorecards, their consent basis, what has happened with them recently, their upcoming interviews, and any records that may be the same person (merging happens in the product). Name exactly one of person_id (the ATS) or outbound_person_id (the recruiter's network). Ids come from search, search_the_book or who_do_we_know.
| person_id? | id | A person on the ATS: placed, screened or applied. |
| outbound_person_id? | id | A person in the recruiter's LinkedIn network. |
Since 2026-09.
get_company
Read. Touches nothing.One company: sector, size, whether it is tracked, its open roles with the agency, and who the agency already knows there. Name exactly one of company_id or outbound_company_id.
| company_id? | id | A company on the ATS: a client or a buyer. |
| outbound_company_id? | id | A prospect the agency sells to, not yet a client. |
Since 2026-09.
get_job
Read. Touches nothing.One role: the client, how long it has been open, its stages with how many people are in each, the titles the book is searched under for it, and its upcoming interviews (booking one happens in the product).
| job_id | id | A role the agency is working. |
Since 2026-09.
search
Read. Touches nothing.Find people, companies and roles by name, for when you have words rather than an id. People are also found by what their CV says, each such match with the line that matched (cv_line). People come from both pools with the consent filter applied, so a person whose consent has lapsed is not returned. Use the ids in get_person, get_company and get_job.
| query | string | A name, a company, a role title, or words from a CV ("python django", ""account manager"", "-intern"). At least two characters. |
| limit? | integer | Per pool. Default 10. |
Since 2026-09.
The day's work
whats_due_today
Read. Touches nothing.The actions due now across the agency's campaigns, oldest first, grouped by where the work happens (LinkedIn, email, phone). Each is a person, the campaign, and the step. Also the recruiter's own tasks due by the end of today, as a separate list. Nothing here sends: the recruiter does each one in Pulse, and Pulse records it.
| limit? | integer | Default 30. |
Since 2026-09.
whats_waiting_on_me
Read. Touches nothing.People who replied and have not been answered, newest first: the thread, who they are, and the last thing they said. The most valuable list in the product.
| limit? | integer | Default 30. |
Since 2026-09.
The desk
roles_matching_my_desk
Read. Touches nothing.Advertised roles at tracked companies that match what the agency places: the function, the level and the place the desk has set. Newest first. Refuses until the desk's niche is set in Settings, because without it every posting would match.
| limit? | integer | Default 30. |
| days? | integer | Posted within this many days. Default 14. |
Since 2026-09.
company_signals
Read. Touches nothing.What a tracked company has done lately: funding, hires, departures, postings, news. Refuses for a company that is not tracked, because nothing is polled for one.
| company_id | id | A tracked company on the ATS. |
| days? | integer | Default 30. |
Since 2026-09.
Campaigns
campaign_performance
Read. Touches nothing.How the campaigns are doing over a window, counted in people: contacted (any send in the window), accepted (a connection request accepted), replied (a reply, or a reply label a recruiter set), interested, meetings booked, roles opened from replies and placements on them, each later stage inside the one before. An accepted invitation is not a reply. Per live campaign, with its people by status now, and the workspace's total.
| days? | integer | The window. Default 30. |
| campaign_id? | id | One campaign, or every live one when omitted. |
Since 2026-09.
draft_message
Spend. Costs money, so it reserves first and asks past your threshold.A drafted message for a named person, in the org's style, from the facts Pulse holds and nothing invented. A draft, never a send: it lands where the recruiter reads it before anything goes. Give an enrolment_id for a person in a campaign, or a person id with a job_id for anybody else.
| enrolment_id? | id | A person in a campaign, from whats_due_today. The step's template and the campaign's context are used. |
| outbound_person_id? | id | Or a person in the network with no campaign. Pair with job_id. |
| person_id? | id | Or a person on the ATS with no campaign. Pair with job_id. |
| job_id? | id | The role the message is about, when there is no enrolment. |
| style? | string | A style key from the org's list. Default: the step's, or 'direct'. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
enrol
Write. Changes a record, as you.Add people to a campaign. Every exclusion rule, the do-not-contact list and the off-limits register apply unchanged, and the answer says who was added, who was already in, and who was skipped and why. Nothing is sent: the first action appears in whats_due_today for a person to do. Refused for a draft or archived campaign.
| campaign_id | id | An active campaign, from campaign_performance. |
| outbound_person_ids? | array | People in the network. |
| person_ids? | array | Or people on the ATS. |
| off_limits_reason? | string | Only for ONE person skipped as off limits (at a client, or on the agency's register): why approaching them is allowed. Ask the recruiter; never invent it. It is kept. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
set_reply_intent
Write. Changes a record, as you.Record what a reply meant, as the recruiter's reading of it. Interested, not now (with a date), a referral, not interested, an objection, or an auto reply. It changes what happens next in the campaign, so quote what they said in why.
| enrolment_id | id | The enrolment whose reply this reads, from whats_waiting_on_me or whats_due_today. |
| intent | interested | not_now | referral | not_interested | objection | auto_reply | |
| why? | string | One line quoting what they said. A label with no basis cannot be argued with. |
| come_back? | string | For not_now: when to try again. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
preview_market_candidate
Write. Changes a record, as you.Start marketing a candidate, free: the net starts from the candidate (companies like their employers when asked), and once judging starts the run adds the companies with jobs open now for their titles, the Dream 100 companies and market builds that hire for them, and a hiring search; minus every company the candidate worked for, applied to or is in process with, recruitment agencies, the do not contact list, and companies whose every open role is outside their area. Returns the counts, what was left out and why, and the projected cost of judging and drafting in credits. Nothing is judged and nothing is spent; market_candidate does that with this run_id and projected_credits.
| person_id | id | A candidate who is marketable, from get_person. |
| lookalikes? | boolean | Also companies like the candidate's previous employers, from PredictLeads, free. Default false. |
| off_limits_reason? | string | Required when the candidate's current employer is a client: why marketing its employee is allowed. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
market_candidate
Spend. Costs money, so it reserves first and asks past your threshold.Run the market for a candidate inside a budget (the projected credits, reserved once): every company is judged against the candidate's characteristics from its own postings and signals, the proposal (fits, maybes with a live role, close matches) is ticked on the shortlist, up to two people a shortlisted company are found by the workspace's seniority ladder (those with a verified address and a clear greeting approved by themselves), and an email, a LinkedIn message and a connection note are drafted for each approved person. It stops at review: no message is ever sent until a person approves it on the run page. Always asks first with the projected cost.
| run_id | id | The run preview_market_candidate built. |
| projected_credits | integer | The projection preview_market_candidate returned, in credits. The ceiling the person agrees to. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
save_mpc_campaign
Write. Changes a record, as you.Save a run as a campaign in one transaction: a list of the approved people at the shortlisted companies and the campaign marketing the candidate with the Market a candidate sequence. Saving again adds anybody approved since. The campaign opens in draft, and each message sends only once a person has approved it. Always asks first.
| run_id | id | A run market_candidate judged. |
| name | string | The campaign's name, which is also the list's. Unique in the workspace. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
open_role_from_reply
Write. Changes a record, as you.Turn an interested reply on a campaign that markets a candidate into a role: a job at the replier's company, created as a prospect when the company is not on the book, with the marketed candidate on its first stage and the reply linked to it. Needs the candidate's OK to be named to that company, which is recorded. Admins only, as every role is. Always asks first. Nothing is sent.
| enrolment_id | id | The enrolment whose reply was marked interested, from whats_waiting_on_me. |
| candidate_agreed | boolean | True only once the candidate has agreed to be named to this company. Recorded on the enrolment with the date and who said so. |
| title? | string | The role's title. Default: the candidate's own title. |
| company_name? | string | Only when the replier has no company on record: the company the role is at. Created as a prospect when it is not on the book. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
Writing back
add_note
Write. Changes a record, as you.Add a note about a person or a role. It appears in the activity stream marked as written by an external assistant, beside the notes people wrote. Name at least one of person_id, outbound_person_id or job_id.
| body | string | The note, in plain words. |
| person_id? | id | About a person on the ATS. |
| outbound_person_id? | id | About a person in the network. |
| job_id? | id | About a role. A note about a person on a role names both. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
create_task
Write. Changes a record, as you.Create a task for the recruiter: a reminder or to-do of their own. It lands in their task list marked as created by Claude, private to them unless it is about a person or company. Nothing is assigned to anybody else and nothing is sent.
| title | string | What has to be done, as a person would write it. |
| detail? | string | |
| due? | string | When, if the person named a time. Leave out for no date. |
| priority? | low | normal | high | urgent | |
| person_id? | id | The person it is about, on the ATS. |
| company_id? | id | The company it is about. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
Tags
list_tags
Read. Touches nothing.The agency's candidate tags, such as Python, Hot or Met at event: each tag's name, colour and how many people carry it, most used first.
Since 2026-10.
set_person_tags
Write. Changes a record, as you.Add or remove tags on one person on the ATS, by name. A new name becomes a tag, as it does in the product. Up to 10 names a call. Tagging many people at once stays in the product.
| person_id | id | A person on the ATS, from search or get_person. |
| add? | array | Tag names to add. A name that does not exist yet is created. |
| remove? | array | Tag names to take off. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-10.
Enrichment
enrich_person
Spend. Costs money, so it reserves first and asks past your threshold.Refresh what Pulse holds about a person from the vendor path the sourcing panel uses. For a person in the network that is their LinkedIn profile, read once a day at most, through the agency's own connected account. Refused when their consent has lapsed.
| outbound_person_id? | id | A person in the network. Their LinkedIn profile is read and kept. |
| person_id? | id | A person on the ATS. Not refreshable yet; see the answer. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
enrich_company
Spend. Costs money, so it reserves first and asks past your threshold.Read a prospect's LinkedIn company page and keep a dated observation of its headcount beside the record, leaving the record itself as it was. One observation per company per day.
| outbound_company_id | id | A prospect. Its LinkedIn page is read and a dated observation kept beside it. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
propose_facts
Write. Changes a record, as you.Fill empty fields on a person Pulse already holds, from evidence you can cite. A field that already has a value is left alone whoever wrote it, a fact with no evidence is skipped, and an email or a phone is never accepted here. The answer says what was filled and what was skipped and why.
| person_id? | id | A person on the ATS. |
| outbound_person_id? | id | A person in the network. |
| facts | object | Field to value. ATS: title, company_name, location, linkedin_url. Network: title, headline, company_name, linkedin_url. |
| evidence | object | Field to the URL or quote it came from. A fact with no evidence is skipped. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
Dream 100
promote_to_dream_100
Write. Changes a record, as you.Start tracking a company: its signals are polled weekly from now on and appear in company_signals. Refused by the database past the plan's tracking allowance. Use set_tracking_tier for daily.
| company_id | id | A company on the ATS. A prospect is promoted by adding it as a company first, in Pulse. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
set_tracking_tier
Write. Changes a record, as you.Move a tracked company between weekly and daily polling. Daily is a small allowance, refused by the database when it is full. A company not yet tracked is tracked at the tier you name.
| company_id | id | A tracked company. |
| tier | daily | weekly | |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
dream_100_status
Read. Touches nothing.What is tracked, at which tier, what fired in the last week, and how much of the tracking allowance is used.
Since 2026-09.
Markets
save_icp
Write. Changes a record, as you.Store a market definition once the person has confirmed it: the ICP paragraph with its exclusions, a headcount band, places, the seed accounts the client likes, and eight to twelve slices with real industry picklist values and thematic keywords. The first call refuses with the whole definition in the confirmation; the retry with the id and identical content writes. Give id to update, from_id to start from an existing market. Refuses while the desk niche is unset.
| id? | id | Absent to create, present to update. |
| from_id? | id | Copy an existing market as the starting draft; anything not given is taken from it. |
| name? | string | The client or the market. Unique in the workspace. |
| icp? | string | One paragraph: who qualifies and who explicitly does not. |
| exclusions? | array | Disqualifiers, locked. |
| headcount? | object | |
| places? | array | |
| seeds? | array | Five to fifteen domains the client already likes. |
| slices? | array | Eight to twelve sub-segments, the SOP's step 1. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
list_icps
Read. Touches nothing.Every market definition the workspace has saved: name, paragraph, exclusions, seeds, slices, who confirmed it and when, and the builds made from it. Pick one by name for the next build rather than redrafting.
| include_retired? | boolean | Also list markets that were retired. Default false. |
Since 2026-09.
delete_icp
Write. Changes a record, as you.Retire a saved market definition. Its builds and the companies on the book are untouched. Refused while a build on it is in progress.
| icp_id | id | The market to retire. Its builds and the companies they wrote stay. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
build_tam
Spend. Costs money, so it reserves first and asks past your threshold.Build a Dream 100 from a saved definition: every slice pulled from every configured source, deduplicated by domain, relationship accounts set aside, each company judged FIT, MAYBE or NOT against the ICP and the desk, and the best hundred kept. Always asks first with the projected cost, which is a ceiling. Runs unattended for minutes to an hour; ask tam_status for progress. The full market behind the hundred, usually five to twenty five thousand companies, is a service the RecruiterGTM team builds by hand and is asked for in the product, not here.
| icp_id | id | A saved, confirmed market definition, from list_icps. |
| market? | string | A name for the Dream 100, so it can be found later. Defaults to the definition's name. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-09.
tam_status
Read. Touches nothing.Where a build is: the stage, rows in and out, spend so far against the projection, which sources it used and which were missing, what it was built from, and how many companies wait on a person's verdict.
| run_id? | id | A build, from build_tam. Absent: the latest builds in the workspace. |
Since 2026-09.
tam_summary
Read. Touches nothing.How big a market is and how it segments by slice, sector, size and place; the overlap between sources; twenty FIT companies sampled at random with their reasons, for the SOP's spot check; the companies waiting for a person's verdict, the first 300 by name with the total; and the relationship accounts set aside. Counted over every company in the build, however large.
| run_id | id | A finished build. |
Since 2026-09.
settle_tam_verdict
Write. Changes a record, as you.Record a person's decision on a company the model could not decide. The verdict becomes fit or not, stamped as the recruiter's, and no later model verdict overwrites it.
| company_id | id | The company on the book, from tam_summary's needs_a_verdict. |
| verdict | fit | not | The person's call. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
add_build_to_dream_100
Write. Changes a record, as you.Put companies from a finished build onto the Dream 100, the set Pulse watches for signals, and take others off in the same press. The list holds a hundred: the database refuses the whole call, writing nothing, when what is coming in does not fit what is going out, and says how many more to release. A company already watched is counted, not re-added. A company with no domain cannot be watched at all and is reported. Only a qualified company joins: one still for review joins once settled as a fit, and anything else is reported as not qualified.
| run_id | id | A finished build, from build_tam. |
| company_ids | array | Companies from this build to start watching, from tam_summary's fit_sample or the build's page. |
| replacing? | array | Companies on the book to stop watching, to make room. From dream_100 or the build's page. They keep their record and their signals; they stop being polled. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
Lead lists
list_lead_lists
Read. Touches nothing.Every lead list in the workspace: its source (hand-picked, people who match, buyers at companies), the rule in prose, how many people are on it, when it was refreshed, and which campaigns point at it. Also what a new rule may use: the finished market builds, the signal kinds and job functions that exist, and the approved buyer titles.
Since 2026-09.
preview_lead_list
Read. Touches nothing.How many people a rule would put on a list, with a sample of forty and the skips, without saving anything. Run this, show the person the count and a few names, then save_lead_list once they agree. Thirty a minute.
| source | people | buyers | people: connections filtered by the ideal client and what has happened. buyers: the deciders at a set of companies. |
| touched? | never | quiet | any | never: no message yet. quiet: nothing for quiet_days. Default any. |
| quiet_days? | integer | |
| has_email? | boolean | Only people with a deliverable email. |
| kind? | contact | candidate | any | Default contact: people who could buy. |
| score_min? | integer | people: ideal client score at least this. |
| title_any? | array | people: title contains any of these. |
| title_none? | array | people: title contains none of these. |
| company_status? | array | people: the employer's status on the book. |
| run_id? | id | people: employer fits this finished market build. |
| dream_100? | boolean | people: employer is on the Dream 100. |
| companies? | object | buyers: which companies. |
| who? | object | buyers: who at them. buyers means the approved ideal client titles. |
Since 2026-09.
save_lead_list
Write. Changes a record, as you.Create a lead list from a rule, or change the rule of one, and refresh it. Pulse keeps it current: every campaign pointed at it follows on the fifteen-minute sweep. The first call refuses with the rule in prose; the retry with the confirmation id and identical arguments writes. Free.
| list_id? | id | Absent to create, present to change the rule of an existing rule list. |
| name | string | Unique in the workspace. |
| source | people | buyers | people: connections filtered by the ideal client and what has happened. buyers: the deciders at a set of companies. |
| touched? | never | quiet | any | never: no message yet. quiet: nothing for quiet_days. Default any. |
| quiet_days? | integer | |
| has_email? | boolean | Only people with a deliverable email. |
| kind? | contact | candidate | any | Default contact: people who could buy. |
| score_min? | integer | people: ideal client score at least this. |
| title_any? | array | people: title contains any of these. |
| title_none? | array | people: title contains none of these. |
| company_status? | array | people: the employer's status on the book. |
| run_id? | id | people: employer fits this finished market build. |
| dream_100? | boolean | people: employer is on the Dream 100. |
| companies? | object | buyers: which companies. |
| who? | object | buyers: who at them. buyers means the approved ideal client titles. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
Other
list_tasks
Read. Touches nothing.The recruiter's open tasks, their own reminders and to-dos, soonest first: title, due time, priority, repeat, list, and the person or company it is about. Not campaign actions; those are whats_due_today.
| which? | due_soon | mine | everyone | due_soon: your tasks overdue, today or tomorrow (default). mine: all your open tasks. everyone: every open task you can see. |
| limit? | integer | Default 30. |
Since 2026-09.
complete_task
Write. Changes a record, as you.Mark one of the recruiter's tasks done, as them. A repeating task makes its next occurrence. Nothing is sent and nothing else changes.
| task_id | id | A task from list_tasks. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-09.
list_pools
Read. Touches nothing.Every candidate pool in the workspace: the agency's own people kept for future roles, built from stacked filters (CV words, stage reached, client, silver medallists, consent and so on) with people filed from roles or added by hand. Each with its filters in prose, how many people are in it and when it was refreshed. get_person names a person's pools.
Since 2026-09.
list_artifacts
Read. Touches nothing.Research briefs, candidate profiles and submission packs, newest first, each at its latest version: the kind, title, version, whether it is frozen (shared or sent, so it no longer changes), and for a profile whether a newer CV is on file than the one it was built from (stale). get_artifact reads one.
| kind? | research_brief | candidate_profile | submission_pack | Only this kind. Leave out for all three. |
| person_id? | id | Only a candidate's profiles. |
| job_id? | id | Only a role's packs. |
| limit? | integer | At most this many, newest first. 20 when left out. |
Since 2026-10.
get_artifact
Read. Touches nothing.One version of a research brief, candidate profile or submission pack: its body as Pulse keeps it and the sources it rests on, each with its type (cv_file, profile_field, answer or search), label, the line it quotes and any link.
| document_id | id | An id from list_artifacts. Any version, not only the latest. |
Since 2026-10.
get_pack_answers
Read. Touches nothing.How the client answered on each candidate in a pack: per recipient the pack was sent to, interested or not for us, any note they left, when, and whether somebody has acted on it in Pulse. Candidates nobody has answered on are listed as unanswered.
| pack_id | id | The pack's id: the one create_pack returned, or a shortlist that became a pack. |
Since 2026-10.
save_research_brief
Write. Changes a record, as you.Keep research you did as a research brief in the agency's library, version 1, frozen, with the sources it rests on. Refused without at least one source. It can then be shared from Pulse like any brief.
| question | string | The question the research answers, as the person asked it. |
| answer | string | Your answer, in plain words. Kept exactly; nothing in it may go beyond what the sources say. |
| sources | array | Every source the answer rests on: a page with its link, or a named document. At least one. |
| title? | string | A title for the brief. The question when left out. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-10.
build_candidate_profile
Spend. Costs money, so it reserves first and asks past your threshold.Ask Pulse to build, or rebuild, a candidate's profile from their CV in the agency's house format. Pulse's own builder reads the CV with the person's name and contact details removed, and keeps only lines it can quote from the CV; the current employer is described, not named. About 2 credits for a text CV, more for a scan. With no CV it is built from the record and costs nothing. Returns the profile's id for get_artifact.
| person_id | id | The candidate. Built from the CV marked as theirs, in the agency's house format. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-10.
create_pack
Write. Changes a record, as you.Start a submission pack for a role from candidates on it, in the order given. Returns the pack's id, who still needs a profile (build_pack_profiles) or a pitch (draft_pack_pitches), and anything that would stop the pack going out, per candidate (off limits, no lawful basis to share). Nothing is frozen or sent: a person finishes and sends it in Pulse.
| job_id | id | The role the pack is for. |
| person_ids | array | Candidates on that role, in the order the client should read them. |
| request_id? | string | An idempotency key. A retry with the same key returns the original result. |
Since 2026-10.
build_pack_profiles
Spend. Costs money, so it reserves first and asks past your threshold.Build a profile for each named candidate on a pack who has none yet, through Pulse's own builder, one at a time. About 2 credits each for a text CV. Reports honestly: what was built, what it cost, and who failed and why.
| pack_id | id | |
| person_ids | array | The candidates on the pack to build a profile for: those create_pack listed without one. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-10.
draft_pack_pitches
Spend. Costs money, so it reserves first and asks past your threshold.Draft the pitch, why the agency puts them forward, for each named candidate on a pack. Pulse drafts it from the candidate's profile and the role's brief, and keeps it only if every line it quotes is in the profile; it is saved on the pack for a person to edit. About 1 credit each. Replaces a pitch already there.
| pack_id | id | |
| person_ids | array | The candidates on the pack to draft a pitch for. Each needs a profile first. |
| confirmation_id? | id | The id from an earlier over_threshold refusal, with the same arguments. |
Since 2026-10.
Confirmation and refusals
A spend over your threshold does not run. It refuses with an id, and the retry has to carry it.
A refusal, as a tool result
{
"ok": false,
"refused": "over_threshold",
"message": "About 8.24 USD. Confirm to go ahead.",
"confirmation_id": "9b1c...",
"projected_usd": 8.24
}The id is minted by the server after refusing, bound to a digest of the exact arguments, single use, ten minutes, and bound to your connection. Where the assistant supports elicitation the server asks the person in-call and proceeds on a click; where it does not, the assistant relays the cost and calls again with confirmation_id and the same arguments. Every success carries ok: true. Write tools accept request_id: a retry with the same key returns the original result and acts once.
| over_threshold | The projected cost is over your threshold. The answer carries confirmation_id and projected_usd. Show the person the cost; call again with the id and identical arguments once they have said yes. |
| over_ceiling | This connection has spent its monthly ceiling. Reading tools keep working. The answer carries ceiling_left_usd. |
| rate_limited | More than sixty calls in a minute. The answer carries retry_after_s. |
| confirmation_mismatch | The confirmation was for different arguments, another tool, or another connection. Ask again. |
| confirmation_used | The confirmation was already used. A retry after success finds it consumed, which is what makes a retry safe. |
| confirmation_expired | Ten minutes passed. Ask again. |
| refused_by_rule | A rule inside the tool refused: no such person, consent lapsed, a cap reached, a fact with no evidence. The message says which. |
The guards, and the log
Three limits per connection, all set on the settings screen, and one log the admin reads.
- A monthly ceiling in dollars, default 25. Above it, spending tools refuse and reading tools keep working.
- A confirmation threshold in dollars, default 1. A single call projected above it asks first.
- Sixty calls a minute per connection, counting calls still in flight.
- A vendor tool's declared cost counts against the same ceiling and threshold, and is logged as paid by the agency.
- Every call, including refusals, is logged with who asked, which tool, what it cost, and which record it was about. The record is a reference, never a name, so erasing a person nulls it. The log keeps 180 days. Admins see every connection in the agency.
Your own vendors
An admin can put a vendor the agency already pays for beside these tools. Each tool on it is declared by hand, only reads, and returns one of the four records below or a refusal.
- A vendor tool is named for its vendor, like
clay_find_company, and is listed only to connections in the agency that declared it, never on this page. - It calls the vendor with GET or POST on the agency's own key. The key goes into a header on the server and is in no tool result, log row or error, and an answer that carries it back is refused.
- The address is the admin's https base URL and a path over the declared arguments, and no argument is called url. The host is looked up at call time and refused if it resolves to a private, loopback or link-local address. Redirects are not followed, and an answer stops at 1 MB or 30 seconds.
- The answer is mapped onto one record. Fields the map does not name are dropped, and an answer that does not fit is
refused_by_rulewith nothing returned. - Nothing a vendor returns is saved. To keep a fact about somebody already on the book, the assistant calls
propose_facts.
person
A person. Needs name.| name | string | Their full name. |
| title? | string | Their current job title. |
| headline? | string | The line under their name on a profile. |
| company_name? | string | Where they work now. |
| location? | string | Where they are based. |
| linkedin_url? | string | Their LinkedIn profile address. |
company
A company. Needs name.| name | string | The company's name. |
| domain? | string | Its website's domain, like acme.com. |
| industry? | string | What it does. |
| sector? | string | The sector it sells into. |
| headcount? | number | How many people work there, as a number. |
| headcount_band? | string | A size band, like 51-200. |
| location? | string | Where it is headquartered. |
| open_roles? | number | How many roles it is advertising, as a number. |
job
A job. Needs title.| title | string | The role's title. |
| company? | string | Who is hiring. |
| location? | string | Where the role is. |
| work_pattern? | string | Remote, hybrid or on site. |
| salary_min? | number | The bottom of the range, as a number. |
| salary_max? | number | The top of the range, as a number. |
| salary_currency? | string | The currency of the range, like GBP. |
| opened_at? | string | When it was posted. |
| closes_at? | string | When applications close. |
observation
An observation. Needs at least one of headcount, industry, location, linkedin_slug.| headcount? | number | How many people work there today, as a number. |
| industry? | string | What it does, as seen today. |
| location? | string | Where it is, as seen today. |
| linkedin_slug? | string | The company's LinkedIn page name. |
Changelog
Names are permanent. Arguments and results only gain fields. A retired tool keeps its name for ninety days and says what replaced it.