
# Using the API and MCP Tools

Everything you do in <span data-t="appName">SDRPilot</span> can also be done from a script or an AI assistant: your leads, the connection request queue, your ICP rules, your inbox, your agents and your settings.

The API and the MCP tools are included on every plan.

## Create an API key

1. In <span data-t="appName">SDRPilot</span>, open **Settings → API**.
2. Choose **Create key**.
3. Copy the key straight away. It starts with `sdrp_live_` and is shown only once. If you lose it, create a new one.

The key acts as you, with the same permissions you have in the app, so treat it like a password. You can revoke a key at any time on the same page, and every script or assistant using it is cut off at once.

::: info
**Champions Circle members** can also use the API key of their DM Champ account.
:::
:::


## Calling the API directly

- **Base URL:** `https://app.sdrpilot.ai/api/v1`
- **Header:** `X-API-Key: YOUR_API_KEY` or `Authorization: Bearer YOUR_API_KEY`
- **Machine readable description:** `https://app.sdrpilot.ai/api/v1/docs/openapi.yaml`, also available as `.json`

The OpenAPI document is public and describes every route, so most API tools and code generators can import it without a key. Route by route details live on the [API Reference](reference.md) page.

Both headers work the same way. What you cannot do is send a wrong key and hope it falls through to something else: once a key is presented, the answer is based on that key.

What comes back when something is wrong:

| Response | What it means |
|---|---|
| `401` | The key is missing, invalid or revoked. |
| `402` | `subscription_required`: the workspace has no active plan or trial. Starting or resuming an agent and actions that reach LinkedIn are refused until a plan is active; the message points you to **Settings → Billing**. |
| `403` | The key is valid, but its workspace is not allowed, for example a Champions Circle member's DM Champ key whose workspace is not linked. |
| `429` | Too many rejected attempts in a minute for that key. Slow down. |
| `502` | We could not check your key just then. It is never treated as a pass. Retry. |

## Connecting an AI assistant

The <span data-t="appName">SDRPilot</span> tools live on the MCP endpoint `https://mcp.sdrpilot.ai/mcp`, authenticated with the same API key in the `X-API-Key` header.

The tools are all prefixed **`linkedin_`**, so they are easy to spot in your assistant's tool list and easy to ask for by name. There is one per API operation, and there are no per tool permissions: a tool can do whatever you can do in the app. When a tool takes a request body, the fields go under a single `body` argument.

### The plugin for Claude Code

On Claude Code the quickest way is the plugin. It sets up the connection and adds a skill, a playbook Claude reads first: how <span data-t="appName">SDRPilot</span> is put together (your ICP, scoring, the connection queue, conversations), the common workflows step by step, and what it must ask you before it acts, such as anything that reaches a real person on LinkedIn.

```bash
claude plugin marketplace add sdrpilot/claude-plugin
claude plugin install sdrpilot@sdrpilot
```

Then start `claude`, run `/plugin configure sdrpilot@sdrpilot` and paste your API key (see [Create an API key](#create-an-api-key)). It is stored in your system keychain, not in a file. Restart Claude Code and run `/mcp` to see the server connected.

### The help docs are built in

The MCP endpoint also gives your assistant this documentation. Three tools, `docs_search`, `docs_read` and `docs_list`, read the help docs live, so the assistant can look up how a feature works instead of guessing. There is nothing to set up, and the docs tools never touch your account data.

## Example 1: only accept leads from certain countries

Your ICP profile holds the rules leads have to pass. This sets an allow list of the Netherlands and Belgium. Country codes are two letters, uppercase.

```bash
curl -X PUT https://app.sdrpilot.ai/api/v1/icp/YOUR_ICP_ID/filters \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"countries":{"mode":"allow","codes":["NL","BE"]}}'
```

Two things to know. The body is the **complete** set of rules, not a patch: a rule you leave out is switched off. And the answer includes an `impact_on_queue` block telling you how many already queued connection requests would fail the new rules. Saving rules does not withdraw anything by itself.

In an assistant you would say: "Set my ICP to accept only the Netherlands and Belgium, and tell me what that would do to my queue."

## Example 2: see what is queued

Connection requests that are approved but not sent yet:

```bash
curl -s "https://app.sdrpilot.ai/api/v1/connections/queue?status=queued&limit=100" \
  -H "X-API-Key: YOUR_API_KEY"
```

Each row carries the person's name, headline, company, location, country, their connection and follower counts, and the score. Narrow the list with `country`, `min_score`, `max_score` and `source`, and page through it with `limit` and `cursor`. Requests already sent are history and cannot be changed.

## Example 3: withdraw everyone outside your countries

After changing your rules, re-check the queue against them. Without `apply=true` this is a preview and changes nothing:

```bash
curl -X POST "https://app.sdrpilot.ai/api/v1/connections/queue/rescreen" \
  -H "X-API-Key: YOUR_API_KEY"
```

You get back how many were examined, how many would be withdrawn, and a breakdown per rule, such as `country` or the lead's network being smaller than your minimum. Happy with it? Run it again with `?apply=true` and those requests are withdrawn.

## Example 4: score the queue again with AI

Rescreen checks the hard rules. Re-scoring asks the AI to grade the queued leads again against your current ideal customer profile, which is what you want after rewriting your ICP description rather than its filters. Preview first:

```bash
curl -X POST "https://app.sdrpilot.ai/api/v1/connections/queue/rescore" \
  -H "X-API-Key: YOUR_API_KEY"
```

This one has no preview. It queues one AI re-qualification per lead right away (up to 500 per call, included in your plan) and answers with how many were queued. As each lead is re-scored, its score and reasoning are updated; a request whose lead no longer passes your rules or your qualification bar is withdrawn, the rest stay queued. Pass `{"ids": [...]}` or `{"filter": {...}}` in the body to limit it to part of the queue; an empty body re-scores every waiting request.

## Long-running work does not block the call

Anything that touches LinkedIn or the AI is queued and the call returns at once, so a request never sits open for minutes. You get back what was queued and you read the result later:

| What you start | What to poll | Done when |
| --- | --- | --- |
| Connect a LinkedIn account (`POST /auth/linkedin/connect`, `/verify-2fa`, `/refresh`) | `GET /auth/linkedin/progress`, then `GET /auth/linkedin/status` | the seat shows `active` |
| Re-scrape and re-score a lead (`POST /leads/{id}/enrich`) | `GET /leads/{id}` | `profile_scraped_at` and the score update |
| Build or rebuild an ICP (`POST /icp`, `POST /icp/{id}/rebuild`) | `GET /icp/{id}` | `build_status` is `ready` |
| Re-check, withdraw or re-score the queue (`POST /connections/queue/...`) | `GET /connections/queue` | each request is `queued` or `withdrawn` |
| Send a message (`POST /conversations/{id}/messages`) | `GET /conversations/{id}` | the message appears in the thread |
| Generate, schedule or publish a post (`POST /content/...`) | `GET /content/{id}` | `status` moves from `draft` or `scheduled` to `posted` |
| Scrape a monitored post (`POST /posts/{id}/scrape`) | `GET /posts` and `GET /leads` | new engagers appear as leads |
| Profile optimizer sync, analyze, apply | `GET /profile-optimizer` | `status` is `ready` |

Webhooks fire for lead and conversation events (`lead.created`, `lead.qualified`, `connection.sent`, `connection.accepted`, `conversation.started`, `meeting.booked` and more); builds, content generation and login have no webhook yet, so poll those.

A few calls still do their work inside the request and answer when it is finished. They can take longer than a typical client timeout, so give them up to two minutes:

* `POST /agents/simulate` and `/agents/simulate/personas` (several AI calls, up to 90 seconds)
* `POST /onboarding/analyze-website` and `POST /competitors/discover` (a site fetch plus AI)
* `POST /leads/customers/import` and `POST /icp/{id}/customers/import` with a large CSV
* `POST /leads/{id}/connect` and accepting an invitation (one live LinkedIn action, usually 5 to 15 seconds)
* `POST /icp/{id}/filters/chat` and `POST /conversations/{id}/suggest-replies` (one AI call)

## Troubleshooting

- **`401` on every call.** The key is missing, invalid or revoked. Create a new one in **Settings → API**.
- **`402` with `subscription_required`.** The workspace has no active plan or trial. Reading your leads, conversations and settings still works, but starting an agent and anything that reaches LinkedIn is refused until you choose a plan in **Settings → Billing**.
- **`403` although the key is accepted.** The key's workspace is not allowed. For a Champions Circle member using a DM Champ key, sign in to <span data-t="appName">SDRPilot</span> with that DM Champ account first so the workspace is linked.
- **A rule matches nothing.** Country codes must be two letters uppercase (`NL`, not `Netherlands` or `nl`), and language codes lowercase.
- **The assistant shows no tools.** Reconnect it so it reloads the tool list, and check the API key has not been revoked.
