Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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".
.claude/skills/jeremylongshore-klaviyo-common-errors/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 14% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 78% | 0% |
Quick reference for the most common Klaviyo API errors with real error payloads, root causes, and solutions. Because Klaviyo returns JSON:API errors (a status code plus a structured errors[] array), this skill walks you from a raw exception to a targeted fix: extract the status code, match it against the catalog, apply the documented remedy.
The full per-status-code catalog and the SDK-level failure table live in references/ to keep this workflow scannable — drill in once you know which status code you are chasing.
klaviyo-api SDK installed (npm install klaviyo-api — note: not @klaviyo/sdk)pk_*) exported as KLAVIYO_PRIVATE_KEYcan Read the stack trace and Grep for the status code and error code
Extract the status code and error detail from the caught exception. Read the log line or wrap the call so the structured payload is visible:
typescripttry { 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 } }
If you only have raw logs, Grep for the status code (grep -E "40[0-9]|429|50[0-9]") and the code field to isolate the failing request.
Map the status code to its root cause and remedy. Each row links into the full catalog, which carries the actual response payload and the fix code block:
| Status | Meaning | Most common root cause | |--------|---------|------------------------| | 400 | Bad Request | Missing field, non-E.164 phone, or snake_case instead of camelCase | | 401 | Unauthorized | Missing KLAVIYO_PRIVATE_KEY, or a public key used as a private key | | 403 | Forbidden | API key lacks the required scope (e.g. profiles:write) | | 404 | Not Found | Wrong resource ID or a dead /api/v2/ path | | 409 | Conflict | Duplicate — use createOrUpdateProfile upsert | | 429 | Rate Limited | Exceeded burst (75/s) or steady (700/min); honor Retry-After | | 500/503 | Server Error | Klaviyo-side — check status page, retry with backoff |
The most common one, 400, is almost always a casing mismatch (the SDK expects camelCase):
typescript// Wrong: snake_case // Right: camelCase (SDK convention) { first_name: 'Jane' } { firstName: 'Jane' }
See the full error catalog for every status code's real payload, complete cause list, and fix. For client-side failures that never reach the network (wrong import, response.data vs response.body.data, bad filter syntax) plus copy-paste diagnostic commands, see diagnostics & SDK errors.
Working through this skill produces a diagnosis and a fix, not a generated artifact:
code identifying the failure classan API response. Check the SDK-level errors table in diagnostics (module-not-found, wrong constructor).
echo $KLAVIYO_PRIVATE_KEY | head -c 3 (must print pk_).
RateLimit-Remaining header — expected. On a 429 Klaviyo returnsonly Retry-After; do not depend on the reset headers, honor Retry-After.
klaviyo-debug-bundle,check status.klaviyo.com, then open a support ticket with the request IDs from the error responses.
Example — 403 permission_denied on profile create. The exception shows status: 403, detail: "...required scope: profiles:write". Match to the 403 row: the key lacks a scope. Fix: mint a new key with profiles:write at Settings > API Keys. Full payload and the endpoint→scope table are in the error catalog under the 403 section.
Example — intermittent 429 under load. Requests fail once traffic exceeds 700/min. Honor the Retry-After header and back off instead of tight-retrying:
typescriptif (error.status === 429) { const retryAfter = parseInt(error.headers?.['retry-after'] || '10'); // seconds await new Promise(r => setTimeout(r, retryAfter * 1000)); // then retry }
More worked cases (400 casing, 404 stale ID, 409 upsert) are in the error catalog.
klaviyo-debug-bundle skill| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→pass | 16,612 | 17,641 | +6% | 1 | 1 | 0% | 2,031 | 3,048 | +50% | 0 | 0 | — |
case-01 | fail→fail | 17,532 | 15,540 | -11% | 1 | 1 | 0% | 2,531 | 3,513 | +39% | 0 | 0 | — |
case-02 | fail→pass | 25,424 | 18,594 | -27% | 1 | 1 | 0% | 3,661 | 4,125 | +13% | 0 | 0 | — |
case-03 | fail→pass | 20,588 | 14,586 | -29% | 1 | 1 | 0% | 2,928 | 3,324 | +14% | 0 | 0 | — |
case-04 | pass→pass | 16,458 | 26,722 | +62% | 1 | 1 | 0% | 3,102 | 6,181 | +99% | 0 | 0 | — |
case-05 | pass→pass | 12,670 | 9,663 | -24% | 1 | 1 | 0% | 1,067 | 2,313 | +117% | 0 | 0 | — |
case-06 | pass→pass | 15,673 | 17,437 | +11% | 1 | 1 | 0% | 3,146 | 4,120 | +31% | 0 | 0 | — |
case-07 | fail→pass | 22,210 | 12,389 | -44% | 1 | 1 | 0% | 3,159 | 3,534 | +12% | 0 | 0 | — |
case-08 | fail→pass | 11,695 | 9,468 | -19% | 1 | 1 | 0% | 1,174 | 2,092 | +78% | 0 | 0 | — |
case-09 | pass→pass | 14,340 | 11,175 | -22% | 1 | 1 | 0% | 1,707 | 2,720 | +59% | 0 | 0 | — |
case-11 | fail→pass | 16,318 | 11,262 | -31% | 1 | 1 | 0% | 1,609 | 2,451 | +52% | 0 | 0 | — |
case-12 | pass→pass | 15,350 | 13,361 | -13% | 1 | 1 | 0% | 2,005 | 3,120 | +56% | 0 | 0 | — |
case-13 | pass→pass | 13,965 | 10,959 | -22% | 1 | 1 | 0% | 2,392 | 3,450 | +44% | 0 | 0 | — |
case-14 | pass→pass | 18,098 | 13,937 | -23% | 1 | 1 | 0% | 3,135 | 4,034 | +29% | 0 | 0 | — |
case-15 | fail→pass | 5,166 | 8,304 | +61% | 1 | 1 | 0% | 913 | 2,155 | +136% | 0 | 0 | — |
case-16 | fail→fail | 10,739 | 11,958 | +11% | 1 | 1 | 0% | 1,873 | 2,889 | +54% | 0 | 0 | — |
case-17 | fail→pass | 6,059 | 8,761 | +45% | 1 | 1 | 0% | 1,046 | 2,197 | +110% | 0 | 0 | — |
case-18 | fail→pass | 20,402 | 10,028 | -51% | 1 | 1 | 0% | 2,171 | 3,383 | +56% | 0 | 0 | — |
case-19 | fail→pass | 15,266 | 7,703 | -50% | 1 | 1 | 0% | 1,671 | 1,968 | +18% | 0 | 0 | — |
case-20 | fail→pass | 26,481 | 10,855 | -59% | 1 | 1 | 0% | 2,299 | 2,628 | +14% | 0 | 0 | — |
case-21 | pass→pass | 11,961 | 11,093 | -7% | 1 | 1 | 0% | 1,208 | 2,628 | +118% | 0 | 0 | — |
case-22 | pass→pass | 16,770 | 3,779 | -77% | 1 | 1 | 0% | 2,113 | 2,164 | +2% | 0 | 0 | — |
case-23 | pass→fail | 23,537 | 16,179 | -31% | 1 | 1 | 0% | 2,705 | 3,578 | +32% | 0 | 0 | — |
case-24 | pass→pass | 8,109 | 8,882 | +10% | 1 | 1 | 0% | 1,637 | 2,323 | +42% | 0 | 0 | — |
case-25 | pass→pass | 8,490 | 7,669 | -10% | 1 | 1 | 0% | 566 | 1,995 | +252% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +40 percentage points is the difference between those two pass rates over the 25 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.