Claude-code-plugins-plus-skills klaviyo-common-errors
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/klaviyo-pack/skills/klaviyo-common-errors" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-klaviyo-common-errors && rm -rf "$T"
manifest:
plugins/saas-packs/klaviyo-pack/skills/klaviyo-common-errors/SKILL.mdsource content
Klaviyo Common Errors
Overview
Quick reference for the most common Klaviyo API errors with real error payloads, root causes, and solutions.
Prerequisites
SDK installedklaviyo-api- 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
value in JSON:API payloadtype - Sending
instead ofsnake_case
(SDK uses camelCase)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
environment variableKLAVIYO_PRIVATE_KEY - 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 |
|---|---|
| |
| |
| |
| |
| |
404 -- Not Found
Typical causes:
- Wrong resource ID (profile, list, segment, campaign)
- Using v1/v2 URL paths instead of new API (
is dead, use/api/v2/
)/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 |
|---|---|---|
| Wrong package | (not ) |
| Wrong import | Use not |
| Wrong access pattern | Use (not ) |
| Bad filter syntax | Use not |
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.