# Pipevoy API and MCP server

> Everything the app does, from your own code and agents: sign up, build the company profile, connect senders, launch campaigns, approve drafts, answer replies and track meetings. Every message still goes through the same engine: warm-up, working hours, daily and weekly limits, approvals and the billing gate.

Source: https://pipevoy.com/docs/api (Markdown: https://pipevoy.com/docs/api.md)

## Give this to your agent

agent-brief.txt

```text
You can run LinkedIn outreach through Pipevoy, a headless AI SDR. It drafts LinkedIn invites, follow-ups and replies for a workspace and sends them through its own engine, which enforces warm-up, per-sender daily and weekly limits, working hours and approvals. Never send on LinkedIn yourself.

REST API  https://pipevoy.com/api/v1
MCP       https://pipevoy.com/api/mcp
Docs      https://pipevoy.com/docs/api
OpenAPI   https://pipevoy.com/api/v1/openapi.json
llms.txt  https://pipevoy.com/llms.txt
Auth      Authorization: Bearer <api key> (same key for REST and MCP)

No key yet: POST /api/v1/signup with {"email", "company_name"}, no auth. Then poll GET /api/v1/signup/{id} with header X-Claim-Token until api_key appears. It is returned once; store it.

Start each session with GET /api/v1/me. Its setup.next_step says what to do next. Approving a draft only queues it; the engine decides when it is safe to send. Every error has error.hint with the next step.

Ask your human for exactly three things:
1. Open the confirmation email Pipevoy sends after signup.
2. Open each sender's connect_url and log in to LinkedIn once.
3. Pay the Stripe Checkout link before the 14-day trial ends.
```

## Facts

| What | Value |
| --- | --- |
| REST base URL | `https://pipevoy.com/api/v1` |
| MCP server | `https://pipevoy.com/api/mcp` (Streamable HTTP, JSON responses) |
| Auth header | `Authorization: Bearer pv_…` (same key for REST and MCP) |
| Signup | `POST /api/v1/signup` (no key) → a person confirms the email → `GET /api/v1/signup/{id}` with `X-Claim-Token` returns `api_key` once |
| API index | [https://pipevoy.com/api/v1](https://pipevoy.com/api/v1) |
| OpenAPI 3.1 | [https://pipevoy.com/api/v1/openapi.json](https://pipevoy.com/api/v1/openapi.json) |
| MCP server card | [https://pipevoy.com/api/mcp/server-card](https://pipevoy.com/api/mcp/server-card) |
| llms.txt | [https://pipevoy.com/llms.txt](https://pipevoy.com/llms.txt) |
| Docs | [https://pipevoy.com/docs/api](https://pipevoy.com/docs/api) (Markdown: [/docs/api.md](https://pipevoy.com/docs/api.md)) |
| Trial | 14 days, no card, API and MCP included |
| Human steps | Confirm the signup email; log in to LinkedIn once per sender; pay |
| Product | Pipevoy, a headless AI SDR for LinkedIn |

## Zero to booked meetings with no UI

An agent can do the whole setup itself. Three steps need a person, and we won’t pretend otherwise: confirming the signup email, logging in to LinkedIn once per sender account, and paying when the trial ends.

Quickstart with curl

```bash
# 1. Sign up (a person confirms the emailed link)
curl -X POST https://pipevoy.com/api/v1/signup -H "Content-Type: application/json" \
  -d '{"email":"founder@acme.com","company_name":"Acme"}'
# → { "id": "…", "claim_token": "pvc_…" }

# 2. Poll until the key arrives (returned once)
curl https://pipevoy.com/api/v1/signup/$SIGNUP_ID -H "X-Claim-Token: $CLAIM_TOKEN"
export PIPEVOY_API_KEY=pv_…

# 3. Company profile from the website
curl -X POST https://pipevoy.com/api/v1/company/analyze -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" -d '{"website":"https://acme.com"}'

# 4. A LinkedIn sender; a person opens connect_url once and logs in
curl -X POST https://pipevoy.com/api/v1/senders -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"Sara Khan","timezone":"America/New_York","booking_url":"https://cal.com/sara/intro"}'
curl https://pipevoy.com/api/v1/senders/$SENDER_ID -H "Authorization: Bearer $PIPEVOY_API_KEY"   # wait for "status": "connected"

# 5. Events to your agent (optional; or poll)
curl -X POST https://pipevoy.com/api/v1/webhooks -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://agent.example.com/hooks","events":["draft.created","reply.received","meeting.booked"]}'

# 6. Draft a sequence, then create and start the campaign
curl -X POST https://pipevoy.com/api/v1/campaigns/draft -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"objective":"Book intro calls with RevOps leaders","cta":"a 20-minute call"}'
curl -X POST https://pipevoy.com/api/v1/campaigns -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" -H "Idempotency-Key: launch-1" \
  -d '{"name":"RevOps East Coast","cta":"a 20-minute call","steps":[…],"sender_ids":["'$SENDER_ID'"]}'

# 7. Enroll prospects
curl -X POST https://pipevoy.com/api/v1/campaigns/$CAMPAIGN_ID/enroll -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"linkedin_urls":["https://www.linkedin.com/in/jane-doe"]}'

# 8. Approve drafts (the engine sends within limits and working hours)
curl https://pipevoy.com/api/v1/approvals -H "Authorization: Bearer $PIPEVOY_API_KEY"
curl -X POST https://pipevoy.com/api/v1/approvals/$DRAFT_ID/approve -H "Authorization: Bearer $PIPEVOY_API_KEY"

# 9. Replies and meetings
curl "https://pipevoy.com/api/v1/conversations?since=2026-10-20T00:00:00Z" -H "Authorization: Bearer $PIPEVOY_API_KEY"
curl -X POST https://pipevoy.com/api/v1/conversations/$CONVERSATION_ID/reply -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" -d '{"body":"Does Tuesday work?"}'
curl https://pipevoy.com/api/v1/meetings -H "Authorization: Bearer $PIPEVOY_API_KEY"

# 10. Before the trial ends: a person pays
curl -X POST https://pipevoy.com/api/v1/billing/checkout -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" -d '{"plan":"growth","interval":"month"}'
```

### The same sequence with MCP tools

1. `signup` `{ email, company_name }`: no key needed
2. `signup_status` `{ id, claim_token }`: returns api_key once; reconnect with it
3. `analyze_company` `{ website }`
4. `create_sender` `{ name, timezone, booking_url }`: a person opens connect_url
5. `get_sender` `{ id }`: until status is connected
6. `create_webhook` `{ url, events }`: optional
7. `draft_campaign` `{ objective, cta }`
8. `create_campaign` `{ name, steps, sender_ids }`: require_approval defaults to true
9. `enroll_prospects` `{ id, linkedin_urls }`
10. `list_pending_approvals → approve_draft` `{ id, body? }`
11. `list_conversations → get_conversation → draft_reply` `{ id, body }`: then approve_draft
12. `list_meetings → update_meeting` `{ id, status, qualified }`
13. `create_checkout_link` `{ plan, interval }`: a person pays

## Connect over MCP

One server, `https://pipevoy.com/api/mcp`, with the same API key as REST. Pick your client and paste:

Claude Desktop: claude_desktop_config.json

```json
{
  "mcpServers": {
    "pipevoy": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://pipevoy.com/api/mcp",
               "--header", "Authorization: Bearer ${PIPEVOY_API_KEY}"],
      "env": { "PIPEVOY_API_KEY": "pv_your_key_here" }
    }
  }
}
```

Claude Code

```bash
claude mcp add --transport http pipevoy https://pipevoy.com/api/mcp \
  --header "Authorization: Bearer $PIPEVOY_API_KEY"
```

Any MCP client (Streamable HTTP)

```json
{
  "type": "streamable-http",
  "url": "https://pipevoy.com/api/mcp",
  "headers": { "Authorization": "Bearer pv_your_key_here" }
}
```

No key yet? Without one, only `signup` and `signup_status` work, so an agent can create its own workspace. Full details in [MCP server](#mcp).

## From zero to the first approved invite

The same setup, one call at a time. Or paste the brief above into your agent and let it work through `GET /api/v1/me`, which always names the next step.

### 1. Sign up

No key needed. A person confirms the emailed link; then the key is returned once to whoever holds the claim token.

POST /api/v1/signup

```bash
curl -X POST https://pipevoy.com/api/v1/signup -H "Content-Type: application/json" \
  -d '{"email":"founder@acme.com","company_name":"Acme"}'
# { "id": "…", "claim_token": "pvc_…", "status": "pending" }

curl https://pipevoy.com/api/v1/signup/$SIGNUP_ID -H "X-Claim-Token: $CLAIM_TOKEN"
# { "status": "verified", "api_key": "pv_…" }
```

### 2. Analyse your company

The AI reads your website and writes value propositions, ideal customer and personas. Adjust them with `PUT /api/v1/company`.

POST /api/v1/company/analyze

```bash
curl -X POST https://pipevoy.com/api/v1/company/analyze -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"website":"https://acme.com"}'
```

### 3. Create a sender

One LinkedIn account. Its owner opens `connect_url` once and logs in; nobody sees the password. Poll until it is connected.

POST /api/v1/senders

```bash
curl -X POST https://pipevoy.com/api/v1/senders -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"Sara Khan","timezone":"America/New_York","booking_url":"https://cal.com/sara/intro"}'
# { "sender": { "status": "pending", … }, "connect_url": "https://account.unipile.com/…" }
```

### 4. Create a campaign

Draft the sequence with AI, edit it if you like, then create it. It starts as soon as a sender is connected.

POST /api/v1/campaigns

```bash
curl -X POST https://pipevoy.com/api/v1/campaigns/draft -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"objective":"Book intro calls with RevOps leaders","cta":"a 20-minute call"}'

curl -X POST https://pipevoy.com/api/v1/campaigns -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"RevOps East Coast","steps":[…],"sender_ids":["'$SENDER_ID'"],"require_approval":true}'
```

### 5. Approve drafts, or subscribe to webhooks

Poll the queue, or get `draft.created`, `reply.received` and `meeting.booked` pushed to you, signed with HMAC-SHA256.

POST /api/v1/approvals/{id}/approve

```bash
curl https://pipevoy.com/api/v1/approvals -H "Authorization: Bearer $PIPEVOY_API_KEY"
curl -X POST https://pipevoy.com/api/v1/approvals/$DRAFT_ID/approve -H "Authorization: Bearer $PIPEVOY_API_KEY"

curl -X POST https://pipevoy.com/api/v1/webhooks -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://agent.example.com/hooks","events":["draft.created","reply.received"]}'
```

## What a human still does

Three steps need a person, by design. The API returns the link for each, so your agent knows when to ask.

| Step | Why | Link comes from |
| --- | --- | --- |
| Confirm the signup email | The workspace and its API key exist only after a person clicks the link. | `POST /api/v1/signup` |
| Log in to LinkedIn once per sender | A hosted sign-in. Neither we nor your agent see the password. | `connect_url` from `POST /api/v1/senders` |
| Pay | By card on Stripe Checkout, before the 14-day trial ends. Unpaid, sending pauses; nothing is deleted. | `url` from `POST /api/v1/billing/checkout` |

## Endpoints an agent uses most

36 REST endpoints under `/api/v1`, each with a matching MCP tool, JSON Schema and an error hint that says what to do next. These are the ones an agent uses most; every endpoint is documented below.

| Method | Path | What it does | MCP tool |
| --- | --- | --- | --- |
| POST | `/api/v1/signup` | Create a workspace without the UI. A person confirms the emailed link; no key needed. | `signup` |
| POST | `/api/v1/company/analyze` | Read your website and write the company profile every draft is based on. | `analyze_company` |
| POST | `/api/v1/senders` | Add a LinkedIn account. Returns connect_url for its owner's one-time login. | `create_sender` |
| POST | `/api/v1/campaigns` | Create and start a campaign from AI-drafted or your own steps. | `create_campaign` |
| POST | `/api/v1/campaigns/{id}/enroll` | Enroll prospects by id or LinkedIn URL. Unknown URLs are added first. | `enroll_prospects` |
| GET | `/api/v1/approvals` | List drafts waiting for approval, with the prospect and thread. | `list_pending_approvals` |
| POST | `/api/v1/approvals/{id}/approve` | Approve a draft, optionally edited. It joins the send queue. | `approve_draft` |
| GET | `/api/v1/conversations` | Replies, already classified by intent, newest first. | `list_conversations` |
| POST | `/api/v1/conversations/{id}/reply` | Write a reply. It becomes a draft; approve it to send. | `draft_reply` |
| PATCH | `/api/v1/meetings/{id}` | Record held, no-show or qualified. A no-show drafts a rebook note. | `update_meeting` |
| POST | `/api/v1/signals` | Push buying signals from Clay, your CRM or your own research. | `push_signal` |
| POST | `/api/v1/webhooks` | Subscribe to signed events instead of polling. | `create_webhook` |
| POST | `/api/v1/billing/checkout` | Get a Stripe Checkout link for a person to pay. | `create_checkout_link` |

### Designed to compose

1. Clay or your CRM: A stage changes or a column fills
2. `POST /api/v1/signals`: or enroll people directly by LinkedIn URL
3. Pipevoy engine: Drafts, waits for approval, sends inside the limits
4. Webhook `reply.received`: Signed with HMAC-SHA256
5. Your agent or HubSpot: Answers, books, updates the record

## Safety rules the engine enforces

An agent can be talked into anything. These rules are enforced in code on every send, whatever the request says. No endpoint sends to LinkedIn directly.

| Rule | Your agent on LinkedIn directly | Your agent calling Pipevoy |
| --- | --- | --- |
| Warm-up ramp | A new account sends at full volume on day one. | Limits ramp 25%, 50%, 75%, 100% over weeks 1 to 4, from the account's first send. |
| Daily and weekly limits | Whatever your loop sends. | Per sender, in the sender's own day: 20 invites and 40 messages by default. Invites are also capped at 100 over a rolling 7 days. Plan ceilings: 100 invites a week, 50 to 100 messages a day; higher values are refused. |
| Working hours | Sends at 3am if that is when the job runs. | Only inside each sender's hours and days (default 09:00 to 18:00, Monday to Friday, in their timezone), 2 to 6 minutes apart and paced across the day, up to 30 minutes between sends. |
| Approval required | The first draft goes straight out. | `require_approval` defaults to true. Drafts wait as `pending_approval` until a person or your agent approves them. |
| Outcome unknown, never resent | A timeout looks like a failure, so it retries and the prospect gets the message twice. | A message or reply that hits a timeout or 5xx is marked failed with “Outcome unknown”, still counts toward the limits, and pauses that sequence until a person checks LinkedIn. Invites can retry, because LinkedIn rejects a duplicate invitation. |
| Restrictions | Keeps going after LinkedIn pushes back. | A restricted sender pauses itself and fires the `sender.restricted` webhook. No retry loop. |
| Kill switch | Stop the process and hope nothing is in flight. | `PATCH /api/v1/senders/{id}` with `paused: true` stops one account. `SENDING_DISABLED=true` stops every send on the deployment; drafts still queue. Unpaid trials pause sending too, without deleting data. |

Honest caveat: LinkedIn’s User Agreement restricts automated activity, and any tool that acts on an account carries some risk of restriction. These rules reduce that risk; they cannot remove it.

## Authentication

Create a key in **Settings → API** (owners and admins), or let an agent sign up with `POST /api/v1/signup`. Send it on every request as `Authorization: Bearer pv_…`. A key is shown once and stored only as a hash, belongs to one workspace, and can be revoked at any time. Approvals made with a key are recorded as the person who created it.

API and MCP access is part of the Growth, Agency, Enterprise and Done-For-You plans and of the free trial. On Starter, requests answer 403 `plan_upgrade_required`, except the billing endpoints, so an agent can still start an upgrade.

## Rate limits, pagination, idempotency, errors

- **Rate limit:** 120 requests per minute per key. Over it you get 429 with `Retry-After`; every response carries `X-RateLimit-Remaining`.
- **Pagination:** list endpoints take `?limit=` (1 to 100, default 25) and `&cursor=`, and return `{ data, next_cursor }`. Pass next_cursor back unchanged; null means the last page.
- **Idempotency:** send `Idempotency-Key` on any POST. The first response is stored for 24 hours and replayed for retries with the same key and body; the same key with a different body answers 422.
- **Errors:** `{ "error": { "code", "message", "hint" } }` with a matching status (400 validation, 401 key, 403 plan or limit, 404 not in this workspace, 409 state conflict, 422 can’t do that, 429 slow down). The hint says what to do next.
- **Safety:** no endpoint sends to LinkedIn directly. Approving queues a message; the engine sends it inside the sender’s working hours, limits and warm-up, and only while billing allows sending.

Error example

```json
{
  "error": {
    "code": "plan_limit",
    "message": "Your Growth plan includes 3 LinkedIn senders (you have 3). Upgrade to Agency for 25 in Settings → Billing.",
    "hint": "Free up capacity or upgrade: POST /api/v1/billing/checkout {\"plan\":\"agency\",\"interval\":\"month\"} and have a human pay the returned URL.",
    "resource": "senders",
    "limit": 3,
    "used": 3
  }
}
```

## Why a send is held

Approving queues a message; the engine decides when it goes. The table shows the engine’s own explanation for each state, produced by the same code that fills the Next column in the app (example: a New York sender working Monday to Friday, 09:00 to 18:00 ET, on a Friday afternoon). Over the API you read the inputs: `can_send` and `sending_paused_reason` on `GET /me`, and `status`, `effective_limits` and `today` on `GET /senders/{id}`.

| When | Reason | Engine says |
| --- | --- | --- |
| Kill switch on (`SENDING_DISABLED=true`) | `sending_disabled` | Sending disabled |
| Trial ended unpaid, or payment failed past the grace period | `billing_paused` | Sending paused (billing) |
| The sender has not finished the LinkedIn login | `sender_not_connected` | Sender not connected |
| Outside the sender's working hours | `outside_hours` | Outside working hours · next send Mon 09:00 America/New_York |
| Warm-up week 1: 25% of the configured limits | `warmup_limit` | Warm-up limit reached (week 1) · resumes Mon 09:00 America/New_York |
| Daily invite limit used | `daily_invite_limit` | Daily invite limit reached · resumes Mon 09:00 America/New_York |
| Rolling 7-day invite limit used | `weekly_invite_limit` | Weekly invite limit reached · resumes Mon 09:00 America/New_York |
| Daily message limit used (sequence messages keep 20% back for replies) | `daily_message_limit` | Daily message limit reached · resumes Mon 09:00 America/New_York |
| Spacing sends from the same account | `pacing` | Pacing between sends · next send Fri 17:18 America/New_York |
| Nothing holding it | `ready` | Sending shortly |

## Workspace and billing

Start every session with GET /me: its setup.next_step says what to do next.

### Workspace, plan, limits and setup

`GET /api/v1/me` (MCP tool `get_workspace`)

Plan, trial, limits and usage, whether sending is allowed right now, and a setup checklist.

```bash
curl https://pipevoy.com/api/v1/me -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "workspace": {
    "id": "6f1d2c3b-8a47-4e15-9b0c-3d2e1f4a5b6c",
    "name": "Acme",
    "created_at": "2026-10-20T09:14:00.000Z"
  },
  "plan": "growth",
  "plan_name": "Growth",
  "status": "trialing",
  "trial_ends_at": "2026-11-03T09:14:00.000Z",
  "days_left_in_trial": 14,
  "can_send": true,
  "sending_paused_reason": null,
  "limits": {
    "senders": 3,
    "seats": 5,
    "active_campaigns": 25,
    "signal_watches": 10,
    "monthly_ai_drafts": 5000
  },
  "usage": {
    "senders": 1,
    "seats": 1,
    "active_campaigns": 1,
    "signal_watches": 0,
    "ai_drafts_this_month": 12
  },
  "api_key": {
    "prefix": "pv_8KfQ2mZx1a",
    "name": "Sales agent",
    "rate_limit_per_minute": 120
  },
  "setup": {
    "company_profile": true,
    "senders_created": 1,
    "senders_connected": 1,
    "active_campaigns": 1,
    "next_step": "Add prospects and enroll them, then approve drafts: GET /api/v1/approvals."
  }
}
```

### Billing status

`GET /api/v1/billing` (MCP tool `get_billing`)

Works on every plan, including Starter.

```bash
curl https://pipevoy.com/api/v1/billing -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "plan": "growth",
  "plan_name": "Growth",
  "status": "trialing",
  "interval": null,
  "trial_ends_at": "2026-11-03T09:14:00.000Z",
  "days_left_in_trial": 14,
  "current_period_end": null,
  "grace_until": null,
  "has_subscription": false,
  "can_send": true,
  "sending_paused_reason": null,
  "limits": {
    "senders": 3,
    "seats": 5,
    "active_campaigns": 25,
    "signal_watches": 10,
    "monthly_ai_drafts": 5000
  },
  "usage": {
    "senders": 1,
    "seats": 1,
    "active_campaigns": 1,
    "signal_watches": 0,
    "ai_drafts_this_month": 12
  }
}
```

### Checkout link for a person to pay

`POST /api/v1/billing/checkout` (MCP tool `create_checkout_link`)

Returns a Stripe Checkout URL. A person opens it and pays; an agent can't. If the workspace already has a subscription you get the billing portal instead, so nobody pays twice.

```bash
curl -X POST https://pipevoy.com/api/v1/billing/checkout -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"plan":"growth","interval":"month"}'
```

Response:

```json
{
  "url": "https://checkout.stripe.com/c/pay/cs_…",
  "plan": "growth",
  "interval": "month",
  "human_step": "A person must open this URL and pay by card. Poll GET /api/v1/billing until status is \"active\"."
}
```

## Signup without the UI

No key needed. Nothing is created until a person confirms the emailed link.

### Start a signup

`POST /api/v1/signup` (MCP tool `signup`)

Emails a verification link to the address. Returns a claim_token, shown once. Rate-limited per network and per email domain.

```bash
curl -X POST https://pipevoy.com/api/v1/signup -H "Content-Type: application/json" \
  -d '{"email":"founder@acme.com","company_name":"Acme","website":"https://acme.com"}'
```

Response:

```json
{
  "id": "0f1e2d3c-4b5a-4968-8776-655443322110",
  "status": "pending",
  "email": "founder@acme.com",
  "claim_token": "pvc_…",
  "expires_at": "2026-10-21T09:14:00.000Z",
  "poll": "GET /api/v1/signup/0f1e2d3c-4b5a-4968-8776-655443322110 with header X-Claim-Token: <claim_token>",
  "next": "A verification link was emailed to founder@acme.com. A person must open it and confirm…"
}
```

### Poll for the API key

`GET /api/v1/signup/{id}` (MCP tool `signup_status`)

Poll every 10 to 30 seconds. After confirmation the first response carries api_key; later responses don't.

```bash
curl https://pipevoy.com/api/v1/signup/0f1e2d3c-4b5a-4968-8776-655443322110 -H "X-Claim-Token: $CLAIM_TOKEN"
```

Response:

```json
{
  "id": "0f1e2d3c-4b5a-4968-8776-655443322110",
  "status": "verified",
  "email": "founder@acme.com",
  "workspace_id": "6f1d2c3b-8a47-4e15-9b0c-3d2e1f4a5b6c",
  "api_key": "pv_…",
  "expires_at": "2026-10-21T09:14:00.000Z",
  "next": "Store this key now; it is not shown again…"
}
```

## Company profile

Every draft is written from this profile: value props, ICP, personas.

### Build the profile from the website

`POST /api/v1/company/analyze` (MCP tool `analyze_company`)

Reads a few pages of the site and writes the profile with AI. Uses one request from the daily AI allowance.

```bash
curl -X POST https://pipevoy.com/api/v1/company/analyze -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"website":"https://acme.com"}'
```

Response:

```json
{
  "company": {
    "website": "https://acme.com",
    "company_name": "Acme",
    "summary": "Acme sells revenue analytics to B2B SaaS teams.",
    "value_props": [
      "Forecasts you can trust in week one"
    ],
    "proof_points": [],
    "icp": {
      "industries": [
        "SaaS"
      ],
      "company_sizes": [
        "51-200"
      ],
      "geographies": [
        "US",
        "Canada"
      ],
      "titles": [
        "VP Sales",
        "Head of RevOps"
      ],
      "seniorities": [
        "VP",
        "Head"
      ],
      "pain_points": [
        "Forecast misses"
      ],
      "exclusions": []
    },
    "personas": [
      {
        "name": "RevOps lead",
        "titles": [
          "Head of RevOps"
        ],
        "goals": [
          "Clean pipeline data"
        ],
        "pains": [
          "Manual reporting"
        ],
        "messaging_angle": "Time back every Monday"
      }
    ],
    "competitors": [],
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Read the profile

`GET /api/v1/company` (MCP tool `get_company`)

null until it has been built or written.

```bash
curl https://pipevoy.com/api/v1/company -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "company": {
    "website": "https://acme.com",
    "company_name": "Acme",
    "summary": "Acme sells revenue analytics to B2B SaaS teams.",
    "value_props": [
      "Forecasts you can trust in week one"
    ],
    "proof_points": [],
    "icp": {
      "industries": [
        "SaaS"
      ],
      "company_sizes": [
        "51-200"
      ],
      "geographies": [
        "US",
        "Canada"
      ],
      "titles": [
        "VP Sales",
        "Head of RevOps"
      ],
      "seniorities": [
        "VP",
        "Head"
      ],
      "pain_points": [
        "Forecast misses"
      ],
      "exclusions": []
    },
    "personas": [
      {
        "name": "RevOps lead",
        "titles": [
          "Head of RevOps"
        ],
        "goals": [
          "Clean pipeline data"
        ],
        "pains": [
          "Manual reporting"
        ],
        "messaging_angle": "Time back every Monday"
      }
    ],
    "competitors": [],
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Update the profile

`PUT /api/v1/company` (MCP tool `update_company`)

Send only the fields to change. Lists replace the stored list; ICP fields merge.

```bash
curl -X PUT https://pipevoy.com/api/v1/company -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"icp":{"geographies":["US","Canada"]},"personas":[{"name":"RevOps lead","titles":["Head of RevOps"]}]}'
```

Response:

```json
{
  "company": {
    "website": "https://acme.com",
    "company_name": "Acme",
    "summary": "Acme sells revenue analytics to B2B SaaS teams.",
    "value_props": [
      "Forecasts you can trust in week one"
    ],
    "proof_points": [],
    "icp": {
      "industries": [
        "SaaS"
      ],
      "company_sizes": [
        "51-200"
      ],
      "geographies": [
        "US",
        "Canada"
      ],
      "titles": [
        "VP Sales",
        "Head of RevOps"
      ],
      "seniorities": [
        "VP",
        "Head"
      ],
      "pain_points": [
        "Forecast misses"
      ],
      "exclusions": []
    },
    "personas": [
      {
        "name": "RevOps lead",
        "titles": [
          "Head of RevOps"
        ],
        "goals": [
          "Clean pipeline data"
        ],
        "pains": [
          "Manual reporting"
        ],
        "messaging_angle": "Time back every Monday"
      }
    ],
    "competitors": [],
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

## Senders

A sender is one LinkedIn account. Its owner logs in once through connect_url; that is the only human step besides paying.

### Create a sender

`POST /api/v1/senders` (MCP tool `create_sender`)

Returns connect_url, a hosted LinkedIn sign-in (the password is never seen by us or your agent). Limits are capped at your plan's safety maximums; warm-up is on by default.

```bash
curl -X POST https://pipevoy.com/api/v1/senders -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"Sara Khan","timezone":"America/New_York","work_start":"09:00","work_end":"18:00","booking_url":"https://cal.com/sara/intro"}'
```

Response:

```json
{
  "sender": {
    "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "name": "Sara Khan",
    "status": "pending",
    "connected": false,
    "linkedin_url": null,
    "timezone": "America/New_York",
    "work_start": "09:00",
    "work_end": "18:00",
    "work_days": [
      1,
      2,
      3,
      4,
      5
    ],
    "limits": {
      "daily_invites": 20,
      "daily_messages": 40,
      "weekly_invites": 100
    },
    "warmup_enabled": true,
    "effective_limits": {
      "daily_invites": 10,
      "daily_messages": 20,
      "weekly_invites": 50,
      "warmup_week": 2
    },
    "health": {
      "score": 86,
      "grade": "good",
      "factors": [
        {
          "label": "Acceptance rate",
          "value": "38%",
          "impact": 0
        }
      ],
      "recommendations": []
    },
    "today": {
      "invites": 3,
      "messages": 5
    },
    "booking_url": "https://cal.com/sara/intro",
    "voice_notes": null,
    "last_error": null,
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  },
  "connect_url": "https://account.unipile.com/…",
  "connect_error": null,
  "human_step": "A person must open connect_url once and log in to LinkedIn…"
}
```

### List senders

`GET /api/v1/senders` (MCP tool `list_senders`)

Status, configured and effective (warm-up) limits, today's sends and health score.

```bash
curl https://pipevoy.com/api/v1/senders -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
      "name": "Sara Khan",
      "status": "connected",
      "connected": true,
      "linkedin_url": null,
      "timezone": "America/New_York",
      "work_start": "09:00",
      "work_end": "18:00",
      "work_days": [
        1,
        2,
        3,
        4,
        5
      ],
      "limits": {
        "daily_invites": 20,
        "daily_messages": 40,
        "weekly_invites": 100
      },
      "warmup_enabled": true,
      "effective_limits": {
        "daily_invites": 10,
        "daily_messages": 20,
        "weekly_invites": 50,
        "warmup_week": 2
      },
      "health": {
        "score": 86,
        "grade": "good",
        "factors": [
          {
            "label": "Acceptance rate",
            "value": "38%",
            "impact": 0
          }
        ],
        "recommendations": []
      },
      "today": {
        "invites": 3,
        "messages": 5
      },
      "booking_url": "https://cal.com/sara/intro",
      "voice_notes": null,
      "last_error": null,
      "created_at": "2026-10-20T09:14:00.000Z",
      "updated_at": "2026-10-20T09:14:00.000Z"
    }
  ],
  "next_cursor": null
}
```

### Get a sender

`GET /api/v1/senders/{id}` (MCP tool `get_sender`)

Poll this after sending connect_url to a person; status turns connected.

```bash
curl https://pipevoy.com/api/v1/senders/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "sender": {
    "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "name": "Sara Khan",
    "status": "connected",
    "connected": true,
    "linkedin_url": null,
    "timezone": "America/New_York",
    "work_start": "09:00",
    "work_end": "18:00",
    "work_days": [
      1,
      2,
      3,
      4,
      5
    ],
    "limits": {
      "daily_invites": 20,
      "daily_messages": 40,
      "weekly_invites": 100
    },
    "warmup_enabled": true,
    "effective_limits": {
      "daily_invites": 10,
      "daily_messages": 20,
      "weekly_invites": 50,
      "warmup_week": 2
    },
    "health": {
      "score": 86,
      "grade": "good",
      "factors": [
        {
          "label": "Acceptance rate",
          "value": "38%",
          "impact": 0
        }
      ],
      "recommendations": []
    },
    "today": {
      "invites": 3,
      "messages": 5
    },
    "booking_url": "https://cal.com/sara/intro",
    "voice_notes": null,
    "last_error": null,
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Update hours, limits, warm-up

`PATCH /api/v1/senders/{id}` (MCP tool `update_sender`)

paused: true stops all sends from the account; paused: false re-checks the connection and resumes. Limits above your plan's safety maximum are refused with limit_above_safety_maximum.

```bash
curl -X PATCH https://pipevoy.com/api/v1/senders/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"daily_invite_limit":15,"work_days":[1,2,3,4]}'
```

Response:

```json
{
  "sender": {
    "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "name": "Sara Khan",
    "status": "connected",
    "connected": true,
    "linkedin_url": null,
    "timezone": "America/New_York",
    "work_start": "09:00",
    "work_end": "18:00",
    "work_days": [
      1,
      2,
      3,
      4,
      5
    ],
    "limits": {
      "daily_invites": 20,
      "daily_messages": 40,
      "weekly_invites": 100
    },
    "warmup_enabled": true,
    "effective_limits": {
      "daily_invites": 10,
      "daily_messages": 20,
      "weekly_invites": 50,
      "warmup_week": 2
    },
    "health": {
      "score": 86,
      "grade": "good",
      "factors": [
        {
          "label": "Acceptance rate",
          "value": "38%",
          "impact": 0
        }
      ],
      "recommendations": []
    },
    "today": {
      "invites": 3,
      "messages": 5
    },
    "booking_url": "https://cal.com/sara/intro",
    "voice_notes": null,
    "last_error": null,
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Fresh connect link

`POST /api/v1/senders/{id}/connect` (MCP tool `get_connect_link`)

Links expire. Use this when a person needs a new one, or when an account needs to reconnect.

```bash
curl -X POST https://pipevoy.com/api/v1/senders/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9/connect -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "connect_url": "https://account.unipile.com/…",
  "connect_error": null,
  "human_step": "A person must open connect_url once and log in to LinkedIn…"
}
```

## Prospects

Deduplicated by LinkedIn profile URL. Adding a prospect contacts nobody.

### Add prospects

`POST /api/v1/prospects` (MCP tool `add_prospects`)

One object, an array, or { prospects: [...] }; up to 500 per call. Existing prospects are updated and blanks never overwrite.

```bash
curl -X POST https://pipevoy.com/api/v1/prospects -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: import-2026-10-20" \
  -d '[{"linkedin_url":"https://www.linkedin.com/in/jane-doe","first_name":"Jane","title":"VP Sales","company":"Northwind"}]'
```

Response:

```json
{
  "created": 1,
  "updated": 0,
  "errors": [],
  "data": [
    {
      "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "linkedin_url": "https://www.linkedin.com/in/jane-doe",
      "first_name": "Jane",
      "last_name": "Doe",
      "headline": null,
      "title": "VP Sales",
      "company": "Northwind",
      "company_domain": "northwind.com",
      "email": null,
      "location": "New York",
      "industry": null,
      "account_id": null,
      "source": "api",
      "research": null,
      "created_at": "2026-10-20T09:14:00.000Z",
      "updated_at": "2026-10-20T09:14:00.000Z"
    }
  ]
}
```

### List prospects

`GET /api/v1/prospects` (MCP tool `list_prospects`)

Newest first. search= matches name, company, title, headline, email and URL.

```bash
curl "https://pipevoy.com/api/v1/prospects?limit=50&search=northwind" -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "linkedin_url": "https://www.linkedin.com/in/jane-doe",
      "first_name": "Jane",
      "last_name": "Doe",
      "headline": null,
      "title": "VP Sales",
      "company": "Northwind",
      "company_domain": "northwind.com",
      "email": null,
      "location": "New York",
      "industry": null,
      "account_id": null,
      "source": "api",
      "research": null,
      "created_at": "2026-10-20T09:14:00.000Z",
      "updated_at": "2026-10-20T09:14:00.000Z"
    }
  ],
  "next_cursor": "eyJ0Ijoi…",
  "total": 128
}
```

### Get a prospect

`GET /api/v1/prospects/{id}` (MCP tool `get_prospect`)

```bash
curl https://pipevoy.com/api/v1/prospects/a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "prospect": {
    "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
    "linkedin_url": "https://www.linkedin.com/in/jane-doe",
    "first_name": "Jane",
    "last_name": "Doe",
    "headline": null,
    "title": "VP Sales",
    "company": "Northwind",
    "company_domain": "northwind.com",
    "email": null,
    "location": "New York",
    "industry": null,
    "account_id": null,
    "source": "api",
    "research": null,
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

## Campaigns

require_approval defaults to true, so every message waits for an approval your agent can give.

### Draft a sequence with AI

`POST /api/v1/campaigns/draft` (MCP tool `draft_campaign`)

Generates an invite and follow-ups from the company profile. Saves nothing. Optional: tone, language, follow_ups, ab_test, signal_types; or pass your own steps to validate them.

```bash
curl -X POST https://pipevoy.com/api/v1/campaigns/draft -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"objective":"Book intro calls with RevOps leaders at Series B SaaS","cta":"a 20-minute call","tone":"direct"}'
```

Response:

```json
{
  "draft": {
    "name": "RevOps leaders, Series B",
    "rationale": "Opens on hiring signals…",
    "steps": [
      {
        "type": "invite",
        "delay_days": 0,
        "guidance": "Short note referencing their recent hiring push. No pitch.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 1,
        "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 4,
        "guidance": "Brief, low-pressure follow-up with one proof point.",
        "template": null
      }
    ],
    "ai": true
  },
  "next": "Review or edit the steps, then POST /api/v1/campaigns…"
}
```

### Create and start a campaign

`POST /api/v1/campaigns` (MCP tool `create_campaign`)

start defaults to true. If it can't start yet (no connected sender, no company profile, plan limit), it is saved as a draft and activation_error says why.

```bash
curl -X POST https://pipevoy.com/api/v1/campaigns -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"name":"RevOps leaders, East Coast","objective":"Book intro calls","cta":"a 20-minute call","steps":[…],"sender_ids":["5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"],"require_approval":true}'
```

Response:

```json
{
  "campaign": {
    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "name": "RevOps leaders, East Coast",
    "status": "active",
    "kind": "standard",
    "goal": "Book intro calls with RevOps leaders",
    "persona": null,
    "cta": "a 20-minute call",
    "language": "en",
    "require_approval": true,
    "steps": [
      {
        "type": "invite",
        "delay_days": 0,
        "guidance": "Short note referencing their recent hiring push. No pitch.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 1,
        "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 4,
        "guidance": "Brief, low-pressure follow-up with one proof point.",
        "template": null
      }
    ],
    "signal_rules": null,
    "sender_ids": [
      "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
    ],
    "stats": {
      "enrolled": 42,
      "invited": 30,
      "connected": 12,
      "replied": 4,
      "booked": 1,
      "pending_approval": 3
    },
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  },
  "started": true,
  "activation_error": null,
  "next": "Enroll prospects: POST /api/v1/campaigns/:id/enroll."
}
```

### List campaigns

`GET /api/v1/campaigns` (MCP tool `list_campaigns`)

Optional status= draft, active, paused or completed.

```bash
curl "https://pipevoy.com/api/v1/campaigns?status=active" -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
      "name": "RevOps leaders, East Coast",
      "status": "active",
      "kind": "standard",
      "goal": "Book intro calls with RevOps leaders",
      "persona": null,
      "cta": "a 20-minute call",
      "language": "en",
      "require_approval": true,
      "steps": [
        {
          "type": "invite",
          "delay_days": 0,
          "guidance": "Short note referencing their recent hiring push. No pitch.",
          "template": null
        },
        {
          "type": "message",
          "delay_days": 1,
          "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
          "template": null
        },
        {
          "type": "message",
          "delay_days": 4,
          "guidance": "Brief, low-pressure follow-up with one proof point.",
          "template": null
        }
      ],
      "signal_rules": null,
      "sender_ids": [
        "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
      ],
      "stats": {
        "enrolled": 42,
        "invited": 30,
        "connected": 12,
        "replied": 4,
        "booked": 1,
        "pending_approval": 3
      },
      "created_at": "2026-10-20T09:14:00.000Z",
      "updated_at": "2026-10-20T09:14:00.000Z"
    }
  ],
  "next_cursor": null
}
```

### Get a campaign

`GET /api/v1/campaigns/{id}` (MCP tool `get_campaign`)

```bash
curl https://pipevoy.com/api/v1/campaigns/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "campaign": {
    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "name": "RevOps leaders, East Coast",
    "status": "active",
    "kind": "standard",
    "goal": "Book intro calls with RevOps leaders",
    "persona": null,
    "cta": "a 20-minute call",
    "language": "en",
    "require_approval": true,
    "steps": [
      {
        "type": "invite",
        "delay_days": 0,
        "guidance": "Short note referencing their recent hiring push. No pitch.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 1,
        "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 4,
        "guidance": "Brief, low-pressure follow-up with one proof point.",
        "template": null
      }
    ],
    "signal_rules": null,
    "sender_ids": [
      "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
    ],
    "stats": {
      "enrolled": 42,
      "invited": 30,
      "connected": 12,
      "replied": 4,
      "booked": 1,
      "pending_approval": 3
    },
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Update a campaign

`PATCH /api/v1/campaigns/{id}` (MCP tool `update_campaign`)

Name, objective, persona, cta, language, steps, require_approval, sender_ids, signal_rules.

```bash
curl -X PATCH https://pipevoy.com/api/v1/campaigns/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"require_approval":false}'
```

Response:

```json
{
  "campaign": {
    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "name": "RevOps leaders, East Coast",
    "status": "active",
    "kind": "standard",
    "goal": "Book intro calls with RevOps leaders",
    "persona": null,
    "cta": "a 20-minute call",
    "language": "en",
    "require_approval": false,
    "steps": [
      {
        "type": "invite",
        "delay_days": 0,
        "guidance": "Short note referencing their recent hiring push. No pitch.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 1,
        "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 4,
        "guidance": "Brief, low-pressure follow-up with one proof point.",
        "template": null
      }
    ],
    "signal_rules": null,
    "sender_ids": [
      "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
    ],
    "stats": {
      "enrolled": 42,
      "invited": 30,
      "connected": 12,
      "replied": 4,
      "booked": 1,
      "pending_approval": 3
    },
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Enroll prospects

`POST /api/v1/campaigns/{id}/enroll` (MCP tool `enroll_prospects`)

By prospect_ids and/or linkedin_urls (unknown URLs are added first). People already in a running campaign, in a conversation or opted out are skipped.

```bash
curl -X POST https://pipevoy.com/api/v1/campaigns/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/enroll -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"linkedin_urls":["https://www.linkedin.com/in/jane-doe"]}'
```

Response:

```json
{
  "enrolled": 1,
  "skipped": 0,
  "created_prospects": 0,
  "invalid_linkedin_urls": []
}
```

### Pause

`POST /api/v1/campaigns/{id}/pause` (MCP tool `pause_campaign`)

Nothing more is drafted or sent for it.

```bash
curl -X POST https://pipevoy.com/api/v1/campaigns/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/pause -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "campaign": {
    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "name": "RevOps leaders, East Coast",
    "status": "paused",
    "kind": "standard",
    "goal": "Book intro calls with RevOps leaders",
    "persona": null,
    "cta": "a 20-minute call",
    "language": "en",
    "require_approval": true,
    "steps": [
      {
        "type": "invite",
        "delay_days": 0,
        "guidance": "Short note referencing their recent hiring push. No pitch.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 1,
        "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 4,
        "guidance": "Brief, low-pressure follow-up with one proof point.",
        "template": null
      }
    ],
    "signal_rules": null,
    "sender_ids": [
      "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
    ],
    "stats": {
      "enrolled": 42,
      "invited": 30,
      "connected": 12,
      "replied": 4,
      "booked": 1,
      "pending_approval": 3
    },
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

### Resume or activate

`POST /api/v1/campaigns/{id}/resume` (MCP tool `resume_campaign`)

Counts toward the plan's active-campaign limit.

```bash
curl -X POST https://pipevoy.com/api/v1/campaigns/c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f/resume -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "campaign": {
    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "name": "RevOps leaders, East Coast",
    "status": "active",
    "kind": "standard",
    "goal": "Book intro calls with RevOps leaders",
    "persona": null,
    "cta": "a 20-minute call",
    "language": "en",
    "require_approval": true,
    "steps": [
      {
        "type": "invite",
        "delay_days": 0,
        "guidance": "Short note referencing their recent hiring push. No pitch.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 1,
        "guidance": "Thank them for connecting; one line on the problem; ask the CTA.",
        "template": null
      },
      {
        "type": "message",
        "delay_days": 4,
        "guidance": "Brief, low-pressure follow-up with one proof point.",
        "template": null
      }
    ],
    "signal_rules": null,
    "sender_ids": [
      "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
    ],
    "stats": {
      "enrolled": 42,
      "invited": 30,
      "connected": 12,
      "replied": 4,
      "booked": 1,
      "pending_approval": 3
    },
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

## Approvals

Approving through the API is the same as approving in the app. The engine still decides when it is safe to send.

### Pending drafts

`GET /api/v1/approvals` (MCP tool `list_pending_approvals`)

Oldest first, with the prospect and recent thread. Optional campaign_id=.

```bash
curl https://pipevoy.com/api/v1/approvals -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
      "type": "invite",
      "status": "pending_approval",
      "purpose": null,
      "body": "Hi Jane, saw Northwind is hiring three SDRs in Austin. Would be glad to connect.",
      "ai_draft": null,
      "step_index": 0,
      "sender_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
      "prospect_id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "conversation_id": null,
      "scheduled_for": "2026-10-20T09:14:00.000Z",
      "approved_at": null,
      "sent_at": null,
      "created_at": "2026-10-20T09:14:00.000Z",
      "sender_name": "Sara Khan",
      "campaign_name": "RevOps leaders, East Coast",
      "prospect": {
        "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
        "first_name": "Jane",
        "last_name": "Doe",
        "title": "VP Sales",
        "company": "Northwind",
        "linkedin_url": "https://www.linkedin.com/in/jane-doe"
      },
      "thread": []
    }
  ],
  "next_cursor": null
}
```

### Approve

`POST /api/v1/approvals/{id}/approve` (MCP tool `approve_draft`)

Optionally send an edited body. will_send is false while billing or SENDING_DISABLED holds sending; the approval stays queued.

```bash
curl -X POST https://pipevoy.com/api/v1/approvals/d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a/approve -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"body":"Hi Jane, saw Northwind is hiring in Austin. Glad to connect."}'
```

Response:

```json
{
  "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
  "status": "approved",
  "will_send": true,
  "sending_paused_reason": null,
  "note": "Approved messages are sent by the engine inside the sender's working hours, daily/weekly limits and warm-up, with randomised pacing."
}
```

### Reject

`POST /api/v1/approvals/{id}/reject` (MCP tool `reject_draft`)

Never sent. Rejecting a sequence step pauses that prospect's sequence.

```bash
curl -X POST https://pipevoy.com/api/v1/approvals/d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a/reject -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
  "status": "rejected"
}
```

## Conversations

Replies are classified automatically and the AI drafts an answer for approval.

### List conversations

`GET /api/v1/conversations` (MCP tool `list_conversations`)

Most recent activity first. since= (ISO time) and filter= attention, interested or booked.

```bash
curl "https://pipevoy.com/api/v1/conversations?since=2026-10-20T00:00:00Z" -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
      "intent": "interested",
      "intent_confidence": 88,
      "needs_attention": true,
      "last_message_at": "2026-10-20T09:14:00.000Z",
      "prospect": {
        "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
        "first_name": "Jane",
        "last_name": "Doe",
        "title": "VP Sales",
        "company": "Northwind",
        "linkedin_url": "https://www.linkedin.com/in/jane-doe"
      },
      "sender": {
        "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        "name": "Sara Khan"
      },
      "campaign_name": "RevOps leaders, East Coast",
      "last_message": {
        "direction": "inbound",
        "body": "Sounds interesting, what does it cost?",
        "sent_at": "2026-10-20T09:14:00.000Z"
      },
      "has_pending_draft": true,
      "meeting_at": null
    }
  ],
  "next_cursor": null
}
```

### Get a conversation

`GET /api/v1/conversations/{id}` (MCP tool `get_conversation`)

All messages, the pending reply draft and queued replies.

```bash
curl https://pipevoy.com/api/v1/conversations/7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "conversation": {
    "id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
    "intent": "interested",
    "intent_confidence": 88,
    "intent_reason": "Asks about pricing",
    "needs_attention": true,
    "last_message_at": "2026-10-20T09:14:00.000Z",
    "prospect": {
      "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "linkedin_url": "https://www.linkedin.com/in/jane-doe",
      "first_name": "Jane",
      "last_name": "Doe",
      "headline": null,
      "title": "VP Sales",
      "company": "Northwind",
      "company_domain": "northwind.com",
      "email": null,
      "location": "New York",
      "industry": null,
      "account_id": null,
      "source": "api",
      "research": null,
      "created_at": "2026-10-20T09:14:00.000Z",
      "updated_at": "2026-10-20T09:14:00.000Z"
    },
    "sender": {
      "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
      "name": "Sara Khan"
    },
    "campaign": {
      "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
      "name": "RevOps leaders, East Coast"
    },
    "messages": [
      {
        "id": "…",
        "direction": "inbound",
        "body": "Sounds interesting, what does it cost?",
        "sent_at": "2026-10-20T09:14:00.000Z"
      }
    ],
    "pending_draft": {
      "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
      "type": "reply",
      "status": "pending_approval",
      "purpose": "reply",
      "body": "Hi Jane, saw Northwind is hiring three SDRs in Austin. Would be glad to connect.",
      "ai_draft": null,
      "step_index": 0,
      "sender_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
      "prospect_id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "conversation_id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
      "scheduled_for": "2026-10-20T09:14:00.000Z",
      "approved_at": null,
      "sent_at": null,
      "created_at": "2026-10-20T09:14:00.000Z"
    },
    "queued": [],
    "meeting": null
  }
}
```

### Write a reply (as a draft)

`POST /api/v1/conversations/{id}/reply` (MCP tool `draft_reply`)

Never sends directly. Creates a pending draft that replaces any older one; approve it to send.

```bash
curl -X POST https://pipevoy.com/api/v1/conversations/7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e/reply -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"body":"Happy to walk you through it. Does Tuesday work?"}'
```

Response:

```json
{
  "draft": {
    "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
    "type": "reply",
    "status": "pending_approval",
    "purpose": "manual",
    "body": "Happy to walk you through it. Does Tuesday work?",
    "ai_draft": null,
    "step_index": 0,
    "sender_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "prospect_id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
    "conversation_id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
    "scheduled_for": "2026-10-20T09:14:00.000Z",
    "approved_at": null,
    "sent_at": null,
    "created_at": "2026-10-20T09:14:00.000Z"
  },
  "next": "Approve it with POST /api/v1/approvals/d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a/approve."
}
```

## Meetings

Booked through each sender's booking link.

### List meetings

`GET /api/v1/meetings` (MCP tool `list_meetings`)

Latest start time first. Optional status=.

```bash
curl https://pipevoy.com/api/v1/meetings -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "9d0e1f2a-3b4c-4d5e-af6b-7c8d9e0f1a2b",
      "status": "booked",
      "start_time": "2026-10-23T19:00:00.000Z",
      "end_time": "2026-10-23T19:20:00.000Z",
      "attendee_name": "Jane Doe",
      "attendee_email": "jane@northwind.com",
      "qualified": null,
      "notes": null,
      "prospect": {
        "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
        "first_name": "Jane",
        "last_name": "Doe",
        "title": "VP Sales",
        "company": "Northwind",
        "linkedin_url": "https://www.linkedin.com/in/jane-doe"
      },
      "sender_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
      "sender_name": "Sara Khan",
      "campaign_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
      "campaign_name": "RevOps leaders, East Coast",
      "conversation_id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
      "rebook": null,
      "rebook_attempts_left": 2,
      "created_at": "2026-10-20T09:14:00.000Z",
      "updated_at": "2026-10-20T09:14:00.000Z"
    }
  ],
  "next_cursor": null
}
```

### Record the outcome

`PATCH /api/v1/meetings/{id}` (MCP tool `update_meeting`)

status held, no_show or cancelled (or no_show: true), qualified, notes. A no-show drafts a rebook note for approval, at most twice.

```bash
curl -X PATCH https://pipevoy.com/api/v1/meetings/9d0e1f2a-3b4c-4d5e-af6b-7c8d9e0f1a2b -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"status":"held","qualified":true,"notes":"Wants a pilot in Q1"}'
```

Response:

```json
{
  "meeting": {
    "id": "9d0e1f2a-3b4c-4d5e-af6b-7c8d9e0f1a2b",
    "status": "held",
    "start_time": "2026-10-23T19:00:00.000Z",
    "end_time": "2026-10-23T19:20:00.000Z",
    "attendee_name": "Jane Doe",
    "attendee_email": "jane@northwind.com",
    "qualified": true,
    "notes": "Wants a pilot in Q1",
    "prospect": {
      "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "first_name": "Jane",
      "last_name": "Doe",
      "title": "VP Sales",
      "company": "Northwind",
      "linkedin_url": "https://www.linkedin.com/in/jane-doe"
    },
    "sender_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "sender_name": "Sara Khan",
    "campaign_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "campaign_name": "RevOps leaders, East Coast",
    "conversation_id": "7b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
    "rebook": null,
    "rebook_attempts_left": 2,
    "created_at": "2026-10-20T09:14:00.000Z",
    "updated_at": "2026-10-20T09:14:00.000Z"
  }
}
```

## Signals

Same behaviour as the inbound signal webhook: deduplicated, scored and attached to accounts.

### Push buying signals

`POST /api/v1/signals` (MCP tool `push_signal`)

Up to 100 per call. Each needs text and one of company, domain or person_linkedin_url. Send type and text to store it as-is, or only text to let the AI decide whether it is a signal. Shares the inbound limits: 60 requests an hour and 1,000 signals a day.

```bash
curl -X POST https://pipevoy.com/api/v1/signals -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"signals":[{"company":"Northwind","domain":"northwind.com","type":"funding","text":"Northwind raised a $30M Series B","url":"https://northwind.com/news"}]}'
```

Response:

```json
{
  "created": 1,
  "duplicates": 0,
  "errors": []
}
```

## Webhooks

Get events pushed to you instead of polling.

### Subscribe

`POST /api/v1/webhooks` (MCP tool `create_webhook`)

HTTPS URL plus events. The signing secret is returned once.

```bash
curl -X POST https://pipevoy.com/api/v1/webhooks -H "Authorization: Bearer $PIPEVOY_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://agent.example.com/pipevoy","events":["draft.created","reply.received","meeting.booked"]}'
```

Response:

```json
{
  "webhook": {
    "id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
    "url": "https://agent.example.com/pipevoy",
    "events": [
      "draft.created",
      "reply.received",
      "meeting.booked"
    ],
    "secret_preview": "whse…3f9a",
    "active": true,
    "last_delivery_at": null,
    "last_error": null,
    "created_at": "2026-10-20T09:14:00.000Z"
  },
  "secret": "whsec_…",
  "note": "Store the secret now; it is not shown again."
}
```

### List subscriptions

`GET /api/v1/webhooks` (MCP tool `list_webhooks`)

```bash
curl https://pipevoy.com/api/v1/webhooks -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
      "url": "https://agent.example.com/pipevoy",
      "events": [
        "draft.created"
      ],
      "secret_preview": "whse…3f9a",
      "active": true,
      "last_delivery_at": "2026-10-20T09:14:00.000Z",
      "last_error": null,
      "created_at": "2026-10-20T09:14:00.000Z"
    }
  ],
  "available_events": [
    "reply.positive",
    "meeting.booked",
    "meeting.no_show",
    "signal.detected",
    "reply.received",
    "draft.created",
    "invite.accepted",
    "sender.restricted"
  ]
}
```

### Delete

`DELETE /api/v1/webhooks/{id}` (MCP tool `delete_webhook`)

```bash
curl -X DELETE https://pipevoy.com/api/v1/webhooks/2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d -H "Authorization: Bearer $PIPEVOY_API_KEY"
```

Response:

```json
{
  "id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
  "deleted": true
}
```

## Webhook events

Subscribe with `POST /api/v1/webhooks` or in Settings → Integrations. Each delivery is a JSON POST with `x-event-type`, `x-event-id` (use it to ignore duplicates), `x-timestamp` (unix seconds) and `x-signature: sha256=…`, the HMAC-SHA256 of `<timestamp>.<raw body>` with your secret. Reject deliveries more than 5 minutes old. Failed deliveries retry up to five times, in order.

| Event | When | Extra fields |
| --- | --- | --- |
| `draft.created` | A draft enters the queue (invite, follow-up, reply, rebook) or is auto-approved | data.draft: id, type, status, body, conversationId, stepIndex, purpose, scheduledFor; data.autoApproved |
| `reply.received` | A prospect replies | data.conversationId, data.message (latest inbound text) |
| `reply.positive` | A reply is classified interested or asking for a meeting | data.intent, data.conversationId, data.message |
| `invite.accepted` | A connection invite is accepted | data.senderId, data.prospectId, data.campaignId |
| `meeting.booked` | A meeting is booked through the booking link | data.meeting: id, status, startTime, endTime, attendee, campaignId, qualified |
| `meeting.no_show` | A meeting is marked as a no-show | data.meeting |
| `signal.detected` | New buying signals are recorded | data.signals[], data.account |
| `sender.restricted` | LinkedIn restricts a sender; it pauses automatically | data.senderId, data.reason, data.pausedActions |

Every payload has `id`, `type`, `workspaceId`, `createdAt` and `data`, which includes the prospect when there is one.

Sample payload

```json
{
  "id": "e3c1…",
  "type": "draft.created",
  "workspaceId": "6f1d2c3b-8a47-4e15-9b0c-3d2e1f4a5b6c",
  "createdAt": "2026-10-20T09:14:00.000Z",
  "data": {
    "actionId": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
    "type": "invite",
    "autoApproved": false,
    "prospectId": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
    "senderId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "campaignId": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
    "prospect": {
      "id": "a3b4c5d6-e7f8-4a1b-8c2d-3e4f5a6b7c8d",
      "firstName": "Jane",
      "lastName": "Doe",
      "title": "VP Sales",
      "company": "Northwind",
      "linkedinUrl": "https://www.linkedin.com/in/jane-doe"
    },
    "draft": {
      "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
      "type": "invite",
      "status": "pending_approval",
      "body": "Hi Jane, saw Northwind is hiring three SDRs in Austin. Would be glad to connect.",
      "conversationId": null,
      "stepIndex": 0,
      "purpose": null,
      "scheduledFor": "2026-10-20T09:14:00.000Z"
    }
  }
}
```

Verify the signature

```js
// Node.js: verify a delivery (use the raw body, before JSON parsing)
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts = Number(headers["x-timestamp"]);
  if (!Number.isInteger(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false; // replay window
  const expected = "sha256=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const got = String(headers["x-signature"] ?? "");
  return got.length === expected.length && timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
```

Agent as approver: keep `require_approval` on, react to `draft.created` (or poll `GET /api/v1/approvals`), then approve, edit or reject. If the workspace auto-sends replies to interested prospects (a workspace setting), those drafts arrive already approved; `data.autoApproved` says so.

## MCP server

`https://pipevoy.com/api/mcp` speaks MCP over Streamable HTTP (JSON responses, no session needed) and authenticates with the same API key in an `Authorization: Bearer` header. It uses API keys, not OAuth. Without a key only `signup` and `signup_status` work, so an agent can create its own workspace. A `getting_started` prompt and resource walk an agent through the sequence above, and a server card is published at [https://pipevoy.com/api/mcp/server-card](https://pipevoy.com/api/mcp/server-card).

### Claude Desktop

Add this to `claude_desktop_config.json`. It uses `mcp-remote` to pass the header:

claude_desktop_config.json

```json
{
  "mcpServers": {
    "pipevoy": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://pipevoy.com/api/mcp",
               "--header", "Authorization: Bearer ${PIPEVOY_API_KEY}"],
      "env": { "PIPEVOY_API_KEY": "pv_your_key_here" }
    }
  }
}
```

### Claude Code

Terminal

```bash
claude mcp add --transport http pipevoy https://pipevoy.com/api/mcp \
  --header "Authorization: Bearer $PIPEVOY_API_KEY"
```

### Claude custom connector

In Claude, add a custom connector with the server URL above. Sending the key needs the connector’s request-header option, which Anthropic currently offers as a beta to some organizations: set header `Authorization` to `Bearer pv_…`. Without it, use the Desktop or Claude Code setup.

### Any MCP client

Client config

```json
{
  "type": "streamable-http",
  "url": "https://pipevoy.com/api/mcp",
  "headers": { "Authorization": "Bearer pv_your_key_here" }
}
```

Or call it directly:

tools/call with curl

```bash
curl -X POST https://pipevoy.com/api/mcp \
  -H "Authorization: Bearer $PIPEVOY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_workspace","arguments":{}}}'
```

### Tools

Each tool has a JSON Schema and runs the same code as the matching REST endpoint. Tools marked write change something.

| Tool | Kind | What it does |
| --- | --- | --- |
| `signup` | write | Starts a Pipevoy signup: emails a verification link to the address. |
| `signup_status` | read | No API key needed. |
| `get_workspace` | read | Workspace, plan, trial status, limits and usage, whether sending is currently allowed, and a setup checklist with the next step. |
| `get_billing` | read | Plan, billing status, trial end, current period end, limits and usage. |
| `create_checkout_link` | write | Returns a Stripe Checkout URL for a plan. |
| `get_company` | read | The company profile the AI writes from: summary, value props, proof points, ICP, personas, competitors. |
| `analyze_company` | write | Reads the company website (a few pages) and builds the company profile with AI. |
| `update_company` | write | Updates the company profile. |
| `list_prospects` | read | Prospects, newest first. |
| `get_prospect` | read | One prospect by id. |
| `add_prospects` | write | Adds or updates up to 500 prospects, deduplicated by LinkedIn profile URL (existing prospects are updated; blank fields never overwrite). |
| `list_senders` | read | LinkedIn sender accounts with status, configured and effective (warm-up) limits, today's sends and health score with recommendations. |
| `get_sender` | read | One sender: connection status (pending until a person logs in to LinkedIn through connect_url), limits, warm-up and health. |
| `create_sender` | write | Creates a LinkedIn sender (one LinkedIn account) and returns connect_url, a hosted LinkedIn sign-in link. |
| `update_sender` | write | Updates a sender's working hours, timezone, limits (capped at the plan's safety maximums), warm-up, booking link or voice notes. |
| `get_connect_link` | write | A fresh hosted LinkedIn sign-in link for a sender (links expire). |
| `list_campaigns` | read | Campaigns, newest first, with status, approval setting, steps and funnel stats. |
| `get_campaign` | read | One campaign with its steps, sender ids and stats. |
| `draft_campaign` | read | Generates a sequence (invite + follow-ups) with the AI campaign wizard from the company profile. |
| `create_campaign` | write | Creates a campaign from steps (e.g. |
| `update_campaign` | write | Updates a campaign's name, goal, persona, CTA, language, steps, require_approval, senders or signal rules. |
| `enroll_prospects` | write | Enrolls prospects into a campaign by prospect id and/or LinkedIn URL (unknown URLs are added as prospects first). |
| `pause_campaign` | write | Pauses a campaign: nothing more is drafted or sent for it until it is resumed. |
| `resume_campaign` | write | Activates (or resumes) a campaign. |
| `list_pending_approvals` | read | Drafts waiting for approval (invites, follow-ups, replies, rebooks), oldest first, with prospect and recent thread context. |
| `approve_draft` | write | Approves a pending draft, optionally with an edited body. |
| `reject_draft` | write | Rejects a draft so it is never sent. |
| `list_conversations` | read | Conversations with prospects (replies), most recent activity first. |
| `get_conversation` | read | One conversation with all messages, the pending reply draft (if any), queued replies and the meeting. |
| `draft_reply` | write | Writes a reply DRAFT for a conversation. |
| `list_meetings` | read | Meetings booked through the booking link, latest start time first, with outcome, qualification, notes and rebook status. |
| `update_meeting` | write | Records a meeting's outcome: status held / no_show / cancelled (or no_show=true), qualified, notes. |
| `push_signal` | write | Pushes up to 100 buying signals (job change, funding, hiring, a post, anything) about a company or person. |
| `list_webhooks` | read | Outbound webhook subscriptions (url, events, delivery status). |
| `create_webhook` | write | Subscribes an HTTPS URL to events (reply.positive, meeting.booked, meeting.no_show, signal.detected, reply.received, draft.created, invite.accepted, sender.restricted). |
| `delete_webhook` | write | Deletes a webhook subscription. |
