maintainx-rate-limits
Implements exponential backoff, request queuing, and pagination for MaintainX API integrations.
Install
mkdir -p .claude/skills/maintainx-rate-limits && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/6307" && unzip -o skill.zip -d .claude/skills/maintainx-rate-limits && rm skill.zipInstalls to .claude/skills/maintainx-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.
Implement MaintainX API rate limiting, pagination, and backoff patterns.Key capabilities
- →Throttle API requests using a concurrency-limited queue
- →Implement exponential backoff for retryable errors
- →Handle 429 rate limit errors with jitter
- →Perform cursor-based pagination for list endpoints
- →Monitor API usage rates over time
How it works
The client uses a request queue and a throttle mechanism to limit concurrent requests, combined with a retry loop that honors 'Retry-After' headers and applies exponential backoff.
Inputs & outputs
When to use maintainx-rate-limits
- →Implement exponential backoff
- →Handle 429 rate limits
- →Optimize request throughput
- →Implement request queuing
About this skill
MaintainX Rate Limits
Overview
Handle MaintainX API rate limits gracefully with exponential backoff, cursor-based pagination, and request queuing to maximize throughput without triggering 429 errors.
Prerequisites
- MaintainX API access configured
- Node.js 18+ with
axios - Understanding of async/await patterns
Instructions
Step 1: Rate-Limited Client Wrapper
// src/rate-limited-client.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
export class RateLimitedClient {
private http: AxiosInstance;
private requestQueue: Array<() => void> = [];
private activeRequests = 0;
private maxConcurrent = 5;
private minDelayMs = 100; // 10 requests/second max
constructor(apiKey?: string) {
const key = apiKey || process.env.MAINTAINX_API_KEY;
if (!key) throw new Error('MAINTAINX_API_KEY required');
this.http = axios.create({
baseURL: 'https://api.getmaintainx.com/v1',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
},
timeout: 30_000,
});
}
private async throttle(): Promise<void> {
if (this.activeRequests >= this.maxConcurrent) {
await new Promise<void>((resolve) => this.requestQueue.push(resolve));
}
this.activeRequests++;
await new Promise((r) => setTimeout(r, this.minDelayMs));
}
private release() {
this.activeRequests--;
const next = this.requestQueue.shift();
if (next) next();
}
async request<T>(method: string, url: string, data?: any, params?: any): Promise<T> {
await this.throttle();
try {
const response = await this.retryWithBackoff(
() => this.http.request<T>({ method, url, data, params }),
);
return response.data;
} finally {
this.release();
}
}
private async retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000, // 1 second initial backoff delay
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
const axiosErr = err as AxiosError;
const status = axiosErr.response?.status;
if (status !== 429 && !(status && status >= 500) || attempt === maxRetries) {
throw err;
}
// Honor Retry-After header
const retryAfter = axiosErr.response?.headers?.['retry-after'];
const delayMs = retryAfter
? parseInt(retryAfter) * 1000
: baseDelay * Math.pow(2, attempt) + Math.random() * 500;
console.warn(
`Rate limited (HTTP ${status}). Retry ${attempt + 1}/${maxRetries} in ${Math.round(delayMs)}ms`,
);
await new Promise((r) => setTimeout(r, delayMs));
}
}
throw new Error('Unreachable');
}
}
Step 2: Cursor-Based Pagination
MaintainX returns a cursor field in list responses. Pass it as a query parameter to fetch the next page.
async function paginateAll<T>(
client: RateLimitedClient,
endpoint: string,
resultKey: string,
params?: Record<string, any>,
): Promise<T[]> {
const allItems: T[] = [];
let cursor: string | undefined;
do {
const response: any = await client.request('GET', endpoint, undefined, {
...params,
limit: 100,
cursor,
});
const items = response[resultKey] as T[];
allItems.push(...items);
cursor = response.cursor ?? undefined;
// Log progress for long-running operations
if (allItems.length % 500 === 0) {
console.log(` Fetched ${allItems.length} items so far...`);
}
} while (cursor);
return allItems;
}
// Usage
const allWorkOrders = await paginateAll(client, '/workorders', 'workOrders', {
status: 'OPEN',
});
console.log(`Total: ${allWorkOrders.length} open work orders`);
Step 3: Batch Operations with p-queue
import PQueue from 'p-queue';
// 5 concurrent requests, max 10 per second
const queue = new PQueue({
concurrency: 5,
interval: 1000, // 1 second window for rate cap
intervalCap: 10,
});
async function batchUpdate(
client: RateLimitedClient,
updates: Array<{ id: number; data: any }>,
) {
const results = await Promise.allSettled(
updates.map((update) =>
queue.add(() =>
client.request('PATCH', `/workorders/${update.id}`, update.data),
),
),
);
const succeeded = results.filter((r) => r.status === 'fulfilled').length;
const failed = results.filter((r) => r.status === 'rejected').length;
console.log(`Batch update: ${succeeded} succeeded, ${failed} failed`);
return results;
}
// Close 100 completed work orders
const completedOrders = await paginateAll(
client, '/workorders', 'workOrders', { status: 'COMPLETED' },
);
await batchUpdate(
client,
completedOrders.map((wo: any) => ({ id: wo.id, data: { status: 'CLOSED' } })),
);
Step 4: Rate Limit Monitoring
// src/rate-monitor.ts
class RateMonitor {
private requests: number[] = [];
private windowMs = 60_000; // 1 minute window
record() {
this.requests.push(Date.now());
this.cleanup();
}
cleanup() {
const cutoff = Date.now() - this.windowMs;
this.requests = this.requests.filter((t) => t > cutoff);
}
getRate(): number {
this.cleanup();
return this.requests.length;
}
report() {
const rate = this.getRate();
const status = rate > 50 ? 'WARNING' : 'OK';
console.log(`[RateMonitor] ${rate} req/min - ${status}`);
return { rate, status };
}
}
Output
- Rate-limited client wrapper with built-in throttling and retry
- Cursor-based pagination utility collecting all results
- Batch operations with controlled concurrency via
p-queue - Rate monitoring to track and alert on API usage
Error Handling
| Scenario | Strategy |
|---|---|
| 429 Too Many Requests | Exponential backoff with jitter, honor Retry-After header |
Retry-After header present | Wait the specified number of seconds before retrying |
| Burst of requests | Queue with p-queue (concurrency: 5, intervalCap: 10/sec) |
| Large data sets (1000+ items) | Paginate with limit: 100, delay between pages |
Resources
- MaintainX API Reference
- p-queue -- Promise queue with concurrency control
- Exponential Backoff
Next Steps
For security configuration, see maintainx-security-basics.
Examples
Adaptive rate limiting based on response headers:
// Adjust concurrency dynamically based on remaining quota
function adaptRate(headers: Record<string, string>, queue: PQueue) {
const remaining = parseInt(headers['x-ratelimit-remaining'] || '100');
if (remaining < 10) {
queue.concurrency = 1;
console.warn('Approaching rate limit, reducing concurrency to 1');
} else if (remaining < 50) {
queue.concurrency = 3;
} else {
queue.concurrency = 5;
}
}
When not to use it
- →When the application only makes infrequent, low-volume API calls
- →When the API usage is well below the defined rate limits
Prerequisites
Limitations
- →Maximum of 5 concurrent requests
- →Default retry limit is 3 attempts
- →Pagination is limited to 100 items per request
How it compares
This pattern proactively manages request flow and handles transient failures, whereas a basic fetch implementation would fail immediately upon hitting rate limits.
Compared to similar skills
maintainx-rate-limits side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| maintainx-rate-limits (this skill) | 1 | 27d | Caution | Advanced |
| bullmq-specialist | 25 | 6mo | No flags | Intermediate |
| managing-api-cache | 2 | 27d | Review | Advanced |
| rate-limiting-apis | 1 | 27d | Review | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
bullmq-specialist
davila7
BullMQ expert for Redis-backed job queues, background processing, and reliable async execution in Node.js/TypeScript applications. Use when: bullmq, bull queue, redis queue, background job, job queue.
managing-api-cache
jeremylongshore
Implement intelligent API response caching with Redis, Memcached, and CDN integration. Use when optimizing API performance with caching. Trigger with phrases like "add caching", "optimize API performance", or "implement cache layer".
rate-limiting-apis
jeremylongshore
Implement sophisticated rate limiting with sliding windows, token buckets, and quotas. Use when protecting APIs from excessive requests. Trigger with phrases like "add rate limiting", "limit API requests", or "implement rate limits".
prisma-connection-pool-exhaustion
blader
Fix Prisma "Too many connections" and connection pool exhaustion errors in serverless environments (Vercel, AWS Lambda, Netlify). Use when: (1) Error "P2024: Timed out fetching a new connection from the pool", (2) PostgreSQL "too many connections for role", (3) Database works locally but fails in production serverless, (4) Intermittent database timeouts under load.
generating-grpc-services
jeremylongshore
Generate gRPC service definitions, stubs, and implementations from Protocol Buffers. Use when creating high-performance gRPC services. Trigger with phrases like "generate gRPC service", "create gRPC API", or "build gRPC server".
azure-monitor-opentelemetry-ts
microsoft
Instrument applications with Azure Monitor and OpenTelemetry for JavaScript (@azure/monitor-opentelemetry). Use when adding distributed tracing, metrics, and logs to Node.js applications with Application Insights.