> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agentkit`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# HeyReach

**Authentication:** API Key
**Categories:** Outreach, Linkedin, Sales, Crm
## What you can do

Connect this agent connector to let your agent:

- **Add leads to a campaign** — Enroll up to 100 leads in an active campaign, bound to LinkedIn sender accounts
- **Verify the API key** — Confirm the stored key is valid before running other tools
- **List campaigns** — Page through outreach campaigns, statuses, and sender account IDs
- **List LinkedIn sender accounts** — Discover connected profiles used to send outreach
- **List lead lists** — Find list IDs for bulk lead retrieval
- **Get campaign details** — Load one campaign’s status, stats, lists, and sender accounts
- **Monitor inbox conversations** — Filter and page LinkedIn replies across accounts and campaigns
- **Look up a lead** — Fetch one lead by LinkedIn profile URL
- **Get leads from a list** — Page leads from a specific list
- **Track performance stats** — Pull aggregate acceptance, reply, and InMail metrics for accounts and campaigns

## Authentication

This connector uses **API Key** authentication. Your users provide their HeyReach API key once, and Scalekit stores and manages it securely. Your agent code never handles keys directly — you only pass a `connectionName` and a user `identifier`.

Before calling this connector from your code, create the HeyReach connection in **AgentKit** > **Connections** and copy the exact **Connection name** from that connection into your code. The value in code must match the dashboard exactly.

## Set up the connector

Register your HeyReach API key with Scalekit so it can authenticate and proxy LinkedIn outreach requests on behalf of your users. HeyReach uses API key authentication — there is no redirect URI or OAuth flow.

1. ## Generate a HeyReach API key

   - Sign in to [app.heyreach.io](https://app.heyreach.io) and open **Dashboard -> Settings -> Integrations -> Get API Key**.

   - Create a new API key, give it a descriptive name (e.g., `HeyReach Agent`), and confirm.

     > Image: Screenshot

   - Copy the generated key. **It is shown only once** — store it somewhere safe before navigating away.

   > caution: Keep your API key secret
>
> Your HeyReach API key grants full access to your workspace — including launching campaigns, reading inbox conversations, and modifying lead lists. Never expose it in client-side code or commit it to source control.

2. ## Create a connection in Scalekit

   - In [Scalekit dashboard](https://app.scalekit.com), go to **AgentKit** > **Connections** > **Create Connection**. Find **HeyReach** and click **Create**.

   - Note the **Connection name** — you will use this as `connection_name` in your code (e.g., `heyreach`).

3. ## Add a connected account

   Connected accounts link a specific user identifier in your system to a HeyReach API key. Add accounts via the dashboard for testing, or via the Scalekit API in production.

   **Via dashboard (for testing)**

   - Open the connection you created and click the **Connected Accounts** tab → **Add account**.

   - Fill in:
     - **Your User's ID** — a unique identifier for this user in your system (e.g., `user_123`)
     - **API Key** — the HeyReach API key you copied in step 1

   - Click **Save**.

   **Via API (for production)**

   
     ### Node.js

```typescript
await scalekit.actions.upsertConnectedAccount({
  connectionName: 'heyreach',
  identifier: 'user_123',
  credentials: { api_key: 'your-heyreach-api-key' },
});
```

     ### Python

```python
scalekit_client.actions.upsert_connected_account(
    connection_name="heyreach",
    identifier="user_123",
    credentials={"api_key": "your-heyreach-api-key"}
)
```

   

   > tip: Production usage tip
>
> In production, call `upsertConnectedAccount` when a user connects their HeyReach account — for example, after they paste their API key into a settings page in your app.

> note: Rate limits
>
> HeyReach enforces a rate limit of **300 requests per minute** across all public API endpoints. Exceeding it returns `429 Too Many Requests`. Batch operations (like adding up to 100 leads in a single `heyreach_add_leads_to_campaign` call) help you stay well under the limit.

## Code examples

Once a connected account is set up, call the HeyReach API through the Scalekit proxy. Scalekit injects the API key automatically — you never handle credentials in your application code.

## Proxy API calls

  ### Node.js

```typescript

const connectionName = 'heyreach';   // connection name from your Scalekit dashboard
const identifier = 'user_123';       // your user's unique identifier

// Get your credentials from app.scalekit.com → Developers → Settings → API Credentials
const scalekit = new ScalekitClient(
  process.env.SCALEKIT_ENV_URL,
  process.env.SCALEKIT_CLIENT_ID,
  process.env.SCALEKIT_CLIENT_SECRET
);
const actions = scalekit.actions;

// Verify the connected API key works — no key needed in your code
const result = await actions.request({
  connectionName,
  identifier,
  path: '/auth/CheckApiKey',
  method: 'GET',
});
console.log('API key valid:', result.status === 200);
```

  ### Python

```python

from dotenv import load_dotenv
load_dotenv()

connection_name = "heyreach"   # connection name from your Scalekit dashboard
identifier = "user_123"        # your user's unique identifier

# Get your credentials from app.scalekit.com → Developers → Settings → API Credentials
scalekit_client = scalekit.client.ScalekitClient(
    client_id=os.getenv("SCALEKIT_CLIENT_ID"),
    client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
    env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit_client.actions

# Verify the connected API key works — no key needed in your code
result = actions.request(
    connection_name=connection_name,
    identifier=identifier,
    path="/auth/CheckApiKey",
    method="GET"
)
print("API key valid:", result.status_code == 200)
```

> tip: No OAuth flow needed
>
> HeyReach uses API key auth — unlike OAuth connectors, there is no authorization link or redirect flow. Once you call `upsertConnectedAccount` (or add an account via the dashboard), your users can make requests immediately.

## Scalekit tools

Use `execute_tool` to call HeyReach tools directly from your code. Scalekit resolves the connected account, injects the API key, and returns a structured response — no raw HTTP needed.

### Add leads to a campaign

The most common HeyReach workflow: pick an active campaign, choose a LinkedIn sender account to send from, and add up to 100 leads in a single call. Each lead is bound to a specific sender — so a campaign with multiple senders can round-robin or be sharded by your code.

```python title="examples/heyreach_add_leads.py"

from dotenv import load_dotenv
load_dotenv()

scalekit_client = scalekit.client.ScalekitClient(
    client_id=os.getenv("SCALEKIT_CLIENT_ID"),
    client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
    env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit_client.actions

# Resolve connected account (API key auth — no OAuth redirect needed)
response = actions.get_or_create_connected_account(
    connection_name="heyreach",
    identifier="user_123"
)
connected_account = response.connected_account

# Step 1: Pick a campaign and one of its sender accounts
campaigns = actions.execute_tool(
    tool_name="heyreach_get_all_campaigns",
    connected_account_id=connected_account.id,
    tool_input={"limit": 25}
)
campaign = campaigns.result["items"][0]              # or filter by name/status
sender_account_id = campaign["campaignAccountIds"][0]
print(f"Adding leads to campaign {campaign['id']} via sender {sender_account_id}")

# Step 2: Add up to 100 leads bound to that sender account
new_leads = [
    {"profileUrl": "https://www.linkedin.com/in/jane-doe"},
    {"profileUrl": "https://www.linkedin.com/in/john-smith"},
]
result = actions.execute_tool(
    tool_name="heyreach_add_leads_to_campaign",
    connected_account_id=connected_account.id,
    tool_input={
        "campaignId": campaign["id"],
        "accountLeadPairs": [
            {"linkedInAccountId": sender_account_id, "lead": lead}
            for lead in new_leads
        ],
        # Auto-resume the campaign if it's paused or already finished
        "resumePausedCampaign": True,
        "resumeFinishedCampaign": True,
    }
)
print(f"Added {len(new_leads)} leads:", result.result)
```

### Look up a lead before reaching out

Verify a lead already exists in HeyReach (and check their tags or enrichment status) before adding them to a campaign — this avoids duplicate outreach and lets you skip leads that are already engaged.

```python title="examples/heyreach_get_lead.py"
result = actions.execute_tool(
    tool_name="heyreach_get_lead",
    connected_account_id=connected_account.id,
    tool_input={
        "profileUrl": "https://www.linkedin.com/in/jane-doe"
    }
)

lead = result.result
if lead:
    print(f"{lead['fullName']} — {lead.get('position') or lead.get('summary')}")
    print(f"  Company:  {lead.get('companyName')}")
    print(f"  Location: {lead.get('location')}")
    print(f"  Email:    {lead.get('emailAddress') or lead.get('enrichedEmailAddress')}")
else:
    print("Lead not found — safe to add to a new campaign.")
```

### Monitor inbox replies

After outreach goes out, poll the unified LinkedIn inbox for unseen replies. Filter by campaign or sender account so you only surface conversations relevant to your agent's workflow.

> note: Default cap on conversations
>
> `heyreach_get_conversations` defaults to `limit: 10` to protect LLM context — HeyReach's own default can return 400 KB+ payloads. Pass a larger `limit` explicitly only when you need more.

```python title="examples/heyreach_inbox.py"
result = actions.execute_tool(
    tool_name="heyreach_get_conversations",
    connected_account_id=connected_account.id,
    tool_input={
        "campaignIds": [campaign["id"]],   # filter to one campaign
        "seen": False,                      # only unseen conversations
        "limit": 25,
    }
)

for convo in result.result.get("items", []):
    profile = convo.get("correspondentProfile", {})
    messages = convo.get("messages", [])
    last_msg = messages[-1] if messages else {}
    print(f"📬 {profile.get('fullName')} — {profile.get('profileUrl')}")
    print(f"   {last_msg.get('createdAt')} ({last_msg.get('sender')}): "
          f"{(last_msg.get('body') or '')[:120]}")
```

### Track campaign performance

Pull aggregate metrics for one or more campaigns — connection acceptance rate, message reply rate, InMail performance — to power dashboards or trigger follow-up actions when a campaign underperforms.

```python title="examples/heyreach_stats.py"
# Get all sender accounts associated with the campaign
sender_accounts = campaign["campaignAccountIds"]

stats = actions.execute_tool(
    tool_name="heyreach_get_overall_stats",
    connected_account_id=connected_account.id,
    tool_input={
        "CampaignIds": [campaign["id"]],
        "AccountIds": sender_accounts,
    }
)

# Response wraps aggregates under `overallStats` and a per-day breakdown
# under `byDayStats` — use `overallStats` for top-line numbers.
s = stats.result["overallStats"]
print(f"Campaign {campaign['id']} — '{campaign['name']}'")
print(f"  Connection requests: {s['connectionsSent']} sent / {s['connectionsAccepted']} accepted")
print(f"  Acceptance rate:     {s['connectionAcceptanceRate']:.1%}")
print(f"  Messages:            {s['messagesSent']} sent / {s['totalMessageReplies']} replied")
print(f"  Reply rate:          {s['messageReplyRate']:.1%}")
```

### LangChain integration

Let an LLM decide which HeyReach tool to call based on natural language. This example builds an agent that can list campaigns, add leads, and surface inbox replies on demand.

```python title="examples/heyreach_langchain.py"

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import (
    ChatPromptTemplate, SystemMessagePromptTemplate,
    HumanMessagePromptTemplate, MessagesPlaceholder, PromptTemplate
)
load_dotenv()

scalekit_client = scalekit.client.ScalekitClient(
    client_id=os.getenv("SCALEKIT_CLIENT_ID"),
    client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
    env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit_client.actions

identifier = "user_123"

# Resolve connected account (API key auth — no OAuth redirect needed)
actions.get_or_create_connected_account(
    connection_name="heyreach",
    identifier=identifier
)

# Load all HeyReach tools in LangChain format. Use page_size=100 so connector tool lists are not truncated.
tools = actions.langchain.get_tools(
    identifier=identifier,
    providers=["HEYREACH"],
    page_size=100
)

prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate(prompt=PromptTemplate(
        input_variables=[],
        template=(
            "You are a LinkedIn outreach assistant with access to HeyReach tools. "
            "Use heyreach_get_all_campaigns to find campaigns, heyreach_add_leads_to_campaign "
            "to enroll new prospects, heyreach_get_conversations to monitor replies, and "
            "heyreach_get_overall_stats to report on performance. Always confirm the campaign "
            "and sender account before adding leads."
        )
    )),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    HumanMessagePromptTemplate(prompt=PromptTemplate(
        input_variables=["input"], template="{input}"
    )),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])

llm = ChatOpenAI(model="gpt-4o")
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = agent_executor.invoke({
    "input": "Show me unread replies from my active LinkedIn campaigns in the last 24 hours, and tell me which campaign has the highest acceptance rate."
})
print(result["output"])
```

## Tool list

Use the exact tool names from the **Tool list** below when you call `execute_tool`. If you're not sure which name to use, list the tools available for the current user first.

## Tool list

### `heyreach_add_leads_to_campaign`

Add up to 100 leads to an existing HeyReach campaign. The campaign must be in an ACTIVE state (IN_PROGRESS), or use resumeFinishedCampaign / resumePausedCampaign to auto-resume. Each lead is bound to a specific LinkedIn sender account (linkedInAccountId) that will send the outreach. Use heyreach_get_campaign_by_id to find the campaign's sender accounts (campaignAccountIds). Rate limit: 300 requests/minute.

Parameters:

- `accountLeadPairs` (`array`, required): Array of lead + sender account pairs to add to the campaign (max 100). Each pair binds a lead to the LinkedIn sender account that will reach out. Minimum required per lead: profileUrl.
- `campaignId` (`integer`, required): The ID of the HeyReach campaign to add leads to. Get campaign IDs from heyreach_get_all_campaigns.
- `resumeFinishedCampaign` (`boolean`, optional): If true and the target campaign is in FINISHED state, HeyReach will resume it so the new leads can be processed. Defaults to false.
- `resumePausedCampaign` (`boolean`, optional): If true and the target campaign is in PAUSED state, HeyReach will resume it so the new leads can be processed. Defaults to false.

### `heyreach_check_api_key`

Verify that your HeyReach API key is valid and the connection is working. Returns HTTP 200 with empty body on success. Use this to validate a connection before making other API calls.

### `heyreach_get_all_campaigns`

List all LinkedIn outreach campaigns in your HeyReach account with pagination. Returns campaign metadata including status (DRAFT, IN_PROGRESS, PAUSED, FINISHED, FAILED), progress stats, associated lead list, and campaignAccountIds (LinkedIn sender account IDs needed for heyreach_get_overall_stats). Rate limit: 300 requests/minute.

Parameters:

- `limit` (`integer`, optional): Maximum number of campaigns to return. Defaults to 10.
- `offset` (`integer`, optional): Number of records to skip for pagination. Defaults to 0.

### `heyreach_get_all_linkedin_accounts`

List the LinkedIn sender accounts (connected LinkedIn profiles) in your HeyReach workspace with pagination. Returns each account's ID, name, profile URL, and status. Use the returned account IDs as linkedInAccountId when calling heyreach_add_leads_to_campaign, or as AccountIds in heyreach_get_overall_stats. Rate limit: 300 requests/minute.

Parameters:

- `keyword` (`string`, optional): Optional search keyword to filter accounts by name.
- `limit` (`integer`, optional): Maximum number of LinkedIn accounts to return. Max 100. Defaults to 100.
- `offset` (`integer`, optional): Number of records to skip for pagination. Defaults to 0.

### `heyreach_get_all_lists`

List all lead lists in your HeyReach account with pagination. Returns list metadata including name, total lead count, list type, creation date, and associated campaign IDs. Use list IDs with heyreach_get_leads_from_list to retrieve leads. Rate limit: 300 requests/minute.

Parameters:

- `limit` (`integer`, optional): Maximum number of lists to return. Defaults to 10.
- `offset` (`integer`, optional): Number of records to skip for pagination. Defaults to 0.

### `heyreach_get_campaign_by_id`

Retrieve detailed information about a specific HeyReach campaign by its ID. Returns campaign status, progress stats (total users, in progress, finished, failed), associated lead list, and LinkedIn sender accounts. Use get_all_campaigns first to find campaign IDs.

Parameters:

- `campaignId` (`integer`, required): The unique ID of the campaign to retrieve. Get campaign IDs from heyreach_get_all_campaigns.

### `heyreach_get_conversations`

List LinkedIn inbox conversations across your HeyReach sender accounts with pagination and filters. Returns conversation metadata: participants, last message, seen/unseen status, associated campaign and account. Filter by LinkedIn account IDs, campaign IDs, lead profile URL, tags, search string, or seen status. Useful to monitor replies to outreach sent via heyreach_add_leads_to_campaign. Rate limit: 300 requests/minute.

Parameters:

- `campaignIds` (`array`, optional): Filter conversations to these campaign IDs. Get campaign IDs from heyreach_get_all_campaigns.
- `leadLinkedInId` (`string`, optional): Filter to conversations with a specific lead by their LinkedIn internal ID.
- `leadProfileUrl` (`string`, optional): Filter to conversations with a specific lead by their LinkedIn profile URL.
- `limit` (`integer`, optional): Maximum number of conversations to return (1-100). Defaults to 10 — a client-side cap applied in the jsonnet template to protect LLM context, since the HeyReach API's own default (~100) can return 400KB+ payloads. Pass a larger value explicitly if you need more.
- `linkedInAccountIds` (`array`, optional): Filter conversations to these LinkedIn sender account IDs. Get account IDs from heyreach_get_all_linkedin_accounts.
- `offset` (`integer`, optional): Number of records to skip for pagination. Defaults to 0.
- `searchString` (`string`, optional): Free-text search across conversation content and participant names.
- `seen` (`boolean`, optional): Filter by seen status. true = only seen conversations, false = only unseen. Omit to return both.
- `tags` (`array`, optional): Filter conversations by lead tags.

### `heyreach_get_lead`

Retrieve detailed information about a single HeyReach lead by their LinkedIn profile URL. Returns the lead's profile data (name, headline, location, company, position), email addresses (emailAddress, enrichedEmailAddress, customEmailAddress), tags, and custom fields. Useful to verify a lead exists in HeyReach before or after adding them to a campaign. Rate limit: 300 requests/minute.

Parameters:

- `profileUrl` (`string`, required): The public LinkedIn profile URL of the lead to look up. Example: https://www.linkedin.com/in/janedoe

### `heyreach_get_leads_from_list`

Retrieve leads from a specific HeyReach lead list with pagination. Returns detailed lead profiles including LinkedIn URL, name, headline, location, company, position, tags, and email addresses. Use heyreach_get_all_lists to find list IDs. Rate limit: 300 requests/minute.

Parameters:

- `listId` (`integer`, required): The unique ID of the lead list to retrieve leads from. Get list IDs from heyreach_get_all_lists.
- `limit` (`integer`, optional): Maximum number of leads to return. Defaults to 10 — a client-side cap applied in the jsonnet template to protect LLM context, since lists can hold thousands of leads (observed: 4,054). Pass a larger value explicitly if you need more.
- `offset` (`integer`, optional): Number of records to skip for pagination. Defaults to 0.

### `heyreach_get_overall_stats`

Retrieve overall performance statistics for specific LinkedIn sender accounts and campaigns. Returns aggregate metrics including connection requests sent and accepted, messages sent and replied, InMail stats, and calculated rates (connection acceptance rate, message reply rate). Rate limit: 300 requests/minute.

Parameters:

- `AccountIds` (`array`, required): IDs of the LinkedIn sender accounts (connected LinkedIn profiles) assigned to run this campaign. Each campaign has one or more sender accounts.
- `CampaignIds` (`array`, required): Array of campaign IDs to retrieve stats for. Get campaign IDs from heyreach_get_all_campaigns.


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
