SDR Autopilot Public API
Authenticate with X-API-Key or Authorization: Bearer. Keys are project-scoped (create them under API & agents in the app — requires a CURRENT ACTIVE paid Founder/Growth plan). daily_budget_cents is an internal spend safety budget in cents; retail capacity is billed by new prospects reached (Founder/Growth included + optional top-ups). Identity, inbox, hot-leads, reply, and campaign-start return 402 with code: "paid_required" when the project lacks CURRENT ACTIVE paid entitlement (Free, past_due, or canceled). Historical identities may remain readable in the authenticated app UI.
For AI agents — load these first: /llms.txt, /llms-full.txt, /openapi.json. Hosted MCP (Streamable HTTP): /mcp. Product overview: homepage.
GET /api/v1/projects
List projects for this API key
API keys are project-scoped. Returns the single project the key belongs to (id, name, domain, daily_budget_cents internal spend safety budget, autopilot). Creating keys requires a CURRENT ACTIVE paid Founder/Growth plan.
GET /api/v1/projects/{project_id}/analytics
Project analytics
Totals for the period plus a per-campaign breakdown (same stats as the dashboard). Totals include history from deleted campaigns; the campaigns list does not, so its rows can sum to less than the totals. unattributed_spend_cents (bounded periods) is spend_cents minus the listed campaigns' spend_cents, never below 0: spend not attributed to a listed campaign: project-level spend with no campaign id (e.g. research before a campaign exists), and spend of deleted or archived campaigns.
GET /api/v1/projects/{project_id}/budget
Get project daily budget
Internal daily spend safety budget in cents (estimated vendor-action ledger). 0 pauses further vendor spend for the day. This is not retail subscription pricing — retail capacity is billed by new prospects reached (Founder/Growth included + optional top-ups).
PATCH /api/v1/projects/{project_id}/budget
Set project daily budget
Integer cents, 0–1000000. Sets the internal daily spend safety budget (not prospect capacity). 0 pauses vendor spend once the daily cap is hit (immediately if already spent).
GET /api/v1/projects/{project_id}/autopilot
Get autopilot setting
Autopilot here is the project flag that lets the auto-scaler pause/scale campaigns. Start/stop and budget edits still work while it is ON (unlike Explee).
PATCH /api/v1/projects/{project_id}/autopilot
Set autopilot
Turns the project autopilot / auto-scaler flag on or off. autopilot_enabled must be a JSON boolean. Cost warning: launching a never-started campaign, retrying a failed one, or resuming one paused at the paid gate runs research and lead search (spend) and can start real sending. By default ON only resumes campaigns paused for a normal reason (for example stopped by you). Never-started campaigns and campaigns paused at the paid gate (substatus paid_required or search_pending) are skipped and counted in skipped_not_started unless include_not_started is true; failed campaigns and paused campaigns that still have a failed stage (stopped after failing) are skipped and counted in skipped_failed unless retry_failed is true. include_not_started and retry_failed must be JSON booleans when present (400 otherwise). ON requires current paid send entitlement (402 paid_required on Free / past_due); OFF is always allowed and pauses running/starting campaigns only (never-started ones stay untouched). started / resumed / retried / paused list the campaign ids this call actually changed. autopilot_enabled is derived from live statuses (true only when every non-archived campaign is running or starting), so it can stay false after ON when campaigns were skipped. On a project with no active campaigns, ON records nothing and autopilot_enabled stays as stored; pass include_not_started=true to record ON intent. The dashboard switcher is unchanged: it launches never-started and retries failed campaigns too.
GET /api/v1/campaigns
List campaigns
Every campaign in the key's project with status and stats for a short 'how each is doing' summary. Deleted campaigns are not listed.
POST /api/v1/campaigns
Create a campaign (not started)
Creates a campaign in the key's project the way Auto-create does in the app: status not_started. Nothing is started, queued or sent and the autopilot flag is not changed; call POST /api/v1/campaigns/{campaign_id}/start afterwards. Only name is required; the name is stored exactly as given (trimmed), never replaced by an AI-generated one. A 503 `ai_unavailable` means nothing was created; retry later. Omitted offer / audience fields are generated by AI from the name and the project's domain, as in the app; provided fields override the generated ones field by field, and null equals omitted. When both offer.what_you_offer and audience.target_role are given no AI generation runs and the other omitted fields stay empty. A blank what_you_offer or target_role counts as omitted (they are never stored empty); for every other field an empty string or empty list is stored as given and clears that field. Text fields are at most 2000 characters; each list is at most 50 strings of at most 200 characters (target_geography items are also split on commas and must give at most 50 entries); strings must not contain NUL (null character) or invalid Unicode; unknown fields (top level, offer or audience) return 400. Duplicate names are allowed: each call creates a new campaign. A new not_started campaign makes autopilot_enabled read false until it is started (same as the app). Requires current paid send entitlement (Free / past_due / canceled → 402 paid_required). No CSV import over the API.
GET /api/v1/campaigns/{campaign_id}
Get campaign definition
Offer, audience, budget, status. Read-only — edit targeting in the app.
GET /api/v1/campaigns/{campaign_id}/leads
List campaign leads
People or companies enrolled in the campaign. Supports both cursor-based and page-based pagination, search (q), country filter, and status filter. Requires CURRENT ACTIVE paid Founder/Growth; Free / past_due / canceled return 402 paid_required.
GET /api/v1/campaigns/{campaign_id}/analytics
Campaign analytics
Period-filtered send/reply/hot-lead totals. reply_rate_pct = leads_replied / leads_contacted (max 100) (our own replies never dilute it). total_replies = all inbound messages except bounces; automated_replies = the out-of-office / auto-reply part of total_replies; leads_replied = distinct contacted leads with a human reply (out-of-office, auto-replies, bounces and email-changed notices do not count), so total_replies can exceed leads_replied. spend_cents is returned for today, 7d and 30d, never for all.
PATCH /api/v1/campaigns/{campaign_id}/budget
Set campaign daily budget
Per-campaign internal daily spend safety budget in cents (not retail prospect capacity). Works with autopilot ON.
POST /api/v1/campaigns/{campaign_id}/start
Start or resume a campaign
not_started → launches the pipeline; paused → resumes sending; failed → retries failed stages; already running → idempotent accepted. Requires current paid send entitlement (Free / past_due → 402 paid_required). Always available re: autopilot (no 409 for autopilot). Returns the campaign's resulting status and substatus. When the campaign already has leads with an email and every current lead was already contacted by another campaign of this project, the start still happens and the result adds warning "nothing_to_send" (informational: new leads may still be sourced; omitted otherwise).
POST /api/v1/campaigns/{campaign_id}/stop
Pause a campaign
Stops new sends; inbound replies are still watched. Idempotent. Returns the campaign's resulting status and substatus: paused with paused_manual, or the existing pause reason if it was already paused.
POST /api/v1/campaigns/{campaign_id}/delete
Delete a campaign
Soft-deletes the campaign: it disappears from every list, sending stops, managed sending is released, and queued first-touch emails are cancelled (their prospects are skipped permanently). Replies to existing conversations stay reachable through the inbox endpoints. Idempotent: deleting a deleted campaign returns 200. If managed sending cannot be stopped nothing is deleted and the call returns 503 so you can retry. Use POST /restore to undo; cancelled emails are not restored.
POST /api/v1/campaigns/{campaign_id}/restore
Restore a deleted campaign
Brings a deleted campaign back, paused (use start to resume). Cancelled queued emails are not restored. Idempotent: restoring a campaign that is not deleted returns 200.
POST /api/v1/campaigns/resume
Resume paused campaigns
Resumes paused campaigns in this project. Omit campaign_ids to resume every paused, non-archived campaign. This is an explicit resume: it also resumes campaigns stopped after a failure (re-runs the failed stage) and campaigns paused at the paid gate (paid_required / search_pending; queues a paid lead search), unlike the autopilot ON default. Each id is independent. Not paused, archived, or unknown ids are skipped. `campaigns` lists the resumed campaigns with their resulting status and substatus. At most 50 ids. A non-uuid string is skipped as not_found. Requires current paid send entitlement (Free / past_due → 402 paid_required).
GET /api/v1/campaigns/{campaign_id}/inbox
List inbox conversations
Threads for one campaign (identities). An unknown tab value returns 400 (it is never silently treated as need_reply). Requires CURRENT ACTIVE paid Founder/Growth; Free / past_due / canceled return 402 paid_required. Use person_id with the thread and reply endpoints. latest_intent may be bounced; those threads no longer appear in need_reply, are listed under tab=bounced, and have can_reply=false. tab=all is every thread with an inbound message, including bounces. Classification vocabulary: the stored reply classification 'hot' is exposed as latest_intent 'hot_lead' on the inbox and as reply=hot on the mailbox filter; other values (bounced, auto_reply, ooo, unsubscribe, not_interested, unqualified, neutral, email_changed) are identical everywhere.
GET /api/v1/campaigns/{campaign_id}/inbox/{person_id}
Read a conversation thread
Full thread plus the lead profile. Requires CURRENT ACTIVE paid Founder/Growth (402 paid_required on Free / past_due / canceled). can_reply is false for unsubscribe, bounced, or no inbound yet. lead.job_title, company_name and company_domain get the same display cleaning as campaign leads (company name appended to the title removed, registrable domain); lead.country is the person's own country, else the company's, null when unknown.
POST /api/v1/campaigns/{campaign_id}/inbox/{person_id}/reply
Reply in a thread
Uses the same send path as the in-app Inbox. Requires CURRENT ACTIVE paid Founder/Growth (402 paid_required on Free / past_due / canceled). You write only the body (non-empty, at most 20000 characters). 409 when can_reply is false (bounced, unsubscribe, lead has not written back) and when the identical body was already sent after the lead's last message (retry guard).
GET /api/v1/hot-leads
List hot leads
Inbound replies classified as hot, oldest first. Requires CURRENT ACTIVE paid Founder/Growth (402 paid_required on Free / past_due / canceled). Poll with ?since=<newest became_hot_at you already have>. phone is always null (we do not store phone numbers). Deprecated aliases kept for older clients (do not use in new code): reason (use why_hot), detected_at (use became_hot_at), campaign (campaign name; use campaign_id). Lead naming differs by endpoint: campaign leads use title/company (job_title/company_name are kept there only as deprecated aliases, do not use in new code), hot leads use job_title/company_name. job_title, company_name and company_domain get the same display cleaning as campaign leads (company name appended to the title removed, registrable domain); country is the person's own country, else the company's, null when unknown. Classification vocabulary: the stored reply classification 'hot' is exposed as latest_intent 'hot_lead' on the inbox and as reply=hot on the mailbox filter; other values (bounced, auto_reply, ooo, unsubscribe, not_interested, unqualified, neutral, email_changed) are identical everywhere.
POST /api/v1/suppressions
Add a suppression
Project-wide suppress list (not named lists). Cancels queued sends that match. type is required. email and domain are trimmed and lowercased, and must be a valid address / domain name (400 otherwise). Adding the same target again is a no-op (one row).
GET /api/v1/suppressions
List suppressions
Read-only project-wide do-not-contact list, oldest first: unsubscribes, hard-bounced addresses and manual adds. reason is always do_not_contact (the cause is not stored).
GET /api/v1/mailbox
List mailbox across all campaigns
Paginated mailbox across all campaigns in the project with status counts. Requires CURRENT ACTIVE paid Founder/Growth (402 paid_required on Free / past_due / canceled). Bounced threads excluded from default needs tab; use status=bounced. campaign_id of another project returns empty result. Unknown status/page_size/page/campaign_id = 400.