Customerio Rate LimitsSAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-08Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: customerio-rate-limits description: 'Implement Customer.io rate limiting and backoff. Use when handling high-volume API calls, implementing retry logic, or hitting 429 errors. Trigger: "customer.io rate limit", "customer.io throttle", "customer.io 429", "customer.io backoff", "customer.io too many requests". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(npx:*), Glob, Grep version: 1.14.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - saas - customer-io - api - rate-limiting compatibility: Designed for Claude Code --- # Customer.io Rate Limits ## Prerequisites - Current provider limit documentation/plan, a measured baseline, idempotency design, and a named capacity owner. - Synthetic test payloads and alerting for 429s, queue age, delivery failure, and duplicate-event risk. ## Output - A rate-aware client/workflow configuration with bounded concurrency, backoff, idempotency, monitoring, and a safe replay decision path. - An aggregate load-test or incident receipt showing limit behavior without recipient payloads. ## Examples Send synthetic events at gradually increasing concurrency below the approved limit, record 429s and queue age, and apply exponential backoff with jitter using a stable idempotency key. If throttling occurs, reduce concurrency and allow recovery; do not retry every failed event immediately or widen sends to compensate. ## Overview Understand Customer.io's API rate limits and implement proper throttling: token bucket limiters, exponential backoff with jitter, queue-based processing, and 429 response handling. ## Rate Limit Reference | API | Endpoint | Limit | Scope | |-----|----------|-------|-------| | Track API | `identify`, `track`, `trackAnonymous` | ~100 req/sec | Per workspace | | Track API | Batch operations | ~100 req/sec | Per workspace | | App API | Transactional email/push | ~100 req/sec | Per workspace | | App API | Broadcasts, queries | ~10 req/sec | Per workspace | These are approximate. Customer.io uses sliding window rate limiting. When exceeded, you get a `429 Too Many Requests` response. ## Instructions ### Step 1: Token Bucket Rate Limiter ```typescript // lib/rate-limiter.ts export class TokenBucket { private tokens: number; private lastRefill: number; constructor( private readonly maxTokens: number = 80, // Stay under 100/sec limit private readonly refillRate: number = 80 // Tokens per second ) { this.tokens = maxTokens; this.lastRefill = Date.now(); } private refill(): void { const now = Date.now(); const elapsed = (now - this.lastRefill) / 1000; this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate); this.lastRefill = now; } async acquire(): Promise<void> { this.refill(); if (this.tokens >= 1) { this.tokens -= 1; return; } // Wait until a token is available const waitMs = ((1 - this.tokens) / this.refillRate) * 1000; await new Promise((r) => setTimeout(r, Math.ceil(waitMs))); this.tokens = 0; this.lastRefill = Date.now(); } } ``` ### Step 2: Exponential Backoff with Jitter ```typescript // lib/backoff.ts interface BackoffOptions { maxRetries: number; baseDelayMs: number; maxDelayMs: number; jitter: number; // 0 to 1 } const DEFAULTS: BackoffOptions = { maxRetries: 4, baseDelayMs: 1000, maxDelayMs: 60000, jitter: 0.25, }; export async function withBackoff<T>( fn: () => Promise<T>, opts: Partial<BackoffOptions> = {} ): Promise<T> { const { maxRetries, baseDelayMs, maxDelayMs, jitter } = { ...DEFAULTS, ...opts }; let lastErr: Error | undefined; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (err: any) { lastErr = err; const status = err.statusCode ?? err.status; // Don't retry 4xx errors (except 429) if (status >= 400 && status < 500 && status !== 429) throw err; if (attempt === maxRetries) break; // Check Retry-After header (429 responses) const retryAfter = err.headers?.["retry-after"]; let delay: number; if (retryAfter) { delay = parseInt(retryAfter) * 1000; } else { delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs); } // Add jitter to prevent thundering herd delay += delay * jitter * Math.random(); console.warn(`CIO retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`); await new Promise((r) => setTimeout(r, delay)); } } throw lastErr; } ``` ### Step 3: Rate-Limited Client ```typescript // lib/customerio-rate-limited.ts import { TrackClient, RegionUS } from "customerio-node"; import { TokenBucket } from "./rate-limiter"; import { withBackoff } from "./backoff"; export class RateLimitedCioClient { private client: TrackClient; private limiter: TokenBucket; constructor(siteId: string, apiKey: string, ratePerSec: number = 80) { this.client = new TrackClient(siteId, apiKey, { region: RegionUS }); this.limiter = new TokenBucket(ratePerSec, ratePerSec); } async identify(userId: string, attrs: Record<string, any>): Promise<void> { await this.limiter.acquire(); return withBackoff(() => this.client.identify(userId, attrs)); } async track(userId: string, event: { name: string; data?: any }): Promise<void> { await this.limiter.acquire(); return withBackoff(() => this.client.track(userId, event)); } async trackAnonymous(event: { anonymous_id: string; name: string; data?: any; }): Promise<void> { await this.limiter.acquire(); return withBackoff(() => this.client.trackAnonymous(event)); } async suppress(userId: string): Promise<void> { await this.limiter.acquire(); return withBackoff(() => this.client.suppress(userId)); } async destroy(userId: string): Promise<void> { await this.limiter.acquire(); return withBackoff(() => this.client.d
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__customerio-rate-limits.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-08 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Customerio Rate Limits skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Customerio Rate Limits safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Customerio Rate Limits access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Customerio Rate Limits work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-08. The repository is watched, and a new audit runs when it changes — this is the first audit.