npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-sdk

Authentication

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:

  1. Create an instance of the data object
  2. Set properties on the object
  3. 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 test

Setting 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:

  1. Environment Variables (Recommended):

    Create a .env file 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_id

    The SDK will automatically load these environment variables when running tests.

    You can also create a .env.test file specifically for testing environments.

  2. 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:

  1. Read-Only Tests: Most tests only read data from your account without making any changes.

  2. 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
  3. 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 data
  • getColors: Fetches account color data
  • getEmailStats: Fetches email statistics with date filtering
  • getGrowthStats: Fetches subscriber growth statistics

Broadcast Tests:

  • getBroadcast: Fetches a broadcast by ID
  • getBroadcasts: Fetches a list of broadcasts
  • getBroadcastsPaginated: Fetches broadcasts with pagination

Custom Fields Tests:

  • getCustomFields: Fetches all custom fields
  • getCustomField: Gets a custom field by ID
  • getCustomFieldsPaginated: Fetches custom fields with pagination

Forms Tests:

  • getForms: Gets all forms
  • getFormsPaginated: Gets forms with pagination
  • getForm: Gets a form by ID
  • getFormSubscribers: Gets subscribers for a form
  • getFormSubscribersPaginated: Gets form subscribers with pagination

Tags Tests:

  • getTags: Fetches all tags
  • getTagsPaginated: Lists tags with pagination
  • getTag: Gets a tag by ID
  • getTagSubscribers: 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 broadcast
  • updateBroadcast: Updates an existing broadcast
  • BroadcastData.save: Saves a broadcast (create or update)
  • BroadcastData.send: Sends a draft broadcast
  • BroadcastData.schedule: Schedules a broadcast for future delivery

Custom Fields Tests:

  • createNewCustomField: Creates a new custom field
  • updateCustomField: Updates an existing custom field
  • deleteCustomField: Deletes a custom field
  • bulkCreateCustomFields: Creates multiple custom fields at once

Tags Tests:

  • createNewTag: Creates a new tag
  • updateTag: 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.ts

To run the broadcast functionality tests:

npm test -- tests/integration/broadcast.test.ts

To run a specific test within a file:

npm test -- -t "should fetch account data" tests/integration/kit-account.test.ts

CI/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.