From klaviyo-pack
Diagnose and fix common Klaviyo API errors and exceptions. Use when encountering Klaviyo 4xx/5xx errors, debugging failed requests, or troubleshooting SDK integration issues. Trigger with phrases like "klaviyo error", "fix klaviyo", "klaviyo not working", "debug klaviyo", "klaviyo 400", "klaviyo 429".
npx claudepluginhub flight505/skill-forge --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.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
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.