Klaviyo Common Errors
Overview
Quick reference for the most common Klaviyo API errors with real error payloads, root causes, and solutions.
Prerequisites
klaviyo-apiSDK installed- API credentials configured
- Access to application logs
Instructions
Step 1: Identify the Error
Klaviyo 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 }
}
Step 2: Match and Fix
400 -- Bad Request (Validation Error)
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:
- Missing required field (email, metric name, list name)
- Invalid phone number format (must be E.164:
+15551234567) - Invalid filter syntax in query params
- Wrong
typevalue in JSON:API payload - Sending
snake_caseinstead ofcamelCase(SDK uses camelCase)
Fix:
// Wrong: snake_case
{ first_name: 'Jane', phone_number: '+155...' }
// Right: camelCase (SDK convention)
{ firstName: 'Jane', phoneNumber: '+15551234567' }
401 -- Unauthorized
Actual response:
{
"errors": [{
"code": "not_authenticated",
"title": "Authentication credentials were not provided.",
"detail": "Missing or invalid Authorization header."
}]
}
Root causes:
- Missing
KLAVIYO_PRIVATE_KEYenvironment variable - Using a public key (6 chars) instead of private key (
pk_*) - API key was revoked or rotated
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/"
403 -- Forbidden (Missing Scope)
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 |
404 -- Not Found
Typical causes:
- Wrong resource ID (profile, list, segment, campaign)
- Using v1/v2 URL paths instead of new API (
/api/v2/is dead, use/api/) - Resource was deleted
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');
409 -- Conflict (Duplicate)
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' },
},
});
429 -- Rate Limited
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
}
}
500/503 -- Klaviyo Server Error
Fix:
- Check Klaviyo Status Page
- Retry with exponential backoff (see
klaviyo-rate-limits) - If persistent, check Klaviyo's changelog for known issues
Common SDK-Level Errors
| 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 |
Quick Diagnostic Commands
# 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
Escalation Path
- Collect evidence with
klaviyo-debug-bundle - Check status.klaviyo.com
- Open ticket at Klaviyo Support with request IDs from error responses
Resources
Next Steps
For comprehensive debugging, see klaviyo-debug-bundle.