API Documentation
API, Webhooks and Integrations
Reachkit provides a REST API that allows you to integrate our platform with your existing systems and automate your cold email outreach workflows.
Note: API access and integrations are available exclusively on the Scale plan.
Getting Started
The Reachkit API gives you programmatic access to manage inboxes, campaigns, leads, and more. This documentation covers all available endpoints, with examples of request and response formats.
Who should use the API?
Our API is designed for:
- Developers integrating Reachkit with other business systems
- Power users who want to automate repetitive tasks
- Teams building custom reporting or dashboards
Common use cases
- Synchronizing leads between your CRM and Reachkit
- Automatically creating and managing campaigns
- Building custom analytics dashboards
- Integrating Reachkit with your internal tools
Authentication
All API requests require authentication using an API key. You can provide your API key in one of three ways:
-
Authorization Header (Bearer Token)
Authorization: Bearer your_api_key_here -
X-API-Key Header
X-API-Key: your_api_key_here -
Query Parameter
GET /api/v1/campaigns?api_key=your_api_key_here
To obtain an API key:
- Navigate to Settings → Integrations
- Click Add API Key
- Enter a name for the key
- Store your key securely - it will only be shown once
Response Format
All responses are returned in JSON format with a standard structure:
-
Success responses include a
datafield containing the requested information -
Error responses include an
errorfield with a message describing the error and sometimes adetailsfield with more specific information
API Endpoints
The following sections provide detailed information about all available API endpoints, organized by resource type.
Blocklist Endpoints
Check email
GET /api/v1/blocklist/check
Checks if an email is in the blocklist.
Query Parameters:
- email: string (required) - Email address to check
Response:
{
"data": {
"blocked": boolean
}
}
Create
POST /api/v1/blocklist
Creates new blocklist entries.
Request:
{
"email": string # Email address to add to blocklist
}
Response:
{
"data": {
"id": string,
"email": string,
"created_at": string
}
}
Delete
DELETE /api/v1/blocklist/:id
Deletes a blocklist entry.
Response: 204 No Content
Index
GET /api/v1/blocklist
Lists all blocklist entries for the authenticated user.
Query Parameters:
- page_size: integer (optional, default: 30, max: 100) - Number of entries per page
- page: integer (optional, default: 1) - Page number
- search: string (optional) - Search term for entries
- sort_by: string (optional) - Field to sort by
- sort_order: string (optional, “asc” or “desc”) - Sort direction
Response:
{
"data": [
{
"id": string,
"email": string,
"created_at": string
}
],
"metadata": {
"page": integer,
"page_size": integer,
"total_count": integer,
"total_pages": integer
}
}
Campaign Endpoints
Analytics
GET /api/v1/campaigns/:campaign_id/analytics
Returns the full analytics view for a campaign — the headline numbers
inline, plus a daily array (last 30 days) and a sequences array
(per-sequence and per-variant performance).
The same headline fields are also embedded as data.stats.* on
GET /campaigns/:id and GET /campaigns, so for “show me this campaign”
use cases you can read them inline without an extra request.
Response:
{
"data": {
"progress": integer, # Campaign progress percentage (0-100)
"sequence_steps": integer, # Number of sequence steps
"leads_count": integer, # Total number of leads
"sent": integer, # Total emails sent
"contacted": integer, # Distinct leads who received at least one email
"replies": integer, # Total replies received (distinct leads)
"reply_rate": float, # Reply rate percentage (replies / contacted)
"positive_replies": integer, # Number of positive conversations
"positive_reply_rate": float, # Positive reply rate percentage
"bounced": integer, # Number of bounced emails
"bounce_rate": float, # Bounce rate percentage
"daily": [
# 30 days of data (today and previous 29 days)
{
"date": string, # Format: YYYY-MM-DD
"sent": integer, # Emails sent that day
"replies": integer # Replies received that day
}
],
"sequences": [
{
"id": string,
"step": integer,
"variants": [
{
"id": string,
"enabled": boolean,
"sent": integer,
"replies": integer,
"reply_rate": float,
"positive_replies": integer,
"positive_reply_rate": float
}
]
}
]
}
}
Attach inboxes
POST /api/v1/campaigns/:campaign_id/inboxes/attach
Attaches a set of inboxes to a campaign. Idempotent: inboxes already attached are silently kept, and duplicates in the request are deduped. All-or-nothing on authorization: if any id is unknown or belongs to a different workspace, the request returns 404 and no changes are applied.
Request:
{
"inbox_ids": [string] # one or more inbox UUIDs from this workspace
}
Response (the full set of inboxes attached to this campaign after the
operation; same projection used inline on GET /campaigns/:id):
{
"data": [
{
"id": string,
"email": string,
"provider": string, # one of: google, microsoft, other
"is_active": boolean,
"connection_status": string # one of: pending, validating, connected, error
}
]
}
Create
POST /api/v1/campaigns
Creates a new campaign in the authenticated workspace. Only name is
required — sensible defaults are applied for everything else (an empty
first sequence and variant are created so the campaign is editable
immediately). Inboxes, leads, schedule, and options are added via their
own endpoints.
Request:
{
"name": string # required, max 150 chars
}
Response (campaign is created in draft status):
{
"data": {
"id": string,
"name": string,
"status": string, # one of: draft, active, paused
"progress": integer, # 0-100, cached campaign progress
"options": {
"daily_limit": integer,
"stop_on_reply": boolean,
"stop_on_company_reply": boolean,
"text_only": boolean,
"esp_matching": boolean,
"unsubscribe_option": boolean
},
"schedule": {
"timezone": string, # IANA, e.g. "America/Chicago"
"default_window": {"start": "HH:MM", "end": "HH:MM"},
"days": {
"monday": {"enabled": boolean, "start": string|null, "end": string|null},
"tuesday": {...},
...
}
},
"stats": {
"progress": integer, "sequence_steps": integer, "leads_count": integer,
"sent": integer, "contacted": integer,
"replies": integer, "reply_rate": float,
"positive_replies": integer, "positive_reply_rate": float,
"bounced": integer, "bounce_rate": float,
"daily": [
{"date": "YYYY-MM-DD", "sent": integer, "replies": integer}
],
"sequences": [
{"id": string, "step": integer, "variants": [
{"id": string, "enabled": boolean, "sent": integer, "replies": integer,
"reply_rate": float, "positive_replies": integer, "positive_reply_rate": float}
]}
]
},
"inboxes": [
{
"id": string,
"email": string,
"provider": string, # one of: google, microsoft, other
"is_active": boolean,
"connection_status": string # one of: pending, validating, connected, error
}
],
"created_at": string,
"updated_at": string
}
}
Delete
DELETE /api/v1/campaigns/:id
Permanently deletes a campaign and the resources owned by it: its schedule, sequences, variants, sent emails, and leads.
This is not reversible. Returns 204 on success.
Detach inboxes
POST /api/v1/campaigns/:campaign_id/inboxes/detach
Detaches a set of inboxes from a campaign. Idempotent: inboxes that weren’t attached are silently skipped. All-or-nothing on authorization: if any id is unknown or belongs to a different workspace, returns 404 and nothing is detached.
Request:
{
"inbox_ids": [string] # one or more inbox UUIDs from this workspace
}
Response (the full set of inboxes attached to this campaign after the
operation; same projection used inline on GET /campaigns/:id):
{
"data": [
{
"id": string,
"email": string,
"provider": string, # one of: google, microsoft, other
"is_active": boolean,
"connection_status": string # one of: pending, validating, connected, error
}
]
}
Index
GET /api/v1/campaigns
Lists campaigns for the authenticated workspace.
Each item is the full campaign projection — identity, status, options,
schedule, summary stats, and associated inboxes. List items omit the
daily array and per-sequence breakdowns from stats; for those,
fetch a single campaign via GET /campaigns/:id.
Query Parameters:
- page_size: integer (optional, default: 30, max: 100)
- page: integer (optional, default: 1)
- search: string (optional)
- sort_by: string (optional)
- sort_order: string (optional, “asc” or “desc”)
Response:
{
"data": [
{
"id": string,
"name": string,
"status": string, # one of: draft, active, paused
"progress": integer, # 0-100, cached campaign progress
"options": {
"daily_limit": integer,
"stop_on_reply": boolean,
"stop_on_company_reply": boolean,
"text_only": boolean,
"esp_matching": boolean,
"unsubscribe_option": boolean
},
"schedule": {
"timezone": string, # IANA, e.g. "America/Chicago"
"default_window": {"start": "HH:MM", "end": "HH:MM"},
"days": {
"monday": {"enabled": boolean, "start": string|null, "end": string|null},
"tuesday": {...},
...
}
},
"stats": {
"progress": integer, "sequence_steps": integer, "leads_count": integer,
"sent": integer, "contacted": integer,
"replies": integer, "reply_rate": float,
"positive_replies": integer, "positive_reply_rate": float,
"bounced": integer, "bounce_rate": float
},
"inboxes": [
{
"id": string,
"email": string,
"provider": string, # one of: google, microsoft, other
"is_active": boolean,
"connection_status": string # one of: pending, validating, connected, error
}
],
"created_at": string,
"updated_at": string
}
],
"metadata": {
"page": integer,
"page_size": integer,
"total_count": integer,
"total_pages": integer
}
}
Show
GET /api/v1/campaigns/:id
Shows details for a specific campaign.
Response:
{
"data": {
"id": string,
"name": string,
"status": string, # one of: draft, active, paused
"progress": integer, # 0-100, cached campaign progress
"options": {
"daily_limit": integer,
"stop_on_reply": boolean,
"stop_on_company_reply": boolean,
"text_only": boolean,
"esp_matching": boolean,
"unsubscribe_option": boolean
},
"schedule": {
"timezone": string, # IANA, e.g. "America/Chicago"
"default_window": {"start": "HH:MM", "end": "HH:MM"},
"days": {
"monday": {"enabled": boolean, "start": string|null, "end": string|null},
"tuesday": {...},
...
}
},
"stats": {
"progress": integer, "sequence_steps": integer, "leads_count": integer,
"sent": integer, "contacted": integer,
"replies": integer, "reply_rate": float,
"positive_replies": integer, "positive_reply_rate": float,
"bounced": integer, "bounce_rate": float,
"daily": [
{"date": "YYYY-MM-DD", "sent": integer, "replies": integer}
],
"sequences": [
{"id": string, "step": integer, "variants": [
{"id": string, "enabled": boolean, "sent": integer, "replies": integer,
"reply_rate": float, "positive_replies": integer, "positive_reply_rate": float}
]}
]
},
"inboxes": [
{
"id": string,
"email": string,
"provider": string, # one of: google, microsoft, other
"is_active": boolean,
"connection_status": string # one of: pending, validating, connected, error
}
],
"created_at": string,
"updated_at": string
}
}
Update
PATCH /api/v1/campaigns/:id
Updates a campaign. Provide any combination of the fields below; omitted fields are unchanged. All updates apply together — if any field fails validation, none of the requested changes are saved. Schedule changes have their own endpoint.
-
name(string, max 150) -
status(one of:active,paused) — only valid transitions are accepted; you cannot launch a campaign without at least one variant and one inbox -
daily_limit(integer, ≥ 0) — total daily sending across all inboxes -
stop_on_reply(boolean) -
stop_on_company_reply(boolean) -
text_only(boolean) — sends as plain text only, omitting the HTML body -
unsubscribe_option(boolean) — adds the List-Unsubscribe header and an{{unsubscribe_url}}link to every variant body when enabled; removes both when disabled -
esp_matching(boolean) — only send to leads whose ESP matches the sending inbox’s ESP
Response:
{
"data": {
"id": string,
"name": string,
"status": string, # one of: draft, active, paused
"progress": integer, # 0-100, cached campaign progress
"options": {
"daily_limit": integer,
"stop_on_reply": boolean,
"stop_on_company_reply": boolean,
"text_only": boolean,
"esp_matching": boolean,
"unsubscribe_option": boolean
},
"schedule": {
"timezone": string, # IANA, e.g. "America/Chicago"
"default_window": {"start": "HH:MM", "end": "HH:MM"},
"days": {
"monday": {"enabled": boolean, "start": string|null, "end": string|null},
"tuesday": {...},
...
}
},
"stats": {
"progress": integer, "sequence_steps": integer, "leads_count": integer,
"sent": integer, "contacted": integer,
"replies": integer, "reply_rate": float,
"positive_replies": integer, "positive_reply_rate": float,
"bounced": integer, "bounce_rate": float
},
"inboxes": [
{
"id": string,
"email": string,
"provider": string, # one of: google, microsoft, other
"is_active": boolean,
"connection_status": string # one of: pending, validating, connected, error
}
],
"created_at": string,
"updated_at": string
}
}
Update schedule
PATCH /api/v1/campaigns/:campaign_id/schedule
Updates the campaign’s sending schedule. Provide any combination of
timezone, default_window, and per-day overrides. Omitted fields
are unchanged.
Per-day start/end override the default_window for that day.
Setting either to null falls back to the default window. enabled: false stops sending entirely on that day regardless of window.
Time strings are 24-hour "HH:MM". Times are stored on a 30-minute
grid to match the UI: minutes are rounded to the nearest 00 or 30
(ties round up), and the maximum is 23:30. The response echoes the
stored (rounded) value, so a request with "09:23" returns "09:30".
Timezone is an IANA zone name from the supported list below. Any other
value returns 422 with a timezone validation error.
Supported zones:
-
Pacific/Honolulu,America/Anchorage,America/Los_Angeles,America/Vancouver,America/Phoenix,America/Denver,America/Chicago,America/New_York,America/Toronto,America/Sao_Paulo -
Europe/London,Europe/Paris,Europe/Berlin,Europe/Kiev,Europe/Moscow -
Asia/Tehran,Asia/Dubai,Asia/Karachi,Asia/Kolkata,Asia/Bangkok,Asia/Shanghai,Asia/Hong_Kong,Asia/Singapore,Asia/Tokyo -
Australia/Sydney,Pacific/Auckland
Request:
{
"timezone": string, # optional
"default_window": {"start": "HH:MM", "end": "HH:MM"}, # optional
"days": { # optional, any subset of days
"monday": {"enabled": boolean, "start": "HH:MM"|null, "end": "HH:MM"|null},
"tuesday": {...},
"wednesday": {...},
"thursday": {...},
"friday": {...},
"saturday": {...},
"sunday": {...}
}
}
Response:
{
"data": {
"timezone": string,
"default_window": {"start": "HH:MM", "end": "HH:MM"},
"days": {
"monday": {"enabled": boolean, "start": string|null, "end": string|null},
"tuesday": {...},
"wednesday": {...},
"thursday": {...},
"friday": {...},
"saturday": {...},
"sunday": {...}
}
}
}
Conversation Endpoints
Create
POST /api/v1/unibox/conversations
Creates a new conversation and sends the first email.
Request:
{
"inbox_id": string, # ID of the inbox to send from
"to": [ # Recipients (at least one required)
{"email": string, "name": string|null}
],
"subject": string, # Email subject line
"content": string # HTML content of the email
}
If inbox_id is omitted, the first active inbox in the workspace is
used. Returns 422 if no active inbox exists.
Response:
{
"data": {
"id": string,
"label": string|null,
"is_positive": boolean,
"lead_id": null,
"messages": [...],
"created_at": string,
"updated_at": string
}
}
Download attachment
GET /api/v1/unibox/conversations/:conversation_id/attachments/:id
Downloads an attachment from a conversation message.
Response: Binary file content with appropriate content-type header
Index
GET /api/v1/unibox/conversations
Lists all conversations.
Query Parameters:
- page_size: integer (optional, default: 20, max: 100) - Number of conversations per page
- page: integer (optional, default: 1) - Page number
- search: string (optional) - Search term for conversations
- folder: string (optional) - Filter by folder (“primary” or “others”)
Response:
{
"data": [
{
"id": string,
"label": string|null, # Effective label: interested, not_interested, meeting_requested, etc.
"is_positive": boolean, # Whether the conversation is positively engaged
"lead_id": string|null, # Associated lead ID (null for manual conversations)
"messages": [
{
"direction": string, # One of: outgoing, incoming
"inbox_id": string, # ID of the inbox this message went through
"subject": string,
"text_body": string,
"html_body": string,
"from_email": string,
"to_recipients": [{"email": string, "name": string}],
"cc_recipients": [{"email": string, "name": string}],
"bcc_recipients": [{"email": string, "name": string}],
"email_date": string,
"is_read": boolean,
"attachments": [
{
"id": string,
"filename": string,
"content_type": string,
"size": integer
}
]
}
],
"created_at": string,
"updated_at": string
}
],
"metadata": {
"page": integer,
"page_size": integer,
"total_count": integer,
"total_pages": integer
}
}
Reply
POST /api/v1/unibox/conversations/:conversation_id/reply
Reply to a conversation.
Request:
{
"content": string # HTML content of the reply
}
Response:
{
"data": {
"direction": string, # Will be "outgoing"
"inbox_id": string, # ID of the inbox the reply went through
"subject": string,
"text_body": string,
"html_body": string,
"from_email": string,
"to_recipients": [{"email": string, "name": string}],
"cc_recipients": [{"email": string, "name": string}],
"bcc_recipients": [{"email": string, "name": string}],
"email_date": string,
"is_read": boolean,
"attachments": []
}
}
Show
GET /api/v1/unibox/conversations/:id
Shows details for a specific conversation.
Response:
{
"data": {
"id": string,
"label": string|null, # Effective label: interested, not_interested, meeting_requested, etc.
"is_positive": boolean, # Whether the conversation is positively engaged
"lead_id": string|null, # Associated lead ID (null for manual conversations)
"messages": [
{
"direction": string, # One of: outgoing, incoming
"subject": string,
"text_body": string,
"html_body": string,
"from_email": string,
"to_recipients": [{"email": string, "name": string}],
"cc_recipients": [{"email": string, "name": string}],
"bcc_recipients": [{"email": string, "name": string}],
"email_date": string,
"is_read": boolean,
"attachments": [
{
"id": string,
"filename": string,
"content_type": string,
"size": integer
}
]
}
],
"created_at": string,
"updated_at": string
}
}
Update
PATCH /api/v1/unibox/conversations/:id
Updates a conversation. Provide any combination of label_override
and is_read. Omitted fields are unchanged.
label_override accepts one of: interested, not_interested,
needs_more_info, meeting_requested, wrong_person, out_of_office,
referral, question, neutral, unsubscribe, left_company. Pass
null to clear the override and revert to the AI-classified label.
is_read: true marks every message in the conversation as read.
is_read: false marks every message as unread, useful for surfacing a
thread back into triage.
Request:
{
"label_override": string|null, # optional
"is_read": boolean # optional
}
Response:
{
"data": {
"id": string,
"label": string|null, # Effective label: interested, not_interested, meeting_requested, etc.
"is_positive": boolean, # Whether the conversation is positively engaged
"lead_id": string|null, # Associated lead ID (null for manual conversations)
"messages": [
{
"direction": string, # One of: outgoing, incoming
"inbox_id": string, # ID of the inbox this message went through
"subject": string,
"text_body": string,
"html_body": string,
"from_email": string,
"to_recipients": [{"email": string, "name": string}],
"cc_recipients": [{"email": string, "name": string}],
"bcc_recipients": [{"email": string, "name": string}],
"email_date": string,
"is_read": boolean,
"attachments": [
{
"id": string,
"filename": string,
"content_type": string,
"size": integer
}
]
}
],
"created_at": string,
"updated_at": string
}
}
Inbox Endpoints
Index
GET /api/v1/inboxes
Lists all inboxes for the authenticated user.
Query Parameters:
- page_size: integer (optional, default: 30, max: 100) - Number of inboxes per page
- page: integer (optional, default: 1) - Page number
- search: string (optional) - Search term for inboxes
- sort_by: string (optional) - Field to sort by
- sort_order: string (optional, “asc” or “desc”) - Sort direction
Response:
{
"data": [
{
"id": string,
"email": string,
"domain": string,
"provider": string, # one of: google, microsoft, other
"connection_type": string, # one of: oauth, imap_smtp
"connection_status": string, # one of: pending, validating, connected, error
"first_name": string,
"last_name": string,
"reply_to": string,
"signature": string,
"is_active": boolean,
"is_warming": boolean,
"daily_campaign_limit": integer,
"daily_warmup_limit": integer,
"created_at": string,
"updated_at": string
}
],
"metadata": {
"page": integer,
"page_size": integer,
"total_count": integer,
"total_pages": integer
}
}
Show
GET /api/v1/inboxes/:id
Shows details for a specific inbox.
Response:
{
"data": {
"id": string,
"email": string,
"domain": string,
"provider": string, # one of: google, microsoft, other
"connection_type": string, # one of: oauth, imap_smtp
"connection_status": string, # one of: pending, validating, connected, error
"first_name": string,
"last_name": string,
"reply_to": string,
"signature": string,
"is_active": boolean,
"is_warming": boolean,
"daily_campaign_limit": integer,
"daily_warmup_limit": integer,
"campaigns": [
{
"id": string,
"name": string,
"status": string,
"created_at": string,
"updated_at": string
}
],
"created_at": string,
"updated_at": string
}
}
Update
PATCH /api/v1/inboxes/:id
Updates editable settings on an inbox. Provide any combination of the fields below; omitted fields are unchanged.
To stop sending from an inbox, set is_active: false.
Request:
{
"is_active": boolean, # optional — pause/resume campaign sending
"is_warming": boolean, # optional — toggle warmup
"first_name": string, # optional
"last_name": string, # optional
"reply_to": string, # optional — empty string clears it
"signature": string, # optional — appended to the bottom of every outgoing email
"daily_campaign_limit": integer, # optional — between 1 and 500
"daily_warmup_limit": integer # optional — between 1 and 100
}
Response:
{
"data": {
"id": string,
"email": string,
"domain": string,
"provider": string, # one of: google, microsoft, other
"connection_type": string, # one of: oauth, imap_smtp
"connection_status": string, # one of: pending, validating, connected, error
"first_name": string,
"last_name": string,
"reply_to": string,
"signature": string,
"is_active": boolean,
"is_warming": boolean,
"daily_campaign_limit": integer,
"daily_warmup_limit": integer,
"campaigns": [
{
"id": string,
"name": string,
"status": string,
"created_at": string,
"updated_at": string
}
],
"created_at": string,
"updated_at": string
}
}
Warmup stats
GET /api/v1/inboxes/:inbox_id/warmup/stats
Returns warmup statistics for an inbox over the last 7 days.
Response:
{
"data": {
"sent": integer, # Total warmup emails sent
"received": integer, # Total warmup emails received
"saved_from_spam": integer, # Emails moved from spam folder
"health_score": integer, # 0-100 score based on spam rate
"ramp_up_progress": integer, # 0-100 percentage of warmup progress
"daily": [
{
"date": string, # Format: YYYY-MM-DD
"sent": integer, # Emails sent that day
"spam_count": integer # Emails that went to spam that day
}
]
}
}
Lead Endpoints
Create
POST /api/v1/campaigns/:campaign_id/leads
Creates a new lead for a campaign.
Custom variables feed sequence templates: reference any key as
{{key}} in a variant subject or body and the rendered email will
interpolate the value. Variable names are normalized on match, so
{{First Name}}, {{first_name}}, and {{firstName}} all
resolve to the same key. Use company for the lead’s company name.
Every lead also exposes built-in variables: first_name, last_name,
email, full_name, contact.
Request:
{
"email": string, # Required
"first_name": string, # Optional
"last_name": string, # Optional
"variables": object, # Optional, e.g. {"company": "Acme", "role": "VP Sales"}
"if_exists": string # Optional: "fail" (default), "skip", "update"
}
Response:
{
"data": {
"id": string,
"email": string,
"first_name": string,
"last_name": string,
"status": string, # One of: uncontacted, contacted, replied
"is_bounced": boolean, # Whether the lead has bounced
"is_unsubscribed": boolean, # Whether the lead has unsubscribed
"provider": string|null, # null while ESP detection is pending; otherwise one of: other, google, microsoft, yahoo, zoho
"variables": object, # Merge-tag values, e.g. {"company": "Acme"}
"created_at": string,
"updated_at": string
}
}
Delete
DELETE /api/v1/campaigns/:campaign_id/leads/:id
Deletes a lead from a campaign.
Response: 204 No Content
Index
GET /api/v1/campaigns/:campaign_id/leads
Lists all leads for a specific campaign.
Query Parameters:
- page_size: integer (optional, default: 30, max: 100) - Number of leads per page
- page: integer (optional, default: 1) - Page number
- search: string (optional) - Search term for leads
- sort_by: string (optional) - Field to sort by
- sort_order: string (optional, “asc” or “desc”) - Sort direction
Response:
{
"data": [
{
"id": string,
"email": string,
"first_name": string,
"last_name": string,
"status": string, # One of: uncontacted, contacted, replied
"is_bounced": boolean, # Whether the lead has bounced
"is_unsubscribed": boolean, # Whether the lead has unsubscribed
"provider": string|null, # null while ESP detection is pending; otherwise one of: other, google, microsoft, yahoo, zoho
"variables": object, # Merge-tag values, e.g. {"company": "Acme"}
"created_at": string,
"updated_at": string
}
],
"metadata": {
"page": integer,
"page_size": integer,
"total_count": integer,
"total_pages": integer
}
}
Show
GET /api/v1/campaigns/:campaign_id/leads/:id
Shows details for a specific lead.
Response:
{
"data": {
"id": string,
"email": string,
"first_name": string,
"last_name": string,
"status": string, # One of: uncontacted, contacted, replied
"is_bounced": boolean, # Whether the lead has bounced
"is_unsubscribed": boolean, # Whether the lead has unsubscribed
"provider": string|null, # null while ESP detection is pending; otherwise one of: other, google, microsoft, yahoo, zoho
"variables": object, # Merge-tag values, e.g. {"company": "Acme"}
"created_at": string,
"updated_at": string
}
}
Sequence Endpoints
Create
POST /api/v1/campaigns/:campaign_id/sequences
Adds a new step to a campaign’s sequence. The new step is added to the
end. A default empty variant is created alongside so the step is
immediately editable; edit it via
PATCH /campaigns/:id/variants/:variant_id or add more with
POST /sequences/:id/variants.
delay_days is the gap to the next step. The new step is the last
step, so its delay_days is returned as null. When you later add
another step after it, the previous step’s delay_days becomes
meaningful again.
Request:
{
"delay_days": integer # optional, defaults to 3
}
Response:
{
"data": {
"id": string,
"step": integer,
"delay_days": integer|null, # null on the last step
"variants": [
{
"id": string,
"letter": "A",
"subject": null,
"body_html": string|null, # null, or the unsubscribe-link block when the campaign has unsubscribe_option enabled
"body_text": string|null,
"enabled": true
}
]
}
}
Create variant
POST /api/v1/campaigns/:campaign_id/sequences/:id/variants
Adds a new variant to a sequence. Same content rules as
PATCH /campaigns/:campaign_id/variants/:id: HTML is normalized,
unsubscribe link is preserved, merge tags are validated.
Request:
{
"subject": string, # required, non-empty
"body_html": string, # optional
"enabled": boolean # optional, defaults to true
}
Response:
{
"data": {
"id": string,
"subject": string,
"body_html": string,
"body_text": string,
"enabled": boolean
}
}
Delete
DELETE /api/v1/campaigns/:campaign_id/sequences/:id
Deletes a step and all of its variants. Subsequent steps shift up so
step numbers stay contiguous (e.g. deleting step 2 from [1, 2, 3, 4]
leaves [1, 2, 3]). Returns 204 on success.
Delete variant
DELETE /api/v1/campaigns/:campaign_id/variants/:variant_id
Deletes a variant. Returns 422 with cannot_delete_last_variant if it
would leave the sequence with no enabled variants.
Index
GET /api/v1/campaigns/:campaign_id/sequences
Lists all sequences for a campaign with their variants, ordered by step.
delay_days is the number of days to wait after this step before
sending the next step. The last step in a campaign has no next step,
so its delay_days is null.
Response:
{
"data": [
{
"id": string,
"step": integer,
"delay_days": integer|null, # null on the last step
"variants": [
{
"id": string,
"letter": string, # A, B, C, etc.
"subject": string,
"body_html": string,
"body_text": string,
"enabled": boolean
}
]
}
]
}
Update
PATCH /api/v1/campaigns/:campaign_id/sequences/:id
Updates a step’s delay_days.
Request:
{
"delay_days": integer # required
}
Response:
{
"data": {
"id": string,
"step": integer,
"delay_days": integer|null, # null on the last step
"variants": [
{
"id": string,
"letter": string, # A, B, C, etc.
"subject": string,
"body_html": string,
"body_text": string,
"enabled": boolean
}
]
}
}
Update variant
PATCH /api/v1/campaigns/:campaign_id/variants/:variant_id
Updates a sequence variant. Provide any combination of subject,
body_html, and enabled. Omitted fields are unchanged.
Body HTML is restricted to a small allowlist (<b>, <i>, <u>, <a>,
<img>) and normalized server-side, so the saved markup may differ
slightly from what you submit. Other tags are unwrapped, unknown
attributes are dropped.
Merge tags {{variable}}, {{variable|fallback}}, {{LOOM|column|fallback}},
and {{SPINTAX|opt1|opt2}} are supported in both subject and body_html.
Use \{{...}} to include a literal {{ in the email. Unbalanced braces
return 422 with the offset of the offending {{.
The last enabled variant in a sequence cannot be disabled. Campaigns with
unsubscribe headers enabled keep an <a href="{{unsubscribe_url}}"> link
in the body. If you remove it, it will be re-added.
Request:
{
"subject": string, # optional
"body_html": string, # optional
"enabled": boolean # optional
}
Response:
{
"data": {
"id": string,
"subject": string,
"body_html": string,
"body_text": string,
"enabled": boolean
}
}
Need Help?
If you have questions about using the API or need additional endpoints for your specific use case, please contact our support team. We’re continually expanding our API capabilities based on user feedback.