Followkite

Followkite API

A JSON REST API at https://followkite.com/api/v1. It does what the app does, with the same validation, plan limits, do-not-contact rules and activity log, so nothing you send through it can bypass the safety controls.

Authentication and keys

Create a key in Integrations → API keys. Send it as Authorization: Bearer fk_live_…. A key belongs to one workspace and acts with the role of the person who created it, narrowed to its scopes. If that person leaves the workspace, the key stops working. Browser cookies are never accepted on /api/v1.

ScopeAllows
contacts:readRead contacts, lists and custom fields
contacts:writeCreate, update, delete and verify contacts; add them to lists
campaigns:readRead campaigns, their results and enrollments
campaigns:writeLaunch, pause and resume campaigns
suppressions:readRead the do-not-contact list
suppressions:writeAdd addresses and domains to it
inbox:readRead Unibox conversations
reports:readRead reports and deliverability
sendSend transactional email and read its messages and templates

Errors and limits

Errors are JSON: {"error": "invalid_input", "field": "…"}. Common codes: invalid_api_key (401), insufficient_scope (403, with required), ip_not_allowed (403), plan_limit_… (403), rate_limited (429). Each key may make 120 requests a minute; see the X-RateLimit-* headers.

Endpoints

MethodPathWhat it doesScope
GET/meThe workspace and key in use—
GET/contactsList contacts. Query: q, list, status (active, suppressed, valid, risky, invalid, unverified), limit, offsetcontacts:read
POST/contactsCreate a contact: email, firstName, lastName, company, title, customFields, listIdscontacts:write
GET/contacts/{id}One contact with its recent emailscontacts:read
PATCH/contacts/{id}Update a contactcontacts:write
DELETE/contacts/{id}Delete a contactcontacts:write
POST/contacts/verifyVerify up to 200 contacts: contactIds, or none for the oldest unverifiedcontacts:write
GET/listsContact listscontacts:read
POST/lists/{id}/membersAdd contacts to a list: contactIdscontacts:write
GET/campaignsCampaigns with countscampaigns:read
GET/campaigns/{id}/statsResults by email and A/B versioncampaigns:read
POST/campaigns/{id}/pausePause a campaign (also launch, resume)campaigns:write
GET/suppressionsThe do-not-contact listsuppressions:read
POST/suppressionsAdd addressessuppressions:write
GET/uniboxConversations. Query: view (leads, interested, all, unread, auto, bounces, archived)inbox:read
GET/reportsSending reportreports:read
GET/deliverabilityInbox health, domain DNS and blocklists, safety settingsreports:read
POST/sendSend one transactional emailsend
POST/send/batchSend to up to 1,000 recipients with personal datasend
GET/messagesTransactional activity. Query: status, to, tag, batch, offsetsend
GET/messages/{id}One message with its eventssend
POST/messages/{id}/cancelCancel a scheduled or waiting messagesend
GET/templatesTransactional templatessend
const res = await fetch('https://followkite.com/api/v1/contacts', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.FOLLOWKITE_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'ann@clinic.com', firstName: 'Ann', company: 'Bright Smiles', listIds: ['…'] }),
});
if (res.status === 429) await new Promise((r) => setTimeout(r, Number(res.headers.get('retry-after')) * 1000));

Transactional email

Send receipts, password resets and alerts from your app. Set up your sending domain under Transactional email → Settings (or connect your own Amazon SES or SMTP relay there), and send from a domain you have verified on the Domains page.

curl https://followkite.com/api/v1/send \
  -H "Authorization: Bearer fk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-receipt" \
  -d '{
    "from": "Shop <orders@yourdomain.com>",
    "to": "ann@example.com",
    "template": "order-receipt",
    "data": { "name": "Ann", "order": { "id": "1042" }, "items": [{ "title": "Cap", "qty": 2 }] },
    "tags": ["receipt"],
    "metadata": { "orderId": "1042" }
  }'
HTTP/1.1 202 Accepted
{ "id": "7c1e…", "status": "SENT", "to": "ann@example.com", "subject": "Receipt #1042",
  "tags": ["receipt"], "metadata": { "orderId": "1042" }, "template": { "id": "…", "version": 3 }, "duplicate": false }

Fields

Batch: POST /send/batch takes the same fields plus recipients: [{ to, data, tags, metadata }] (up to 1,000). Each recipient is checked on its own; the response lists accepted and rejected rows. Batches go out from the queue within a minute; single sends are attempted at once.

Templates

Hi {{name|there}},                       variable with a fallback
Order {{order.id}}                        nested fields
{{#if paid}}Paid{{else}}Due{{/if}}        conditionals ({{#unless}} too)
{{#each items}}{{this.title}} × {{this.qty}}{{/each}}   loops; {{@index}} is the position
{{{raw_html}}}                            not escaped (HTML only)

Every save is a new version; restore an old one from the editor. Set text direction to right-to-left for Arabic or Urdu.

Statuses

SCHEDULED, QUEUED, SENT (accepted by your provider), DELIVERED (SES confirmed), DEFERRED (retrying), BOUNCED, COMPLAINED, DROPPED (address suppressed), FAILED, CANCELLED, SANDBOX. A send whose outcome is unknown is marked FAILED with DELIVERY_UNCERTAIN and never retried, so nobody gets two password resets.

Limits and safety

Your plan sets a monthly allowance. In a workspace's first week, transactional email is limited to 200 a day. Sending pauses automatically when bounces reach 5% or spam complaints 0.1% over 100 or more emails in 7 days. Hard bounces and complaints are added to the do-not-contact list.

Webhooks

Add an endpoint in Integrations. Each event is an HTTPS POST signed with X-Followkite-Signature: t=…,v1=… (HMAC-SHA256 of t.body with your signing secret). Failed deliveries are retried with backoff. Events: reply.received, lead.interested, contact.unsubscribed, contact.bounced, campaign.completed, and for transactional email email.sent, email.delivered, email.deferred, email.bounced, email.complained, email.dropped, email.failed, email.opened, email.clicked. Slack alerts for interested leads are set up separately in Integrations.

Questions

How do I authenticate with the Followkite API?

Create an API key under Integrations → API keys (workspace owners and admins). Send it on every request as Authorization: Bearer fk_live_…. Keys are shown once, stored only as a hash, limited to the scopes you choose, and can be limited to IP addresses and given an expiry date.

What are the API rate limits?

Each key can make 120 requests a minute. Every response has X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers; over the limit you get 429 with Retry-After.

How do I avoid sending a transactional email twice?

Send an Idempotency-Key header (or idempotencyKey in the body). A second request with the same key returns the first message with "duplicate": true and sends nothing.

Which providers deliver transactional email?

Followkite sending by default: add the three DNS records shown under Transactional email → Settings → Sending domains, and delivery, bounce and complaint events are included. You can instead use your own Amazon SES account, or any SMTP relay such as Postmark, Mailgun, Brevo or SendGrid. Connect one in Transactional email → Settings. With SES, delivery, bounce and complaint events come back through an SNS subscription; SMTP relays only report that they accepted the message.

Do campaign unsubscribes block transactional email?

No. Receipts and password resets are not marketing, so only addresses that hard-bounced or reported spam are skipped. Those messages get status DROPPED.

Last updated .