From klaviyo-pack
Diagnose and fix common Klaviyo API errors like 400 bad request, 401 unauthorized, 403 forbidden using real payloads, causes, and TypeScript SDK fixes.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin klaviyo-packThis skill is limited to using the following tools:
Quick reference for the most common Klaviyo API errors with real error payloads, root causes, and solutions.
Applies production-ready patterns for klaviyo-api Node.js SDK: singleton sessions, type-safe wrappers, lazy API clients, and error handling. For Klaviyo integrations in TypeScript projects.
Integrates Klaviyo email/SMS marketing API: manage profiles, track events, build flows, and segment customers. Use for e-commerce marketing features with Node.js, Python SDKs or direct HTTP.
Diagnoses and fixes common Customer.io errors: API status codes, SDK auth failures, timestamp issues, delivery problems, and campaign triggers.
Share bugs, ideas, or general feedback.
Quick reference for the most common Klaviyo API errors with real error payloads, root causes, and solutions.
klaviyo-api SDK installedKlaviyo returns JSON:API error responses. Extract the status code and error detail:
try {
await profilesApi.createProfile(payload);
} catch (error: any) {
console.error('Status:', error.status);
console.error('Errors:', JSON.stringify(error.body?.errors, null, 2));
// error.body.errors[] has: { id, code, title, detail, source }
}
Actual Klaviyo response:
{
"errors": [{
"id": "abc-123",
"code": "invalid",
"title": "Invalid input.",
"detail": "The email field is required.",
"source": { "pointer": "/data/attributes/email" }
}]
}
Common causes:
+15551234567)type value in JSON:API payloadsnake_case instead of camelCase (SDK uses camelCase)Fix:
// Wrong: snake_case
{ first_name: 'Jane', phone_number: '+155...' }
// Right: camelCase (SDK convention)
{ firstName: 'Jane', phoneNumber: '+15551234567' }
Actual response:
{
"errors": [{
"code": "not_authenticated",
"title": "Authentication credentials were not provided.",
"detail": "Missing or invalid Authorization header."
}]
}
Root causes:
KLAVIYO_PRIVATE_KEY environment variablepk_*)Fix:
# Verify key is set and starts with pk_
echo $KLAVIYO_PRIVATE_KEY | head -c 3
# Should print: pk_
# Test with cURL
curl -s -w "%{http_code}" -o /dev/null \
-H "Authorization: Klaviyo-API-Key $KLAVIYO_PRIVATE_KEY" \
-H "revision: 2024-10-15" \
"https://a.klaviyo.com/api/accounts/"
Actual response:
{
"errors": [{
"code": "permission_denied",
"title": "You do not have permission to perform this action.",
"detail": "The API key does not have the required scope: profiles:write"
}]
}
Fix: Generate a new API key with the required scope at Settings > API Keys > Create Private API Key.
| Endpoint | Required Scope |
|---|---|
POST /api/profiles/ | profiles:write |
GET /api/segments/ | segments:read |
POST /api/events/ | events:write |
POST /api/campaigns/ | campaigns:write |
POST /api/data-privacy-deletion-jobs/ | data-privacy:write |
Typical causes:
/api/v2/ is dead, use /api/)Fix:
// Verify the resource exists first
const lists = await listsApi.getLists();
const targetList = lists.body.data.find(l => l.attributes.name === 'Newsletter');
if (!targetList) throw new Error('List not found');
Actual response:
{
"errors": [{
"code": "duplicate",
"title": "Conflict.",
"detail": "A profile already exists with the email customer@example.com"
}]
}
Fix: Use createOrUpdateProfile (upsert) instead of createProfile:
// This handles both create and update
await profilesApi.createOrUpdateProfile({
data: {
type: 'profile' as any,
attributes: { email: 'customer@example.com', firstName: 'Updated' },
},
});
Headers on 429 response:
Retry-After: 10
Klaviyo rate limits (per-account, fixed window):
| Window | Limit |
|---|---|
| Burst (1 second) | 75 requests |
| Steady (1 minute) | 700 requests |
Note: When rate limited, RateLimit-Remaining and RateLimit-Reset headers are NOT returned. Only Retry-After (integer seconds) is present.
Fix: Honor Retry-After header:
catch (error: any) {
if (error.status === 429) {
const retryAfter = parseInt(error.headers?.['retry-after'] || '10');
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
// Retry the request
}
}
Fix:
klaviyo-rate-limits)| Error | Cause | Fix |
|---|---|---|
Cannot find module 'klaviyo-api' | Wrong package | npm install klaviyo-api (not @klaviyo/sdk) |
TypeError: ... is not a constructor | Wrong import | Use new ProfilesApi(session) not new KlaviyoClient() |
response.data is undefined | Wrong access pattern | Use response.body.data (not response.data) |
filter is not valid | Bad filter syntax | Use equals(field,"value") not field = value |
# Check Klaviyo API health
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Klaviyo-API-Key $KLAVIYO_PRIVATE_KEY" \
-H "revision: 2024-10-15" \
"https://a.klaviyo.com/api/accounts/"
# Check Klaviyo status page
curl -s https://status.klaviyo.com/api/v2/status.json | python3 -m json.tool
# Verify local env
env | grep KLAVIYO
npm list klaviyo-api
klaviyo-debug-bundleFor comprehensive debugging, see klaviyo-debug-bundle.