Rate Limits & Quotas
Truthlock enforces rate limits to ensure fair usage and platform stability. This guide explains limits by tier and how to handle rate limiting gracefully.
Limits by Tier
| Tier | Requests/min | Attestations/day | Issuers | Keys/Issuer |
|---|---|---|---|---|
| Free | 60 | 100 | 2 | 2 |
| Starter | 300 | 10,000 | 10 | 5 |
| Professional | 1,000 | 100,000 | 50 | 10 |
| Enterprise | Custom | Custom | Unlimited | Unlimited |
Burst Capacity: All tiers include 2x burst capacity for up to 10 seconds. This allows handling traffic spikes without immediate rate limiting.
Per-Endpoint Limits
Some endpoints have additional specific limits:
| Endpoint | Limit | Window | Reason |
|---|---|---|---|
POST /v1/attestations/mint | 100/min | 60s | Signing is CPU-intensive |
POST /v1/verify | No limit | - | Public endpoint, cached |
POST /v1/issuers | 10/hour | 1h | Prevent issuer spam |
POST /v1/api-keys | 20/day | 24h | Security measure |
GET /v1/audit/events | 30/min | 60s | Database-heavy query |
Rate Limit Headers
Every API response includes headers to help you track your usage:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1705147260
X-RateLimit-Policy: "1000;w=60"| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Policy | Policy description (requests;window=seconds) |
Handling 429 Responses
When you exceed the rate limit, you'll receive a 429 status code:
HTTP/1.1 429 Too Many Requests
Retry-After: 32
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705147260
{
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 32 seconds.",
"http_status": 429,
"details": {
"limit": 1000,
"window_seconds": 60,
"retry_after_seconds": 32
}
}Important: Always respect the
Retry-Afterheader. Repeatedly hitting rate limits may result in temporary IP blocks.Best Practices
Exponential Backoff
async function withBackoff<T>(
fn: () => Promise<T>,
maxRetries = 5,
baseDelayMs = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status !== 429 || attempt === maxRetries - 1) {
throw error;
}
const retryAfter = error.headers?.['retry-after']
? parseInt(error.headers['retry-after']) * 1000
: baseDelayMs * Math.pow(2, attempt);
const jitter = Math.random() * 1000;
await new Promise(r => setTimeout(r, retryAfter + jitter));
}
}
throw new Error('Max retries exceeded');
}Request Batching
// Instead of minting one at a time:
for (const user of users) {
await client.attestations.mint({ ... });
}
// Batch with controlled concurrency:
import pLimit from 'p-limit';
const limit = pLimit(10); // Max 10 concurrent requests
const results = await Promise.all(
users.map(user =>
limit(() => client.attestations.mint({ ... }))
)
);Monitor Usage
// Track remaining quota in your metrics
function trackRateLimit(response: Response) {
const remaining = response.headers.get('X-RateLimit-Remaining');
const limit = response.headers.get('X-RateLimit-Limit');
metrics.gauge('truthlock.ratelimit.remaining', parseInt(remaining));
metrics.gauge('truthlock.ratelimit.usage_percent',
(1 - parseInt(remaining) / parseInt(limit)) * 100
);
}Requesting Quota Increases
If your usage requires higher limits:
- Starter/Professional: Contact support with your use case and expected volume
- Enterprise: Custom limits are negotiated during contract discussions
- Temporary Increase: For events or migrations, request a temporary burst increase 48 hours in advance
Tip: Include metrics showing your current usage patterns and growth projections when requesting increases.