Implement Abridge rate limiting, backoff, and session throttling patterns. Use when handling 429 errors, managing concurrent encounter sessions, or optimizing API throughput for high-volume clinical deployments. Trigger: "abridge rate limit", "abridge 429", "abridge throttling", "abridge concurrent sessions".
Abridge enforces rate limits per organization to ensure platform stability across thousands of concurrent clinical encounters. Limits vary by contract tier and are enforced at the org_id level.
| Tier | Concurrent Sessions | API Calls/min | Note Generations/min | WebSocket Connections |
|---|---|---|---|---|
| Pilot | 10 | 60 | 10 | 10 |
| Standard | 100 | 600 | 50 | 100 |
| Enterprise | 1,000+ | 6,000 | 500 | 1,000 |
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1711234567
Retry-After: 30
// src/abridge/rate-limiter.ts
interface RateLimitState {
remaining: number;
limit: number;
resetAt: Date;
retryAfter?: number;
}
class AbridgeRateLimiter {
private state: RateLimitState = {
remaining: Infinity,
limit: Infinity,
resetAt: new Date(),
};
private queue: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];
private processing = false;
updateFromHeaders(headers: Record<string, string>): void {
if (headers['x-ratelimit-remaining']) {
this.state.remaining = parseInt(headers['x-ratelimit-remaining']);
}
if (headers['x-ratelimit-limit']) {
this.state.limit = parseInt(headers['x-ratelimit-limit']);
}
if (headers['x-ratelimit-reset']) {
this.state.resetAt = new Date(parseInt(headers['x-ratelimit-reset']) * 1000);
}
if (headers['retry-after']) {
this.state.retryAfter = parseInt(headers['retry-after']);
}
}
async waitForSlot(): Promise<void> {
if (this.state.remaining > 0) {
this.state.remaining--;
return;
}
// Wait until rate limit resets
const waitMs = Math.max(0, this.state.resetAt.getTime() - Date.now());
console.log(`Rate limited — waiting ${waitMs}ms for reset`);
await new Promise(r => setTimeout(r, waitMs));
this.state.remaining = this.state.limit;
}
get utilizationPercent(): number {
return ((this.state.limit - this.state.remaining) / this.state.limit) * 100;
}
}
export { AbridgeRateLimiter };
// src/abridge/session-pool.ts
import Bottleneck from 'bottleneck';
// Manage concurrent encounter sessions within org limits
const sessionLimiter = new Bottleneck({
maxConcurrent: 50, // Stay under tier limit
minTime: 100, // 100ms between session creates
reservoir: 50, // Burst capacity
reservoirRefreshInterval: 60000, // Refill every minute
reservoirRefreshAmount: 50,
});
sessionLimiter.on('depleted', () => {
console.warn('Session pool depleted — queuing new sessions');
});
async function createSessionThrottled(
api: any,
patientId: string,
providerId: string,
specialty: string,
): Promise<any> {
return sessionLimiter.schedule(() =>
api.post('/encounters/sessions', {
patient_id: patientId,
provider_id: providerId,
specialty,
encounter_type: 'outpatient',
})
);
}
export { createSessionThrottled, sessionLimiter };
// src/abridge/retry-429.ts
import axios, { AxiosError } from 'axios';
async function handleRateLimitedRequest<T>(
requestFn: () => Promise<T>,
maxRetries: number = 5,
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await requestFn();
} catch (err) {
const axiosErr = err as AxiosError;
if (axiosErr.response?.status !== 429 || attempt === maxRetries) {
throw err;
}
const retryAfter = axiosErr.response.headers['retry-after'];
const waitMs = retryAfter
? parseInt(retryAfter as string) * 1000
: Math.min(1000 * Math.pow(2, attempt), 60000);
console.log(`429 rate limited — retry ${attempt}/${maxRetries} in ${waitMs}ms`);
await new Promise(r => setTimeout(r, waitMs));
}
}
throw new Error('Unreachable');
}
// src/abridge/usage-monitor.ts
interface UsageSnapshot {
timestamp: string;
activeSessions: number;
apiCallsThisMinute: number;
noteGenerationsThisMinute: number;
utilizationPercent: number;
}
class UsageMonitor {
private snapshots: UsageSnapshot[] = [];
private apiCallCount = 0;
private noteGenCount = 0;
recordApiCall(): void { this.apiCallCount++; }
recordNoteGeneration(): void { this.noteGenCount++; }
takeSnapshot(activeSessions: number): UsageSnapshot {
const snapshot: UsageSnapshot = {
timestamp: new Date().toISOString(),
activeSessions,
apiCallsThisMinute: this.apiCallCount,
noteGenerationsThisMinute: this.noteGenCount,
utilizationPercent: (activeSessions / 100) * 100, // Adjust per tier
};
this.snapshots.push(snapshot);
this.apiCallCount = 0;
this.noteGenCount = 0;
return snapshot;
}
getAlerts(): string[] {
const latest = this.snapshots.at(-1);
if (!latest) return [];
const alerts: string[] = [];
if (latest.utilizationPercent > 80) alerts.push('Session utilization > 80%');
if (latest.apiCallsThisMinute > 500) alerts.push('API calls approaching limit');
return alerts;
}
}
| Error | Cause | Solution |
|---|---|---|
429 Too Many Requests | Exceeded org rate limit | Use Retry-After header; implement backoff |
503 Service Unavailable | Platform-wide throttling | Back off exponentially; check status page |
| Session create rejected | Concurrent session limit | Queue creates with Bottleneck |
For security configuration, see abridge-security-basics.