Skip to main content

API Rate Limits and Quotas

Comprehensive guide to Trading Card API rate limits, usage quotas, and best practices for efficient API usage.

📊 Quick Overview

The Trading Card API implements fair usage policies to ensure reliable service for all users:

TierRequests/HourRequests/MonthBurst Limit
Free1,00010,000100/minute
Premium10,000100,000500/minute
EnterpriseCustomCustomCustom
Beta Program Notice

During beta, all users receive Free tier limits. Premium and Enterprise tiers will be available at general availability.

Want early access to premium features? Join our Early Access program or subscribe to our newsletter for updates.

🚦 Rate Limiting Details

Request Rate Limits

Hourly Limits

  • Free Tier: 1,000 requests per hour
  • Premium Tier: 10,000 requests per hour (Coming Soon)
  • Enterprise Tier: Custom limits based on agreement

Burst Limits

  • Free Tier: 100 requests per minute (short bursts allowed)
  • Premium Tier: 500 requests per minute
  • Enterprise Tier: Configurable burst capacity

Daily and Monthly Quotas

  • Free Tier: 10,000 requests per month
  • Premium Tier: 100,000 requests per month
  • Enterprise Tier: Custom monthly allowances

Rate Limit Enforcement

Rate limits are enforced using a sliding window approach:

  • Limits reset continuously rather than at fixed intervals
  • Recent request history determines current rate limit status
  • Fair distribution of requests across time periods

📋 Rate Limit Headers

Every API response includes rate limiting information in the headers:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1693526400
X-RateLimit-Window: 3600
Retry-After: 1800

Header Descriptions

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed per period1000
X-RateLimit-RemainingRequests remaining in current period847
X-RateLimit-ResetUnix timestamp when limits reset1693526400
X-RateLimit-WindowRate limit window in seconds3600 (1 hour)
Retry-AfterSeconds to wait before retry (when rate limited)1800

⚠️ Rate Limit Responses

When rate limits are exceeded, the API returns a 429 Too Many Requests response:

{
"errors": [
{
"status": "429",
"title": "Rate Limit Exceeded",
"detail": "API rate limit exceeded. Please wait before making additional requests.",
"meta": {
"rate_limit": {
"limit": 1000,
"remaining": 0,
"reset": 1693526400,
"retry_after": 1800
}
}
}
]
}

💻 Code Examples

JavaScript (Node.js)

const axios = require('axios');

class TradingCardAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://api.tradingcardapi.com';
}

async makeRequest(endpoint, options = {}) {
try {
const response = await axios({
url: `${this.baseURL}${endpoint}`,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Accept': 'application/vnd.api+json',
...options.headers
},
...options
});

// Log rate limit status
this.logRateLimit(response.headers);

return response.data;
} catch (error) {
if (error.response?.status === 429) {
return this.handleRateLimit(error.response, endpoint, options);
}
throw error;
}
}

logRateLimit(headers) {
const limit = headers['x-ratelimit-limit'];
const remaining = headers['x-ratelimit-remaining'];
const reset = headers['x-ratelimit-reset'];

console.log(`Rate Limit: ${remaining}/${limit} requests remaining`);
console.log(`Resets at: ${new Date(reset * 1000).toISOString()}`);
}

async handleRateLimit(response, endpoint, options) {
const retryAfter = parseInt(response.headers['retry-after']) || 60;

console.warn(`Rate limited. Retrying after ${retryAfter} seconds...`);

await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));

return this.makeRequest(endpoint, options);
}
}

// Usage
const api = new TradingCardAPI('your-api-key');

// Get cards with automatic rate limit handling
const cards = await api.makeRequest('/cards', {
params: { 'page[limit]': 25 }
});

Python

import requests
import time
from datetime import datetime

class TradingCardAPI:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = 'https://api.tradingcardapi.com'
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {api_key}',
'Accept': 'application/vnd.api+json'
})

def make_request(self, endpoint, **kwargs):
try:
response = self.session.get(f'{self.base_url}{endpoint}', **kwargs)

# Log rate limit status
self._log_rate_limit(response.headers)

response.raise_for_status()
return response.json()

except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
return self._handle_rate_limit(e.response, endpoint, **kwargs)
raise

def _log_rate_limit(self, headers):
limit = headers.get('X-RateLimit-Limit')
remaining = headers.get('X-RateLimit-Remaining')
reset = headers.get('X-RateLimit-Reset')

if all([limit, remaining, reset]):
reset_time = datetime.fromtimestamp(int(reset))
print(f"Rate Limit: {remaining}/{limit} requests remaining")
print(f"Resets at: {reset_time.isoformat()}")

def _handle_rate_limit(self, response, endpoint, **kwargs):
retry_after = int(response.headers.get('Retry-After', 60))

print(f"Rate limited. Retrying after {retry_after} seconds...")
time.sleep(retry_after)

return self.make_request(endpoint, **kwargs)

# Usage
api = TradingCardAPI('your-api-key')

# Get cards with automatic rate limit handling
cards = api.make_request('/cards', params={'page[limit]': 25})

PHP

<?php

class TradingCardAPI
{
private $apiKey;
private $baseUrl = 'https://api.tradingcardapi.com';

public function __construct($apiKey)
{
$this->apiKey = $apiKey;
}

public function makeRequest($endpoint, $params = [])
{
$url = $this->baseUrl . $endpoint;
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}

$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => [
'Authorization: Bearer ' . $this->apiKey,
'Accept: application/vnd.api+json'
]
]
]);

$response = file_get_contents($url, false, $context);

if ($response === false) {
$statusLine = $http_response_header[0];
if (strpos($statusLine, '429') !== false) {
return $this->handleRateLimit($http_response_header, $endpoint, $params);
}
throw new Exception('API request failed');
}

$this->logRateLimit($http_response_header);

return json_decode($response, true);
}

private function logRateLimit($headers)
{
$rateHeaders = [];
foreach ($headers as $header) {
if (strpos($header, 'X-RateLimit-') === 0) {
list($key, $value) = explode(': ', $header, 2);
$rateHeaders[$key] = $value;
}
}

if (isset($rateHeaders['X-RateLimit-Remaining'], $rateHeaders['X-RateLimit-Limit'])) {
$remaining = $rateHeaders['X-RateLimit-Remaining'];
$limit = $rateHeaders['X-RateLimit-Limit'];
echo "Rate Limit: {$remaining}/{$limit} requests remaining\n";
}
}

private function handleRateLimit($headers, $endpoint, $params)
{
$retryAfter = 60; // Default
foreach ($headers as $header) {
if (strpos($header, 'Retry-After:') === 0) {
$retryAfter = (int) trim(substr($header, 12));
break;
}
}

echo "Rate limited. Retrying after {$retryAfter} seconds...\n";
sleep($retryAfter);

return $this->makeRequest($endpoint, $params);
}
}

// Usage
$api = new TradingCardAPI('your-api-key');

// Get cards with automatic rate limit handling
$cards = $api->makeRequest('/cards', ['page[limit]' => 25]);
?>

🎯 Best Practices

1. Monitor Rate Limit Headers

Always check rate limit headers in responses to avoid hitting limits:

function checkRateLimit(response) {
const remaining = parseInt(response.headers['x-ratelimit-remaining']);
const limit = parseInt(response.headers['x-ratelimit-limit']);

if (remaining < limit * 0.1) { // Less than 10% remaining
console.warn('Approaching rate limit. Consider slowing down requests.');
}
}

2. Implement Exponential Backoff

When rate limited, use exponential backoff for retries:

async function exponentialBackoff(attempt, maxAttempts = 5) {
if (attempt >= maxAttempts) {
throw new Error('Max retry attempts exceeded');
}

const delay = Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30 seconds
await new Promise(resolve => setTimeout(resolve, delay));
}

3. Cache Responses Intelligently

Reduce API calls by caching responses when appropriate:

class APICache {
constructor(ttl = 300000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttl;
}

get(key) {
const item = this.cache.get(key);
if (!item) return null;

if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}

return item.data;
}

set(key, data) {
this.cache.set(key, {
data,
expires: Date.now() + this.ttl
});
}
}

4. Batch Requests When Possible

Use includes to fetch related data in single requests:

// ❌ Multiple requests
const card = await api.get('/cards/123');
const set = await api.get(`/sets/${card.relationships.set.data.id}`);
const player = await api.get(`/players/${card.relationships.oncard.data[0].id}`);

// ✅ Single request with includes
const cardWithRelations = await api.get('/cards/123?include=set,oncard');

5. Use Appropriate Page Sizes

Balance between fewer requests and manageable response sizes:

// ❌ Too many small requests
for (let page = 1; page <= 100; page++) {
await api.get(`/cards?page[number]=${page}&page[limit]=10`);
}

// ✅ Fewer larger requests
for (let page = 1; page <= 10; page++) {
await api.get(`/cards?page[number]=${page}&page[limit]=100`);
}

📜 Usage Policies

Fair Use Guidelines

Acceptable Use

  • Building trading card applications and tools
  • Personal collection management
  • Educational and research purposes
  • Commercial applications with appropriate tier

Prohibited Uses

  • Excessive automated scraping without proper rate limiting
  • Reselling raw API data without value addition
  • Bypassing rate limits through multiple accounts
  • Using the API for spam or malicious purposes

Commercial vs Personal Use

Personal Use (Free Tier)

  • Individual developers and hobbyists
  • Non-commercial projects and experimentation
  • Educational use and learning

Commercial Use (Premium/Enterprise)

  • Production applications serving users
  • Revenue-generating services
  • High-volume integrations
  • Mission-critical applications requiring SLA

🔧 Troubleshooting

Common Rate Limiting Issues

Issue: Constant 429 Errors

// Problem: Not respecting Retry-After header
setTimeout(() => retry(), 1000); // ❌ Fixed delay

// Solution: Use Retry-After header value
const retryAfter = response.headers['retry-after'];
setTimeout(() => retry(), retryAfter * 1000); // ✅ Proper delay

Issue: Hitting Burst Limits

// Problem: Sending requests too quickly
Promise.all(urls.map(url => api.get(url))); // ❌ All at once

// Solution: Control concurrency
async function limitConcurrency(urls, limit = 5) {
const results = [];
for (let i = 0; i < urls.length; i += limit) {
const batch = urls.slice(i, i + limit);
const batchResults = await Promise.all(batch.map(url => api.get(url)));
results.push(...batchResults);

// Small delay between batches
if (i + limit < urls.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}

Issue: Unexpected Rate Limit Resets

  • Rate limits use sliding windows, not fixed periods
  • Previous hour's requests affect current limit status
  • Monitor X-RateLimit-Reset for next window availability

Error Handling Checklist

  • Check for 429 status codes in all API calls
  • Parse and respect Retry-After header values
  • Implement exponential backoff for failed requests
  • Log rate limit headers for monitoring
  • Set up alerts for approaching rate limits
  • Cache responses to reduce API calls
  • Use batch operations when available

📞 Need Higher Limits?

Requesting Limit Increases

If your application requires higher limits:

  1. Contact Support: Email [email protected]
  2. Provide Details:
    • Current usage patterns
    • Expected future needs
    • Application description
    • Business case for increase
  3. Upgrade Path: Consider Premium or Enterprise tiers

Enterprise Solutions

For high-volume applications, we offer:

  • Custom Rate Limits: Tailored to your needs
  • Dedicated Infrastructure: Isolated resources
  • SLA Guarantees: Uptime and performance commitments
  • Priority Support: Direct access to our engineering team
  • Custom Integrations: Specialized endpoints for your use case

📊 Monitoring Your Usage

Usage Tracking Tips

  1. Log Rate Limit Headers: Track your usage patterns
  2. Set Up Alerts: Warn when approaching limits
  3. Monitor Response Times: Detect performance issues
  4. Track Error Rates: Identify problematic endpoints

Sample Monitoring Code

class UsageMonitor {
constructor() {
this.requests = [];
this.errors = [];
}

recordRequest(endpoint, headers) {
this.requests.push({
endpoint,
timestamp: Date.now(),
remaining: parseInt(headers['x-ratelimit-remaining']),
limit: parseInt(headers['x-ratelimit-limit'])
});
}

recordError(endpoint, status, error) {
this.errors.push({
endpoint,
status,
error: error.message,
timestamp: Date.now()
});
}

getUsageStats() {
const now = Date.now();
const lastHour = now - 3600000;

const recentRequests = this.requests.filter(r => r.timestamp > lastHour);
const recentErrors = this.errors.filter(e => e.timestamp > lastHour);

return {
requestsLastHour: recentRequests.length,
errorsLastHour: recentErrors.length,
errorRate: recentErrors.length / recentRequests.length,
mostRecentLimit: recentRequests[recentRequests.length - 1]?.remaining
};
}
}

🔄 Rate Limit Changes

Notification Process

We'll notify users of rate limit changes through:

  • Email announcements to registered users
  • API changelog documentation updates
  • GitHub releases for SDK updates
  • Blog posts for major changes

Backwards Compatibility

  • Rate limit increases: Applied immediately
  • Rate limit decreases: 30-day advance notice
  • New rate limit tiers: Opt-in basis
  • Emergency changes: Immediate with explanation

Quick Reference

MetricFree TierPremium TierEnterprise
Requests/Hour1,00010,000Custom
Requests/Month10,000100,000Custom
Burst/Minute100500Custom
SupportCommunityEmailPhone/SLA

Need help? Contact [email protected] or check our support documentation.