Understanding API error responses and implementing proper error handling is crucial for building reliable integrations with the Streamforge External API.
HTTP Status Codes
The API uses standard HTTP status codes to indicate request outcomes:
| Code | Meaning | When It Occurs |
|---|
200 | Success | Request completed successfully |
400 | Bad Request | Invalid parameters, missing required fields, or validation errors |
401 | Unauthorized | Missing x-api-key header |
403 | Forbidden | Invalid or revoked API key |
404 | Not Found | Resource doesn’t exist (profile, content, game) |
429 | Too Many Requests | Rate limit exceeded |
5xx | Server Error | Internal server issues (retry with backoff) |
All error responses follow a consistent structure:
{
"error": {
"status": 404,
"message": "Profile not found"
},
"meta": {
"request_id": "5e7c0137-1ed3-4b4b-8e5f-3d9cf00f7ac4"
}
}
The request_id can be used when contacting support about specific errors.
Handling Specific Errors
400 Bad Request
Invalid parameters or malformed requests:
if (response.status === 400) {
const error = await response.json();
console.error('Validation error:', error.error.message);
// Check your request parameters
}
Common causes:
- Invalid
platform value (must be twitch, youtube, or tiktok)
limit out of range (must be 1-50)
- Missing required fields in bulk requests
- Invalid date format in query parameters
401 Unauthorized
Missing API key:
if (response.status === 401) {
console.error('API key is missing');
// Add x-api-key header to your request
}
Solution: Ensure all requests include the x-api-key header.
403 Forbidden
Invalid or revoked API key:
if (response.status === 403) {
console.error('API key is invalid or revoked');
// Contact [email protected] for a new key
}
Common causes:
- Typo in API key
- Key was rotated or revoked
- Key is inactive
404 Not Found
Resource doesn’t exist:
if (response.status === 404) {
const error = await response.json();
// Resource doesn't exist - this is normal for some use cases
// Handle gracefully (e.g., skip, log, or retry later)
}
Note: 404s are normal in some scenarios:
- Profile IDs that don’t exist
- Content that was deleted
- Games not in the database
Handle 404s gracefully rather than treating them as errors.
429 Too Many Requests
Rate limit exceeded:
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const seconds = parseInt(retryAfter, 10);
// Wait before retrying
await sleep(seconds * 1000);
// Retry the request
}
Response headers:
Retry-After: Seconds to wait before retrying
X-RateLimit-*-remaining: Remaining quota (often 0)
Retry Strategies
Exponential Backoff
Implement exponential backoff for retryable errors (429, 5xx):
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) {
return response;
}
// Don't retry 4xx errors (except 429)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
throw new Error(`Client error: ${response.status}`);
}
// Calculate backoff delay
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
// For 429, use Retry-After header
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
await sleep(parseInt(retryAfter, 10) * 1000);
continue;
}
}
await sleep(delay);
}
throw new Error('Max retries exceeded');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Handling Bulk Operation Errors
Bulk operations use partial success - some items may be missing:
async function handleBulkResponse(response) {
const data = await response.json();
if (response.ok) {
// Check for missing items
if (data.meta.missing_ids && data.meta.missing_ids.length > 0) {
console.warn(`Missing items: ${data.meta.missing_ids.join(', ')}`);
// Handle missing items (e.g., log, retry individually, or skip)
}
return data.payload;
} else {
// Handle full request failure
throw new Error(`Bulk request failed: ${data.error.message}`);
}
}
Best Practices
Always check response status before parsing JSON. Some errors may not return JSON bodies.
- Check status first: Verify
response.ok or response.status before parsing JSON
- Handle 404s gracefully: Missing resources are normal; don’t treat as errors
- Respect Retry-After: For 429 errors, wait the specified time before retrying
- Log request IDs: Include
request_id from error responses in your logs
- Monitor error rates: Track error rates to detect issues early
- Implement circuit breakers: Stop retrying if error rate is too high
Example: Complete Error Handler
async function apiRequest(url, options) {
try {
const response = await fetch(url, {
...options,
headers: {
'x-api-key': process.env.API_KEY,
'Content-Type': 'application/json',
...options.headers
}
});
// Handle rate limiting
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new RateLimitError(parseInt(retryAfter, 10));
}
// Parse response
const data = await response.json();
if (!response.ok) {
throw new ApiError(data.error.message, response.status, data.meta.request_id);
}
return data;
} catch (error) {
if (error instanceof RateLimitError) {
// Handle rate limit
await sleep(error.retryAfter * 1000);
return apiRequest(url, options); // Retry
}
throw error;
}
}
class ApiError extends Error {
constructor(message, status, requestId) {
super(message);
this.status = status;
this.requestId = requestId;
}
}
class RateLimitError extends Error {
constructor(retryAfter) {
super('Rate limit exceeded');
this.retryAfter = retryAfter;
}
}
Don’t retry 4xx errors (except 429) - they indicate client errors that won’t be fixed by retrying.