| setup_profileA | Set up HeyLead by connecting your LinkedIn account and analyzing your writing style. REQUIRED for first-time users — must be called before any other tool.
This analyzes your LinkedIn profile, posts, and writing style to create
a "voice signature" so every outreach message sounds like YOU, not a bot.
Handles LinkedIn automation setup, SDR onboarding, account connection,
and voice analysis for personalized outreach.
First-time setup: sign in at https://heylead.dev/auth/login-url, click
'Connect' on the LinkedIn row, copy the setup message ('Copy' under
'Get Started'), then call this tool with the eyJ... token from it as
backend_jwt. No API keys needed on the hosted backend.
Args:
llm_api_key: Optional — only if you want to use your own AI key instead of the backend's.
llm_provider: Which AI to use if providing your own key: "gemini", "claude", or "openai".
backend_url: HeyLead Backend API URL. Leave empty — defaults to production server.
backend_jwt: Your authentication token from HeyLead.
|
| generate_icpA | Generate a rich Ideal Customer Profile with buyer personas. The same profile describes whoever the user needs to reach: buyers,
candidates to recruit, research or user-interview participants, hiring
managers for a job search, investors or partners. Pass goal= so the
profile is of the right people.
Creates 2-4 ICP personas with pain points, fears, barriers,
LinkedIn search parameters, and confidence scores. The result
is saved and can be reused with create_campaign(icp_id=...).
Supports target audience analysis, customer segmentation, buyer persona
creation, ideal customer profiling, and B2B market research.
Args:
target_description: Who to target (e.g., "CTOs at fintech startups",
"freelance UX designers in London", "yoga studio owners in California")
company_context: Optional URL or text about your company/product.
Providing this makes the ICP more precise and evidence-backed.
focus_query: Optional focus (e.g., "enterprise segment only",
"focus on pain points around compliance")
decision_makers_only: Keep every persona's seniority to people who
hold budget authority — owner, cxo, vp, director. Default True.
Pass False only when the target really is individual contributors
(developers, designers, analysts); the ICP then keeps whatever
levels the description implies. Applies only to sell, partner
and buy; managers hire, so a job search keeps them.
goal: What the campaign is for, which decides whose profile this is:
"sell" (customers, the default), "job_search" (the people who hire for
or refer into the role), "hire" (candidates), "partner" (who can sign
a partnership or invest), "buy" (vendors), "research" (participants).
Always pass it; a job search with goal="sell" produces peers, not
hiring managers.
|
| create_campaignA | Create a LinkedIn outreach campaign from a natural language description. Finds people on LinkedIn who match the description and saves a draft;
nothing is sent until campaign(action='launch'). Use it for sales
prospecting, recruiting and candidate sourcing, research and
user-interview recruitment, job-search networking, investor and partner
outreach, vendor scouting and event invitations.
Reaching people you can already name — an article author, a warm intro, a
speaker, one founder a customer mentioned — is the `people` argument: pass
their profile URLs and the campaign is seeded from exactly them, with no
LinkedIn search. Everything else is unchanged: draft until launch, rate
limits, sending window, opt-outs.
Describe who you need to reach and HeyLead finds them on LinkedIn:
customers (lead generation, B2B prospecting, cold outreach), candidates
(recruiting and sourcing), hiring managers and referrals (job search),
investors and partners, vendors, and user-interview or research participants.
On first campaign, project_brief is asked explicitly, in the goal's words
(see project_brief below) — a homepage alone is not enough.
Args:
target_description: Who to target (e.g., "CTOs at fintech startups",
"freelance UX designers in London", "yoga studio owners in California")
campaign_name: Optional name for the campaign.
icp_id: Optional ID of a saved ICP from generate_icp. If provided,
uses the saved ICP's enriched LinkedIn codes for precise targeting
instead of generating a new one.
company_context: Optional. Your website URL or 1-2 sentences about your
product/company. Copied into project_brief when project_brief is omitted.
project_brief: Optional. Full project paste the model sees, in the goal's
words. sell: what you offer and who it is for. job_search: the role,
the kind of company, what you bring. hire: the role, who fits it, what
it offers. partner: what you want from a partner, what you bring.
buy: what you need, by when and how much, what a vendor must confirm.
research: what you research, who you want to hear from, what you ask.
Required before launch, resume, or auto-send.
mode: Always autopilot (copilot mode was removed). Whether opening DMs
and follow-ups wait for a person is the WORKSPACE's approval mode,
not this: a hosted workspace that never chose holds them
(inspect(action="waiting") lists them); scheduler(action=
"approval_mode") switches it.
company_url: Optional LinkedIn company URL for account-based targeting.
Searches for employees at that specific company matching the ICP.
Example: "https://www.linkedin.com/company/google"
voice_mode: "text_only". Messages are text.
connections_only: "on" to create a DM-only campaign targeting existing
LinkedIn connections. Skips invitations and warm-up — sends DMs
directly to people you're already connected with. Use when user says
"existing connections", "DM my network", "message my connections".
exclude_connections: ON BY DEFAULT for a new campaign: nobody who was
already a 1st-degree connection before this campaign started is
reached — refused at enrolment and skipped at send time rather than
DMed. People who accept this campaign's own invitation still get
the opener. Pass "off" to include existing connections (or turn it
off later in the campaign settings). Defaults off only for a
connections_only campaign. Use "off" when the user says
"include my existing connections"; the old "on" is still accepted
for "don't message my existing connections", "cold only",
"skip people I already know". Cannot be combined with
connections_only, which is its exact inverse.
people: LinkedIn profile URLs or public identifiers, comma or newline
separated (e.g. "linkedin.com/in/jane-doe, linkedin.com/in/john-doe").
The campaign is seeded from exactly these people: no LinkedIn
search runs, the goal <-> ICP audit is skipped (the audience is
stated, not inferred), a low ICP score does not drop anybody, and
discovery stays off so nothing tops the queue up with strangers.
Use it whenever the user names who to reach. Cannot be combined
with connections_only.
campaign_type: Prompt family: "outbound" (default) or "job_search".
job_search writes a job-search campaign: the invitation note and
the first DM may name the recipient's company and the role, use
one credible proof point at most and never list a CV. InMail is
not routed by this switch. Pair with connections_only="on" to
write to people the sender is already connected to.
force: True to create the campaign even when the goal <-> ICP audit
returns `mismatch` (the ICP holds no plausible buyer for the goal).
Leave False; a `partial` verdict never blocks, it only warns.
goal: What the campaign is for: "sell" (default), "job_search",
"hire", "partner", "buy" or "research". It sets campaign_type and
campaign_intent, picks whose profile the ICP describes, and picks
the fit question the goal <-> ICP audit asks. hire, partner and
research run on your project_brief until their message sets exist.
|
| book_meetingA | Book a meeting on your Google Calendar and invite a prospect. Use this when a reply agrees to a call. Creates the event on the calendar
you connected, attaches a Google Meet link, and emails the attendee an
invitation.
Args:
attendee_email: Who to invite — the prospect who replied.
start: When it starts, ISO 8601, e.g. 2026-09-01T10:00:00.
duration_minutes: How long the meeting runs. Defaults to 30.
summary: Event title. Defaults to naming the attendee.
description: Optional agenda or notes included in the invitation.
|
| check_repliesA | Read new LinkedIn replies and record what they mean. Fetches new messages, classifies each reply (positive, negative, question)
and lists the people worth answering first.
It sends nothing, but it does write: it marks an invitation accepted once
the person answers or connects, moves an outreach to replied, stores the
messages, and sets opted-out when someone asks not to be contacted, which
stops all future outreach to them. It is the only path that notices an
accepted invitation, so follow-ups depend on it having run.
|
| show_statusA | Show your outreach dashboard — campaigns, stats, hot leads, account health. The chat is the front door to your dashboard. View pipeline metrics, prospect
funnel, engagement rates, and campaign performance. Ask "how's my outreach?" anytime.
Hosted accounts get a dashboard link and a snapshot card; relay the link,
because some clients show the image only to the model.
Args:
campaign_id: Show stats for a specific campaign. Shows all if empty.
|
| send_messageA | Send follow-ups, replies, or InMail to prospects. Args:
action: What to do:
"followup" — Send a follow-up DM after connection accepted
"reply" — Reply to a prospect who has messaged you
"delete" — Delete a recently sent message (within 60 min on LinkedIn)
"inmail" — Send an InMail to a NON-connection. Preconditions
(each fails closed with no send): prospect has a
provider_id; they are NOT a 1st-degree connection
(use followup/DM for those); no InMail already sent
on this outreach; InMail credits remaining > 0.
A pending invitation to the same person is allowed
— that is the escalation path. Requires outreach_id.
campaign_id: Which campaign to send from. Uses active if empty.
outreach_id: Specific outreach to target. Required for inmail.
format: 'text'. For followup/reply.
text: Custom message text. Auto-generates if empty.
For delete: optionally pass a Unipile message_id directly.
|
| suggest_next_actionA | Suggest the best next action for your outreach. Hosted accounts get a dashboard link and a snapshot card; relay the link,
because some clients show the image only to the model.
Analyzes all active campaigns and recommends what to do next,
prioritized by impact: hot leads first, then pending approvals,
follow-ups, engagement warm-ups, and new invitations.
Args:
campaign_id: Focus on a specific campaign. Analyzes all active if empty.
|
| edit_campaignA | Change one running or drafted campaign's settings. Pass only what you want to change; every field left empty keeps its current
value. Use it to rename a campaign, give it a booking link, feed it context
that makes the messages more specific (offerings, case studies, project
brief), say who it is for (campaign_intent, campaign_type), or set the
limits it sends under (volume, caps, follow-ups, business hours).
It edits settings only: it sends nothing and adds no prospects. A change
applies to the messages written from now on, not to ones already sent. Use
create_campaign for a new campaign, and campaign(action=...) to launch,
pause or stop one.
Args:
campaign_id: Which campaign to edit. Edits the first active campaign if empty.
name: New campaign name. Leave empty to keep current name.
mode: Only "autopilot" supported (copilot mode was removed). Whether
messages wait for a person is the workspace's approval mode:
scheduler(action="approval_mode").
booking_link: Calendar/booking URL (e.g., "https://cal.com/you/15min").
Used in reply_to_prospect() for positive replies to suggest meetings.
offerings: What you offer (products, services, value props). Used in follow-up messages.
case_studies: Brief case studies or success stories. Used for social proof in messages.
social_proofs: Social proof (logos, metrics, testimonials). Used in follow-up messages.
campaign_preferences: Custom messaging preferences (tone, topics to avoid, etc.).
campaign_intent: Message stance: "sell", "buy", "partner", "recruit" or "research".
campaign_type: Prompt family: "outbound" (default) or "job_search".
job_search replaces the intent-specific invitation note and first
DM with ones that may name the company and the role, use one
credible proof point at most and never list a CV. InMail is not
routed by this switch, and campaign_intent still selects the
system prompt. Empty keeps the current value.
goal: What the campaign is for: "sell", "job_search", "hire",
"partner", "buy" or "research". Rewrites campaign_type and
campaign_intent to match. Empty keeps the current value.
project_brief: Full project paste the model sees. Required before launch,
resume, or auto-send.
offer_outcome: The Offer card's outcome: what changes for the reader,
in their words. Editing it unconfirms the card.
offer_how: The Offer card's how: what the sender does, said only in a
reply. Editing it unconfirms the card.
offer_proof: The Offer card's proof, used at most once per thread.
Editing it unconfirms the card.
offer_ask: The Offer card's one question. Editing it unconfirms the card.
offer_confirm: "on" confirms the Offer card; first messages resume.
product: Optional structured fact: product / what you buy or sell.
go_live: Optional structured fact: go-live date.
volume: Optional structured fact: volume model.
must_confirm: Optional comma-separated questions a vendor must confirm.
voice_mode: "text_only". Messages are text. Leave empty to keep current value.
enable_profile_views: View prospect profiles before following: "on" or "off".
enable_follows: Follow prospects before inviting: "on" or "off".
enable_endorsements: Endorse skills before inviting: "on" or "off".
enable_engagements: Comment/react on posts before inviting: "on" or "off".
enable_followups: Send follow-up DMs after connection: "on" or "off".
enable_auto_replies: Auto-reply to prospect messages: "on" or "off".
enable_invitations: Send connection invitations: "on" or "off".
When off, campaign only DMs existing connections (no invitations sent).
enable_discovery: Auto-find and enrol new prospects: "on" or "off".
exclude_connections: "on" to never message anyone who was already a
1st-degree connection before this campaign started — they are
refused at enrolment and skipped at send time instead of being
DMed. People who accept this campaign's own invitation still get
the opener. Turning it on turns connections_only off.
connections_only: "on" to target only your existing 1st-degree
connections (DM-only, no invitations). Turning it on turns
exclude_connections off.
Turn off for curated campaigns with a fixed, hand-picked list.
exclude_competitors: "on" to never first-touch people who work at
competing companies. Default on. Empty list excludes nobody
until research or competitor_companies names them.
competitor_companies: Comma-separated employer names to skip.
enable_reply_agent: Reply exception agent: "on" (act), "off", or
"observe". Empty keeps the current value. Unset defaults to act.
enable_strategist_replan_agent: Strategist replan: "on", "off", or
"observe". Empty keeps the current value.
enable_hot_lead_closer: Hot-lead closer: "on", "off", or "observe".
Empty keeps the current value.
enable_coordinator_agent: Coordinator digest/hold: "on", "off", or
"observe". Empty keeps the current value.
engagement_mode: Engagement style: "auto" (30% react / 70% comment),
"comment_only", or "react_only".
max_followups: Max follow-up messages (1-5). 0 to keep current.
weekly_meeting_target: Meetings this campaign should book per week.
The daily report reads it as the Key Result and says whether the
campaign is on track. 0 means no goal this week; -1 keeps current.
followup_delay_days: Custom day intervals as comma-separated list
(e.g., "1,3,7,14"). Leave empty to keep current.
invite_note: Invitations carry a note: "on" (default) or "off". Off sends
the bare invitation; the first words are the DM a working day after
they accept.
withdraw_stale_invites: Auto-withdraw stale invites: "on" or "off".
stale_invite_days: Days before withdrawing stale invites (7-60). 0 to keep current.
inmail_fallback: Escalate quiet invitations with one InMail: "on" or "off".
Free tier sends only to Open Profile members (zero credits).
inmail_fallback_days: Quiet days before the InMail (1-60). 0 to keep current.
inmail_first_touch: InMail as first touch: "on" or "off". Unset follows inmail_fallback.
send_in_business_hours: Send only in business hours: "on" or "off". On by default
(weekdays 08:00-22:00 in your own timezone, or London when it is unknown,
unless the workspace set its own window).
active_days: Active send days as comma-separated numbers (0=Mon, 6=Sun).
E.g., "0,1,2,3,4" for weekdays. Leave empty to keep current.
|
| analyticsA | Campaign analytics — reports, comparisons, and exports. Hosted accounts get a dashboard link and a snapshot card; relay the link,
because some clients show the image only to the model.
Args:
action: What to do:
"report" — Detailed analytics with outcomes, conversion rates, stale leads
"compare" — Compare 2+ campaigns side by side
"export" — Export campaign results as table, CSV, or JSON
campaign_id: Which campaign. Uses active if empty.
campaign_ids: Comma-separated IDs (for 'compare'). Compares all if empty.
format: Output format for 'export': 'table', 'csv', or 'json'.
|
| accountsA | List the LinkedIn accounts connected to this HeyLead. Changes nothing. Args:
action: "list" (switching, unlinking and connecting email are the account tool).
|
| campaign_statusA | Watch a campaign run, show its plan, or read its status history. Changes nothing. Args:
action: "monitor" (live progress; a draft is started with
campaign(action='launch')), "plan" (what happens after launch,
step by step: finding, warm-up, invitations, opening message,
follow-ups, replies, leads) or "status_history".
campaign_id: Which campaign. Uses the active one if empty.
|
| prospect_viewA | Read the message thread with one prospect, or their timeline. Changes nothing. Args:
action: "conversation" or "timeline".
outreach_id: Which outreach.
campaign_id: Which campaign. Uses the active one if empty.
|
| scheduler_statusA | Read the scheduler: status, logs, activity, diagnostics or the daily report. Args:
action: "status", "logs", "activity" or "diagnostics". The daily
report is the scheduler tool: it switches reporting on and off.
cloud: Read the hosted scheduler rather than this machine's.
hours: Lookback window in hours.
event_type: Filter the log by event type.
campaign_id: Filter by campaign.
|
| update_contactA | Change one contact: tag it, note it, move its stage, or link it to a campaign. Reading and searching contacts is the contacts tool.
Args:
action: "tag", "note", "stage", "link" or "enrich".
contact_id: Which contact.
lifecycle_stage: The stage to move it to.
tag: The tag to add.
note: The note to record.
campaign_id: Which campaign (link).
match: How to match when linking.
dry_run: Show what would change without changing it.
|
| answer_inboxA | Reply in a LinkedIn conversation, or approve or discard a drafted reply. "reply" and "approve_draft" SEND a LinkedIn message and cannot be undone.
Reading the inbox is the inbox tool.
Args:
action: "reply", "approve_draft" or "discard_draft".
chat_id: Which conversation, or which draft.
text: What to send, or an edited version of the draft.
|
| accountA | Switch the active LinkedIn account, unlink it, or connect an email account. Which account is active decides who outreach is sent as. Listing them is
the accounts tool.
Args:
action: "switch", "switch_to", "unlink", "connect_email" or "refresh_tier".
account_id: The account id (switch_to).
|
| campaignA | Launch, pause, resume, archive, delete or emergency-stop a campaign. Every action here changes what happens next. Watching one run, or reading
its history, is campaign_status.
Args:
action: "launch", "pause", "resume", "archive", "delete",
"emergency_stop", "retry_failed", "repair_queue" or
"clear_coordinator_hold".
campaign_id: Which campaign. Uses the active one if empty.
confirm: Required by the actions that destroy work.
|
| prospectA | Skip a prospect, close one with an outcome, or dismiss it. Someone who is not a fit is skipped, not closed: skip leaves them out of
this campaign and records nothing else. Close records a result and
needs an outcome; won and lost are for people HeyLead has written to.
Closing with outcome="opt_out" stops all future contact with that person.
Reading a prospect is prospect_view.
Args:
action: "skip", "close" or "dismiss".
outreach_id: Which outreach.
campaign_id: Which campaign. Uses the active one if empty.
outcome: "won", "lost" or "opt_out". Required for close; there is
no default.
reason: Why, recorded with the outcome.
meeting_link: The booked meeting, recorded with a won outcome.
confirm: Required where the action cannot be undone.
reason_code: Why, for skip and close: "not_a_fit", "negative_reply",
"asked_to_stop", "handled_elsewhere" or "other".
reason_note: A free note alongside the code.
deal_value: What the won deal is worth (close with outcome="won").
deal_currency: Three-letter code, e.g. USD. Defaults to the last deal's.
|
| schedulerA | Turn the autonomous scheduler on or off, or change how it runs. Off means nothing sends until it is on again. Reading its state, logs,
activity or diagnostics is scheduler_status.
Args:
action: "toggle", "observe", "always_on", "backfill_cloud",
"send_from" or "report".
enabled: True to enable, False to disable (toggle, report).
cloud: Act on the hosted scheduler rather than this machine's.
host: "cloud" (send_from); a hosted account never sends from here.
hours: The reporting interval (report).
campaign_id: Which campaign (report).
|
| contactsA | Search, browse and read your contacts, and export them. Changes nothing. Tagging, noting, re-staging and linking are update_contact.
Args:
action: "list", "search", "view", "stats", "export", "linkedin_search"
or "my_connections".
query: Search text for "search", "linkedin_search" and "my_connections".
For "linkedin_search" the query is passed to LinkedIn as KEYWORDS,
matched literally — a company name, a job title, a person's name, or a
combination such as 'Acme Corp CTO' or 'Jane Doe'. A natural-language
question ('who is the CTO of Acme?') is sent through unchanged and
usually comes back empty, so prefer keywords. Nothing is filtered out
locally. An empty result and a failed search are reported in different
words, so a "no matches" line means LinkedIn really returned nobody
rather than "the search broke". Results are capped at 25 per call
because every result costs a profile fetch and a LinkedIn read.
contact_id: Which contact (view).
lifecycle_stage: Filter by stage.
min_fit_score: Only those scoring at least this.
limit: How many.
format: "table", "csv" or "json" (export).
campaign_id: Filter by campaign.
connected_since: Only those connected on or after this date.
connected_before: Only those connected before this date.
|
| inboxA | Read any conversation in your LinkedIn inbox, and the drafted replies. Replying, approving a draft and discarding one are answer_inbox.
Args:
action: "list", "read" or "comment_drafts".
chat_id: Which conversation.
name: Find the conversation by the person's name.
limit: How many to show.
|