| 500/503 |
Server Error |
Klaviyo-side — check status page, retry with bac
'Execute Klaviyo primary workflow: profiles, lists, and subscriptions.
ReadWriteEditBash(npm:*)Grep
Klaviyo Core Workflow A -- Profiles, Lists & Subscriptions
Overview
Primary money-path workflow: create/update profiles, manage lists, and subscribe contacts for email and SMS marketing via the klaviyo-api SDK. This skill covers the six-step path from a raw customer record to a consented, segmentable subscriber. High-level flow lives here; the full code for every step is in references/implementation.md.
Prerequisites
- Completed the
klaviyo-install-auth setup so KLAVIYOPRIVATEKEY is available in the environment.
- A Klaviyo private API key scoped to
profiles:read, profiles:write, lists:read, and lists:write.
- The
klaviyo-api npm package installed in the project (npm install klaviyo-api).
- Node.js with TypeScript configured, since all examples use the typed SDK.
Instructions
Every call authenticates through a single ApiKeySession built from the private key:
import { ApiKeySession, ProfilesApi, ListsApi } from 'klaviyo-api';
const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
const profilesApi = new ProfilesApi(session);
const listsApi = new ListsApi(session);
The workflow runs in six steps. Use the linked walkthrough for the complete code of each:
- Create or update a profile — prefer
createOrUpdateProfile (upsert) over createProfile so re-syncs don't 409 on an existing email.
- Create a list —
listsApi.createList(...) returns the listId you use downstream; getLists() enumerates existing lists.
- Add profiles to a list —
createListRelationships adds membership only; it does NOT grant marketing consent.
- Subscribe profiles —
subscribeProfiles records email/SMS marketing consent with a consentTimestamp. This is the correct way to create real subscribers.
- Query profiles with filters —
getProfiles({ filter, sort }) supports equals, greater-than, and contains for segmentation.
- Bulk import — batch upserts in groups of 100 to stay within rate limits.
Full code for all six steps: references/implementation.md.
Output
- Profiles created/updated in Klaviyo
- Lists created and populated
- Subscribers opted in with consent timestamps
- Queryable customer data for segmentation
Error Handling
| Error |
Status |
Cause |
Solution |
Execute the Klaviyo secondary workflow: event tracking, segments, and campaigns.
ReadWriteBash(npm:*)
Klaviyo Core Workflow B -- Events, Segments & Campaigns
Overview
Secondary workflow: track customer events, query segments, create/send campaigns, and
trigger metric-based flows via the klaviyo-api SDK. This page summarizes the five steps
and their skeletons; the full copy-ready code lives in
references/implementation.md and worked scenarios in
references/examples.md.
Prerequisites
- Completed
klaviyo-core-workflow-a (profiles/lists set up)
- API key scopes:
events:write, segments:read, campaigns:read, campaigns:write, flows:read
klaviyo-api installed and KLAVIYOPRIVATEKEY set in the environment
Instructions
Open one session, then use the API class each step needs. Full parameter shapes for every
step are in references/implementation.md.
import { ApiKeySession, EventsApi } from 'klaviyo-api';
const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
- Step 1 — Track server-side events.
new EventsApi(session).createEvent(...). Include
metric.data.attributes.name (auto-creates the metric), a profile, a value for revenue
attribution, and a uniqueId for deduplication. Custom metrics trigger listening flows.
- Step 2 — Query events and metrics.
MetricsApi.getMetrics() lists event types;
EventsApi.getEvents({ sort: '-datetime', filter: 'equals(metric_id,"...")' }) reads recent events.
- Step 3 — Work with segments.
SegmentsApi.getSegments() lists them,
getSegmentProfiles() reads members, and getSegment({ additionalFieldsSegment: ['profile_count'] })
returns the size — check it before a send.
- Step 4 — Create an email campaign. Four ordered calls: create a template, create the
campaign (with audiences.included/excluded), assign the template to the campaign message,
then create the campaign-send-job. Sending before the template is assigned returns a 400.
- Step 5 — Query flows (read-only).
FlowsApi.getFlows() lists flows;
getFlowFlowActions({ id }) returns each flow's steps and their status.
Output
- Event tracking returns an HTTP 202 Accepted acknowledgement (Klaviyo queues events
asynchronously); the event appears in the profile's
activity feed and fire
'Optimize Klaviyo costs through plan selection, contact management, and.
ReadWriteEditGrep
Klaviyo Cost Tuning
Overview
Optimize Klaviyo costs through active profile management, list hygiene, event sampling, and API usage monitoring. Klaviyo bills primarily by active profiles and message volume, not API calls.
Prerequisites
- Access to Klaviyo billing dashboard
- Understanding of active profile definition
klaviyo-api SDK for programmatic management
Klaviyo Pricing Model
Klaviyo bills based on active profiles (contacts who have received or been targeted by marketing), not API requests.
| Component |
How It's Billed |
Cost Driver |
| Email |
Per active profile tier |
Number of marketable profiles |
| SMS |
Per message sent + carrier fees |
Message volume |
| Push |
Included with email plan |
N/A |
| API calls |
Free (rate limited, not billed) |
N/A |
| Reviews |
Per request volume |
Review request sends |
Email Pricing Tiers (Approximate)
| Active Profiles |
Monthly Cost |
| 0 - 250 |
Free |
| 251 - 500 |
$20/mo |
| 501 - 1,000 |
$30/mo |
| 1,001 - 1,500 |
$45/mo |
| 1,501 - 5,000 |
$60-$100/mo |
| 5,001 - 10,000 |
$100-$150/mo |
| 10,001 - 25,000 |
$150-$375/mo |
| 25,001+ |
Custom pricing |
> Key insight: Reducing active profiles has the biggest cost impact. Cleaning suppressed/unengaged contacts directly reduces your bill.
Instructions
Work the levers in order — active-profile reduction has the largest impact, so start
there before touching event sampling or API monitoring. Each step maps to a klaviyo-api
routine in the full walkthrough; the complete five-step implementation
carries the runnable code for every step, and worked examples show
the dollar impact of each.
- Audit active profile count — page through
ProfilesApi.getProfiles with a minimal
fieldset to establish the current tier. Skeleton below.
- Identify unengaged profiles — query an "Unengaged 180+ Days" segment via
SegmentsApi.
- Suppress unengaged contacts — unsubscribe (stays but unmarketable = not billed) or
add a global suppression property. This is what actually lowers the bill.
- Sample non-critical events — keep
'Implement Klaviyo data privacy, GDPR/CCPA compliance, and PII handling.
ReadWriteEdit
Klaviyo Data Handling
Overview
Handle profile data, PII, and privacy compliance with Klaviyo's Data Privacy API, GDPR right-to-deletion, CCPA requests, and safe logging patterns. This skill covers five workflows: GDPR profile deletion, Data Subject Access Requests (DSAR), PII redaction in logs, consent management, and compliance audit logging.
The GDPR deletion skeleton is inline below. The deeper step-by-step code — DSAR export, PII redaction, consent management, and audit logging — lives in references/implementation.md so this file stays scannable. Read the summary here, then drill into the reference for full copy-ready code.
Prerequisites
klaviyo-api SDK installed
- API key with
data-privacy:write scope (for deletion requests)
- Understanding of GDPR/CCPA requirements
- Audit logging infrastructure
Klaviyo Data Privacy API
Klaviyo provides a dedicated Data Privacy API for GDPR/CCPA profile deletion. When you delete a profile via this API, Klaviyo performs a full GDPR erasure — the profile is permanently removed and cannot be recovered.
Instructions
The workflow has five steps. Step 1 (deletion) is shown in full here because it is the highest-risk, most-requested operation. Steps 2–5 follow the same session pattern and are fully implemented in references/implementation.md.
Step 1: GDPR Profile Deletion (Right to Erasure)
Request deletion with exactly one identifier (email, phone, or profile ID). Providing more than one returns an error. Deletion is irreversible, so always audit-log the request.
import { ApiKeySession, DataPrivacyApi } from 'klaviyo-api';
const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
const dataPrivacyApi = new DataPrivacyApi(session);
async function requestProfileDeletion(email: string): Promise<void> {
await dataPrivacyApi.requestProfileDeletion({
data: {
type: 'data-privacy-deletion-job',
attributes: {
profile: { data: { type: 'profile', attributes: { email } } },
},
},
});
await auditLog({
action: 'GDPR_DELETION_REQUESTED',
identifier: email,
service: 'klaviyo',
timestamp: new Date().toISOString(),
});
}
await requestProfileDeletion('user-wants-deletion@example.com');
The multi-identifier form (email / phone / profile ID with validation) is in references/implementation.md § Step 1.
Step 2: Data Subject Access Request (DSAR)
Export every profile attribute, event, and list membership for a subject (GDPR Article 15) using ProfilesApi + EventsApi. Full exportProfileData() in
'Collect Klaviyo debug evidence for support tickets and troubleshooting.
Bash(grep:*)Bash(curl:*)Bash(tar:*)Bash(npm:*)
Klaviyo Debug Bundle
Overview
Collect all diagnostic information needed for a Klaviyo support ticket into one
redacted, shareable tarball: SDK version, API connectivity, auth result, rate
limit status, recent errors, and environment config. Every secret (API keys,
emails, phone numbers, webhook secrets) is redacted before packaging, so the
bundle is safe to attach to a ticket.
The workflow builds a single shell script (klaviyo-debug-bundle.sh) in five
steps, then runs it. The full script and a programmatic TypeScript alternative
live in the reference files linked below.
Prerequisites
- The
klaviyo-api SDK installed (npm list klaviyo-api to confirm).
- The
KLAVIYOPRIVATEKEY environment variable set to a private API key.
- Read access to your application's log directory (defaults scanned:
logs/,
/var/log/app/).
curl, tar, and python3 available on the host.
Instructions
Assemble the five blocks below (verbatim from the reference) into one
klaviyo-debug-bundle.sh, make it executable, and run it from your application
root so the log-collection step can find logs/.
- Create the bundle dir — timestamped
klaviyo-debug-YYYYMMDD-HHMMSS/ and a summary.txt header.
- Collect environment info — Node/npm/OS versions,
klaviyo-api SDK version, and a redacted API-key presence check.
- Run connectivity tests — DNS resolve, an authenticated
GET /api/accounts/ (captures the HTTP code), rate-limit headers, and the Klaviyo status page.
- Collect logs — grep known log dirs for Klaviyo errors, redacting keys and emails inline.
- Package and clean up —
tar -czf the dir, remove the working copy, print the tarball path.
The skeleton of step 1:
#!/bin/bash
# klaviyo-debug-bundle.sh
set -euo pipefail
BUNDLE_DIR="klaviyo-debug-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE_DIR"
See the full shell implementation for all five
steps verbatim. To collect the same signals as a structured object (SDK version,
connectivity, latency) instead of a tarball, use the TypeScript helper in
examples.
Output
klaviyo-debug-YYYYMMDD-HHMMSS.tar.gz containing:
summary.txt -- Environment, SDK version, API key status, connectivity
rate-limits.txt -- Current rate limit header values
api-response.json -- Account API response
'Deploy Klaviyo integrations to Vercel, Fly.
ReadWriteEditBash(vercel:*)Bash(fly:*)Bash(gcloud:*)
Klaviyo Deploy Integration
Overview
Deploy Klaviyo-powered applications to Vercel, Fly.io, and Google Cloud Run
with proper secrets management and health checks. Every platform follows the
same shape — store the private key + webhook secret, wire a config file,
deploy, verify. The lean skeleton lives here; full per-platform recipes live
in references/platform-deployments.md.
Prerequisites
- Klaviyo production API key (
pk_*)
- Platform CLI installed (
vercel, fly, or gcloud)
- Application tested with
klaviyo-api SDK
klaviyo-prod-checklist completed
Instructions
The workflow is identical across platforms; only the CLI verbs change. Read
the target platform's section in
references/platform-deployments.md, then:
- Store secrets — inject
KLAVIYOPRIVATEKEY and
KLAVIYOWEBHOOKSIGNING_SECRET via the platform's secret store
(vercel env add, fly secrets set, or gcloud secrets create). Never
commit these to the repo.
- Write the platform config — use Write/Edit to create
vercel.json,
fly.toml, or a Dockerfile that binds the secrets and exposes a health
path. See the reference for the exact file contents.
- Add the universal health check — expose
src/health.ts (identical on
all platforms) at the path each platform probes (/api/health on Vercel,
/health on Fly.io and Cloud Run).
- Deploy — run
vercel --prod, fly deploy, or gcloud run deploy.
- Verify —
curl the health endpoint and confirm
services.klaviyo.connected is true.
Vercel skeleton (first example)
vercel env add KLAVIYO_PRIVATE_KEY production # paste pk_*** when prompted
vercel env add KLAVIYO_WEBHOOK_SIGNING_SECRET production
# configure vercel.json (see reference), then:
vercel --prod
curl -s https://your-app.vercel.app/api/health | jq '.services.klaviyo'
Fly.io (fly secrets set + fly.toml + fly deploy) and Cloud Run
(gcloud secrets create + Dockerfile + gcloud run deploy --set-secrets)
follow the same five steps — full commands and config files are in the
reference walkthroughs.
Output
Configure Klaviyo enterprise access control with API key scopes and OAuth.
ReadWrite
Klaviyo Enterprise RBAC
Overview
Enterprise access control for Klaviyo: API key scoping with granular read/write permissions, OAuth app authorization flows, and application-level RBAC built on top of Klaviyo's scope system. Klaviyo has no built-in roles, so you compose least-privilege access from scoped keys plus an application permission layer.
Prerequisites
- Klaviyo account with API key management access
- Understanding of OAuth 2.0 (for OAuth apps)
- Application requiring per-user or per-role Klaviyo access
Klaviyo Access Control Model
Klaviyo uses scoped API keys and OAuth for access control. There are no built-in "roles" in Klaviyo's API -- you implement RBAC by creating multiple API keys with different scopes.
API Key Scopes
| Scope |
Read |
Write |
What It Controls |
accounts |
Account info |
N/A |
Organization name, timezone |
campaigns |
List campaigns |
Create/send campaigns |
Email, SMS, push campaigns |
catalogs |
Browse items |
CRUD catalog items |
Product catalog management |
coupons |
List coupons |
Create coupons |
Coupon/discount codes |
data-privacy |
N/A |
Delete profiles |
GDPR/CCPA deletion requests |
events |
Query events |
Track events |
Server-side event tracking |
flows |
List flows |
Create/update flows |
Flow automation |
images |
List images |
Upload images |
Email template images |
lists |
List lists |
CRUD lists/members |
List management |
metrics |
Query metrics |
N/A |
Metric aggregations |
profiles |
Read profiles |
Create/update profiles |
Profile management |
segments |
Read segments |
N/A |
Segment queries |
tags |
Read tags |
CRUD tags |
Resource tagging |
templates |
Read templates |
Create/update templates |
Email templates |
webhooks |
List webhooks |
CRUD webhooks |
Webhook subscriptions |
Instructions
The full, copy-ready code for every step lives in
the implementation walkthrough. At a high level
the workflow is five steps:
- Create scoped API keys
'Create a minimal working Klaviyo example with real API calls.
WriteBash(npm:*)Bash(npx:*)
Klaviyo Hello World
Overview
Minimal working example: create a profile, track an event, and query the result
using the klaviyo-api Node.js SDK against a.klaviyo.com/api/*. This is the
smoke test that proves your API key, SDK install, and network path all work
end-to-end before you build anything real.
Prerequisites
- Completed the
klaviyo-install-auth setup so credentials are in place.
KLAVIYOPRIVATEKEY exported in your environment (a private API key with
Profiles and Events scopes).
klaviyo-api installed in the project (npm install klaviyo-api).
tsx available to run the TypeScript file (npx tsx …).
Instructions
Write the code into a single hello-klaviyo.ts file, then run it with
npx tsx hello-klaviyo.ts. The full script performs four things in order:
- Create a profile —
profilesApi.createProfile(...) with a JSON:API
payload. The essential skeleton:
import { ApiKeySession, ProfilesApi, ProfileEnum } from 'klaviyo-api';
const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
const profilesApi = new ProfilesApi(session);
const profile = await profilesApi.createProfile({
data: {
type: ProfileEnum.Profile,
attributes: { email: 'hello@example.com', firstName: 'Hello', lastName: 'World' },
},
});
console.log('Profile created:', profile.body.data.id);
- Track an event —
eventsApi.createEvent(...) with a metric (created on
first use) linked to the profile by email.
- Retrieve the profile —
profilesApi.getProfiles({ filter: '...' }) to
confirm the write landed.
- Run the combined script —
npx tsx hello-klaviyo.ts.
For the complete step-by-step code (all payloads with camelCase and JSON:API
detail), see the full walkthrough. For the
single combined runnable script and variations, see
worked examples.
Output
Running the combined script prints one line per operation. The profile ID is a
26-character ULID; Verified echoes the firstName read back from the API,
proving the round trip succeeded:
Profile created: 01JXXXXXXXXXXXXXXXXXXXXXX
Event tracked successfully
Verified: Hello
Error Handling
| Error |
Status |
Cause |
Solution |
Duplicate profi
Execute Klaviyo incident response procedures with triage, mitigation, and postmortem.
ReadBash(curl:*)Bash(kubectl:*)Bash(npm:*)
Klaviyo Incident Runbook
Overview
Rapid incident response for Klaviyo API outages and integration failures: quick
triage, decision trees, mitigation steps, and postmortem templates. Use this
skill to move from "Klaviyo is broken" to a classified severity, an applied
mitigation, and a written postmortem — without improvising under pressure.
The heavy content (full triage script, per-error remediation blocks, and the
communication + postmortem templates) lives in references/ so this file stays
a fast high-level runbook you can follow end-to-end, then drill into for depth.
Prerequisites
KLAVIYOPRIVATEKEY exported in the shell (a private API key, pk_...).
curl and python3 available for the triage and monitoring commands.
- Read access to your app's health endpoint and, ideally, its Prometheus metrics.
- Access to the Klaviyo dashboard to rotate a key if needed.
- Klaviyo's
revision header value your app ships (this runbook pins 2024-10-15,
a dated stable API version — Klaviyo requires the header on every request).
Severity Levels
| Level |
Definition |
Response Time |
Example |
| P1 |
Complete outage |
<15 min |
All Klaviyo API calls returning 5xx |
| P2 |
Degraded service |
<1 hour |
429 rate limiting, high latency |
| P3 |
Minor impact |
<4 hours |
Webhook delays, single endpoint errors |
| P4 |
No user impact |
Next business day |
Monitoring gaps, deprecation warnings |
Instructions
Work the incident in five steps. Each step points at the reference file that
carries the full, copy-paste-ready detail.
- Triage immediately. Run the quick-triage script to answer the four
questions that classify every Klaviyo incident: Is Klaviyo itself down? Can
we authenticate? Are we rate limited? Is our app healthy? See the full script
in references/triage.md.
- Classify the failure. Walk the decision tree in
references/triage.md to split a Klaviyo-side outage
(status page shows an incident → enable fallback, monitor, communicate) from
an integration issue (route by status code: 401/403, 429, 400, 5xx).
- Assign a severity from the table above and set the response-time clock.
- Apply the remediation for the observed error type — auth failure (401),
rate limit (429), or Klaviyo server error (5xx). The
'Install and configure Klaviyo Node.
ReadWriteEditBash(npm:*)Bash(pnpm:*)Bash(pip:*)Grep
Klaviyo Install & Auth
Overview
Set up the official klaviyo-api Node.js SDK and configure private API key authentication against Klaviyo's REST API (revision 2024-10-15). The workflow below is the high-level path; the verbatim code for every step lives in the full implementation walkthrough, and copy-paste sequences live in worked examples.
Prerequisites
- Node.js 18+ (or Python 3.10+ for Python SDK)
- Klaviyo account at https://www.klaviyo.com/
- Private API key from Settings > API Keys in Klaviyo dashboard
- Public API key (for client-side only -- never use in server code)
Instructions
The full sequence is five steps. The essentials are below; drill into
references/implementation.md for the complete
code of each step (verify script, revision header, Python setup, scope table).
Step 1: Install the Official SDK
# Node.js (official SDK -- NOT @klaviyo/sdk, that's deprecated)
npm install klaviyo-api
> Important: The npm package is klaviyo-api, not @klaviyo/sdk. The SDK exports per-resource API classes (ProfilesApi, EventsApi, etc.) that each take an ApiKeySession.
Step 2: Configure Authentication
Store the private key in .env and confirm it is gitignored. Klaviyo uses two key types:
| Key Type |
Prefix |
Use Case |
Header |
| Private API Key |
pk_ |
Server-side REST API |
Authorization: Klaviyo-API-Key pk_*** |
| Public API Key |
6-char |
Client-side Track/Identify |
Query param company_id |
Step 3: Initialize the SDK
// src/klaviyo/client.ts
import { ApiKeySession, ProfilesApi, EventsApi, ListsApi } from 'klaviyo-api';
const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
export const profilesApi = new ProfilesApi(session);
export const eventsApi = new EventsApi(session);
export const listsApi = new ListsApi(session);
Steps 4-5: Verify & set the revision header
Run a one-time verification against AccountsApi.getAccounts() to prove the key
works, and remember every request needs a revision: 2024-10-15 header (the SDK
adds it automatically; raw HTTP does not). Full verify script + cURL smoke test:
references/implementation.md.
Output
klaviyo-api package installed in node_modules
.env file with
'Configure Klaviyo local development with hot reload, mocking, and testing.
ReadWriteEditBash(npm:*)Bash(pnpm:*)Bash(npx:*)Grep
Klaviyo Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for Klaviyo integrations with hot reload, SDK mocking, and integration tests. The loop keeps three concerns separate: a lazily-instantiated SDK client singleton, mocked unit tests that never hit the network, and live integration tests gated behind an opt-in flag so they only run in CI.
Prerequisites
- Completed
klaviyo-install-auth setup (provides your private API key)
- Node.js 18+ with
npm or pnpm on the PATH
klaviyo-api package installed as a project dependency
tsx and vitest installed as dev dependencies for hot reload and tests
Instructions
Follow six steps to stand up the loop. The full file contents for each step —
project layout, .env templates, package.json scripts, and the client
singleton — live in full walkthrough. The
complete test files live in test examples.
- Project structure — create
src/klaviyo/ for SDK modules and tests/{unit,integration}/. Keep secrets in a git-ignored .env.local, ship a committed .env.example.
- Environment configuration — define
KLAVIYOPRIVATEKEY / KLAVIYOPUBLICKEY and wire the dev, test, test:watch, test:integration, and typecheck scripts.
- SDK client singleton — read the key once, cache the
ApiKeySession, and export lazy per-API accessors so you only instantiate what you use:
// src/klaviyo/client.ts
import { ApiKeySession, ProfilesApi } from 'klaviyo-api';
let session: ApiKeySession | null = null;
function getSession(): ApiKeySession {
if (!session) {
const key = process.env.KLAVIYO_PRIVATE_KEY;
if (!key) throw new Error('KLAVIYO_PRIVATE_KEY not set');
session = new ApiKeySession(key);
}
return session;
}
export const profiles = () => new ProfilesApi(getSession());
- Unit testing with mocks —
vi.mock('klaviyo-api', ...) the whole SDK so unit tests are deterministic and offline. See test examples.
- Integration test — a
describe.skipIf(!process.env.KLAVIYO_TEST) suite that exercises the live account. See test examples.
- Hot reload development — run
npm run dev (tsx watch) in one terminal and npm run test:watch in another for a tight edit-test cyc
'Use when you are moving an email/CDP stack onto Klaviyo — off the.
ReadWriteEditBash(npm:*)Bash(node:*)
Klaviyo Migration Deep Dive
Overview
Comprehensive guide for migrating to Klaviyo from legacy APIs (v1/v2), competing ESPs (Mailchimp, SendGrid, etc.), or re-platforming with the strangler fig pattern. Covers data migration, API mapping, batch import, and post-migration validation.
This SKILL.md is the high-level workflow. The full, copy-paste code for every step
lives in references/implementation.md; worked
end-to-end scenarios live in references/examples.md.
Prerequisites
- Target Klaviyo account configured
klaviyo-api SDK installed (npm install klaviyo-api)
- Source system access for data export
- Feature flag infrastructure (for gradual rollout)
- Auth: a Klaviyo private API key (
pk***) exported as KLAVIYOPRIVATE_KEY — used by the SDK's ApiKeySession. Legacy v1/v2 calls used a public token in the request body; the current REST API uses the private key in the session header. See references/implementation.md.
Migration Types
| Migration |
Complexity |
Duration |
Risk |
| Klaviyo v1/v2 to current API |
Low-Medium |
1-2 weeks |
Low |
| Mailchimp/SendGrid to Klaviyo |
Medium |
2-4 weeks |
Medium |
| Custom ESP to Klaviyo |
High |
4-8 weeks |
High |
| Full re-platform |
High |
2-3 months |
High |
Instructions
Pick your migration type from the table above, then work the five steps. Each step
has full code in references/implementation.md.
- Legacy v1/v2 to current API — replace deprecated
track / identify / v2 subscribe HTTP calls with the klaviyo-api SDK (createOrUpdateProfile, createEvent, subscribeProfiles). The session skeleton every step builds on:
import { ApiKeySession, ProfilesApi, EventsApi } from 'klaviyo-api';
const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
const profilesApi = new ProfilesApi(session);
const eventsApi = new EventsApi(session);
- API field mapping — rename v1/v2 fields to the current schema: drop the
$ prefix, camelCase everything ($first_name → firstName), and nest address fields under location. Full mapping table in
'Configure Klaviyo across development, staging, and production environments.
ReadWriteEditBash(aws:*)Bash(gcloud:*)Bash(vault:*)
Klaviyo Multi-Environment Setup
Overview
Configure Klaviyo across development, staging, and production with separate API
keys, environment detection, secret management, and production safeguards. Every
environment follows the same shape — detect the env, resolve its config, load
its secret, guard destructive/send operations. The lean workflow and first
skeleton live here; full step-by-step code lives in
references/implementation.md.
Prerequisites
- Separate Klaviyo accounts or API keys per environment
- Secret management solution (GCP Secret Manager, AWS Secrets Manager, Vault)
klaviyo-api SDK installed
Environment Strategy
| Environment |
Klaviyo Account |
API Key |
Use Case |
| Development |
Test account |
pktestdev_*** |
Local development, exploration |
| Staging |
Test account |
pkteststaging_*** |
Pre-prod validation, integration tests |
| Production |
Production account |
pklive*** |
Real customer data, live sends |
> Important: Klaviyo does not have a sandbox mode. Use a separate test account for dev/staging to avoid sending real emails.
Instructions
The pattern is the same across all three environments; only the API key and the
per-env flags change. Read the matching section in
references/implementation.md, then:
- Write the config module — use Write/Edit to create
src/config/klaviyo.ts
with a detectEnvironment() helper and a per-env ENV_CONFIGS map that flips
enableSending, cache, and rate-limit concurrency by environment.
- Store secrets per environment — create one secret per env in your platform's
secret store (gcloud secrets create, aws secretsmanager create-secret, or
Vault). Never commit keys; local dev reads a git-ignored .env.local.
- Add environment guards — wrap sends and deletions so non-production simply
logs instead of touching real data (guardCampaignSend, guardedProfileDeletion).
- Wire multi-env CI — a GitHub Actions matrix maps
staging→staging key and
main→prod key, verifies connectivity, then deploys.
- Validate on startup — connect once, log the resolved account name, and warn
loudly if a pklive*** key is detected outside prod
'Set up observability for Klaviyo integrations with metrics, traces,.
ReadWriteEdit
Klaviyo Observability
Overview
Comprehensive observability for Klaviyo integrations: Prometheus metrics for API call tracking, OpenTelemetry tracing, structured logging, and alerting rules tuned to Klaviyo's rate limits and error patterns. The pattern centers on one instrumentation wrapper that every Klaviyo call routes through, so metrics, traces, and logs stay consistent across profiles, events, and webhooks.
Prerequisites
- Prometheus or compatible metrics backend
- OpenTelemetry SDK installed (optional)
- Grafana or similar dashboarding tool (optional)
klaviyo-api SDK installed
Key Metrics to Track
| Metric |
Type |
Why It Matters |
klaviyoapirequests_total |
Counter |
Track total API volume by endpoint |
klaviyoapiduration_seconds |
Histogram |
Detect latency degradation |
klaviyoapierrors_total |
Counter |
4xx/5xx error rates |
klaviyoratelimit_remaining |
Gauge |
Predict when you'll hit 429s |
klaviyoprofilessynced_total |
Counter |
Profile sync throughput |
klaviyoeventstracked_total |
Counter |
Event tracking volume |
klaviyowebhookreceived_total |
Counter |
Inbound webhook volume |
Instructions
Read any existing Klaviyo client code first, then build the layers in order. Each
step writes one module; steps 5–6 wire the alerting and scrape endpoint.
- Instrumented API wrapper — write
src/klaviyo/instrumented-client.ts with the
Prometheus counters, histogram, and gauge, exposed through a single
instrumentedCall() helper.
- Route every call through
instrumentedCall(endpoint, method, () => ...) in
the service layer so profile/event/webhook traffic is all counted.
- OpenTelemetry tracing (optional) — add
tracedKlaviyoCall() to emit spans
with Klaviyo operation + error attributes.
- Structured logging — add a
pino logger with an email-redacting serializer.
- Alert rules — drop
prometheus/klaviyo-alerts.yml in place for error-rate,
429, latency, down, and low-headroom alerts.
- Metrics endpoint — expose
GET /metrics from the shared registry.
The wrapper is the load-bearing piece — the skeleton is:
export async function ins
Optimize Klaviyo API performance with caching, batching, and pagination tuning.
ReadWriteEdit
Klaviyo Performance Tuning
Overview
Optimize Klaviyo API performance with response caching, request batching, cursor-based pagination, sparse fieldsets, and connection pooling. This skill diagnoses where an integration is slow, then applies the right technique — payload reduction, memory caching, bounded pagination, request coalescing, or rate-limit-aware concurrency.
Read this file for the workflow and the decision guide. Full, copy-pasteable code for every step lives in references/implementation.md; combined real-world scenarios live in references/examples.md.
Prerequisites
klaviyo-api SDK installed
- Understanding of Klaviyo's rate limits (75 req/s burst, 700 req/min)
- Redis or in-memory cache (optional)
- For batching/concurrency helpers:
dataloader, p-queue, lru-cache
Klaviyo API Performance Characteristics
| Operation |
Typical Latency |
Max Page Size |
| Get Profile by ID |
50-150ms |
N/A |
| Get Profiles (list) |
100-300ms |
20 (default), 100 (some endpoints) |
| Create Profile |
100-200ms |
N/A |
| Create Event |
50-100ms |
N/A |
| Get Segment Profiles |
200-500ms |
20 |
| Campaign Operations |
200-500ms |
20 |
Instructions
Apply the techniques in order — each is independent, so start with the one that matches your bottleneck. Every step below has a full implementation in references/implementation.md.
Step 1: Sparse Fieldsets (Reduce Payload Size)
Klaviyo supports JSON:API sparse fieldsets — request only the fields you need instead of the 20+ default attributes. This is the cheapest win and applies to every read.
// GOOD: Only fetch the fields you use (much smaller payload)
const profiles = await profilesApi.getProfiles({
fieldsProfile: ['email', 'first_name', 'created'], // snake_case = API names
});
Step 2: Response Caching
Wrap read calls in an LRUCache keyed by query, with per-resource TTLs (profiles 5 min, segments 15 min — they change less often). See the caching implementation in references/implementation.md.
Step 3: Efficient Pagination
Use a fetchAllPages helper that follows the links.next cursor with a maxPages ceiling so large exports terminate predictably. See the pagination implementation in references/implementation.md.
Step 4: Reques
'Execute Klaviyo production deployment checklist and validation procedures.
ReadBash(curl:*)Bash(npm:*)Grep
Klaviyo Production Checklist
Overview
Complete checklist for deploying Klaviyo integrations to production, with health
checks, rollback procedures, and validation against real Klaviyo API endpoints.
Work the pre-deployment checklist below, run the pre-flight script, then verify
the live health endpoint before declaring the deploy done.
Prerequisites
- Staging environment tested and verified
- Production API key with correct scopes (
pk_*)
- Webhook signing secret configured
- Monitoring and alerting ready
Instructions
Follow these steps in order. Steps 1–2 are read-only audits of the codebase and
config; steps 3–5 exercise the live API and health surface.
- Audit secrets and code. Confirm the production key lives in a secret
manager and no keys are hardcoded — run Grep/grep -r "pk_" src/ to catch
leaks, and Read the deployment manifest to verify scopes. See the
Pre-Deployment Checklist below.
- Audit the integration, resilience, and webhooks. Walk the remaining
checklist sections (API integration, error handling, webhook security,
monitoring).
- Run the pre-flight script (
scripts/preflight-klaviyo.sh) to validate the
status page, API auth, rate-limit headroom, and pinned SDK version.
- Deploy, then verify the health endpoint returns
healthy.
- Keep the rollback path ready (feature flag first) in case metrics regress.
Health check, pre-flight script, and rollback code are in
references/implementation.md.
Pre-Deployment Checklist
Authentication & Secrets
- [ ] Production
KLAVIYOPRIVATEKEY stored in secret manager (not env file)
- [ ] Key has minimal scopes (only what the app needs)
- [ ] Webhook signing secret (
KLAVIYOWEBHOOKSIGNING_SECRET) configured
- [ ] Public key (
KLAVIYOPUBLICKEY) set for client-side tracking (if used)
- [ ] No hardcoded keys in codebase (
grep -r "pk_" src/)
API Integration
- [ ] All API calls use
klaviyo-api SDK (not raw HTTP)
- [ ] SDK version pinned in
package.json (not ^ or *)
- [ ]
revision header set to 2024-10-15 (or current supported revision)
- [ ] All profile creates use
createOrUpdateProfile (upsert, not create)
- [ ] Events include
uniqueId for deduplication where applicable
- [ ] Phone numbers validated as E.164 format (
+15551234567)
'Implement Klaviyo rate limiting, backoff, and request queuing patterns.
ReadWriteEdit
Klaviyo Rate Limits
Overview
Handle Klaviyo's per-account fixed-window rate limits with proper Retry-After header handling, exponential backoff, and request queuing. This skill installs a small set of composable helpers: a retry wrapper that honors Klaviyo's Retry-After, a request queue that paces sustained throughput, a monitor that reads live rate-limit headers, and a rate-aware bulk import.
Prerequisites
- The
klaviyo-api SDK installed in the target project (npm install klaviyo-api).
- The
p-queue package installed for the request queue (npm install p-queue).
- A working knowledge of Klaviyo's dual-window (burst + steady) rate limiting, summarized in the architecture table below.
- Write access to the project's
src/klaviyo/ directory, where the generated helper files land.
Klaviyo Rate Limit Architecture
Klaviyo uses per-account fixed-window rate limiting with two distinct windows:
| Window |
Duration |
Limit |
Description |
| Burst |
1 second |
75 requests |
Short spike protection |
| Steady |
1 minute |
700 requests |
Sustained throughput cap |
Both windows apply simultaneously. Exceeding either triggers a 429 Too Many Requests.
Rate Limit Headers
On successful requests:
| Header |
Description |
RateLimit-Limit |
Max requests for the window |
RateLimit-Remaining |
Remaining requests in window |
RateLimit-Reset |
Seconds until window resets |
On 429 responses (different headers!):
| Header |
Description |
Retry-After |
Integer seconds to wait before retrying |
> Critical: When you hit a 429, RateLimit-* headers are NOT returned. Only Retry-After is present.
Instructions
Step 1: Retry-After Aware Backoff (core)
Use Write to create src/klaviyo/rate-limiter.ts with the retry wrapper below. This is the foundation every other helper builds on: it retries only on 429 and 5xx, always honors Klaviyo's Retry-After on a 429, and falls back to exponential backoff with jitter for 5xx.
// src/klaviyo/rate-limiter.ts
export async function withRateLimitRetry<T>(
operation: () => Promise<T>,
options = { maxRetries: 5, baseDelayMs: 1000
'Implement Klaviyo reference architecture with best-practice project.
ReadWriteEditGrep
Klaviyo Reference Architecture
Overview
Production-ready architecture for Klaviyo integrations: a layered project structure, service patterns, event-driven sync, and the klaviyo-api SDK wired into a real application. SKILL.md gives you the four-layer contract and the skeleton you scaffold from; the deep material lives in references/ so you pull code only when you reach that layer.
- Layout & layering (full directory tree, layer contract, data flow): architecture.md
- Working code for every layer (config, profile sync, event tracker): implementation.md
Prerequisites
- TypeScript project with
klaviyo-api installed
- Understanding of layered architecture
- Redis (for caching/queuing) and database (for audit/sync state)
Instructions
Use Write to scaffold the directory tree, then fill each layer bottom-up. The four layers and their one-way call rule:
API / Webhook Layer → routes + webhook handlers (calls Service only)
Service Layer → profile-sync, event-tracker, campaigns (calls SDK + Infra)
Klaviyo SDK Layer → ApiKeySession, ProfilesApi, EventsApi (never calls upward)
Infrastructure Layer → Redis cache, BullMQ queue, Prisma DB, OTel monitoring
- Scaffold the tree. Create the
src/{klaviyo,services,webhooks,jobs,middleware,config,health} layout. Full annotated tree: architecture.md.
- Config layer first. A single
loadConfig() returns environment-specific keys, rate limits, and cache TTLs — every other layer reads from it. Code: implementation.md Step 1.
- Service layer. Build
ProfileSyncService (bidirectional upsert) and EventTracker (server-side Placed Order / custom events). Both route Klaviyo calls through withRateLimitRetry. Code: implementation.md Steps 2–3.
- Wire the data flow. Signup →
syncToKlaviyo(), purchase → trackPurchase(), inbound profile.updated webhook → WebhookRouter.routeEvent() → local DB. Diagram: architecture.md.
When reviewing an existing project, Read its src/ tree and Grep for cross-layer imports that break the one-way rule (SDK importing a service, a route importing the SDK directly).
Output
Applying this skill produces:
- A scaffolded
src/ tree matching the four-layer contract, with SDK, service, webhook, job, middleware
'Apply production-ready Klaviyo SDK patterns for the klaviyo-api package.
ReadWriteEdit
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 KLAVIYOPRIVATEKEY 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
'Apply Klaviyo security best practices for API key management and access.
ReadWriteEditGrep
Klaviyo Security Basics
Overview
Security best practices for Klaviyo: API key types, OAuth scopes, webhook HMAC-SHA256 signature verification, and secret rotation procedures.
Prerequisites
- Klaviyo account with API key access
- Understanding of environment variables and secret management
- Access to Klaviyo dashboard (Settings > API Keys)
Instructions
Step 1: Understand Key Types
| Key Type |
Format |
Use Case |
Sensitivity |
| Private API Key |
pk_* (40+ chars) |
Server-side REST API |
CRITICAL -- never expose client-side |
| Public API Key |
6 alphanumeric chars |
Client-side Track/Identify only |
Low -- safe in browser JS |
Private keys authenticate via Authorization: Klaviyo-API-Key pk*** header. Public keys pass as companyid query parameter.
Step 2: Store Keys in Environment Variables
Keep every private key and the webhook signing secret out of source: load them
from .env (git-ignored) through a validated config loader that throws on a
missing secret, so misconfiguration fails at boot instead of at first API call.
// src/config/klaviyo.ts -- validated config loader (skeleton)
export const klaviyoConfig = {
privateKey: requireEnv('KLAVIYO_PRIVATE_KEY'), // throws if absent
publicKey: process.env.KLAVIYO_PUBLIC_KEY || '',
webhookSecret: process.env.KLAVIYO_WEBHOOK_SIGNING_SECRET || '',
};
Full .env template, .gitignore entries, and the requireEnv helper:
implementation.md → Environment Variable Configuration.
Step 3: Scope Keys per Environment (Least Privilege)
Issue a separate key for each environment with only the scopes that environment
needs — read-only in dev and CI, full read/write in staging, the exact production
scope set in prod — so a leaked key has the smallest possible blast radius. Scope
table and per-environment env-var layout:
implementation.md → Least-Privilege API Key Scopes.
Step 4: Verify Webhook Signatures (HMAC-SHA256)
Klaviyo signs each webhook payload with your signing secret. Recompute the
HMAC-SHA256 digest over the raw body and compare with crypto.timingSafeEqual
to defeat timing attacks; reject anything that does not match with 401.
const expected = crypto.createHmac('sha256', secret)
.update(rawBody).digest('base64');
return crypt
'Upgrade Klaviyo SDK versions and migrate between API revisions.
ReadEditBash(npm:*)Bash(git:*)
Klaviyo Upgrade & Migration
Overview
Guide for upgrading the klaviyo-api SDK, migrating from legacy v1/v2 APIs, and
handling breaking changes between Klaviyo API revisions. The workflow assesses the
current version, surfaces breaking changes with the TypeScript compiler, applies the
matching migration pattern, and ships behind a staging deploy with a clean rollback.
Deep before/after code and the full command sequence live in references/ so this
file stays a scannable map of the workflow:
Prerequisites
- The
klaviyo-api package installed and a known current version (npm list klaviyo-api).
- Git available, with a clean working tree so the upgrade lands on its own branch.
- A working test suite (
npm test), and ideally a staging integration test target.
- A Klaviyo private API key in the environment for integration verification.
Klaviyo API Revision Timeline
Each revision is supported for 2 years after release. Plan to move to the latest
every 12-18 months so you never fall inside the deprecation window.
| Revision |
Released |
Deprecated |
Key Changes |
2024-10-15 |
Oct 2024 |
Oct 2026 |
Reporting API, campaign message updates |
2024-07-15 |
Jul 2024 |
Jul 2026 |
Custom objects, tracking settings |
2024-02-15 |
Feb 2024 |
Feb 2026 |
Bulk operations, segments V2 |
2023-12-15 |
Dec 2023 |
Dec 2025 |
Profile subscription changes |
2023-07-15 |
Jul 2023 |
Jul 2025 |
Relationship endpoint restructuring |
Instructions
Step 1: Assess the current state
Compare what is installed against what is published to size the jump. A single major
step is routine; skipping several majors means expect casing and import changes.
npm list klaviyo-api # e.g. klaviyo-api@15.0.0
npm view klaviyo-api version # latest, e.g. 21.0.0
Step 2: Find affected usage
Read the releases changelog
for the target major, then locate the call sites that will need edits.
grep -r
'Implement Klaviyo webhooks with HMAC-SHA256 signature verification and.
ReadWriteEditBash(curl:*)Bash(npm:*)
Klaviyo Webhooks & Events
Overview
Set up Klaviyo webhooks with HMAC-SHA256 signature verification, event routing, idempotency handling, and the Webhooks API for programmatic subscription management.
This skill covers the full endpoint lifecycle in six steps: create a webhook subscription via the API, verify each request's signature, receive events in an Express handler, route them to per-topic handlers, deduplicate with Redis, and manage subscriptions. The high-level flow and the security-critical signature check live here; the complete step-by-step source is in references/implementation.md and worked scenarios are in references/examples.md.
Prerequisites
- Klaviyo account with webhooks enabled
- HTTPS endpoint accessible from internet
- API key with scopes:
webhooks:read, webhooks:write
- Redis or database for idempotency (recommended)
Klaviyo Webhook Architecture
Klaviyo webhooks fire when specific topics occur in your account. Each webhook is signed with a secret key using HMAC-SHA256, sent in the webhook-signature header.
| Topic Category |
Example Topics |
| Profile |
profile.created, profile.updated, profile.deleted |
| List |
list.member.added, list.member.removed |
| Segment |
segment.member.added, segment.member.removed |
| Campaign |
campaign.sent, campaign.delivered |
| Flow |
flow.triggered, flow.message.sent |
| Event |
Custom metric events |
Instructions
Follow these six steps in order. Each is fully sourced in references/implementation.md; the security-critical signature check is inlined below because getting it wrong is the most common failure.
- Create a webhook subscription — call
webhooksApi.createWebhook with the target endpointUrl and webhookTopics, then save the signing secret from the response as KLAVIYOWEBHOOKSIGNING_SECRET.
- Verify the signature — recompute the HMAC-SHA256 over the raw request body and compare with a timing-safe check (skeleton below).
- Receive events — mount an Express route with
express.raw({ type: 'application/json' }) so the raw body survives for verification; reject on a bad signature, then parse.
- Route by topic — dispatch
event.type to a per-topic handl
Ready to use klaviyo-pack?
|
|