ConvertKit (Kit)
"ConvertKit (Kit): creator email platform, subscriber management, forms, sequences, broadcasts, tags, automations, commerce, REST API"
ConvertKit (now rebranded as Kit) is a creator-focused email platform with a powerful REST API (v3) at `https://api.convertkit.com/v3/`. It organizes subscribers around tags, forms, and sequences rather than rigid lists. Authentication uses an API key for read operations and an API secret for write operations. The API is designed for simplicity: flat JSON responses, straightforward CRUD, and tag-based subscriber segmentation. Build integrations that leverage tags for dynamic segmentation, sequences for drip campaigns, and webhooks for real-time event processing. ## Key Points - **Use tags for segmentation, not multiple forms**: ConvertKit's power is in tagging. Create one primary form and use tags to segment subscribers by interest, behavior, or source. - **Leverage custom fields for personalization**: Store subscriber metadata (company, role, plan) in custom fields for use in email templates and automations. - **Paginate subscriber lists**: The API returns 50 subscribers per page by default. Always iterate through all pages for complete data exports. - **Use API secret for write operations**: Read endpoints accept api_key, but creates, updates, and deletes require api_secret. Keep secrets server-side only. - **Schedule broadcasts via API**: Use the `send_at` field to schedule broadcasts rather than publishing immediately, allowing time for review. - **Set up webhooks for real-time sync**: Use automation hooks to sync subscriber events to your CRM or database in real time rather than polling. - **Batch tag operations**: When tagging many subscribers, add small delays between requests to respect rate limits (120 requests/minute). - **Using api_secret in client-side code**: The API secret grants full write access to your account. Never expose it in browser JavaScript or mobile apps. - **Creating duplicate tags programmatically**: Always check if a tag exists before creating it. Use `listTags` and match by name to avoid proliferating near-identical tags. - **Polling for subscriber changes**: ConvertKit supports webhooks for subscribe, unsubscribe, and tag events. Use them instead of repeatedly fetching subscriber lists. - **Ignoring the `state` field**: Subscribers can be inactive, bounced, or cancelled. Always filter by state when building send lists or calculating metrics. - **Hardcoding form or sequence IDs**: Store these in configuration. They change when you recreate forms or sequences during redesigns.
skilldb get newsletter-marketing-services-skills/ConvertKit (Kit)Full skill: 306 linesConvertKit (Kit) Creator Email Platform Integration
Core Philosophy
ConvertKit (now rebranded as Kit) is a creator-focused email platform with a powerful REST API (v3) at https://api.convertkit.com/v3/. It organizes subscribers around tags, forms, and sequences rather than rigid lists. Authentication uses an API key for read operations and an API secret for write operations. The API is designed for simplicity: flat JSON responses, straightforward CRUD, and tag-based subscriber segmentation. Build integrations that leverage tags for dynamic segmentation, sequences for drip campaigns, and webhooks for real-time event processing.
Setup
Authentication and Client Configuration
interface ConvertKitConfig {
apiKey: string;
apiSecret: string;
baseUrl?: string;
}
class ConvertKitClient {
private readonly baseUrl: string;
private readonly apiKey: string;
private readonly apiSecret: string;
constructor(config: ConvertKitConfig) {
this.baseUrl = config.baseUrl ?? "https://api.convertkit.com/v3";
this.apiKey = config.apiKey;
this.apiSecret = config.apiSecret;
}
async get<T>(path: string, params?: Record<string, string>): Promise<T> {
const query = new URLSearchParams({ api_key: this.apiKey, ...params });
const res = await fetch(`${this.baseUrl}${path}?${query}`);
if (!res.ok) throw new Error(`ConvertKit GET ${res.status}: ${path}`);
return res.json() as Promise<T>;
}
async post<T>(path: string, body: Record<string, unknown>): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_secret: this.apiSecret, ...body }),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`ConvertKit POST ${res.status}: ${err}`);
}
return res.json() as Promise<T>;
}
async put<T>(path: string, body: Record<string, unknown>): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_secret: this.apiSecret, ...body }),
});
if (!res.ok) throw new Error(`ConvertKit PUT ${res.status}`);
return res.json() as Promise<T>;
}
async delete(path: string): Promise<void> {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_secret: this.apiSecret }),
});
if (!res.ok) throw new Error(`ConvertKit DELETE ${res.status}`);
}
}
const ck = new ConvertKitClient({
apiKey: process.env.CONVERTKIT_API_KEY!,
apiSecret: process.env.CONVERTKIT_API_SECRET!,
});
Verify Account
async function verifyAccount(client: ConvertKitClient): Promise<string> {
const res = await client.get<{ name: string; primary_email_address: string }>(
"/account"
);
console.log(`Authenticated as: ${res.name} (${res.primary_email_address})`);
return res.name;
}
Key Techniques
Subscriber Management
interface CKSubscriber {
id: number;
first_name: string | null;
email_address: string;
state: "active" | "inactive" | "cancelled" | "bounced" | "complained";
created_at: string;
fields: Record<string, string | null>;
}
async function addSubscriberToForm(
client: ConvertKitClient,
formId: number,
email: string,
opts?: { firstName?: string; fields?: Record<string, string>; tags?: number[] }
): Promise<CKSubscriber> {
const res = await client.post<{ subscription: { subscriber: CKSubscriber } }>(
`/forms/${formId}/subscribe`,
{
email,
first_name: opts?.firstName,
fields: opts?.fields,
tags: opts?.tags,
}
);
return res.subscription.subscriber;
}
async function getSubscriber(
client: ConvertKitClient,
subscriberId: number
): Promise<CKSubscriber> {
const res = await client.get<{ subscriber: CKSubscriber }>(
`/subscribers/${subscriberId}`
);
return res.subscriber;
}
async function updateSubscriber(
client: ConvertKitClient,
subscriberId: number,
updates: { firstName?: string; fields?: Record<string, string>; emailAddress?: string }
): Promise<CKSubscriber> {
const res = await client.put<{ subscriber: CKSubscriber }>(
`/subscribers/${subscriberId}`,
{
first_name: updates.firstName,
fields: updates.fields,
email_address: updates.emailAddress,
}
);
return res.subscriber;
}
async function unsubscribe(
client: ConvertKitClient,
email: string
): Promise<void> {
await client.put("/unsubscribe", { email });
}
Tags and Segmentation
interface CKTag {
id: number;
name: string;
created_at: string;
}
async function listTags(client: ConvertKitClient): Promise<CKTag[]> {
const res = await client.get<{ tags: CKTag[] }>("/tags");
return res.tags;
}
async function createTag(client: ConvertKitClient, name: string): Promise<CKTag> {
const res = await client.post<{ tag: CKTag }>("/tags", {
tag: { name },
});
return res.tag;
}
async function tagSubscriber(
client: ConvertKitClient,
tagId: number,
email: string
): Promise<void> {
await client.post(`/tags/${tagId}/subscribe`, { email });
}
async function removeTagFromSubscriber(
client: ConvertKitClient,
tagId: number,
subscriberId: number
): Promise<void> {
await client.delete(`/subscribers/${subscriberId}/tags/${tagId}`);
}
async function getSubscribersByTag(
client: ConvertKitClient,
tagId: number,
page = 1
): Promise<{ subscribers: CKSubscriber[]; totalPages: number }> {
const res = await client.get<{
total_subscribers: number;
page: number;
total_pages: number;
subscribers: CKSubscriber[];
}>(`/tags/${tagId}/subscriptions`, { page: String(page) });
return { subscribers: res.subscribers, totalPages: res.total_pages };
}
Sequences and Broadcasts
interface CKSequence {
id: number;
name: string;
hold: boolean;
created_at: string;
}
async function listSequences(client: ConvertKitClient): Promise<CKSequence[]> {
const res = await client.get<{ courses: CKSequence[] }>("/sequences");
return res.courses;
}
async function addSubscriberToSequence(
client: ConvertKitClient,
sequenceId: number,
email: string,
opts?: { firstName?: string; fields?: Record<string, string>; tags?: number[] }
): Promise<CKSubscriber> {
const res = await client.post<{ subscription: { subscriber: CKSubscriber } }>(
`/sequences/${sequenceId}/subscribe`,
{ email, first_name: opts?.firstName, fields: opts?.fields, tags: opts?.tags }
);
return res.subscription.subscriber;
}
async function createBroadcast(
client: ConvertKitClient,
broadcast: {
subject: string;
content: string;
description?: string;
public?: boolean;
publishedAt?: string;
sendAt?: string;
emailLayoutTemplate?: string;
thumbnailUrl?: string;
}
): Promise<{ id: number }> {
const res = await client.post<{ broadcast: { id: number } }>("/broadcasts", {
subject: broadcast.subject,
content: broadcast.content,
description: broadcast.description,
public: broadcast.public ?? false,
published_at: broadcast.publishedAt,
send_at: broadcast.sendAt,
email_layout_template: broadcast.emailLayoutTemplate,
thumbnail_url: broadcast.thumbnailUrl,
});
return res.broadcast;
}
Webhooks
async function createWebhook(
client: ConvertKitClient,
targetUrl: string,
event: "subscriber.subscriber_activate" | "subscriber.subscriber_unsubscribe" | "subscriber.form_subscribe" | "subscriber.tag_add" | "subscriber.tag_remove" | "purchase.purchase_create"
): Promise<{ id: number }> {
const res = await client.post<{ rule: { id: number } }>("/automations/hooks", {
target_url: targetUrl,
event: { name: event },
});
return { id: res.rule.id };
}
async function deleteWebhook(
client: ConvertKitClient,
ruleId: number
): Promise<void> {
await client.delete(`/automations/hooks/${ruleId}`);
}
Best Practices
- Use tags for segmentation, not multiple forms: ConvertKit's power is in tagging. Create one primary form and use tags to segment subscribers by interest, behavior, or source.
- Leverage custom fields for personalization: Store subscriber metadata (company, role, plan) in custom fields for use in email templates and automations.
- Paginate subscriber lists: The API returns 50 subscribers per page by default. Always iterate through all pages for complete data exports.
- Use API secret for write operations: Read endpoints accept api_key, but creates, updates, and deletes require api_secret. Keep secrets server-side only.
- Schedule broadcasts via API: Use the
send_atfield to schedule broadcasts rather than publishing immediately, allowing time for review. - Set up webhooks for real-time sync: Use automation hooks to sync subscriber events to your CRM or database in real time rather than polling.
- Batch tag operations: When tagging many subscribers, add small delays between requests to respect rate limits (120 requests/minute).
Anti-Patterns
- Using api_secret in client-side code: The API secret grants full write access to your account. Never expose it in browser JavaScript or mobile apps.
- Creating duplicate tags programmatically: Always check if a tag exists before creating it. Use
listTagsand match by name to avoid proliferating near-identical tags. - Polling for subscriber changes: ConvertKit supports webhooks for subscribe, unsubscribe, and tag events. Use them instead of repeatedly fetching subscriber lists.
- Ignoring the
statefield: Subscribers can be inactive, bounced, or cancelled. Always filter by state when building send lists or calculating metrics. - Hardcoding form or sequence IDs: Store these in configuration. They change when you recreate forms or sequences during redesigns.
- Sending broadcasts without testing: Always create broadcasts as drafts first, verify content through the UI or a test send, then schedule programmatically.
Install this skill directly: skilldb add newsletter-marketing-services-skills
Related Skills
Beehiiv
"Beehiiv: newsletter platform API, subscriber management, publications, posts/campaigns, automations, referral program, analytics, REST API"
Buttondown
"Buttondown: minimal newsletter API, subscribers, emails/drafts, tags, automations, webhooks, Markdown-first, REST API"
Loops
"Loops: email for SaaS, transactional + marketing, contacts, events, loops (automations), API sends, webhooks"
Mailchimp
"Mailchimp: email marketing platform API, audience lists, campaigns, automations, segments, templates, analytics, REST API v3"
MailerLite
"MailerLite: email marketing platform API, subscriber groups, campaigns, automations, forms, analytics, REST API v2"
Resend
"Resend: developer-first email API, transactional and marketing emails, React Email templates, domains, audiences, broadcasts, REST API"