算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
posthog-rate-limits【设计与创意】Design evidence-based PostHog private-API throttling with correct endpoint classes, team-wide budgets, backoff, and export alternatives. Use when a private API returns 429 or a polling job needs a request budget. Trigger with "PostHog rate limit", "PostHog 429", or "PostHog backoff".
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: posthog-rate-limits description: | Design evidence-based PostHog private-API throttling with correct endpoint classes, team-wide budgets, backoff, and export alternatives. Use when a private API returns 429 or a polling job needs a request budget. Trigger with "PostHog rate limit", "PostHog 429", or "PostHog backoff". argument-hint: "[integration] [endpoint-class]" allowed-tools: Read, Write, Edit version: 1.14.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io tags:
PostHog rate limits apply to private API endpoints authenticated with a personal API key, project secret key where available, or OAuth. Public POST-only capture and flag endpoints have no PostHog request-level rate limit, but capture can still report billing limits in a successful response.
posthog-node or direct API usage| Endpoint Category | Rate Limit | Examples |
|---|---|---|
Public event capture (/e, /i/v0/e, /batch/) | No request-level limit | SDK capture and batch ingestion |
Public feature flags (/flags) | No request-level limit | Client-side flag evaluation |
| Analytics API (insights, persons, recordings) | 240/min, 1200/hour | Trend queries, person lookup |
Events values (/events/values) | 60/min, 300/hour | Event-property value lookup |
Query API (/api/projects/:id/query/) | 2400/hour | HogQL and structured queries |
| Feature flag local evaluation polling | 600/min | Server SDK flag definition fetch |
| Other private CRUD endpoints | 480/min, 4800/hour | Feature flag CRUD, cohorts, annotations |
These budgets apply to the whole PostHog team, not to each key or process. Coordinate workers through a shared limiter when more than one caller uses the same endpoint class.
Use Read to inspect the relevant configuration and implementation before proposing changes. Use Write only for a new, explicitly requested artifact inside the target project. Use Edit for minimal changes to existing project files after the evidence pass.
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');
}
import PQueue from 'p-queue';
// Conservative analytics budget: 20/min averages to 1200/hour.
// A shared limiter is required when multiple workers use the same PostHog team.
const posthogQueue = new PQueue({
concurrency: 2, // Max parallel requests
interval: 60_000,
intervalCap: 20,
});
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://us.posthog.com/api/projects/${PROJECT_ID}/insights/trend/`,
{ method: 'GET' }
);
// 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://us.posthog.com/api/projects/${PROJECT_ID}/insights/trend/?events=[{"id":"$pageview"}]&date_from=-7d`, { method: 'GET' })
);
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 | 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 query API | >2400 req/hour across the team | Cache query results, reduce frequency, or use an export product |
| 429 on events values | >60 req/min or >300 req/hour | Cache dimensions and avoid interactive polling |
| 429 on other CRUD | >480 req/min or >4800 req/hour | Coordinate callers through a team-wide limiter |
| Thundering herd on retry | All clients retry simultaneously | Add random jitter to backoff |
/flags have no PostHog request-level limit — still inspect successful capture responses for quota_limited and check ingestion warnings.For a scheduled HogQL query job, budget against the documented query limit, serialize retries from Retry-After, add jitter, and stop after a bounded attempt count. Do not apply private-API limits to public capture endpoints; inspect quota_limited and ingestion warnings separately.
See official PostHog references for current authority and verification boundaries.
For security configuration, see posthog-security-basics.