Agent Skills: Klaviyo Common Errors

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/klaviyo-common-errors

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/klaviyo-pack/skills/klaviyo-common-errors

Skill Files

Browse the full folder contents for klaviyo-common-errors.

Download Skill

Loading file tree…

plugins/saas-packs/klaviyo-pack/skills/klaviyo-common-errors/SKILL.md

Skill Metadata

Name
klaviyo-common-errors
Description
|

Klaviyo Common Errors

Overview

Quick reference for the most common Klaviyo API errors with real error payloads, root causes, and solutions.

Prerequisites

  • klaviyo-api SDK 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 type value in JSON:API payload
  • Sending snake_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' }

401 -- Unauthorized

Actual response:

{
  "errors": [{
    "code": "not_authenticated",
    "title": "Authentication credentials were not provided.",
    "detail": "Missing or invalid Authorization header."
  }]
}

Root causes:

  1. Missing KLAVIYO_PRIVATE_KEY environment variable
  2. Using a public key (6 chars) instead of private key (pk_*)
  3. 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:

  1. Check Klaviyo Status Page
  2. Retry with exponential backoff (see klaviyo-rate-limits)
  3. 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

  1. Collect evidence with klaviyo-debug-bundle
  2. Check status.klaviyo.com
  3. Open ticket at Klaviyo Support with request IDs from error responses

Resources

Next Steps

For comprehensive debugging, see klaviyo-debug-bundle.