A few habits make the difference between an API integration that runs smoothly and one that hits rate limits, leaks tokens, or breaks silently.
Cache Data That Rarely Changes#
Catalog data doesn't change often. Cache it locally and cut your API calls dramatically.
| Good to cache | Don't cache |
|---|---|
| Publisher, creator, character, story arc data (hours to days) | User collection, pull list, reading progress |
| Series and issue metadata (hours) | Anything time-sensitive |
Invalidate cache when you know data changed (you just mutated it) or on a reasonable schedule.
Watch Rate Limit Headers#
Every response includes X-RateLimit-Remaining. When it drops below ~10% of your limit, slow down.
function checkRateLimit(response) {
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
if (remaining < 30) console.warn(`Only ${remaining} requests remaining`);
}
Handle 429 Gracefully#
When you hit the limit, the server returns 429 with a Retry-After header. Honor it — don't retry immediately.
async function apiRequest(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
const retryAfter = parseInt(res.headers.get('Retry-After') || '60');
await new Promise(r => setTimeout(r, retryAfter * 1000));
}
throw new Error('Max retries exceeded');
}
Use PRO Nested Routes for Related Data#
Instead of looping over items to fetch relationships one by one, use the nested endpoints (PRO):
GET /api/series/{id}/issues
GET /api/series/{id}/creators
GET /api/issues/{id}/variants
GET /api/teams/{id}/characters
One request per relationship beats dozens of single-item lookups.
Protect Your Token#
- Store tokens in environment variables (add
.envto.gitignore) - Give each integration its own token — revoke individually if compromised
- Never paste tokens in Discord, forums, or client-side JavaScript
- Rotate tokens on a schedule (e.g., every 90 days)
If a token is exposed: delete it at versedb.com/user/api-tokens, generate a new one, update your app.
Paginate Correctly#
List endpoints return up to 50 items per page (default 20). Walk links.next until it's null.
async function fetchAll(url, token) {
let all = [];
let next = url;
while (next) {
const res = await fetch(next, { headers: { Authorization: `Bearer ${token}` } });
const json = await res.json();
all = all.concat(json.data);
next = json.links?.next ? `https://versedb.com${json.links.next}` : null;
await new Promise(r => setTimeout(r, 200)); // spacing
}
return all;
}
Debounce Search#
Don't send a search request on every keystroke. Wait 300ms after the user stops typing, and cache common queries locally.
Test Authentication First#
Before building out an integration, verify your token with a single request to GET /api/user. If that returns your profile, your auth is working.