klaviyo-sdk-patterns

v2026.09.24

Apply production-ready Klaviyo SDK patterns for the klaviyo-api package. Use when implementing Klaviyo integrations, refactoring SDK usage, or establishing team coding standards for Klaviyo API calls. Trigger with phrases like "klaviyo SDK patterns", "klaviyo best practices", "klaviyo code patterns", "idiomatic klaviyo", "klaviyo wrapper".

GitHub
Install command
npx skhub add jeremylongshore/klaviyo-sdk-patterns
Markdown
SKILL.md

Klaviyo SDK Patterns

Overview

Production-ready patterns for the klaviyo-api Node.js SDK: singleton sessions, type-safe wrappers, retry logic, cursor pagination, and multi-tenant support. Read the target project's Klaviyo files, then Write or Edit the src/klaviyo/ modules below into place so every call goes through one consistent, retry-aware layer instead of ad-hoc new ApiKeySession(...) calls scattered across the codebase.

The six patterns are summarized here with the essential skeleton; the full, copy-paste implementation for all of them lives in references/implementation.md, and combined worked examples with expected output are in references/examples.md.

Prerequisites

  • klaviyo-api package installed in the target project.
  • The klaviyo-install-auth setup completed, so KLAVIYO_PRIVATE_KEY is available in the environment.
  • A TypeScript project with strict mode enabled — every pattern is typed.

Instructions

Step 1: Singleton session (the foundation)

Create one lazily-initialized ApiKeySession and reuse it everywhere. Read the key from the environment, fail fast if it is missing, and expose a reset hook for tests.

// src/klaviyo/session.ts
import { ApiKeySession } from 'klaviyo-api';

let _session: ApiKeySession | null = null;

export function getSession(apiKey?: string): ApiKeySession {
  if (!_session) {
    const key = apiKey || process.env.KLAVIYO_PRIVATE_KEY;
    if (!key) throw new Error('KLAVIYO_PRIVATE_KEY is required');
    _session = new ApiKeySession(key);
  }
  return _session;
}
export function resetSession(): void { _session = null; }

Steps 2-6: the rest of the layer

Each builds on the session singleton. Write the corresponding file from references/implementation.md:

  • Step 2 — Type-safe API wrapper (api.ts): lazy getters for all 11 API clients (Profiles, Events, Lists, …) so unused clients are never constructed.
  • Step 3 — Error wrapper (errors.ts): parseKlaviyoError normalizes the raw error and safeCall returns { data, error } instead of throwing.
  • Step 4 — Retry (retry.ts): withRetry retries only on 429/5xx, honoring Klaviyo's Retry-After header, else exponential backoff with jitter.
  • Step 5 — Pagination (pagination.ts): paginate turns any cursor-based list endpoint into an AsyncGenerator, extracting page[cursor] for you.
  • Step 6 — Multi-tenant factory (multi-tenant.ts): getApisForTenant caches one client set per tenant id, isolating each customer's API key.

Output

Applying this skill produces a src/klaviyo/ module set:

FileExportsPurpose
session.tsgetSession, resetSessionOne shared authenticated session
api.tsdefault apisLazy, type-safe access to every API client
errors.tsparseKlaviyoError, safeCallNon-throwing typed error results
retry.tswithRetryRate-limit/5xx retry honoring Retry-After
pagination.tspaginateAsync iteration over cursor pages
multi-tenant.tsgetApisForTenantPer-tenant client isolation

Callers then read as const { data, error } = await safeCall(() => apis.profiles.getProfiles(...)) instead of managing sessions and try/catch by hand.

SDK Conventions

ConventionExample
Property casingfirstName (not first_name)
Response accessresponse.body.data (not response.data)
Payload structure{ data: { type: 'profile', attributes: { ... } } }
Filter syntaxequals(email,"user@example.com")
Sort syntax'-datetime' (descending), 'datetime' (ascending)
Include relations{ include: ['lists'] }

Error Handling

ErrorStatusRetryableSolution
Invalid API key401NoCheck KLAVIYO_PRIVATE_KEY
Missing scope403NoAdd required scope to API key
Validation error400NoFix request payload
Rate limited429YesHonor Retry-After header
Server error500/503YesRetry with backoff
Conflict409NoResource already exists; use update

Examples

A quick taste — wrap any call so a failure returns a typed error instead of throwing:

import apis from './klaviyo/api';
import { safeCall } from './klaviyo/errors';

const { data, error } = await safeCall(
  () => apis.profiles.getProfiles({ pageSize: 20 }),
  'list profiles',
);
if (error) console.error(`Failed (${error.status}):`, error.errors[0].detail);
else console.log(`Fetched ${data!.body.data.length} profiles`);

Full worked examples — retrying a rate-limited write, paginating every profile, and serving two tenants from one process, each with expected output — are in references/examples.md.

Resources

Next Steps

Once the src/klaviyo/ layer is in place, apply the patterns in klaviyo-core-workflow-a for profile and list management — those workflows assume apis, safeCall, withRetry, and paginate already exist.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/.curated/klaviyo-sdk-patterns

Default branch

main

Latest commit

e5a6c3b

Tree SHA

c2dc8e8