API reference
Last updated
Ballast is cold email software with every feature on every plan. Connect unlimited mailboxes, send sequences at a pace your domains survive, and handle replies in one inbox, priced by the emails you send.
The REST API has 107 endpoints in 19 areas, the same operations as the dashboard. Each one is also a tool for your AI through MCP. The OpenAPI spec is at /api/v1/spec.json for Postman or code generators.
Authentication
Create a key in Settings, API keys. It belongs to one workspace and acts with the role of the person who created it, so a member's key can't do what only admins can. Send it as a bearer token. Each key gets 600 requests a minute.
curl https://ballast.to/api/v1/me \
-H "Authorization: Bearer $BALLAST_API_KEY"Errors
Errors come back as JSON with an HTTP status, a code and a message written for people. Invalid input lists each problem under data.issues. 401: missing or revoked key. 402: the workspace needs an active plan. 403: your role can't do this. 429: slow down.
Endpoints
Each area has its own page with every endpoint, its parameters, what it returns, who can call it and a curl example.
- WorkspaceWho you are, the workspace and its sending settings.5
- AnalyticsThe Overview dashboard: sends, replies, bounces and sentiment.1
- CampaignsSequences, steps and variants, leads, schedule, launch and pause.26
- LeadsCreate, import, update, verify and mark leads won.16
- ListsGroup leads to add them to campaigns together.4
- LabelsReply labels and the triggers they run.6
- SuppressionsAddresses and domains that are never emailed.3
- UniboxReply threads from every mailbox, labels and answers.6
- MailboxesConnect, test and tune the mailboxes you send from.8
- WarmupWarmup per mailbox and its inbox placement.3
- Deliverability NetworkThe IPs your SMTP and IMAP mailboxes log in through.4
- DomainsSending domains and their nightly DNS and blacklist report.4
- Link domainThe domain your unsubscribe and tracking links use.5
- IntegrationsYour verifier and your own AI key.3
- Webhook endpointsWhere Ballast sends events, and their delivery log.7
- Offer profileWhat you sell, used by Write with AI.3
- BillingPlan, usage and limits.1
- Domain checkerThe free SPF, DKIM, DMARC and blacklist check.1
- API keysKeys for this workspace.1
Webhooks
Add endpoints in Settings, Webhooks, or with POST /webhooks. Ballast POSTs JSON for each event you pick. Answer with any 2xx within 10 seconds. Anything else is retried 8 more times with growing gaps, for about two hours; redirects are not followed. Every delivery has an id shared by all your endpoints, so you can ignore one you've already handled.
| Event | When |
|---|---|
| lead.replied | A lead answered a campaign email (out-of-office and auto-replies excluded). |
| lead.labeled | A reply got a label, from AI, a rule, a person or the API. |
| lead.bounced | An email to a lead bounced hard. |
| lead.unsubscribed | A lead unsubscribed, by link, one-click header or reply. |
| lead.won | A lead was marked won. |
| campaign.paused | A campaign stopped sending: paused by someone, a label, a breaker or its end date. |
Payload
Lead events carry the lead and its campaign; the rest depends on the event: reply, label, bounce, value for a win, reason for a paused campaign.
{
"id": "0199f2b4-6c1e-7a51-9d8e-3f4b2c1a0e77",
"type": "lead.replied",
"created_at": "2026-10-17T09:14:03.120Z",
"workspace_id": "0199a1b2-...",
"data": {
"lead": {
"id": "0199...",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"company": "Example Inc",
"title": "Head of Sales",
"status": "replied",
"custom": {}
},
"campaign": {
"id": "0199...",
"name": "Q4 founders",
"status": "running"
},
"reply": {
"thread_id": "0199...",
"mailbox": "you@yourdomain.com",
"subject": "Re: Quick question",
"text": "Sounds interesting, what does it cost?",
"received_at": "2026-10-17T09:13:58.000Z"
}
}
}Check the signature
Headers: Ballast-Event, Ballast-Delivery and Ballast-Signature: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the endpoint's secret (whsec_...). Refuse signatures older than 5 minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// header = the Ballast-Signature request header, rawBody = the body exactly as received
export function verifyBallast(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || !parts.v1 || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}