Cache Your Requests#
If you're requesting the same data frequently, cache the results locally. This reduces unnecessary API calls and helps you stay within your hourly rate limit.
- Publisher data (changes infrequently)
- Series/volume details (mostly static)
- Character and creator profiles (rarely updated)
- Story arc information (stable data)
- User collection/lists (personal data changes often)
- Pull list (updated weekly with new releases)
Batch Requests Carefully#
The VerseDB API supports standard CRUD operations per resource. When adding or updating multiple items:
For collections:
POST /api/collections/{id}/items # Add single item
DELETE /api/collections/{id}/items/{item_id} # Remove single item
For user lists:
POST /api/lists/{id}/items # Add single item
DELETE /api/lists/{id}/items/{item_id} # Remove single item
If you need to add multiple items, batch your requests with a small delay between them (100-200ms) to avoid rate limiting.
Monitor Rate Limit Headers#
Every API response includes rate limit headers:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 1712869200
Track these in your code:
const checkRateLimit = (response) => {
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (remaining < 50) {
console.warn(`Only ${remaining} requests remaining`);
}
if (remaining < 10) {
const resetDate = new Date(reset * 1000);
console.warn(`Rate limit low! Resets at ${resetDate}`);
}
};
Handle 429 Responses Gracefully#
When you exceed your rate limit, you'll receive a 429 Too Many Requests response. Stop sending requests and wait for the reset time.
Implement exponential backoff:
async function apiRequestWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited. Waiting ${retryAfter}s before retry ${attempt + 1}`);
await sleep(retryAfter * 1000);
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
Always Authenticate#
All VerseDB API endpoints require authentication. Include your Bearer token in every request, even for read-only endpoints.
fetch('https://versedb.com/api/user/collections', {
headers: {
'Authorization': 'Bearer YOUR_TOKEN_HERE',
'Content-Type': 'application/json'
}
})
Leverage Search Endpoints Efficiently#
Search endpoints have stricter rate limits (60/hour free, 120/hour PRO). Use them wisely:
- Implement debouncing for user input (wait 300ms after typing stops)
- Cache search results for common queries
- Use specific filters when available to reduce result size
- Limit search frequency in your UI
Use Nested Routes for Related Data (PRO)#
PRO users can access nested routes to efficiently fetch related data:
GET /api/series/{id}/issues # Get issues for a series
GET /api/series/{id}/creators # Get creators for a series
GET /api/issues/{id}/variants # Get variants for an issue
GET /api/teams/{id}/characters # Get team members
These relationship endpoints return 402 for free users.
Protect Your Token#
- Store tokens in environment variables
- Use
.envfiles (add to.gitignore) - Regenerate tokens if exposed
- Use different tokens for different applications
- Never commit tokens to Git
- Never log tokens in application logs
- Never share tokens in forums/Discord
If your token is compromised:
- Go to versedb.com/user/api-tokens
- Delete the compromised token immediately
- Generate a new token
- Update your application with the new token
Handle Pagination Correctly#
List endpoints return paginated results:
{
"data": [...],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 500,
"last_page": 25
},
"links": {
"first": "/api/series?page=1",
"next": "/api/series?page=2",
"prev": null,
"last": "/api/series?page=25"
}
}
Iterate through pages:
async function fetchAllPages(baseUrl, token) {
let allData = [];
let nextUrl = baseUrl;
while (nextUrl) {
const response = await fetch(nextUrl, {
headers: { 'Authorization': `Bearer ${token}` }
});
const json = await response.json();
allData = allData.concat(json.data);
nextUrl = json.links.next ? `https://versedb.com${json.links.next}` : null;
// Respect rate limits between pages
await sleep(200);
}
return allData;
}
Test Connectivity First#
Before implementing your integration, test your authentication:
const testConnection = async (token) => {
const response = await fetch('https://versedb.com/api/user', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
console.log('API connection successful');
console.log('Rate limit:', response.headers.get('X-RateLimit-Limit'));
} else {
console.error('Authentication failed:', response.status);
}
};