Rate Limiting
The BookWish API implements rate limiting to ensure fair usage, prevent abuse, and maintain system stability. Rate limits vary by endpoint and user tier.
Rate Limit Types
1. Action-Based Rate Limits
Specific actions have individual rate limits based on their resource intensity and abuse potential:
| Action | Limit | Window | Description |
|---|---|---|---|
create_line | 10 requests | 1 minute | Creating social posts (scribbles) |
create_line_hourly | 50 requests | 1 hour | Hourly limit for social posts |
create_review | 5 requests | 1 hour | Creating book reviews |
follow_user | 30 requests | 1 hour | Following users |
like | 100 requests | 1 hour | Liking content |
report | 10 requests | 1 hour | Reporting content |
search | 60 requests | 1 minute | Search queries |
feed | 120 requests | 1 minute | Feed refresh requests |
2. Monthly Content Creation Limits (Free Tier)
Free tier users have a monthly limit on content creation:
| User Tier | Monthly Limit | Includes |
|---|---|---|
| Free | 30 posts | Lines, reviews, notes combined |
| Premium | Unlimited | No monthly restrictions |
| Bookstore | Unlimited | No monthly restrictions |
| Admin | Unlimited | No monthly restrictions |
Reset: Limits reset on the 1st of each month at 00:00 UTC.
Rate Limit Headers
All API responses include rate limit information in the headers:
Standard Rate Limit Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1702047300000
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Remaining requests in the current window |
X-RateLimit-Reset | Unix timestamp (ms) when the limit resets |
Monthly Limit Headers (Free Tier)
X-Monthly-Limit: 30
X-Monthly-Remaining: 12
X-Monthly-Reset: 2025-01-01T00:00:00.000Z
| Header | Description |
|---|---|
X-Monthly-Limit | Maximum monthly posts allowed |
X-Monthly-Remaining | Remaining posts for the current month |
X-Monthly-Reset | ISO timestamp when the monthly limit resets |
Rate Limit Exceeded Response
When a rate limit is exceeded, the API returns a 429 Too Many Requests status:
Hourly/Minute Rate Limit Exceeded
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again in 45 seconds.",
"retryAfter": 45000,
"limit": 60,
"remaining": 0
}
| Field | Type | Description |
|---|---|---|
error | string | Error code |
message | string | Human-readable error message |
retryAfter | number | Milliseconds until rate limit resets |
limit | number | Maximum requests allowed |
remaining | number | Always 0 when exceeded |
Monthly Limit Exceeded (Free Tier)
{
"error": "monthly_limit_reached",
"message": "Free accounts are limited to 30 posts per month. Upgrade to Premium for unlimited posts.",
"limit": 30,
"current": 30,
"resetsAt": "2025-01-01T00:00:00.000Z",
"upgradeUrl": "/upgrade"
}
| Field | Type | Description |
|---|---|---|
error | string | Error code |
message | string | Human-readable error message |
limit | number | Monthly post limit |
current | number | Current post count |
resetsAt | string | ISO timestamp of next reset |
upgradeUrl | string | Path to upgrade page |
Rate Limit Scope
Authenticated Users
Rate limits are applied per user ID:
Key: ratelimit:{action}:{userId}
This means:
- Each user has independent rate limits
- Limits persist across devices and sessions
- Stored in Redis with automatic expiration
Guest Users / Unauthenticated
For guest users or endpoints without authentication, rate limits are applied per IP address:
Key: ratelimit:{action}:{ipAddress}
Note: Using a VPN or proxy won't bypass limits as the identifier persists in Redis.
Best Practices
1. Monitor Rate Limit Headers
Always check rate limit headers in responses to avoid hitting limits:
const response = await fetch('https://api.bookwish.app/search?q=book');
const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (remaining < 5) {
console.warn(`Approaching rate limit. ${remaining} requests remaining.`);
}
2. Implement Exponential Backoff
When rate limited, implement exponential backoff:
async function makeRequestWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
const data = await response.json();
const retryAfter = data.retryAfter || 1000 * Math.pow(2, i);
console.log(`Rate limited. Retrying in ${retryAfter}ms...`);
await new Promise(resolve => setTimeout(resolve, retryAfter));
}
throw new Error('Max retries exceeded');
}
3. Cache Responses
Cache API responses when appropriate to reduce request volume:
const cache = new Map();
async function getCachedBook(bookId) {
if (cache.has(bookId)) {
return cache.get(bookId);
}
const response = await fetch(`https://api.bookwish.app/books/${bookId}`);
const book = await response.json();
cache.set(bookId, book);
return book;
}
4. Batch Operations
Use batch endpoints when available instead of multiple individual requests:
// Bad: Multiple requests
for (const bookId of bookIds) {
await fetch(`https://api.bookwish.app/books/${bookId}`);
}
// Good: Single batch request (if available)
await fetch('https://api.bookwish.app/books/batch', {
method: 'POST',
body: JSON.stringify({ ids: bookIds })
});
5. Upgrade to Premium
For applications requiring higher limits, consider upgrading to Premium tier:
- Free Tier: 30 posts/month
- Premium Tier: Unlimited posts
- Bookstore Tier: Unlimited posts + store features
Error Handling Example
async function createPost(content) {
try {
const response = await fetch('https://api.bookwish.app/lines', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(content)
});
if (response.status === 429) {
const error = await response.json();
if (error.error === 'monthly_limit_reached') {
// Show upgrade prompt
showUpgradeDialog({
message: error.message,
upgradeUrl: error.upgradeUrl
});
} else {
// Temporary rate limit - retry after delay
const retryAfter = error.retryAfter;
await new Promise(resolve => setTimeout(resolve, retryAfter));
return createPost(content); // Retry
}
}
return response.json();
} catch (error) {
console.error('Failed to create post:', error);
throw error;
}
}
Rate Limit Exceptions
Fail-Open Policy
If the rate limiting service (Redis) is unavailable, the API fails open and allows requests to proceed. This ensures:
- Service availability during infrastructure issues
- Graceful degradation
- No false positives from rate limiting errors
Admin Override
Administrators can reset rate limits for specific users or actions if needed.
Checking Your Current Limits
Via Response Headers
Check rate limit headers on any authenticated request:
curl -I https://api.bookwish.app/users/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Via API (Future)
A dedicated endpoint for checking rate limit status is planned:
GET /users/me/rate-limits
Questions?
If you're consistently hitting rate limits or need higher limits for legitimate use cases:
- Email: api@bookwish.app
- Subject: Rate Limit Increase Request
- Include: Use case description and current usage patterns