@richardpickett/kit-sdk
v1.2.6
Published
Kit sdk wrapper (formerly Convert kit)
Readme
Kit SDK
Description
This SDK provides a TypeScript interface to the Kit API, allowing developers to easily integrate with Kit's platform features.
Features
- API Key and OAuth authentication
- Automatic token refresh
- Pagination support
- Error handling
- Rate limiting support
- TypeScript type definitions
Support
For issues or questions, please open an issue in the GitLab repository.
License
MIT
Classes and Components
The Kit API SDK consists of the following key classes:
Core Classes
- KitSdk: Main class that handles authentication, requests, and provides methods for all API endpoints.
- BaseApiData: Abstract base class for API resources with CRUD operations. Provides common functionality for all data models.
- BaseStatData: Base class for statistical data with date range functionality. Used by EmailStatData and GrowthStatData.
- PaginatedResults: Handles paginated API responses with navigation methods (nextPage, previousPage, goToPage).
Data Models
- AccountData: Represents account information from the /v4/account endpoint.
- ColorData: Manages account colors from the /v4/account/colors endpoint.
- CreatorProfileData: Represents creator profile from the /v4/account/creator_profile endpoint.
- EmailStatData: Handles email statistics from the /v4/account/email_stats endpoint.
- GrowthStatData: Manages subscriber growth stats from the /v4/account/growth_stats endpoint.
- BroadcastData: Represents broadcast information and operations.
- CustomFieldData: Manages custom fields for subscribers.
- EmailTemplateData: Handles email templates for broadcasts and sequences.
- FormData: Handles forms for subscriber sign-ups.
- TagData: Manages tags for categorizing subscribers.
- SequenceData: Manages sequences for automated email campaigns.
- SegmentData: Handles segments for targeting specific subscriber groups.
- WebhookData: Manages webhooks for receiving real-time event notifications.
Error Handling
- KitApiError: Base error class for all Kit API errors.
- KitApiAuthenticationError: Base class for authentication errors.
- KitApiUnauthorizedError: Error for 401 unauthorized responses.
- KitApiForbiddenError: Error for 403 forbidden responses.
- KitApiNotFoundError: Error for 404 not found responses.
- KitApiValidationError: Error for 422 validation errors.
- KitApiRateLimitError: Error for 429 rate limit exceeded responses.
- KitApiServerError: Error for 500-level server errors.
- KitApiNetworkError: Error for network connectivity issues.
- KitApiTimeoutError: Error for request timeouts.
- KitApiRetryError: Error for failed retry attempts.
Installation
npm install @richardpickett/kit-sdkAuthentication
The Kit API SDK supports two authentication methods:
API Key Authentication
The simplest way to authenticate with the Kit API is using your API key:
import { KitSdk } from "@richardpickett/kit-sdk";
// Initialize with API key
const api = new KitSdk({
apiKey: "your_api_key",
});
// Check if authenticated
api
.isAuthenticated()
.then((isAuthenticated) => {
console.log(`Authenticated: ${isAuthenticated}`);
})
.catch((error) => {
console.error("Authentication check failed:", error.message);
});OAuth Authentication
For applications that need to access the Kit API on behalf of users, OAuth authentication is supported:
import { KitSdk } from "@richardpickett/kit-sdk";
// Initialize with OAuth credentials
const api = new KitSdk({
clientId: "your_client_id",
clientSecret: "your_client_secret",
accessToken: "initial_access_token",
refreshToken: "initial_refresh_token",
});
// Make API calls
api
.getAccount()
.then((account) => {
console.log(`Logged in as: ${account.name}`);
})
.catch((error) => {
console.error("Error:", error.message);
});Handling Token Refresh
When using OAuth, you can set up automatic token refresh handling:
import { KitSdk } from "@richardpickett/kit-sdk";
const api = new KitSdk({
clientId: "your_client_id",
clientSecret: "your_client_secret",
accessToken: "initial_access_token",
refreshToken: "initial_refresh_token",
});
// Set up token refresh callback
api.setTokenRefreshCallback((tokenData) => {
// Store the new tokens securely
console.log("Access token refreshed");
console.log(`New access token: ${tokenData.accessToken}`);
console.log(`New refresh token: ${tokenData.refreshToken}`);
// You would typically store these tokens in your database or secure storage
saveTokensToStorage(tokenData.accessToken, tokenData.refreshToken);
});
// Function to save tokens (implementation depends on your application)
function saveTokensToStorage(accessToken, refreshToken) {
// Save tokens to secure storage
}Error Handling for Authentication
Handle authentication errors properly:
import { KitSdk, KitApiUnauthorizedError } from "@richardpickett/kit-sdk";
const api = new KitSdk({
apiKey: "possibly_invalid_api_key",
});
api
.getAccount()
.then((account) => {
console.log(`Logged in as: ${account.name}`);
})
.catch((error) => {
if (error instanceof KitApiUnauthorizedError) {
console.error("Authentication failed: Invalid API key or token");
// Prompt user to re-authenticate or provide a valid API key
} else {
console.error("Error:", error.message);
}
});Creating and Managing Data Objects
The Kit API SDK uses a consistent pattern for creating and managing data objects. All data objects extend the BaseApiData class, which provides standard CRUD operations.
Example: Creating a Subscriber
// Create a new subscriber
api
.createSubscriber({
email_address: "[email protected]",
first_name: "John",
fields: {
company: "Acme Inc.",
role: "Developer",
},
tags: [123, 456], // Tag IDs to apply
})
.then((response) => {
console.log("Subscriber created:", response);
console.log("Subscriber ID:", response.subscriber.id);
})
.catch((error) => {
console.error("Error creating subscriber:", error.message);
});Working with Data Objects
Many objects in the Kit API follow a similar pattern, where you:
- Create an instance of the data object
- Set properties on the object
- Save the object to persist changes
Here's an example with a Tag:
// Create a new tag instance
const tag = new TagData(
{
name: "New Product Launch",
},
api
);
// Save the tag to create it on the server
tag
.save()
.then((savedTag) => {
console.log("Tag created with ID:", savedTag.id);
// Update the tag
savedTag.name = "Product Launch 2025";
return savedTag.save();
})
.then((updatedTag) => {
console.log("Tag updated:", updatedTag.name);
// Delete the tag if needed
// return updatedTag.delete();
})
.catch((error) => {
console.error("Error:", error.message);
});Example with a Custom Field:
// Create a new custom field
const customField = new CustomFieldData(
{
label: "Company Size",
key: "company_size",
data_type: "text",
},
api
);
// Save the custom field
customField
.save()
.then((savedField) => {
console.log("Custom field created with ID:", savedField.id);
})
.catch((error) => {
console.error("Error creating custom field:", error.message);
});Example with a Broadcast:
// Create a new broadcast
const broadcast = new BroadcastData(
{
name: "Weekly Newsletter",
subject: "Your Weekly Update",
body_html: "<h1>Weekly Newsletter</h1><p>Here's what's new this week...</p>",
from_name: "Your Company",
from_email: "[email protected]",
},
api
);
// Save the broadcast
broadcast
.save()
.then((savedBroadcast) => {
console.log("Broadcast created with ID:", savedBroadcast.id);
// Schedule the broadcast
savedBroadcast.scheduled_for = "2025-04-01T09:00:00Z";
return savedBroadcast.save();
})
.then((scheduledBroadcast) => {
console.log("Broadcast scheduled for:", scheduledBroadcast.scheduled_for);
})
.catch((error) => {
console.error("Error:", error.message);
});Other Data Objects
The same pattern works for all these data objects:
- Broadcasts: Create and manage email broadcasts
- CustomFields: Define custom fields for subscribers
- EmailTemplates: Create and manage email templates
- Forms: Create and manage opt-in forms
- Purchases: Track product purchases
- Segments: Create and manage subscriber segments
- Sequences: Create and manage email sequences
- Tags: Create and manage subscriber tags
- Webhooks: Set up webhooks for real-time notifications
Retrieving Single Objects
// Retrieve a subscriber by ID
api
.getSubscriber(123)
.then((subscriber) => {
console.log("Subscriber:", subscriber);
})
.catch((error) => {
console.error("Error retrieving subscriber:", error.message);
});Listing and Searching Objects
The Kit API SDK provides methods for listing and searching objects with filtering, sorting, and pagination options. These methods follow a consistent naming pattern across all resource types.
Basic Listing with Filtering
You can retrieve lists of objects with optional filtering parameters:
// List all subscribers
api
.getSubscribers()
.then((response) => {
console.log(`Found ${response.subscribers.length} subscribers`);
console.log("Pagination info:", response.pagination);
})
.catch((error) => {
console.error("Error listing subscribers:", error);
});
// List subscribers with filtering
api
.getSubscribers({
status: "active",
created_after: "2025-01-01T00:00:00Z",
})
.then((response) => {
console.log(`Found ${response.subscribers.length} active subscribers created after Jan 1, 2025`);
})
.catch((error) => {
console.error("Error listing filtered subscribers:", error);
});This pattern applies to all resource types that support listing:
- Broadcasts:
getBroadcasts(options) - Custom Fields:
getCustomFields(options) - Email Templates:
getEmailTemplates(options) - Forms:
getForms(options) - Purchases:
getPurchases(options) - Segments:
getSegments(options) - Sequences:
getSequences(options) - Tags:
getTags(options) - Webhooks:
getWebhooks(options)
Sorting Options
Many list endpoints support sorting options:
// List tags sorted by name in ascending order
api
.getTags({
sort: "name",
direction: "asc",
})
.then((response) => {
console.log("Tags sorted alphabetically:", response.tags);
})
.catch((error) => {
console.error("Error listing sorted tags:", error);
});
// List subscribers sorted by created_at in descending order (newest first)
api
.getSubscribers({
sort: "created_at",
direction: "desc",
})
.then((response) => {
console.log("Newest subscribers:", response.subscribers);
})
.catch((error) => {
console.error("Error listing sorted subscribers:", error);
});Advanced Filtering
Different resource types support specific filtering options:
// Get broadcasts filtered by status
api
.getBroadcasts({
status: "sent",
})
.then((response) => {
console.log("Sent broadcasts:", response.broadcasts);
})
.catch((error) => {
console.error("Error listing broadcasts:", error);
});
// Get subscribers with multiple filters
api
.getSubscribers({
status: "active",
created_after: "2025-01-01T00:00:00Z",
tag_ids: ["tag-123", "tag-456"], // Tag IDs to apply
search: "example.com", // Search in email addresses
})
.then((response) => {
console.log("Filtered subscribers:", response.subscribers);
})
.catch((error) => {
console.error("Error with advanced filtering:", error);
});Working with Pagination
For endpoints that return large datasets, the SDK provides pagination support:
// Basic pagination with per_page parameter
api
.getSubscribers({
per_page: 50, // Get 50 subscribers per page
})
.then((response) => {
console.log(`Showing ${response.subscribers.length} of ${response.pagination.total} subscribers`);
console.log("Next page cursor:", response.pagination.end_cursor);
})
.catch((error) => {
console.error("Error with pagination:", error);
});
// Getting a specific page using cursor
api
.getSubscribers({
per_page: 50,
after: "cursor-from-previous-page", // Get the next page
})
.then((response) => {
console.log("Next page of subscribers:", response.subscribers);
})
.catch((error) => {
console.error("Error getting next page:", error);
});Using Paginated Methods
For easier pagination handling, use the paginated versions of list methods:
// Get a paginated result object
api
.getSubscribersPaginated({
per_page: 25,
status: "active",
})
.then((paginatedResult) => {
// Access the current page data
console.log("Current page subscribers:", paginatedResult.data);
// Check if there are more pages
if (paginatedResult.hasNextPage()) {
// Get the next page
return paginatedResult.nextPage();
}
return null;
})
.then((nextPageResult) => {
if (nextPageResult) {
console.log("Next page subscribers:", nextPageResult.data);
} else {
console.log("No more pages available");
}
})
.catch((error) => {
console.error("Error with paginated results:", error);
});This pattern applies to all resource types with the naming convention getResourcesPaginated():
getBroadcastsPaginated(options)getCustomFieldsPaginated(options)getEmailTemplatesPaginated(options)getFormsPaginated(options)getPurchasesPaginated(options)getSegmentsPaginated(options)getSequencesPaginated(options)getTagsPaginated(options)getWebhooksPaginated(options)
Error Handling Examples
The Kit API SDK provides comprehensive error handling for various error scenarios. All errors extend from the base KitApiError class, allowing you to catch specific error types or handle all API errors with a single catch.
Handling Not Found Errors
When trying to retrieve an object that doesn't exist, the SDK will throw a KitApiNotFoundError. Here's how to handle this scenario:
import { KitSdk, KitApiNotFoundError } from "@richardpickett/kit-sdk";
const api = new KitSdk({
apiKey: "your_api_key",
});
// Example: Trying to get a non-existent subscriber
api
.getSubscriber("non-existent-id")
.then((subscriber) => {
console.log("Subscriber found:", subscriber);
})
.catch((error) => {
if (error instanceof KitApiNotFoundError) {
console.log("Subscriber not found:", error.message);
// Handle the not found case specifically
} else {
console.error("An unexpected error occurred:", error);
}
});This pattern applies to all resource types that can be retrieved by ID, including:
- Broadcasts:
getBroadcast(id) - Custom Fields:
getCustomField(id) - Email Templates:
getEmailTemplate(id) - Forms:
getForm(id) - Purchases:
getPurchase(id) - Segments:
getSegment(id) - Sequences:
getSequence(id) - Tags:
getTag(id) - Webhooks:
getWebhook(id)
Using Try/Catch with Async/Promise Chains
While the SDK uses promise chaining internally, you can also use try/catch with promise chains:
// Using promise chaining with multiple catches for different error types
api
.getTag("non-existent-tag")
.then((tag) => {
console.log("Tag found:", tag);
})
.catch((error) => {
if (error instanceof KitApiNotFoundError) {
console.log("Tag not found");
// Maybe create the tag instead
return api.createTag({ name: "New Tag" });
}
throw error; // Re-throw other errors
})
.then((newTag) => {
if (newTag) {
console.log("Created new tag:", newTag);
}
})
.catch((error) => {
console.error("Error creating tag:", error);
});Handling Multiple Error Types
The SDK provides specific error classes for different error scenarios:
import {
KitSdk,
KitApiNotFoundError,
KitApiUnauthorizedError,
KitApiValidationError,
KitApiRateLimitError,
} from "@richardpickett/kit-sdk";
api
.getCustomField("some-id")
.then((customField) => {
// Process the custom field
})
.catch((error) => {
if (error instanceof KitApiNotFoundError) {
console.log("Custom field not found");
} else if (error instanceof KitApiUnauthorizedError) {
console.log("Authentication error. Please check your API key");
} else if (error instanceof KitApiValidationError) {
console.log("Validation error:", error.validationErrors);
} else if (error instanceof KitApiRateLimitError) {
console.log("Rate limit exceeded. Retry after:", error.retryAfter);
} else {
console.error("Unexpected error:", error);
}
});Working with Statistics and Reporting
The Kit API SDK provides classes for working with statistical data, including email performance metrics and subscriber growth statistics. These classes extend the BaseStatData class, which provides date range functionality.
Email Statistics
You can retrieve email statistics for your account with optional date range filtering:
// Get email statistics for the default date range (last 30 days)
api
.getEmailStats()
.then((emailStats) => {
console.log("Email statistics:", emailStats);
console.log("Total sent:", emailStats.sent);
console.log("Total opened:", emailStats.opened);
console.log("Total clicked:", emailStats.clicked);
console.log("Open rate:", emailStats.open_rate);
console.log("Click rate:", emailStats.click_rate);
})
.catch((error) => {
console.error("Error retrieving email statistics:", error);
});
// Get email statistics with a specific date range
api
.getEmailStats({
starting_date: "2025-01-01",
ending_date: "2025-01-31",
})
.then((emailStats) => {
console.log("Email statistics for January 2025:");
console.log("Total sent:", emailStats.sent);
console.log("Total opened:", emailStats.opened);
console.log("Total clicked:", emailStats.clicked);
console.log("Open rate:", emailStats.open_rate);
console.log("Click rate:", emailStats.click_rate);
})
.catch((error) => {
console.error("Error retrieving email statistics:", error);
});Growth Statistics
You can retrieve subscriber growth statistics with optional date range filtering:
// Get growth statistics for the default date range (last 30 days)
api
.getGrowthStats()
.then((growthStats) => {
console.log("Growth statistics:", growthStats);
console.log("New subscribers:", growthStats.new_subscribers);
console.log("Cancellations:", growthStats.cancellations);
console.log("Net growth:", growthStats.net_growth);
console.log("Growth rate:", growthStats.growth_rate);
})
.catch((error) => {
console.error("Error retrieving growth statistics:", error);
});
// Get growth statistics with a specific date range
api
.getGrowthStats({
starting_date: "2025-01-01",
ending_date: "2025-03-31",
})
.then((growthStats) => {
console.log("Growth statistics for Q1 2025:");
console.log("New subscribers:", growthStats.new_subscribers);
console.log("Cancellations:", growthStats.cancellations);
console.log("Net growth:", growthStats.net_growth);
console.log("Growth rate:", growthStats.growth_rate);
})
.catch((error) => {
console.error("Error retrieving growth statistics:", error);
});Working with Date Ranges
The BaseStatData class provides date range functionality for all statistical data classes. You can specify date ranges in several formats:
// Using ISO date strings (YYYY-MM-DD)
api
.getEmailStats({
starting_date: "2025-01-01",
ending_date: "2025-01-31",
})
.then((emailStats) => {
console.log("Email statistics for January 2025:", emailStats);
});
// Using JavaScript Date objects
const startDate = new Date(2025, 0, 1); // January 1, 2025
const endDate = new Date(2025, 2, 31); // March 31, 2025
api
.getGrowthStats({
starting_date: startDate,
ending_date: endDate,
})
.then((growthStats) => {
console.log("Growth statistics for Q1 2025:", growthStats);
});
// Using relative time periods
api
.getEmailStats({
last_days: 7, // Last 7 days
})
.then((emailStats) => {
console.log("Email statistics for last 7 days:", emailStats);
});
api
.getGrowthStats({
last_months: 3, // Last 3 months
})
.then((growthStats) => {
console.log("Growth statistics for last 3 months:", growthStats);
});Broadcast Statistics
You can also retrieve statistics for specific broadcasts:
// Get statistics for a specific broadcast
api
.getBroadcastStats("broadcast-123")
.then((broadcastStats) => {
console.log("Broadcast statistics:", broadcastStats);
console.log("Total recipients:", broadcastStats.recipients);
console.log("Total opened:", broadcastStats.opened);
console.log("Total clicked:", broadcastStats.clicked);
console.log("Open rate:", broadcastStats.open_rate);
console.log("Click rate:", broadcastStats.click_rate);
console.log("Unsubscribes:", broadcastStats.unsubscribes);
})
.catch((error) => {
console.error("Error retrieving broadcast statistics:", error);
});Special Operations
The Kit API SDK provides special operations for common tasks that don't fit the standard CRUD pattern. These operations are designed to make common tasks easier and more efficient.
Tagging and Untagging Subscribers
You can add and remove tags from subscribers using dedicated methods:
// Tag a subscriber with one or more tags
api
.tagSubscriber("subscriber-123", ["tag-456", "tag-789"])
.then((response) => {
console.log("Subscriber tagged successfully:", response);
console.log("Applied tags:", response.applied_tags);
})
.catch((error) => {
console.error("Error tagging subscriber:", error.message);
});
// Untag a subscriber
api
.untagSubscriber("subscriber-123", ["tag-456"])
.then((response) => {
console.log("Subscriber untagged successfully:", response);
console.log("Removed tags:", response.removed_tags);
})
.catch((error) => {
console.error("Error untagging subscriber:", error.message);
});
// Batch tag multiple subscribers
api
.batchTagSubscribers(["subscriber-123", "subscriber-456"], ["tag-789"])
.then((response) => {
console.log("Subscribers tagged successfully:", response);
console.log("Affected subscribers:", response.affected_subscribers);
})
.catch((error) => {
console.error("Error batch tagging subscribers:", error.message);
});Adding Subscribers to Sequences
You can add subscribers to email sequences:
// Add a subscriber to a sequence
api
.addSubscriberToSequence("subscriber-123", "sequence-456")
.then((response) => {
console.log("Subscriber added to sequence:", response);
console.log("Sequence ID:", response.sequence_id);
console.log("Subscriber ID:", response.subscriber_id);
})
.catch((error) => {
console.error("Error adding subscriber to sequence:", error.message);
});
// Add multiple subscribers to a sequence
api
.addSubscribersToSequence(["subscriber-123", "subscriber-456"], "sequence-789")
.then((response) => {
console.log("Subscribers added to sequence:", response);
console.log("Affected subscribers:", response.affected_subscribers);
})
.catch((error) => {
console.error("Error adding subscribers to sequence:", error.message);
});
// Remove a subscriber from a sequence
api
.removeSubscriberFromSequence("subscriber-123", "sequence-456")
.then((response) => {
console.log("Subscriber removed from sequence:", response);
})
.catch((error) => {
console.error("Error removing subscriber from sequence:", error.message);
});Sending One-off Broadcasts
You can send one-off broadcasts to specific subscribers:
// Send a one-off broadcast to specific subscribers
api
.sendBroadcast({
name: "Special Offer",
subject: "Limited Time Offer Just for You",
body_html: "<h1>Special Offer</h1><p>Here's your exclusive discount code: SPECIAL20</p>",
from_name: "Your Company",
from_email: "[email protected]",
to_subscribers: ["subscriber-123", "subscriber-456"],
})
.then((response) => {
console.log("Broadcast sent:", response);
console.log("Broadcast ID:", response.broadcast_id);
console.log("Recipients count:", response.recipients_count);
})
.catch((error) => {
console.error("Error sending broadcast:", error.message);
});
// Send a broadcast to subscribers with specific tags
api
.sendBroadcast({
name: "VIP Announcement",
subject: "Important News for VIP Members",
body_html: "<h1>VIP Announcement</h1><p>We have exciting news for our VIP members!</p>",
from_name: "Your Company",
from_email: "[email protected]",
to_tags: ["tag-vip", "tag-premium"],
})
.then((response) => {
console.log("Broadcast sent to tagged subscribers:", response);
})
.catch((error) => {
console.error("Error sending broadcast:", error.message);
});Webhook Management
You can manage webhooks for real-time event notifications:
// Create a webhook for subscriber events
api
.createWebhook({
url: "https://your-app.example.com/webhooks/subscribers",
events: ["subscriber.created", "subscriber.updated", "subscriber.deleted"],
})
.then((webhook) => {
console.log("Webhook created:", webhook);
console.log("Webhook ID:", webhook.id);
console.log("Subscribed events:", webhook.events);
})
.catch((error) => {
console.error("Error creating webhook:", error.message);
});
// Test a webhook
api
.testWebhook("webhook-123")
.then((response) => {
console.log("Webhook test sent:", response);
console.log("Test status:", response.status);
console.log("Test message:", response.message);
})
.catch((error) => {
console.error("Error testing webhook:", error.message);
});
// Delete a webhook
api
.deleteWebhook("webhook-123")
.then((response) => {
console.log("Webhook deleted:", response);
})
.catch((error) => {
console.error("Error deleting webhook:", error.message);
});Custom API Requests
For advanced use cases, you can make custom API requests:
// Make a custom GET request
api
.request("GET", "/v4/custom/endpoint")
.then((response) => {
console.log("Custom API response:", response);
})
.catch((error) => {
console.error("Error making custom request:", error.message);
});
// Make a custom POST request with data
api
.request("POST", "/v4/custom/endpoint", {
custom_field: "custom_value",
another_field: 123,
})
.then((response) => {
console.log("Custom API response:", response);
})
.catch((error) => {
console.error("Error making custom request:", error.message);
});Managing Custom Fields for Subscribers
You can create, update, and manage custom fields for subscribers:
// Create a new custom field
api
.createCustomField({
label: "Company Size",
key: "company_size",
data_type: "text",
})
.then((customField) => {
console.log("Custom field created:", customField);
console.log("Custom field ID:", customField.id);
console.log("Custom field key:", customField.key);
})
.catch((error) => {
console.error("Error creating custom field:", error.message);
});
// Update a custom field
api
.updateCustomField("custom-field-123", {
label: "Company Size (Employees)",
description: "Number of employees at the company",
})
.then((customField) => {
console.log("Custom field updated:", customField);
})
.catch((error) => {
console.error("Error updating custom field:", error.message);
});
// Set custom field values for a subscriber
api
.updateSubscriber("subscriber-123", {
fields: {
company_size: "100-500",
industry: "Technology",
referral_source: "Google",
},
})
.then((subscriber) => {
console.log("Subscriber updated with custom fields:", subscriber);
console.log("Custom fields:", subscriber.fields);
})
.catch((error) => {
console.error("Error updating subscriber custom fields:", error.message);
});
// Get subscribers with specific custom field values
api
.getSubscribers({
fields: {
company_size: "100-500",
industry: "Technology",
},
})
.then((response) => {
console.log("Subscribers with matching custom fields:", response.subscribers);
})
.catch((error) => {
console.error("Error retrieving subscribers by custom fields:", error.message);
});Working with Account Colors
You can manage your account's color scheme:
// Get account colors
api
.getAccountColors()
.then((colors) => {
console.log("Account colors:", colors);
console.log("Primary color:", colors.primary);
console.log("Secondary color:", colors.secondary);
console.log("Accent color:", colors.accent);
})
.catch((error) => {
console.error("Error retrieving account colors:", error.message);
});
// Update account colors
api
.updateAccountColors({
primary: "#3366FF",
secondary: "#FF6633",
accent: "#33FF66",
background: "#FFFFFF",
text: "#333333",
})
.then((colors) => {
console.log("Account colors updated:", colors);
})
.catch((error) => {
console.error("Error updating account colors:", error.message);
});
// Reset account colors to default
api
.resetAccountColors()
.then((colors) => {
console.log("Account colors reset to default:", colors);
})
.catch((error) => {
console.error("Error resetting account colors:", error.message);
});Managing Creator Profiles
You can manage your creator profile information:
// Get creator profile
api
.getCreatorProfile()
.then((profile) => {
console.log("Creator profile:", profile);
console.log("Name:", profile.name);
console.log("Bio:", profile.bio);
console.log("Profile image URL:", profile.profile_image_url);
console.log("Social links:", profile.social_links);
})
.catch((error) => {
console.error("Error retrieving creator profile:", error.message);
});
// Update creator profile
api
.updateCreatorProfile({
name: "Jane Smith",
bio: "Digital marketing expert and newsletter creator",
profile_image_url: "https://example.com/profile-image.jpg",
social_links: {
twitter: "https://twitter.com/janesmith",
instagram: "https://instagram.com/janesmith",
website: "https://janesmith.com",
},
})
.then((profile) => {
console.log("Creator profile updated:", profile);
})
.catch((error) => {
console.error("Error updating creator profile:", error.message);
});
// Update profile image
api
.uploadProfileImage(imageFileBlob)
.then((response) => {
console.log("Profile image uploaded:", response);
console.log("New image URL:", response.profile_image_url);
// Update profile with new image URL
return api.updateCreatorProfile({
profile_image_url: response.profile_image_url,
});
})
.then((profile) => {
console.log("Creator profile updated with new image:", profile);
})
.catch((error) => {
console.error("Error uploading profile image:", error.message);
});Handling Error Conditions
The Kit API SDK provides comprehensive error handling capabilities to help you manage different types of errors that may occur during API interactions.
Handling Validation Errors
Validation errors occur when the API rejects your request due to invalid input data:
// Example of handling validation errors
api
.createSubscriber({
email: "invalid-email", // Invalid email format
first_name: "John",
last_name: "Doe",
})
.then((subscriber) => {
console.log("Subscriber created:", subscriber);
})
.catch((error) => {
if (error.name === "KitApiValidationError") {
console.error("Validation error occurred:", error.message);
console.error("Field errors:", error.fieldErrors);
// Handle specific field errors
if (error.fieldErrors.email) {
console.error("Email error:", error.fieldErrors.email[0]);
// Prompt user to correct email
}
} else {
console.error("Unexpected error:", error);
}
});Handling Rate Limiting
Rate limiting errors occur when you've exceeded the allowed number of requests in a given time period:
// Example of handling rate limiting errors
api
.getSubscribers()
.then((response) => {
console.log("Subscribers:", response.subscribers);
})
.catch((error) => {
if (error.name === "KitApiRateLimitError") {
console.error("Rate limit exceeded:", error.message);
console.log("Retry after (seconds):", error.retryAfter);
// Implement exponential backoff
const retryDelay = error.retryAfter * 1000 || 5000;
console.log(`Retrying after ${retryDelay}ms...`);
// Set up retry with setTimeout
setTimeout(() => {
console.log("Retrying request...");
return api.getSubscribers();
}, retryDelay);
} else {
console.error("Unexpected error:", error);
}
});
// Implementing a reusable retry function with exponential backoff
function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
let retries = 0;
function attempt() {
return fn().catch((error) => {
if (error.name === "KitApiRateLimitError" && retries < maxRetries) {
retries++;
const delay = Math.min(
error.retryAfter * 1000 || baseDelay * Math.pow(2, retries),
30000 // Max 30 seconds
);
console.log(`Rate limited. Retry ${retries}/${maxRetries} after ${delay}ms`);
return new Promise((resolve) => {
setTimeout(() => resolve(attempt()), delay);
});
}
throw error; // Re-throw if not rate limit or max retries exceeded
});
}
return attempt();
}
// Using the retry function
retryWithBackoff(() => api.getSubscribers())
.then((response) => {
console.log("Subscribers retrieved successfully:", response.subscribers);
})
.catch((error) => {
console.error("All retries failed:", error);
});Handling Server Errors
Server errors (5xx) indicate problems on the API server side:
// Example of handling server errors
api
.getBroadcasts()
.then((response) => {
console.log("Broadcasts:", response.broadcasts);
})
.catch((error) => {
if (error.name === "KitApiServerError") {
console.error("Server error occurred:", error.message);
console.error("Status code:", error.statusCode);
// Log the error for debugging
console.error("Request ID:", error.requestId);
console.error("Error details:", error.details);
// Notify user of the issue
alert("We're experiencing technical difficulties. Our team has been notified.");
// Optionally report to your error tracking service
// errorTrackingService.report(error);
} else {
console.error("Unexpected error:", error);
}
});Using Try/Catch with Promise Chains
You can use try/catch blocks with promise chains for more complex error handling scenarios:
// Example of using try/catch with promise chains
function createAndTagSubscriber(email, firstName, lastName, tags) {
let subscriberId;
return api
.createSubscriber({
email,
first_name: firstName,
last_name: lastName,
})
.then((subscriber) => {
subscriberId = subscriber.id;
console.log("Subscriber created:", subscriber);
// Now tag the subscriber
return api.tagSubscriber(subscriberId, tags);
})
.then((response) => {
console.log("Subscriber tagged:", response);
return { success: true, subscriberId, message: "Subscriber created and tagged successfully" };
})
.catch((error) => {
console.error("Error in subscriber creation/tagging process:", error);
// Handle different error types
if (error.name === "KitApiValidationError") {
return {
success: false,
error: "validation_error",
message: "Invalid subscriber data provided",
details: error.fieldErrors,
};
} else if (error.name === "KitApiNotFoundError") {
return {
success: false,
error: "not_found_error",
message: "Subscriber or tag not found",
};
} else if (error.name === "KitApiRateLimitError") {
return {
success: false,
error: "rate_limit_error",
message: "Rate limit exceeded, please try again later",
retryAfter: error.retryAfter,
};
} else {
return {
success: false,
error: "unknown_error",
message: "An unexpected error occurred",
};
}
});
}
// Using the function
createAndTagSubscriber("[email protected]", "John", "Doe", ["new-subscriber", "newsletter"]).then((result) => {
if (result.success) {
// Process successful result
displaySubscriberData(result.data);
} else {
// Handle error based on type
switch (result.error) {
case "not_found_error":
displayNotFoundMessage(result.resourceType, result.resourceId);
break;
case "auth_error":
if (result.requiresReauth) {
initiateReauthFlow();
}
break;
case "rate_limit_error":
if (result.canRetry) {
scheduleRetry(result.retryAfter);
}
break;
case "validation_error":
displayValidationErrors(result.fieldErrors);
break;
case "server_error":
displayServerErrorMessage(result.statusCode, result.requestId);
break;
default:
displayGenericErrorMessage(result.message);
}
}
});Comprehensive Error Handling Strategy
Here's a more comprehensive approach to error handling that you can implement across your application:
// Define error handling utilities
const KitErrorHandler = {
// General error handler
handleError(error) {
if (!error) return { success: false, message: "Unknown error occurred" };
console.error("API Error:", error.message);
// Handle specific error types
switch (error.name) {
case "KitApiValidationError":
return this.handleValidationError(error);
case "KitApiNotFoundError":
return this.handleNotFoundError(error);
case "KitApiAuthError":
return this.handleAuthError(error);
case "KitApiRateLimitError":
return this.handleRateLimitError(error);
case "KitApiServerError":
return this.handleServerError(error);
default:
return {
success: false,
error: "unknown_error",
message: error.message || "An unexpected error occurred",
};
}
},
// Validation error handler
handleValidationError(error) {
return {
success: false,
error: "validation_error",
message: "The provided data is invalid",
fieldErrors: error.fieldErrors || {},
details: error.details || {},
};
},
// Not found error handler
handleNotFoundError(error) {
return {
success: false,
error: "not_found_error",
message: error.message || "The requested resource was not found",
resourceType: error.resourceType || "unknown",
resourceId: error.resourceId || "unknown",
};
},
// Authentication error handler
handleAuthError(error) {
// Potentially trigger a re-authentication flow
return {
success: false,
error: "auth_error",
message: error.message || "Authentication failed",
requiresReauth: true,
};
},
// Rate limit error handler
handleRateLimitError(error) {
return {
success: false,
error: "rate_limit_error",
message: "Rate limit exceeded, please try again later",
retryAfter: error.retryAfter || 60,
canRetry: true,
};
},
// Server error handler
handleServerError(error) {
// Could report to monitoring service here
return {
success: false,
error: "server_error",
message: "The server encountered an error processing your request",
statusCode: error.statusCode || 500,
requestId: error.requestId || "unknown",
};
},
};
// Example usage of the error handler
api
.getSubscriber("non-existent-id")
.then((subscriber) => {
console.log("Subscriber:", subscriber);
return { success: true, data: subscriber };
})
.catch((error) => {
return KitErrorHandler.handleError(error);
})
.then((result) => {
if (result.success) {
// Process successful result
displaySubscriberData(result.data);
} else {
// Handle error based on type
switch (result.error) {
case "not_found_error":
displayNotFoundMessage(result.resourceType, result.resourceId);
break;
case "auth_error":
if (result.requiresReauth) {
initiateReauthFlow();
}
break;
case "rate_limit_error":
if (result.canRetry) {
scheduleRetry(result.retryAfter);
}
break;
case "validation_error":
displayValidationErrors(result.fieldErrors);
break;
case "server_error":
displayServerErrorMessage(result.statusCode, result.requestId);
break;
default:
displayGenericErrorMessage(result.message);
}
}
});By implementing these error handling patterns, you can create a robust application that gracefully handles various error scenarios when interacting with the Kit API.
Running Tests
Unit Tests
To run the unit tests:
npm testSetting Up API Credentials
You'll need to first get a v4 API key from Kit. Login to this page
To run the integration tests, you need to provide a valid API key in one of the following ways:
Setting Up API Credentials
To run the integration tests, you need to provide a valid API key in one of the following ways:
Environment Variables (Recommended):
Create a
.envfile in the project root with your API credentials:# Copy from env.sample to .env and fill in your values KIT_API_KEY=your_api_key_here KIT_API_URL=https://api.kit.com/v4 KIT_TEST_ACCOUNT_ID=your_account_id KIT_TEST_CREATOR_ID=your_creator_idThe SDK will automatically load these environment variables when running tests.
You can also create a
.env.testfile specifically for testing environments.Direct Configuration:
You can also pass the API key directly to the test command:
KIT_API_KEY=your_api_key_here \ KIT_API_URL=https://api.kit.com/v4 \ KIT_TEST_ACCOUNT_ID=your_account_id \ KIT_TEST_CREATOR_ID=your_creator_id npm test
Non-Destructive Testing
The integration tests are designed to be non-destructive to your account data:
Read-Only Tests: Most tests only read data from your account without making any changes.
Safe Updates: For tests that modify data (like
updateColors):- The original values are saved before any changes
- Only a subset of values are modified during testing
- The original values are restored after the test completes
- Error handling ensures cleanup happens even if tests fail
Data Validation: Tests verify data structure without making assumptions about specific values.
Integration Tests
The integration tests require a valid Kit API key to run against the live API. By default, these tests will be skipped if no API key is provided.
When no API key is provided, the following tests are skipped:
Read-Only Operations (19 tests)
These tests only read data and make no changes to your account:
Account Tests:
getAccount: Fetches account datagetColors: Fetches account color datagetEmailStats: Fetches email statistics with date filteringgetGrowthStats: Fetches subscriber growth statistics
Broadcast Tests:
getBroadcast: Fetches a broadcast by IDgetBroadcasts: Fetches a list of broadcastsgetBroadcastsPaginated: Fetches broadcasts with pagination
Custom Fields Tests:
getCustomFields: Fetches all custom fieldsgetCustomField: Gets a custom field by IDgetCustomFieldsPaginated: Fetches custom fields with pagination
Forms Tests:
getForms: Gets all formsgetFormsPaginated: Gets forms with paginationgetForm: Gets a form by IDgetFormSubscribers: Gets subscribers for a formgetFormSubscribersPaginated: Gets form subscribers with pagination
Tags Tests:
getTags: Fetches all tagsgetTagsPaginated: Lists tags with paginationgetTag: Gets a tag by IDgetTagSubscribers: Lists subscribers for a tag
Write Operations (12 tests)
These tests create, update, or delete data, but include cleanup code:
Account Tests:
updateColors: Updates account colors (with restoration of original colors)
Broadcast Tests:
createNewBroadcast: Creates a new broadcastupdateBroadcast: Updates an existing broadcastBroadcastData.save: Saves a broadcast (create or update)BroadcastData.send: Sends a draft broadcastBroadcastData.schedule: Schedules a broadcast for future delivery
Custom Fields Tests:
createNewCustomField: Creates a new custom fieldupdateCustomField: Updates an existing custom fielddeleteCustomField: Deletes a custom fieldbulkCreateCustomFields: Creates multiple custom fields at once
Tags Tests:
createNewTag: Creates a new tagupdateTag: Updates a tag name
Most write operations include cleanup code to restore the original state after testing. For example, the updateColors test saves the original colors and restores them after the test completes. Similarly, custom fields created during testing are deleted afterward.
Running Specific Integration Tests
To run a specific integration test file:
npm test -- tests/integration/kit-account.test.tsTo run the broadcast functionality tests:
npm test -- tests/integration/broadcast.test.tsTo run a specific test within a file:
npm test -- -t "should fetch account data" tests/integration/kit-account.test.tsCI/CD Integration
For CI/CD pipelines, set the KIT_API_KEY as a secure environment variable in your CI/CD settings. The tests will automatically run when the variable is present and skip when it's not.
