openrouter-compliance-review
Review OpenRouter integration for regulatory compliance (SOC2, GDPR,
apollo-rate-limits
Implement Apollo.io rate limiting and backoff.
Full skill instructions
Implement robust rate limiting and backoff for the Apollo.io API. Apollo uses fixed-window rate limiting with per-endpoint limits. Unlike hourly quotas, Apollo limits are per minute with a burst limit per second. Exceeding them returns HTTP 429.
Apollo's official rate limits (as of 2025):
Endpoint Category | Limit/min | Burst/sec | Notes
----------------------------+-----------+-----------+-------------------------------
People Search | 100 | 10 | /mixed_people/api_search (free)
People Enrichment | 100 | 10 | /people/match (1 credit each)
Bulk People Enrichment | 10 | 2 | /people/bulk_match (up to 10/call)
Organization Search | 100 | 10 | /mixed_companies/search
Organization Enrichment | 100 | 10 | /organizations/enrich
Contacts CRUD | 100 | 10 | /contacts/*
Sequences | 100 | 10 | /emailer_campaigns/*
Deals | 100 | 10 | /opportunities/*
Response headers on every successful call:
x-rate-limit-limit — max requests per windowx-rate-limit-remaining — requests remaining in current windowretry-after — seconds to wait (only on 429 responses)// src/apollo/rate-limiter.ts
export class SlidingWindowLimiter {
private timestamps: number[] = [];
constructor(
private maxRequests: number = 100,
private windowMs: number = 60_000,
) {}
async acquire(): Promise<void> {
const now = Date.now();
// Remove timestamps outside the window
this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
if (this.timestamps.length >= this.maxRequests) {
const oldestInWindow = this.timestamps[0];
const waitMs = this.windowMs - (now - oldestInWindow) + 100;
console.warn(`[RateLimit] At capacity (${this.maxRequests}/${this.windowMs}ms). Waiting ${waitMs}ms`);
await new Promise((r) => setTimeout(r, waitMs));
}
this.timestamps.push(Date.now());
}
get remaining(): number {
const now = Date.now();
this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
return this.maxRequests - this.timestamps.length;
}
}
// Create limiters per endpoint category
export const limiters = {
search: new SlidingWindowLimiter(100, 60_000),
enrichment: new SlidingWindowLimiter(100, 60_000),
bulkEnrichment: new SlidingWindowLimiter(10, 60_000),
contacts: new SlidingWindowLimiter(100, 60_000),
sequences: new SlidingWindowLimiter(100, 60_000),
};
// src/apollo/backoff.ts
export async function withBackoff<T>(
fn: () => Promise<T>,
opts: { maxRetries?: number; baseMs?: number; maxMs?: number } = {},
): Promise<T> {
const { maxRetries = 5, baseMs = 1000, maxMs = 60_000 } = opts;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
const status = err.response?.status;
if (status !== 429 && status < 500) throw err;
if (attempt === maxRetries) throw err;
// Prefer Retry-After header, fall back to exponential backoff
const retryAfter = err.response?.headers?.['retry-after'];
const delayMs = retryAfter
? parseInt(retryAfter, 10) * 1000
: Math.min(baseMs * 2 ** attempt + Math.random() * 500, maxMs);
console.warn(`[Apollo] ${status} attempt ${attempt + 1}/${maxRetries + 1}, retry in ${Math.round(delayMs / 1000)}s`);
await new Promise((r) => setTimeout(r, delayMs));
}
}
throw new Error('Unreachable');
}
// src/apollo/queue.ts
import PQueue from 'p-queue';
import { limiters } from './rate-limiter';
type EndpointCategory = keyof typeof limiters;
const queues: Record<EndpointCategory, PQueue> = {
search: new PQueue({ concurrency: 5, intervalCap: 10, interval: 1000 }),
enrichment: new PQueue({ concurrency: 5, intervalCap: 10, interval: 1000 }),
bulkEnrichment: new PQueue({ concurrency: 2, intervalCap: 2, interval: 1000 }),
contacts: new PQueue({ concurrency: 5, intervalCap: 10, interval: 1000 }),
sequences: new PQueue({ concurrency: 3, intervalCap: 5, interval: 1000 }),
};
export async function queuedRequest<T>(
category: EndpointCategory,
fn: () => Promise<T>,
): Promise<T> {
await limiters[category].acquire();
return queues[category].add(() => fn()) as Promise<T>;
}
// src/apollo/rate-monitor.ts
import { AxiosInstance, AxiosResponse } from 'axios';
export function attachRateMonitor(client: AxiosInstance) {
client.interceptors.response.use((response: AxiosResponse) => {
const limit = response.headers['x-rate-limit-limit'];
const remaining = response.headers['x-rate-limit-remaining'];
if (limit && remaining) {
const pct = Math.round(((parseInt(limit) - parseInt(remaining)) / parseInt(limit)) * 100);
if (pct >= 80) {
console.warn(`[Apollo] Rate limit ${pct}% used (${remaining}/${limit} remaining) on ${response.config.url}`);
}
}
return response;
});
}
retry-after headersPQueue-based request queue with per-second burst control| Scenario | Strategy |
|---|---|
429 with retry-after | Wait the specified seconds, then retry |
| 429 without header | Exponential backoff: 1s, 2s, 4s, 8s, up to 60s |
| Bulk enrichment limited | Use dedicated queue with 2/sec burst limit |
| Near quota (>80%) | Log warning, defer non-critical requests |
import { queuedRequest } from './apollo/queue';
import { withBackoff } from './apollo/backoff';
const domains = ['stripe.com', 'notion.so', 'linear.app', /* ... */];
const results = await Promise.all(
domains.map((domain) =>
queuedRequest('search', () =>
withBackoff(() =>
client.post('/mixed_people/api_search', {
q_organization_domains_list: [domain],
per_page: 25,
}),
),
),
),
);
console.log(`Searched ${results.length} domains within rate limits`);
Proceed to apollo-security-basics for API security best practices.
Review OpenRouter integration for regulatory compliance (SOC2, GDPR,
Data synchronization, ETL patterns, and data management for MaintainX.
Execute Groq primary workflow: chat completions with tool use and JSON
Validate CSRF protection implementations for security gaps. Use when
Configure this skill should be used when the user asks about "API contract",
Execute Ideogram incident response with triage, mitigation, and postmortem.
Execute create, select, and transform features to improve machine learning
Reusable patterns for the Gamma REST API (no SDK exists).
Apply production-ready Deepgram SDK patterns for TypeScript and Python.
Migrate to OpenRouter from direct provider APIs or upgrade between SDK/model
Diagnose and fix common OpenRouter API errors. Use when encountering
Optimize Linear API usage, reduce unnecessary calls, and maximize
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience