agentsclimarketplace

Klaviyo rate limits

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/klaviyo-pack/skills/klaviyo-rate-limits

'Implement Klaviyo rate limiting, backoff, and request queuing patterns.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill klaviyo-rate-limits

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.2 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

Klaviyo Rate Limits

Overview

Handle Klaviyo's per-account fixed-window rate limits with proper Retry-After header handling, exponential backoff, and request queuing. This skill installs a small set of composable helpers: a retry wrapper that honors Klaviyo's Retry-After, a request queue that paces sustained throughput, a monitor that reads live rate-limit headers, and a rate-aware bulk import.

Prerequisites

  • The klaviyo-api SDK installed in the target project (npm install klaviyo-api).
  • The p-queue package installed for the request queue (npm install p-queue).
  • A working knowledge of Klaviyo's dual-window (burst + steady) rate limiting, summarized in the architecture table below.
  • Write access to the project's src/klaviyo/ directory, where the generated helper files land.

Klaviyo Rate Limit Architecture

Klaviyo uses per-account fixed-window rate limiting with two distinct windows:

WindowDurationLimitDescription
Burst1 second75 requestsShort spike protection
Steady1 minute700 requestsSustained throughput cap

Both windows apply simultaneously. Exceeding either triggers a 429 Too Many Requests.

Rate Limit Headers

On successful requests:

HeaderDescription
RateLimit-LimitMax requests for the window
RateLimit-RemainingRemaining requests in window
RateLimit-ResetSeconds until window resets

On 429 responses (different headers!):

HeaderDescription
Retry-AfterInteger seconds to wait before retrying

Critical: When you hit a 429, RateLimit-* headers are NOT returned. Only Retry-After is present.

Instructions

Step 1: Retry-After Aware Backoff (core)

Use Write to create src/klaviyo/rate-limiter.ts with the retry wrapper below. This is the foundation every other helper builds on: it retries only on 429 and 5xx, always honors Klaviyo's Retry-After on a 429, and falls back to exponential backoff with jitter for 5xx.

// src/klaviyo/rate-limiter.ts

export async function withRateLimitRetry<T>(
  operation: () => Promise<T>,
  options = { maxRetries: 5, baseDelayMs: 1000, maxDelayMs: 60000 }
): Promise<T> {
  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error: any) {
      if (attempt === options.maxRetries) throw error;

      const status = error.status;

      // Only retry on 429 (rate limit) and 5xx (server errors)
      if (status !== 429 && (status < 500 || status >= 600)) throw error;

      let delayMs: number;

      if (status === 429) {
        // ALWAYS honor Klaviyo's Retry-After header
        const retryAfter = error.headers?.['retry-after'];
        delayMs = retryAfter
          ? parseInt(retryAfter) * 1000
          : options.baseDelayMs * Math.pow(2, attempt);
      } else {
        // 5xx: exponential backoff with jitter
        const exponential = options.baseDelayMs * Math.pow(2, attempt);
        const jitter = Math.random() * options.baseDelayMs;
        delayMs = Math.min(exponential + jitter, options.maxDelayMs);
      }

      console.log(`[Klaviyo] ${status} on attempt ${attempt + 1}. Retrying in ${delayMs}ms...`);
      await new Promise(r => setTimeout(r, delayMs));
    }
  }
  throw new Error('Unreachable');
}

Steps 2–4: Queue, Monitor, and Bulk operations

Layer the sustained-throughput controls on top of the retry wrapper. Each is a small file you Write into src/klaviyo/; the full annotated code for all three is in the implementation walkthrough:

  • Step 2 — Request queue (src/klaviyo/queue.ts): a p-queue capped at 60 req/s (headroom under the 75 req/s burst wall) that wraps every call in withRateLimitRetry. This prevents most 429s instead of just reacting to them.
  • Step 3 — Rate limit monitor (src/klaviyo/monitor.ts): reads the RateLimit-* response headers so throttle decisions track the account's real remaining budget — important when multiple processes share one quota.
  • Step 4 — Rate-aware bulk sync (bulkProfileSync): batches large imports through the queue and paces between batches so a 100k-profile sync never trips the steady (1-minute) cap.

Read references/implementation.md for the complete source of Steps 2–4, and references/examples.md for end-to-end usage.

Output

Applying this skill produces the following in the target project:

  • src/klaviyo/rate-limiter.tswithRateLimitRetry, the Retry-After-honoring retry wrapper (Step 1).
  • src/klaviyo/queue.tsqueuedKlaviyoCall, the paced request queue (Step 2).
  • src/klaviyo/monitor.tsrateLimitMonitor, a live header-driven throttle monitor (Step 3).
  • bulkProfileSync — a rate-aware batch import helper (Step 4).

At runtime the helpers emit [Klaviyo] console logs on each retry, batch, and queue-drain event, and bulkProfileSync returns a { success, failed } count so callers can report import results. Net effect: Klaviyo API traffic stays under both the burst and steady windows, and any 429 that does occur is absorbed by honoring Retry-After rather than failing the request.

Error Handling

ScenarioDetectionSolution
Burst exceeded429 + short Retry-AfterWait Retry-After seconds
Steady exceeded429 + longer Retry-AfterQueue requests, reduce concurrency
Thundering herdMultiple 429s after resumeAdd random jitter to retry delays
Stuck at 429Retry-After keeps growingReduce request volume; check for runaway loops

Examples

Common wirings of the helpers — a single rate-safe call, high-volume writes through the queue, a 100k-profile import, and proactive throttling from live headers — are collected in references/examples.md. The minimal case is one wrapped call:

import { withRateLimitRetry } from './klaviyo/rate-limiter';

const profile = await withRateLimitRetry(() =>
  profilesApi.getProfile('01H...')
);

See the worked examples for Examples 2–4.

Resources

Next Steps

For security configuration, see klaviyo-security-basics.

What ships with it: 2 files

6.5 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.