agentsclimarketplace

Klaviyo sdk patterns

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/klaviyo-pack/skills/klaviyo-sdk-patterns

'Apply production-ready Klaviyo SDK patterns for the klaviyo-api package.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill klaviyo-sdk-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.9 KB, ~1.4k tokens by cl100k_base, as published. Nobody here has run it

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,"[email protected]")
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.

What ships with it: 2 files

8.3 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.