Best Practices for Using the VerseDB API

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

Tip

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#

Warning

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#

Note

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:

Tip
  • 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


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

Note

These relationship endpoints return 402 for free users.


Protect Your Token#

Warning
  • Store tokens in environment variables
  • Use .env files (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:

  1. Go to versedb.com/user/api-tokens
  2. Delete the compromised token immediately
  3. Generate a new token
  4. 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);
  }
};