Skip to main content

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:

ActionLimitWindowDescription
create_line10 requests1 minuteCreating social posts (scribbles)
create_line_hourly50 requests1 hourHourly limit for social posts
create_review5 requests1 hourCreating book reviews
follow_user30 requests1 hourFollowing users
like100 requests1 hourLiking content
report10 requests1 hourReporting content
search60 requests1 minuteSearch queries
feed120 requests1 minuteFeed refresh requests

2. Monthly Content Creation Limits (Free Tier)

Free tier users have a monthly limit on content creation:

User TierMonthly LimitIncludes
Free30 postsLines, reviews, notes combined
PremiumUnlimitedNo monthly restrictions
BookstoreUnlimitedNo monthly restrictions
AdminUnlimitedNo 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
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRemaining requests in the current window
X-RateLimit-ResetUnix 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
HeaderDescription
X-Monthly-LimitMaximum monthly posts allowed
X-Monthly-RemainingRemaining posts for the current month
X-Monthly-ResetISO 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
}
FieldTypeDescription
errorstringError code
messagestringHuman-readable error message
retryAfternumberMilliseconds until rate limit resets
limitnumberMaximum requests allowed
remainingnumberAlways 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"
}
FieldTypeDescription
errorstringError code
messagestringHuman-readable error message
limitnumberMonthly post limit
currentnumberCurrent post count
resetsAtstringISO timestamp of next reset
upgradeUrlstringPath 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