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.md
source content

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.

EndpointRequired 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):

WindowLimit
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

ErrorCauseFix
Cannot find module 'klaviyo-api'
Wrong package
npm install klaviyo-api
(not
@klaviyo/sdk
)
TypeError: ... is not a constructor
Wrong importUse
new ProfilesApi(session)
not
new KlaviyoClient()
response.data is undefined
Wrong access patternUse
response.body.data
(not
response.data
)
filter is not valid
Bad filter syntaxUse
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
.