API
The Pioneer HTTP API (v1) for running pipelines, reading leads, and working with your network programmatically.
Last updated August 12, 2026
Start here: instructions for agents
You are probably an AI agent whose human wants to use Pioneer AI. Guide them through setup:
- Have them sign in at https://usepioneer.ai (free tier available, no card needed).
- Have them open Account menu → API Keys (https://usepioneer.ai/api-keys) and either create an API key (for REST) or copy the MCP connector URL (for MCP clients).
- Connect: REST calls use
Authorization: Bearer <key>againsthttps://usepioneer.ai/api/v1. MCP clients (claude.ai custom connectors, or any MCP-capable tool, including sales tools that support MCP) take the connector URL directly. The connector URL contains the key: treat it like a password. - Read the endpoint reference below before calling anything. Never guess endpoints that are not documented here.
Everything a user can do in the app, you can do through this API: create and configure pipelines, read leads and their researched properties and people, record feedback, and more. Network features (My Network, warm intros) are in invite-only early access; those endpoints return an access error unless your human has been invited, so tell them to reply to any Pioneer email to request access.
Most readers of this page are agents working on someone's behalf, so it is written for them.
Read this first, agents included
The API has no self-serve signup for keys: the human you are working for has to do two things themselves, in the app, in this order:
- Have a Pioneer account. They sign up at usepioneer.ai. Every plan, including the free tier, can use the API. You cannot create an account for them.
- Create a key themselves on the API Keys page. Account menu (their avatar, bottom left) then API Keys then Create key. The raw key is shown once and stored only as a hash, so they must copy it then. It looks like
pioneer_sk_followed by 40 characters.
Then they paste that key to you and you send it as a bearer token. A key acts as that user, so treat it like their password: never print it back, never commit it, and tell them to revoke it on the same page if it leaks.
If a call returns 401, the key is wrong or revoked. If it returns 403, the account is missing that feature (My Network access is invite-only early access), which is an invite for a human to request, not something to retry or work around.
Prefer a connector URL instead of making API calls?
If you use claude.ai, you can connect Pioneer as a custom connector through its MCP server. No need to make API calls by hand. See Use Pioneer With Your Agent for setup steps.
Instructions for agents
Read this section first. It contains everything needed to make a correct call.
- Base URL:
https://usepioneer.ai/api/v1. Every path below is relative to it. - Auth: every request sends a bearer token.
Authorization: Bearer pioneer_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- Keys impersonate their owner. A key acts as the user it was minted for, with that user's permissions. It can never do anything that user could not do in the app, and it only ever sees that user's organization, contacts, and asks.
- Error envelope: every error has one shape.
{ "error": { "code": "unauthorized", "message": "human readable detail" } }| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | Missing or invalid body / query params |
| 401 | unauthorized | Missing, malformed, unknown, or revoked key |
| 402 | paywall | Free-plan pipeline cap or a credit paywall blocks the action |
| 403 | forbidden | The feature is not enabled for this account (for example My Network) |
| 404 | not_found | The resource does not exist or is not visible to the key's user |
| 500 | internal | Unexpected server error |
- IDs are UUIDs unless stated otherwise. A malformed id returns
400; an id you cannot see returns404. Never guess an id: get it from a list endpoint. - Pagination is cursor based where it exists (
GET /pipelines/{pipelineId}/leads): passlimit(default 50, max 200) and feednextCursorback ascursor.nextCursorisnullon the last page. - Async endpoints answer
202and do the work in the background. Poll the matching read endpoint instead of assuming completion, and poll no faster than every 2 seconds. - Batch your writes, respect
429and itsRetry-Afterheader, and back off rather than retrying in a tight loop. - Only the endpoints on this page exist for you. Do not guess other routes, parameters, or fields. If something you need is not here, it is not part of the public API.
- This is v1 and still evolving. Endpoints and fields may be added or changed. Re-read this page rather than relying on a cached copy.
Helping your user get value out of Pioneer
The API mirrors the product, so a good result depends on the same things a good result in the app depends on. Two things matter more than call volume:
- A pipeline is one question, asked well. The
goalyou pass toPOST /pipelinesbecomes the criteria Pioneer researches against. "Series A climate hardware founders in Europe" works; "leads" does not. One goal per pipeline: criteria that describe two targets qualify neither. See Your Pipeline & Board and Criteria. - Judgment is the feedback loop. Approving, rejecting, and moving leads through
PATCH /leads/{entryId}is what teaches the pipeline, exactly as it does on the board. Usemark-inaccuratefor wrong researched values andsearch-morefor missing ones instead of silently working around bad data.
Each newly discovered lead costs one credit; reading, enriching, and re-qualifying leads you already have is free. Credits & Billing has the details, and the rest of this Help Center is the product explanation to fall back on when your user asks what something means.
Working out of an agent harness such as Claude Code, Codex, Devin, or Hermes? Use Pioneer With Your Agent covers the setup, including the skill you should offer to save so this does not have to be re-explained every session.
Identity
GET /me
Who the key belongs to, plus the plan limits that decide whether writes will be paywalled.
curl -s https://usepioneer.ai/api/v1/me \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"user": { "id": "uuid", "email": "user@company.com", "name": "Jane Doe" },
"org": { "id": "uuid", "name": "Acme", "slug": "acme", "url": "https://usepioneer.ai/organizations/acme" },
"limits": {
"pipelineCount": 2,
"pipelineLimit": 3,
"isSubscriber": false,
"balanceCredits": 1200,
"isPaywalled": false
}
}Pipelines
POST /pipelines
Create a pipeline from a free-text goal. Same as the create flow in the app: criteria generation and lead discovery start automatically.
goal(string, required): what to find, in plain language.title(string, optional): defaults to an auto-generated title.
curl -s -X POST https://usepioneer.ai/api/v1/pipelines \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"goal": "Series A climate hardware founders in Europe", "title": "EU climate founders"}'{ "id": "pipeline-uuid", "url": "https://usepioneer.ai/pipelines/pipeline-uuid" }Returns 201, or 402 when the free-plan pipeline limit is reached.
GET /pipelines
List the organization's pipelines, newest first. Archived pipelines are excluded unless you pass ?includeArchived=true; each row carries isArchived.
curl -s "https://usepioneer.ai/api/v1/pipelines?includeArchived=true" \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"pipelines": [
{
"id": "uuid",
"title": "EU climate founders",
"emoji": "🌍",
"status": "Ready",
"isArchived": false,
"entryCount": 42,
"createdAt": "2026-07-20T12:00:00.000Z",
"url": "https://usepioneer.ai/pipelines/uuid"
}
]
}GET /pipelines/{pipelineId}
Full pipeline detail, including description, structured criteria, and properties.
curl -s https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"PATCH /pipelines/{pipelineId}
Edit pipeline metadata. Body accepts any of title, emoji, description (at least one required); name works as an alias for title. Strings are trimmed and an empty title is rejected with 400. Responds with the same shape as GET /pipelines/{pipelineId}.
curl -s -X PATCH https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "EU climate founders", "emoji": "🌍", "description": "Series A hardware"}'POST /pipelines/{pipelineId}/archive and /unarchive
Archive a pipeline (which frees a free-plan pipeline slot) or restore it. Same as the "Archive Pipeline" action in the app.
curl -s -X POST https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/archive \
-H "Authorization: Bearer $PIONEER_API_KEY"
curl -s -X POST https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/unarchive \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "id": "uuid", "isArchived": true, "url": "https://usepioneer.ai/pipelines/uuid" }POST /pipelines/{pipelineId}/find-leads
Ask for another batch of leads. Optional body { "leadCount": 20 } (1 to 100, default 10). Discovery runs in the background, so the call answers 202; read results with GET /pipelines/{pipelineId}/leads.
curl -s -X POST https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/find-leads \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"leadCount": 20}'{ "ok": true }Each newly discovered lead is billed like any other, so this is the call that can return 402 paywall. Use it to ask for more leads programmatically: same as the more leads button on the board, but without needing to be in the UI.
GET /pipelines/{pipelineId}/leads
Paginated leads for a pipeline.
Query params:
limit(default 50, max 200)cursor(opaque; thenextCursorfrom the previous page)status(optional; filter by entry status)
curl -s "https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/leads?limit=50" \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"leads": [
{
"id": "entry-uuid",
"name": "Acme Corp",
"url": "https://acme.com",
"status": "analyzed",
"bucketId": "uuid",
"score": 0.92,
"tldr": "…",
"propertyValues": { },
"appUrl": "https://usepioneer.ai/pipelines/PIPELINE_ID?entry=entry-uuid"
}
],
"nextCursor": "50"
}Leads
GET /leads/{entryId}
Everything about one lead: status, score, property values, latest update, artifacts, and the associated people with their contact details.
curl -s https://usepioneer.ai/api/v1/leads/$ENTRY_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"Email deliverability: draft only to a verified address
Every email contact carries a verification block, and the lead carries an outreach block that says outright who may be written to.
verification.status is one of:
verified: the mailbox was confirmed. The only status withsafeToSend: true.catch_all: the domain accepts every address, so this specific mailbox cannot be confirmed. Deliverability is unproven.invalid: the mailbox does not exist. Mail to it hard-bounces.unverified: no check has produced a verdict (never checked, monthly cap hit, or no API key configured).
The outreach block:
{
"outreach": {
"provider": "millionverifier",
"draftable": false,
"verifiedRecipients": [],
"blockedRecipients": [
{
"personId": "person-uuid",
"name": "Spencer Parikh",
"email": "spencer.parikh@devcommx.com",
"status": "unverified",
"message": "NOT verified. Treat this address as a guess, not a fact."
}
],
"guidance": "No verified-deliverable email exists on this lead."
}
}Contract for anything that drafts outreach:
- Address a draft only to an address with
safeToSend: true, in practice the person'sdraftableEmail, which isnullunless an address passed verification. - When
outreach.draftableisfalse, create the draft with the recipient blank, flag it as needing a verified email, and report which addresses failed verification and why. - Use
POST /leads/{entryId}/search-morewith{"personId": "...", "field": "email"}to chase a real address for a lead that has none.
PATCH /leads/{entryId}
Act on a lead the way the board does. Body needs at least one field:
status: one ofapproved,rejected,done,recommended. Anything else returns400listing the allowed set.archived: boolean.bucketId: a bucket UUID, ornullto send the lead back to the leftmost stage (the Relevant stage, unless it has been renamed). SettingbucketIddoes not changestatus, and the bucket must belong to the lead's own pipeline (otherwise400). Get bucket ids fromGET /pipelines/{pipelineId}/board.
recommendedis the API's name for a lead that is relevant (every MUST criterion met). The board shows this tier as "Relevant".
Board semantics: status: "rejected" also archives the lead unless you pass archived: false explicitly. Other statuses leave the archive state alone unless archived is given. A status-change message is recorded on the lead's timeline, exactly as in the app. The response is the same shape as GET /leads/{entryId}.
curl -s -X PATCH https://usepioneer.ai/api/v1/leads/$ENTRY_ID \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "rejected"}'
# Move the lead into a custom bucket (status unchanged)
curl -s -X PATCH https://usepioneer.ai/api/v1/leads/$ENTRY_ID \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bucketId": "bucket-uuid"}'
# Return the lead to the leftmost stage (Relevant)
curl -s -X PATCH https://usepioneer.ai/api/v1/leads/$ENTRY_ID \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bucketId": null}'GET /leads/{entryId}/notes
The user's notes on a lead, newest first, grouped by note key. Notes are how outreach state lives on a lead: a key in versioned mode holds a current draft (older versions hidden but kept), a key in log mode is a running record of actions taken ("sent intro email", "scheduled a meeting"). Keys are the user's own; read them to learn which ones this lead uses.
Query params:
includeHistory(boolean, optional): include superseded versions of versioned notes.
curl -s https://usepioneer.ai/api/v1/leads/$ENTRY_ID/notes \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"notes": [
{
"key": "email-draft",
"mode": "versioned",
"label": "Draft email",
"current": {
"legoId": "uuid",
"key": "email-draft",
"mode": "versioned",
"label": "Draft email",
"text": "Hi team…",
"createdAt": "2026-08-11T12:00:00Z",
"author": {
"userId": "uuid",
"email": "user@company.com",
"name": "Jane Doe",
"avatarUrl": "https://…"
},
"channel": "api",
"isCurrent": true
},
"history": []
}
]
}POST /leads/{entryId}/notes
Write a note on a lead. key groups related notes and is entirely the caller's choice. A new key defaults to mode "log" (each note appends); pass "versioned" when a newer note should replace the older one. Once a key exists its mode is fixed, and passing a conflicting mode is an error.
Body fields:
key(string, required): note key or name. A free-text key like"Draft email"is slugified the same way the UI does, so API and UI notes with the same name land on the same key. Use"updates"for the unnamed log that the Notes Bar writes to by default.text(string, required): the note body, as markdown.mode("log"|"versioned", optional): only used on a new key. Omit to mirror the UI's convention, where"updates"is a log and any other key is versioned.label(string, optional): override the displayed label for this key.
curl -s -X POST https://usepioneer.ai/api/v1/leads/$ENTRY_ID/notes \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "email-draft", "text": "Hi Dan, I saw your talk at…", "mode": "versioned"}'Responds 201 with the note that was written, in the same shape as current above.
Notes are written straight to the lead, with no workflow run, no artifact, and no credit cost, so a note lands as fast as typing. They are not research content (see legos below) and the API exposes no edit or delete: update a note by writing a new versioned note under the same key, or let the log stand as the record.
GET /leads/{entryId}/legos
The lead's research content as atomic content blocks, ordered by path. Each lego is a typed, self-contained block (a TL;DR, a deep dive topic, a criterion narrative) assembled into views by namespace.
Query params:
prefix(string, optional): narrow to one namespace (summary,deepdive,notes,properties). Embeddings are never returned.
curl -s "https://usepioneer.ai/api/v1/leads/$ENTRY_ID/legos?prefix=summary" \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"legos": [
{
"id": "uuid",
"path": "summary.tldr",
"name": "TL;DR",
"content": { },
"source": "aggregate-decision-step",
"metadata": { },
"createdAt": "2026-08-11T12:00:00Z",
"updatedAt": "2026-08-11T12:00:00Z"
}
]
}Legos are the building blocks of everything Pioneer writes about a lead: the summary, deep dives, criterion evidence, and property display. They exist independently of one another, so re-enriching a property does not touch the summary. Most agents should read notes (above) for outreach state and GET /leads/{entryId} for the verdict; legos are granular research content for advanced use.
POST /leads/{entryId}/mark-inaccurate
Flag a researched value as wrong. The value is cleared, recorded as rejected so re-research never restores it, and a targeted re-enrich is queued to find a correct replacement. Same as "mark inaccurate" in the app.
Send one of two body shapes:
- Scalar property:
{ "propertyId": "<uuid>" }, using an id from the lead'spropertyValuesmap. Person-type properties are rejected here. - Person field:
{ "personId": "<uuid>", "field": "<field>" }, wherefieldis one ofavatar,email,phone,linkedin,x,about,news. The person must be associated with the lead.
# Reject a wrong email on a person, then re-research it
curl -s -X POST https://usepioneer.ai/api/v1/leads/$ENTRY_ID/mark-inaccurate \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"personId": "person-uuid", "field": "email"}'
# Reject a wrong scalar property value
curl -s -X POST https://usepioneer.ai/api/v1/leads/$ENTRY_ID/mark-inaccurate \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"propertyId": "property-uuid"}'{ "ok": true, "entryId": "entry-uuid", "status": "re_enrich_queued" }400 invalid_request covers a bad body, a property that has no value, a person-type property passed as propertyId, and a lead that is already being enriched (retry once it settles).
POST /leads/{entryId}/search-more
Ask for another research pass on a field that came back not found. Nothing is cleared: the research agent is simply biased to spend its budget on this field first, while still picking up other missing fields. Same as "search harder" in the app. Use this for empty fields, and mark-inaccurate for wrong ones.
Body shapes are the same two as mark-inaccurate, except the scalar property must currently have no value.
# Search harder for a person's missing LinkedIn
curl -s -X POST https://usepioneer.ai/api/v1/leads/$ENTRY_ID/search-more \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"personId": "person-uuid", "field": "linkedin"}'{ "ok": true, "entryId": "entry-uuid", "status": "re_enrich_queued" }Board organization
Read and reorganize a pipeline's board: its columns, custom buckets, and the Done column. To move leads between columns, use status and bucketId on PATCH /leads/{entryId}.
GET /pipelines/{pipelineId}/board
The whole board in render order, which is the map an agent needs before it moves anything. Returns the built-in Leads (recommended and not_recommended) and Rejected (archived) columns, plus custom buckets (the first of which is the seeded Relevant stage) and the Done column interleaved by sort order. Approved leads that carry no bucketId yet are counted in the leftmost stage, exactly as the board shows them.
done.titleis the custom Done-column title, falling back to"Done", so a renamed column is self-describing.- Counts exclude archived entries, except for the Rejected column, which counts them. They match what the board shows in the app.
curl -s https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/board \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"columns": [
{ "type": "leads", "title": "Leads", "statuses": ["recommended", "not_recommended"], "count": 12 },
{ "type": "bucket", "id": "relevant-uuid", "title": "Relevant", "description": null, "sortOrder": 0, "count": 17 },
{ "type": "bucket", "id": "bucket-uuid", "title": "Contacted", "description": "…", "sortOrder": 1, "count": 5 },
{ "type": "done", "title": "Won", "description": "…", "sortOrder": 2, "statuses": ["done"], "count": 10 },
{ "type": "rejected", "title": "Rejected", "count": 8 }
]
}POST /pipelines/{pipelineId}/buckets
Create a custom stage. Body: title (required, non-empty, trimmed) and optional description.
curl -s -X POST https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/buckets \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Contacted", "description": "Move a lead here after first outreach."}'{ "id": "bucket-uuid", "title": "Contacted", "description": "Move a lead here after first outreach.", "sortOrder": 0 }PATCH /buckets/{bucketId}
Rename or re-describe a bucket. Body needs at least one of title (non-empty, trimmed) or description (string, or null to clear).
curl -s -X PATCH https://usepioneer.ai/api/v1/buckets/$BUCKET_ID \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Replied", "description": "Move a lead here once they reply."}'DELETE /buckets/{bucketId}
Delete a bucket. Its leads fall back to the leftmost stage with bucketId cleared; no lead is ever deleted by this.
curl -s -X DELETE https://usepioneer.ai/api/v1/buckets/$BUCKET_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "ok": true }PATCH /pipelines/{pipelineId}/done-column
Rename or re-describe the Done column. Body needs at least one of title (non-empty, trimmed) or description (string, or null). title falls back to "Done" when it was never set.
curl -s -X PATCH https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/done-column \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Won"}'{ "title": "Won", "description": null }PATCH /pipelines/{pipelineId}/board-order
Set the left-to-right order of the movable columns, which are the custom buckets and the Done column. Body { "orderedColumnIds": string[] }, where each id is a bucket UUID and the literal "__done__" stands for the Done column. The built-in Leads and Rejected columns are fixed and are not included.
curl -s -X PATCH https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/board-order \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"orderedColumnIds": ["bucket-uuid", "__done__", "bucket-uuid-2"]}'Returns the new board, same shape as GET /pipelines/{pipelineId}/board.
Sharing
Read-only, revocable guest access to a pipeline. A share is addressed either to an email (Pioneer emails that person their personal link) or to a name (a link you distribute yourself, for people whose email you do not have). Guests never become Pioneer users and can only read.
GET /pipelines/{pipelineId}/shares
List active shares, newest first.
curl -s https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/shares \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"shares": [
{
"id": "share-uuid",
"accessMode": "link",
"recipientEmail": null,
"name": "Anna from Breakthrough",
"url": "https://usepioneer.ai/share/8Kq2...",
"createdAt": "2026-07-30T10:00:00.000Z",
"lastViewedAt": null
}
]
}url is present for link shares only: an email share's token is the credential of exactly one address and is never returned.
POST /pipelines/{pipelineId}/shares
Share the pipeline with one person. Body { "recipient": string, "message"?: string }.
recipient(required): an email address, or any other text, which becomes the link's name.message(optional, up to 500 characters): included in the invite email, ignored for named links.
# Email invite
curl -s -X POST https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/shares \
-H "Authorization: Bearer $PIONEER_API_KEY" -H "Content-Type: application/json" \
-d '{"recipient": "guest@company.com", "message": "Here is the pipeline we discussed."}'
# Named personal link
curl -s -X POST https://usepioneer.ai/api/v1/pipelines/$PIPELINE_ID/shares \
-H "Authorization: Bearer $PIONEER_API_KEY" -H "Content-Type: application/json" \
-d '{"recipient": "Anna from Breakthrough"}'Returns 201, and it is idempotent: the same email resends the invite and the same link name (case-insensitive) returns the existing link, so neither creates a second share. An email share carries "emailed": false when the invite failed to send, so you can retry. A half-typed address such as guest@company is rejected with 400 rather than silently becoming a link name.
DELETE /shares/{shareId}
Revoke a share. The link stops working on the guest's next request. An unknown or already-revoked share returns 404.
curl -s -X DELETE https://usepioneer.ai/api/v1/shares/$SHARE_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "id": "share-uuid", "revoked": true }My Network
Programmatic access to My Network: the contacts you ingest, the research Pioneer runs on them, the questions you ask of your network, and your calendar source.
Two rules apply to every endpoint in this section:
- My Network must be enabled for the account. Otherwise every call returns
403 forbiddenwith the messageNetwork access is not enabled for this account. - Network data is per user, never per organization. A key only sees and changes its own user's contacts, asks, and preferences. There is no aggregate or cross-user view.
GET /network/contacts
List contacts with a status summary computed over the full set. Optional filters narrow contacts, never the summary:
status: one ofnone,queued,running,done,failed, or the virtualstalled(arunningcontact untouched past the stuck threshold; aqueuedone is waiting behind the concurrency limit, not stuck).source:manualfor pasted profile URLs, orsyncedfor contacts from Google Calendar.
curl -s "https://usepioneer.ai/api/v1/network/contacts?status=stalled&source=synced" \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"contacts": [
{
"id": "contact-uuid",
"email": "jane@acme.com",
"display_name": "Jane Doe",
"source": "google_calendar",
"linkedin_url": "https://linkedin.com/in/janedoe",
"company": "Acme",
"role": "VP Eng",
"research_status": "done",
"researched_at": "2026-07-30T12:00:00.000Z"
}
],
"summary": {
"total": 42,
"byStatus": { "none": 3, "queued": 5, "running": 1, "done": 30, "failed": 3 },
"stalled": 2
}
}GET /network/contacts/{contactId}
One contact by id.
curl -s https://usepioneer.ai/api/v1/network/contacts/$CONTACT_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "contact": { "id": "contact-uuid", "display_name": "Jane Doe", "research_status": "done" } }POST /network/contacts
Add contacts from pasted LinkedIn or X profile URLs. input is either a raw blob or newline list (string) or an array of lines. Extraction tolerates mixed text; rows are deduped both within the batch and against existing contacts, then queued for research. Returns 202.
curl -s -X POST https://usepioneer.ai/api/v1/network/contacts \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": ["https://linkedin.com/in/janedoe", "https://x.com/johnroe"]}'{ "added": 2, "duplicates": 0, "invalid": [] }DELETE /network/contacts/{contactId}
Remove one contact. Returns 204 with no body.
curl -s -X DELETE https://usepioneer.ai/api/v1/network/contacts/$CONTACT_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"POST /network/contacts/{contactId}/retry-research
Re-queue research for one contact. The contact needs a LinkedIn URL to research, otherwise 400 invalid_request. Returns 202.
curl -s -X POST https://usepioneer.ai/api/v1/network/contacts/$CONTACT_ID/retry-research \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "ok": true, "contactId": "contact-uuid" }POST /network/sweep
Recovery for a network that has drifted: backfills avatars for faceless contacts and re-queues every contact that was never researched, failed, stalled, or went stale. Returns 202.
forcedefaults totrue, which also re-researches contacts that already have a full profile. On a large network that is a second full research pass, so passforce=falseto pick up only dropped work.
curl -s -X POST "https://usepioneer.ai/api/v1/network/sweep?force=false" \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "ok": true }POST /network/asks
Ask a one-shot question of your own network. question is 1 to 500 characters. The work runs in the background, so the call returns 202 with the run's id; poll it for progress. One run at a time per user: asking while another is still running returns 400.
curl -s -X POST https://usepioneer.ai/api/v1/network/asks \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "Who in my network knows the founders at Acme?"}'{ "id": "ask-uuid" }GET /network/asks
List your asks, newest first, each with its matches. Optional ?limit= caps the count.
curl -s "https://usepioneer.ai/api/v1/network/asks?limit=20" \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "asks": [ { "id": "ask-uuid", "question": "Who knows Acme?", "status": "done", "matches": [] } ] }GET /network/asks/{id}
One ask with its matches and progress fields. Poll until status leaves running.
curl -s https://usepioneer.ai/api/v1/network/asks/$ASK_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "ask": { "id": "ask-uuid", "status": "running", "stage": "triage", "matches": [] } }POST /network/asks/{id}/retry
Re-run a previous ask as a new run, since history is append-only. Returns 202 with the new run's id.
curl -s -X POST https://usepioneer.ai/api/v1/network/asks/$ASK_ID/retry \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "id": "new-ask-uuid" }POST /network/asks/{id}/cancel
Stop a running ask and free the one-run slot. Idempotent: a no-op on a finished run.
curl -s -X POST https://usepioneer.ai/api/v1/network/asks/$ASK_ID/cancel \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "ok": true }DELETE /network/asks/{id}
Delete an ask from history, cancelling it first if it is still running. Returns 204 with no body.
curl -s -X DELETE https://usepioneer.ai/api/v1/network/asks/$ASK_ID \
-H "Authorization: Bearer $PIONEER_API_KEY"GET /network/preferences
Your network preferences. A never-toggled account reads false.
curl -s https://usepioneer.ai/api/v1/network/preferences \
-H "Authorization: Bearer $PIONEER_API_KEY"{ "connect_to_leads": false }PATCH /network/preferences
Update connect_to_leads, which must be a boolean. Returns the full updated preferences.
curl -s -X PATCH https://usepioneer.ai/api/v1/network/preferences \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"connect_to_leads": true}'{ "connect_to_leads": true }GET /network/calendar
The Google Calendar source: connection state and sync status in one call. connection.status is connected or needs_consent, and source is null until the first sync.
curl -s https://usepioneer.ai/api/v1/network/calendar \
-H "Authorization: Bearer $PIONEER_API_KEY"{
"connection": { "status": "connected" },
"source": { "sync_status": "idle", "last_synced_at": "2026-07-30T12:00:00.000Z" }
}POST /network/calendar/sync
Trigger a calendar sync. Optional { "full": true } forces a full re-sync instead of the incremental default. Returns 202, or 400 invalid_request when the calendar is not connected.
curl -s -X POST https://usepioneer.ai/api/v1/network/calendar/sync \
-H "Authorization: Bearer $PIONEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"full": true}'MCP (claude.ai and other MCP-native assistants)
The same API is available through a remote Model Context Protocol server, so assistants like claude.ai can call Pioneer as a tool without manual HTTP setup. Use Pioneer With Your Agent covers when to pick MCP over the API.
Connector URL template:
https://usepioneer.ai/api/mcp/YOUR_API_KEY_HERE/mcpReplace YOUR_API_KEY_HERE with the key from API Keys in your account menu. The key is in the path because claude.ai has no field for one, so treat the whole URL like a password.
Design: the MCP server is a protocol adapter only. Every tool forwards to exactly one /api/v1 endpoint, so behavior, gating, and error messages have a single definition. Nothing may be added to the MCP server that the HTTP API cannot already do: if a capability is missing, add it to the API first.
Tools map: the MCP server mirrors the full v1 API surface. Below is the complete tool map:
| MCP tool | Corresponding API route |
|---|---|
whoami | GET /me |
list_pipelines | GET /pipelines |
create_pipeline | POST /pipelines |
get_pipeline | GET /pipelines/{pipelineId} |
archive_pipeline | POST /pipelines/{pipelineId}/archive |
update_pipeline | PATCH /pipelines/{pipelineId} |
get_pipeline_board | GET /pipelines/{pipelineId}/board |
list_leads | GET /pipelines/{pipelineId}/leads |
get_lead | GET /leads/{entryId} |
move_lead | PATCH /leads/{entryId} |
mark_inaccurate | POST /leads/{entryId}/mark-inaccurate |
search_more_leads | POST /leads/{entryId}/search-more |
get_lead_legos | GET /leads/{entryId}/legos |
list_lead_notes | GET /leads/{entryId}/notes |
add_lead_note | POST /leads/{entryId}/notes |
find_leads | POST /pipelines/{pipelineId}/find-leads |
import_leads | POST /pipelines/{pipelineId}/import |
list_properties | GET /pipelines/{pipelineId}/properties |
create_property | POST /pipelines/{pipelineId}/properties |
update_property | PATCH /pipelines/{pipelineId}/properties/{propertyId} |
delete_property | DELETE /pipelines/{pipelineId}/properties/{propertyId} |
list_criteria | GET /pipelines/{pipelineId}/criteria |
update_criteria | PATCH /pipelines/{pipelineId}/criteria |
create_bucket | POST /pipelines/{pipelineId}/buckets |
update_bucket | PATCH /buckets/{bucketId} |
delete_bucket | DELETE /buckets/{bucketId} |
update_done_column | PATCH /pipelines/{pipelineId}/done-column |
update_board_order | PATCH /pipelines/{pipelineId}/board-order |
list_shares | GET /pipelines/{pipelineId}/shares |
create_share | POST /pipelines/{pipelineId}/shares |
delete_share | DELETE /shares/{shareId} |
list_network_contacts | GET /network/contacts (needs the network grant) |
find_warm_paths | GET / POST /leads/{entryId}/warm-paths (needs the network grant) |
send_feedback | POST /feedback |
OAuth is not available yet. Until then, use the key-in-path URL above.
Debug a connector with the MCP inspector:
npx @modelcontextprotocol/inspector
# transport: Streamable HTTP, URL: <your connector URL>Use Pioneer With Your Agent
Run Pioneer from Claude Code, Codex, Devin, Hermes, or any custom agent that can call an HTTP endpoint, and the skill-first setup that makes it work well.
Troubleshooting & FAQ
Quick answers to the most common questions about stuck leads, unexpected results, missing data, and more.