Build on the Sendvanta API

A REST API for your whole outbound operation: create and launch campaigns, import and verify leads, read replies, and monitor deliverability — from your own code. Included on the Pro plan.

Authentication

The API is available on the Pro plan. An owner or admin creates keys in the console under Settings → Integrations → API keys. The secret (svk_…) is shown exactly once — store it like a password. Send it as a bearer token on every request:

curl https://api.sendvanta.com/campaigns \
  -H "Authorization: Bearer $SENDVANTA_API_KEY"

A key is bound to the workspace it was created in — no workspace header is needed, and a key can never reach another workspace. Revoke keys any time from the same settings page; revocation is immediate. Each workspace can hold up to 10 active keys, so you can issue one per integration and rotate them independently.

Conventions

  • Base URL: https://api.sendvanta.com. Requests and responses are JSON — send Content-Type: application/json on writes.
  • Ids are UUIDs. Timestamps are ISO 8601 in UTC. Campaign send windows are evaluated in the campaign's own timezone.
  • List endpoints paginate with limit and offset query parameters and return a total alongside the rows where shown.
  • Email bodies support template variables: {{first_name}}, {{last_name}}, {{company}}, {{title}}, custom import fields, and fallbacks like {{first_name|there}}. Every sequence must end with a step containing {{unsubscribe_url}}.

Errors

Errors share one shape: { "error": string, "details"?: … }.

StatuserrorWhen
400validationMalformed body or query. details carries the field-level issues.
401unauthenticatedMissing, unknown, or revoked API key.
402insufficient_creditsVerification credit balance is empty.
403plan_upgrade_requiredThe workspace is no longer on the Pro plan. Keys are kept, not deleted — access resumes on upgrade.
403forbiddenThe route is not available over the API (billing, members, credentials, key management), or the role can't perform it.
404not_foundThe resource doesn't exist in this workspace.
409conflict / blockersState conflict — e.g. activating with preflight blockers returns { error, blockers: [...] }.
429rate_limitedOver 120 requests/min for this key. Honor Retry-After.
501*_not_configuredThe feature isn't configured on the server (AI, verification, spam check).

Rate limits

120 requests per minute per key. Every key-authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (seconds until the window resets). Exceeding the limit returns 429 with a Retry-After header — back off and retry. Need sustained higher volume? Email support@sendvanta.com.

Quickstart: launch a campaign end to end

# 1. Create a campaign
curl -X POST https://api.sendvanta.com/campaigns \
  -H "Authorization: Bearer $SENDVANTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Roofers — Texas", "timezone": "America/Chicago"}'

# 2. Add steps (repeat per follow-up, bumping stepOrder and delayHours)
curl -X PUT https://api.sendvanta.com/campaigns/$CAMPAIGN_ID/steps \
  -H "Authorization: Bearer $SENDVANTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"stepOrder": 1, "subject": "Quick question, {{first_name|there}}",
       "bodyText": "…\n\n{{unsubscribe_url}}", "delayHours": 0}'

# 3. Assign mailboxes, then enroll a lead list
curl -X PUT https://api.sendvanta.com/campaigns/$CAMPAIGN_ID/mailboxes \
  -H "Authorization: Bearer $SENDVANTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mailboxIds": ["…"]}'

curl -X POST https://api.sendvanta.com/campaigns/$CAMPAIGN_ID/enroll \
  -H "Authorization: Bearer $SENDVANTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"listId": "…"}'

# 4. Preflight, then launch
curl https://api.sendvanta.com/campaigns/$CAMPAIGN_ID/preflight \
  -H "Authorization: Bearer $SENDVANTA_API_KEY"

curl -X POST https://api.sendvanta.com/campaigns/$CAMPAIGN_ID/activate \
  -H "Authorization: Bearer $SENDVANTA_API_KEY"

Campaigns

GET/campaigns

List campaigns, newest first, each with enrollment counts by status.

Response

[
  {
    "id": "…", "name": "Roofers — Texas", "status": "active",
    "timezone": "America/Chicago", "daysOfWeek": [1,2,3,4,5],
    "sendWindowStart": "09:00", "sendWindowEnd": "17:00", "dailyCap": 200,
    "trackOpens": true, "trackClicks": true, "createdAt": "…",
    "stats": { "active": 118, "replied": 9, "completed": 41, "stopped": 3,
               "unsubscribed": 2, "bounced": 4, "paused": 0 }
  }
]
POST/campaigns

Create a draft campaign.

Request body

FieldTypeDescription
namerequiredstring1–120 characters.
timezonestringIANA timezone the send window is evaluated in. Default "America/New_York".
daysOfWeekinteger[]Send days, 0 = Sunday … 6 = Saturday. Default [1,2,3,4,5].
sendWindowStart"HH:MM"Default "09:00".
sendWindowEnd"HH:MM"Default "17:00".
dailyCapintegerMax sends per day for the campaign (1–50,000). Default 200.
startDate"YYYY-MM-DD"Optional earliest send date.
trackOpensbooleanOpen pixel. Default false — tracking is opt-in.
trackClicksbooleanClick-through link rewriting. Default false.
  • The response is the created campaign object. Campaigns always start as drafts — add steps, assign mailboxes, enroll leads, then activate.
GET/campaigns/:id

One campaign with its sequence, assigned mailboxes, and stats.

Response

{ "campaign": { … }, "steps": [ … ], "mailboxes": [ … ], "stats": { … } }
PATCH/campaigns/:id

Update settings. Accepts any subset of the POST /campaigns fields.

  • Only draft or paused campaigns can be edited; otherwise 409.
PUT/campaigns/:id/steps

Create or update a sequence step (upsert keyed on stepOrder).

Request body

FieldTypeDescription
stepOrderrequiredinteger1-based position in the sequence (1–20).
subjectrequiredstring≤500 chars. May be empty when threadMode is reply_to_previous.
bodyTextrequiredstringPlain-text body, ≤50,000 chars. Supports template variables.
bodyHtmlstringOptional HTML body, ≤100,000 chars.
delayHoursrequiredintegerHours after the previous step (0–2160). Step 1 uses 0.
threadMode"new_thread" | "reply_to_previous"Default "new_thread".
aiVariationsEnabledbooleanGenerate ~4 AI wording variants rotated at send time for deliverability. Default false.
  • The final step's body must contain {{unsubscribe_url}} — activation preflight blocks otherwise.
  • AI variants regenerate when the step content changes and preserve the exact template-variable set. They do not count against the AI generation quota.
DELETE/campaigns/:id/steps/:stepId

Remove a step from the sequence.

PUT/campaigns/:id/mailboxes

Replace the campaign's sending-mailbox assignment.

Request body

FieldTypeDescription
mailboxIdsrequireduuid[]1–100 mailbox ids. Replaces the whole set.
POST/campaigns/:id/enroll

Enroll leads from a list and/or by explicit ids.

Request body

FieldTypeDescription
listIduuidEnroll every eligible lead in the list. The list stays connected: leads added to it later are auto-enrolled.
leadIdsuuid[]Explicit leads (≤50,000).
  • Skips suppressed addresses, leads already in an active sequence anywhere in the workspace, and leads whose email verification came back invalid.

Response

{ "enrolled": 482, "skipped": { "suppressed": 3, "duplicate": 11, "invalid": 6, "unverified": 0 } }
GET/campaigns/:id/preflight

Launch-readiness check without launching.

  • Blockers include: no steps, no healthy assigned mailbox, no eligible leads, unresolved template variables, missing unsubscribe link.

Response

{
  "ok": false,
  "blockers": ["Final step is missing {{unsubscribe_url}}"],
  "warnings": [],
  "eligibleLeads": 482, "excludedLeads": 20,
  "mailboxes": 3, "dailyCapacity": 96, "firstSendAt": "…"
}
POST/campaigns/:id/activate

Launch the campaign. Runs preflight first; 409 with the blocker list if not ready.

POST/campaigns/:id/pause

Pause sending. In-flight scheduled messages hold.

POST/campaigns/:id/resume

Resume a paused campaign.

POST/campaigns/:id/seed-test

Send step 1 to your registered seed inboxes for an inbox-placement check. Results land in GET /seed-tests with a launch-readiness verdict.

GET/campaigns/:id/messages

Sent-email log for the campaign, newest first (excludes test sends).

Query parameters

FieldTypeDescription
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.

Response

{
  "messages": [{
    "id": "…", "toEmail": "…", "subject": "…", "status": "sent", "stepOrder": 1,
    "sentAt": "…", "openCount": 2, "clickCount": 1, "verifiedVisitCount": 1,
    "engagementLevel": "verified_visit", "clickVerdict": "likely_human", "leadId": "…"
  }],
  "total": 1284
}
GET/campaigns/:id/schedule

Send-volume forecast in the campaign timezone.

Query parameters

FieldTypeDescription
view"day" | "week"Default "day": 24 hourly buckets. "week": 7 daily buckets.
date"YYYY-MM-DD"Anchor date. Defaults to today in the campaign timezone.

Response

{
  "view": "day", "timezone": "America/Chicago", "date": "2026-09-15",
  "window": { "sendWindowStart": "09:00", "sendWindowEnd": "17:00", "daysOfWeek": [1,2,3,4,5], "dailyCap": 200 },
  "totals": { "scheduled": 96, "sent": 41 },
  "buckets": [{ "key": "09", "scheduled": 12, "sent": 12 }, …]
}
GET/campaigns/:id/list-sources

Lists connected to the campaign (recorded at enrollment; drive auto-enrollment of later additions).

GET/enrollments

List enrollments across campaigns.

Query parameters

FieldTypeDescription
campaignIduuidFilter to one campaign.
statusstringactive | replied | unsubscribed | bounced | completed | stopped | paused.
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.
POST/enrollments/:id/stop

Stop a lead's sequence. Cancels the pending scheduled message.

Request body

FieldTypeDescription
reasonstringOptional note stored on the enrollment.

AI generation

Draft sequences and individual steps with AI. Each successful generation (full sequence or one step) counts once against the plan's monthly quota — 10 per month on Pro. The generator only writes cold-email campaign copy; off-topic prompts return refused: true.

POST/ai/campaign-generate

Draft a single email or a full sequence.

Request body

FieldTypeDescription
productDetailrequiredstringWhat you're selling (≤3,000 chars).
promptstringExtra guidance (≤2,000 chars).
goalstringe.g. "book a demo".
focusAreastringAudience / vertical to focus on.
websiteDetailstringContext about your site or offer.
linksstring[]Up to 10 URLs to weave in.
numberOfSequencesintegerSteps to draft, 1–6. Default 3.
mode"single" | "sequence"Default "sequence".
format"plain" | "richtext" | "html"Body format. Default "richtext".
  • Each email is shaped to PUT straight to /campaigns/:id/steps.
  • Gates: 403 plan_upgrade_required · 403 ai_quota_exceeded · 501 ai_not_configured · 502 ai_generation_failed.

Response

{
  "refused": false,
  "emails": [{ "subject": "…", "bodyText": "…", "bodyHtml": "…", "delayHours": 0 }, …],
  "format": "richtext",
  "remaining": 7
}
POST/ai/step-generate

Write or rewrite one step, using sibling steps for voice continuity.

Request body

FieldTypeDescription
promptrequiredstringWhat the step should say or how to change it.
format"plain" | "richtext" | "html"Default "richtext".
isFirstbooleanWhether this is the opening email. Default false.
current{ subject?, body? }Existing content when revising; omit to write fresh.
otherStepsarraySibling steps [{ stepOrder, subject, bodyText }] for context (≤20).
GET/ai/usage

This month's AI generation quota.

Response

{ "plan": "pro", "limit": 10, "used": 3, "remaining": 7 }

Leads & lists

GET/lead-lists

All lists, newest first.

POST/lead-lists

Create a list.

Request body

FieldTypeDescription
namerequiredstring1–120 characters.
POST/lead-lists/import

Bulk-import leads into a list. Synchronous: normalizes, validates syntax, dedupes against the payload and the workspace, and checks suppressions.

Request body

FieldTypeDescription
listIdrequireduuidTarget list.
fieldMappingrequiredobjectCSV column → field. Fields: "email", "first_name", "last_name", "company", "title", "phone", or "custom:<name>".
rowsrequiredobject[]Parsed rows as { column: value } records.
  • Returns the import record including per-row errors (rowErrors). Import size is limited by your plan.
GET/imports/:id

One import's result record.

GET/leads

Search leads.

Query parameters

FieldTypeDescription
listIduuidFilter to a list.
statusstringLead status filter.
searchstringMatches email, first/last name, company.
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.
  • verificationStatus: valid | invalid | risky | unknown, null = never verified. verificationReason carries the underlying cause (catch_all, role_account, inbox_full, disabled, …).

Response

{
  "leads": [{
    "id": "…", "email": "…", "firstName": "…", "company": "…",
    "verificationStatus": "valid", "verificationReason": null, "verifiedAt": "…"
  }],
  "total": 5210
}
POST/leads

Add a single lead.

Request body

FieldTypeDescription
emailrequiredstringThe address.
listIduuidList to add to.
firstNamestring
lastNamestring
companystring
titlestring
phonestring
POST/leads/suppress

Suppress addresses — they are never emailed again, and any active sequences stop immediately.

Request body

FieldTypeDescription
emailsrequiredstring[]1–10,000 addresses.
reason"manual" | "unsubscribe" | "import"Default "manual".
GET/suppressions

The workspace suppression list.

Query parameters

FieldTypeDescription
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.

Email verification

Verify addresses before sending. 1 credit = 1 address; Pro includes 60,000 credits, more come in $20 / 10,000 packs (purchased in the console — billing is not reachable over the API). Results are cached for 30 days; cached hits are free. Invalid leads are automatically excluded at enrollment and at send time.

POST/verification/verify

Verify one address and update the matching lead.

Request body

FieldTypeDescription
emailrequiredstringAddress to verify.
forcebooleanBypass the 30-day cache. Default false.
  • Gates in order: 403 plan_upgrade_required · 501 verification_not_configured · 402 insufficient_credits · 502 verification_failed (credit refunded).
POST/lead-lists/:id/verify

Start a background job over the list's unverified leads. Returns the job (201).

  • 409 verification_in_progress if a job is live · 400 nothing_to_verify · 402 when the balance is 0.
GET/verification/jobs/:id

Job progress.

Response

{
  "status": "running", "totalLeads": 4100, "processed": 1240,
  "validCount": 1010, "invalidCount": 105, "riskyCount": 88, "unknownCount": 37,
  "creditsUsed": 1240, "errorCount": 0, "stopReason": null
}
GET/verification/jobs

Recent jobs, newest first.

Query parameters

FieldTypeDescription
listIduuidFilter to one list.
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.
GET/verification/credits

Credit balance and plan allowance.

Response

{ "plan": "pro", "balance": 54210, "includedForPlan": 60000, "purchasedTotal": 0, "usedTotal": 5790, "pack": { "credits": 10000, "priceUsd": 20 } }

Mailboxes & warm-up

Read-only over the API: connecting mailboxes and editing credentials stays in the console by design.

GET/mailboxes

All mailboxes with status, allowance, and health. Never returns credentials.

Response

[{
  "id": "…", "provider": "smtp", "email": "…", "senderName": "…",
  "status": "active", "dailyCap": 50, "dailyAllowance": 32, "sentToday": 12,
  "minGapMinutes": 8, "healthScore": 86, "healthBand": "healthy",
  "warmupEnabled": true, "warmupDailyTarget": 6
}]
GET/mailboxes/:id/health

Health-score snapshots over time.

GET/mailboxes/:id/usage

Send volume for the mailbox.

GET/mailboxes/:id/warmup

Warm-up status and recent network activity.

  • peerEmail is null for cross-tenant network peers.

Response

{
  "enabled": true, "dailyTarget": 6, "sentToday": 4, "poolSize": 212,
  "recent": [{ "direction": "sent", "peerEmail": null, "status": "replied", "sentAt": "…" }],
  "counts": { "sent7d": 38, "received7d": 41, "replied7d": 12 }
}
GET/warmup/overview

Workspace warm-up summary: pool size, enabled mailboxes, 7-day counts.

Deliverability

GET/reputation/overview

The layered reputation board: every mailbox, sending domain, DKIM domain, and IP with score, band, and DNS auth status.

  • Bands drive action: healthy 80–100 · reduce 60–79 · pause 40–59 · disable <40.

Response

{
  "mailboxes": [{ "id": "…", "email": "…", "score": 86, "band": "healthy", "reasonCodes": [] }],
  "domains":   [{ "id": "…", "domain": "…", "score": 74, "band": "reduce",
                  "spf": "pass", "dkim": "pass", "dmarc": "warn", "spamComplaintRate": null }],
  "ips": [ … ], "dkimDomains": [ … ]
}
GET/reputation/mailbox/:id

One mailbox's score, contributing signals over 7d/30d windows, and recent provider events.

GET/reputation/domain/:id

Sending-domain detail: SPF/DKIM/DMARC results with per-record explanations, Postmaster signals, child DKIM/IP entities.

GET/provider-events

Classified SMTP failures: throttles, blocks, spam rejections, auth failures.

Query parameters

FieldTypeDescription
mailboxIduuidFilter to one mailbox.
kindstringthrottle | block | spam_reject | auth_fail | rate_limit | restricted | invalid_recipient.
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.
GET/seed-tests

Inbox-placement results from your seed inboxes.

Query parameters

FieldTypeDescription
campaignIduuidFilter to one campaign's runs.
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.
POST/spam-check

SpamAssassin-score a draft before sending. Template variables are scored as realistic sample text.

Request body

FieldTypeDescription
subjectstring≤500 chars.
bodyTextstringPlain-text body.
bodyHtmlstringOptional HTML body.

Response

{ "score": 1.2, "threshold": 5, "isSpam": false, "rules": ["HTML_IMAGE_RATIO_02"] }

Inbox & replies

GET/conversations

Reply conversations, most recent activity first.

Query parameters

FieldTypeDescription
dispositionstringFilter, e.g. "interested" for positive replies.
sinceISO dateOnly conversations with activity after this instant.
campaignIduuidFilter to one campaign.
limitintegerPage size. Sensible default and cap apply.
offsetintegerRows to skip. Defaults to 0.

Response

{
  "conversations": [{
    "id": "…", "subject": "…", "disposition": "interested", "lastMessageAt": "…",
    "lead": { "email": "…", "firstName": "…", "lastName": "…" }
  }],
  "total": 26
}
GET/conversations/:id

One conversation with its message events.

Response

{ "conversation": { … }, "events": [ … ] }

Analytics & account

GET/analytics/overview

Workspace funnel totals. Excludes test sends; opens/clicks are directional (bot-filtered verdicts included).

Query parameters

FieldTypeDescription
fromISO dateWindow start.
toISO dateWindow end.
campaignIduuidFilter to one campaign.
mailboxIduuidFilter to one mailbox.

Response

{
  "scheduled": 1400, "attempted": 1290, "sent": 1284, "failed": 4, "bounced": 22,
  "replied": 31, "unsubscribed": 6, "opened": 507, "clicked": 96,
  "verifiedVisits": 41, "engaged": 17,
  "clickVerdicts": { "likelyHuman": 44, "uncertain": 39, "likelyBot": 13 },
  "byCampaign": [ … ]
}
GET/campaigns/:id/analytics

Per-step funnel for one campaign: sent → opened → clicked → replied, plus bounced/failed per step.

GET/account/usage

Plan limits and live usage for the workspace.

Response

{
  "plan": "pro", "billingStatus": "active",
  "limits": { "maxUsers": null, "maxMailboxes": 2000, "maxActiveLeads": null,
              "monthlyEmailQuota": 200000, "maxSendsPerDay": 8000, "maxImportRows": null,
              "aiGenerationsPerMonth": 10, "verificationCreditsIncluded": 60000 },
  "usage": { "activeLeads": 5210, "mailboxes": 8, "sendsToday": 412, "sendsThisMonth": 9310,
             "aiGenerationsUsed": 3, "verificationCreditsBalance": 54210, "verificationCreditsUsed": 5790 }
}

What keys can — and can't — do

API keys cover everything documented above: campaigns, AI generation, leads, verification, deliverability, replies, and analytics. Billing, team management, mailbox connection and credentials, and API-key management itself are never reachable with a key — those stay in the console. Every API action is recorded in your workspace audit log. If the workspace leaves the Pro plan, keys stop working until you upgrade again (they are not deleted).

Prefer working conversationally? The same capabilities are available through the Sendvanta connector for Claude, ChatGPT, and Grok — no code required, on every paid plan.

Support

Questions, missing endpoints, or higher-volume needs? Email support@sendvanta.com. See also our Privacy Policy and Terms of Service.