posthog-rate-limits
Covers PostHog rate limit tiers and implementation strategies for handling errors and throttling.
Install
mkdir -p .claude/skills/posthog-rate-limits && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4849" && unzip -o skill.zip -d .claude/skills/posthog-rate-limits && rm skill.zipInstalls to .claude/skills/posthog-rate-limits
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Handle PostHog API rate limits with exponential backoff, request queuing,Key capabilities
- →Implement exponential backoff for 429 errors
- →Queue requests to enforce rate limits
- →Cache API responses to reduce frequency
- →Monitor rate limit headers
How it works
Uses a request queue to enforce throughput limits and implements exponential backoff logic that respects the Retry-After header provided by the API.
Inputs & outputs
When to use posthog-rate-limits
- →Handle API rate limit exceptions
- →Implement exponential backoff for requests
- →Monitor API throughput against usage limits
- →Optimize request patterns for admin endpoints
About this skill
PostHog Rate Limits
Overview
PostHog rate limits apply to private API endpoints authenticated with a personal API key (phx_...). Public capture endpoints (/capture/, /batch/, /decide/) are not rate limited. Understanding which endpoints have limits is critical to avoiding 429 errors.
Prerequisites
- PostHog personal API key (
phx_...) for admin endpoints - Understanding of which endpoints you call and how often
posthog-nodeor direct API usage
PostHog Rate Limit Tiers
| Endpoint Category | Rate Limit | Examples |
|---|---|---|
Event capture (/capture/, /batch/) | No limit | posthog.capture(), batch ingestion |
Feature flag decide (/decide/) | No limit | Client-side flag evaluation |
| Analytics API (insights, persons, recordings) | 240/min, 1200/hour | Trend queries, person lookup |
HogQL query API (/api/projects/:id/query/) | 1200/hour | Custom SQL queries |
| Feature flag local evaluation polling | 600/min | Server SDK flag definition fetch |
| All other private endpoints | 240/min, 1200/hour | Feature flag CRUD, cohorts, annotations |
Instructions
Step 1: Implement Exponential Backoff with Retry-After
async function postHogApiCall<T>(
url: string,
options: RequestInit,
maxRetries = 5
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.POSTHOG_PERSONAL_API_KEY}`,
...options.headers,
},
});
if (response.ok) {
return response.json();
}
if (response.status === 429) {
// Honor the Retry-After header from PostHog
const retryAfter = parseInt(response.headers.get('Retry-After') || '0');
const backoffMs = retryAfter > 0
? retryAfter * 1000
: Math.min(1000 * Math.pow(2, attempt) + Math.random() * 500, 32000);
console.warn(`PostHog 429: retrying in ${Math.round(backoffMs)}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(r => setTimeout(r, backoffMs));
continue;
}
// Don't retry client errors (except 429)
if (response.status >= 400 && response.status < 500) {
const body = await response.text();
throw new Error(`PostHog API ${response.status}: ${body}`);
}
// Retry server errors (500+)
if (attempt < maxRetries) {
const delay = 1000 * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
continue;
}
throw new Error(`PostHog API failed after ${maxRetries} retries: ${response.status}`);
}
throw new Error('Unreachable');
}
Step 2: Request Queue for Burst Protection
import PQueue from 'p-queue';
// Enforce 240 requests/minute = 4 requests/second
const posthogQueue = new PQueue({
concurrency: 2, // Max parallel requests
interval: 1000, // Per second
intervalCap: 4, // Max 4 requests per second
});
async function queuedPostHogCall<T>(
url: string,
options: RequestInit
): Promise<T> {
return posthogQueue.add(() => postHogApiCall<T>(url, options));
}
// Usage: all calls are automatically throttled
const insights = await queuedPostHogCall(
`https://app.posthog.com/api/projects/${PROJECT_ID}/insights/trend/`,
{ method: 'GET' }
);
Step 3: Cache Frequently Accessed Data
// Cache insight results to reduce API calls
class PostHogCache {
private cache = new Map<string, { data: any; expiry: number }>();
async get<T>(key: string, fetcher: () => Promise<T>, ttlMs = 300000): Promise<T> {
const cached = this.cache.get(key);
if (cached && Date.now() < cached.expiry) {
return cached.data as T;
}
const data = await fetcher();
this.cache.set(key, { data, expiry: Date.now() + ttlMs });
return data;
}
invalidate(key: string) {
this.cache.delete(key);
}
}
const phCache = new PostHogCache();
// Cache trend data for 5 minutes
const trends = await phCache.get('weekly-pageviews', () =>
queuedPostHogCall(`https://app.posthog.com/api/projects/${PROJECT_ID}/insights/trend/?events=[{"id":"$pageview"}]&date_from=-7d`, { method: 'GET' })
);
Step 4: Monitor Rate Limit Headers
class RateLimitMonitor {
private remaining = Infinity;
private resetAt = 0;
update(headers: Headers) {
const remaining = headers.get('X-RateLimit-Remaining');
const reset = headers.get('X-RateLimit-Reset');
if (remaining) this.remaining = parseInt(remaining);
if (reset) this.resetAt = parseInt(reset) * 1000;
}
shouldThrottle(): boolean {
return this.remaining < 10 && Date.now() < this.resetAt;
}
waitTime(): number {
return Math.max(0, this.resetAt - Date.now());
}
log() {
console.log(`PostHog rate limit: ${this.remaining} remaining, resets in ${Math.round(this.waitTime() / 1000)}s`);
}
}
const rateLimits = new RateLimitMonitor();
// After each API call, update the monitor
const response = await fetch(url, options);
rateLimits.update(response.headers);
if (rateLimits.shouldThrottle()) {
console.warn(`Approaching PostHog rate limit — waiting ${rateLimits.waitTime()}ms`);
await new Promise(r => setTimeout(r, rateLimits.waitTime()));
}
Error Handling
| Error | Cause | Solution |
|---|---|---|
| HTTP 429 on insights | >240 req/min on analytics | Queue requests, cache results |
| 429 on flag polling | >600 req/min local eval fetch | Increase featureFlagsPollingInterval |
| 429 on HogQL | >1200 req/hour | Cache query results, reduce frequency |
| Thundering herd on retry | All clients retry simultaneously | Add random jitter to backoff |
Key Points
- Capture endpoints are NOT rate limited —
posthog.capture()calls will never 429 - Only private API calls are limited — endpoints requiring
Authorization: Bearer phx_... - Cache aggressively — insight data rarely needs real-time refresh
- Honor Retry-After — PostHog tells you exactly how long to wait
Output
- Exponential backoff with Retry-After header support
- Request queue enforcing 4 req/sec
- In-memory cache for API responses
- Rate limit header monitoring
Resources
Next Steps
For security configuration, see posthog-security-basics.
When not to use it
- →Do not apply rate limiting to public capture endpoints
- →Avoid retrying client errors other than 429
Prerequisites
Limitations
- →Analytics API limited to 240 requests per minute
- →HogQL query API limited to 1200 requests per hour
How it compares
This method explicitly handles 429 responses and enforces client-side throttling, whereas standard implementations may fail repeatedly during bursts.
Compared to similar skills
posthog-rate-limits side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| posthog-rate-limits (this skill) | 1 | 27d | Caution | Intermediate |
| mcp-builder | 136 | 3mo | Review | Advanced |
| copilot-sdk | 7 | 4mo | Review | Intermediate |
| openrouter-function-calling | 5 | 27d | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
mcp-builder
anthropics
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
copilot-sdk
github
Build agentic applications with GitHub Copilot SDK. Use when embedding AI agents in apps, creating custom tools, implementing streaming responses, managing sessions, connecting to MCP servers, or creating custom agents. Triggers on Copilot SDK, GitHub SDK, agentic app, embed Copilot, programmable agent, MCP server, custom agent.
openrouter-function-calling
jeremylongshore
Implement function/tool calling with OpenRouter models. Use when building agents or structured outputs. Trigger with phrases like 'openrouter functions', 'openrouter tools', 'openrouter agent', 'function calling'.
chatgpt-app-builder
mcp-use
Build ChatGPT apps with interactive widgets using mcp-use and OpenAI Apps SDK. Use when creating ChatGPT apps, building MCP servers with widgets, defining React widgets, working with Apps SDK, or when user mentions ChatGPT widgets, mcp-use widgets, or Apps SDK development.
create-mcp-app
modelcontextprotocol
This skill should be used when the user asks to "create an MCP App", "add a UI to an MCP tool", "build an interactive MCP View", "scaffold an MCP App", or needs guidance on MCP Apps SDK patterns, UI-resource registration, MCP App lifecycle, or host integration. Provides comprehensive guidance for building MCP Apps with interactive UIs.
windsurf-mcp-integration
jeremylongshore
Manage integrate MCP servers with Windsurf for extended capabilities. Activate when users mention "mcp integration", "model context protocol", "external tools", "mcp server", or "cascade tools". Handles MCP server configuration and integration. Use when working with windsurf mcp integration functionality. Trigger with phrases like "windsurf mcp integration", "windsurf integration", "windsurf".