Skip to main content

Error Handling

Handle API errors gracefully with consistent error responses and proper retry logic.

Overview

RenderDoc API uses conventional HTTP response codes and returns structured error responses in JSON format. All errors follow a consistent schema to make error handling predictable and straightforward.


Error Response Format

All errors return this consistent structure:

{
"statusCode": 400,
"code": "ERR_VALID_001",
"message": "Validation failed",
"timestamp": "2025-11-07T10:30:00.000Z",
"path": "/api/v1/documents/generate",
"relatedInfo": {
"errors": [
{
"field": "templateId",
"message": "Template ID is required"
}
]
}
}

Response Fields

FieldTypeDescription
statusCodenumberHTTP status code
codestringError code (e.g., ERR_VALID_001)
messagestringHuman-readable error message
timestampstringISO 8601 timestamp
pathstringAPI endpoint that generated error
relatedInfoobjectAdditional context (optional)

HTTP Status Codes

Success Codes (2xx)

CodeDescription
200 OKRequest successful
201 CreatedResource created successfully
204 No ContentRequest successful, no content to return

Client Error Codes (4xx)

CodeDescriptionWhen to Expect
400 Bad RequestInvalid request dataValidation errors, malformed JSON
401 UnauthorizedAuthentication requiredMissing or invalid API key
403 ForbiddenInsufficient permissionsAPI key lacks required permission
404 Not FoundResource doesn't existTemplate, document, or endpoint not found
409 ConflictResource conflictDuplicate resource, version mismatch
422 Unprocessable EntityValid syntax but semantic errorsBusiness logic violation
429 Too Many RequestsRate limit exceededToo many requests in time window

Server Error Codes (5xx)

CodeDescriptionWhat to Do
500 Internal Server ErrorUnexpected server errorRetry with exponential backoff
502 Bad GatewayUpstream service errorRetry after delay
503 Service UnavailableService temporarily downCheck status page, retry later
504 Gateway TimeoutRequest timeoutRetry with longer timeout

Error Codes

Authentication Errors (ERR_AUTH_xxx)

{
"statusCode": 401,
"code": "ERR_AUTH_001",
"message": "You are not authorized to access this resource"
}
CodeMessageSolution
ERR_AUTH_001You are not authorized to access this resourceCheck API key is correct
ERR_AUTH_002Invalid email or passwordCheck credentials
ERR_AUTH_003Your session has expiredRefresh your authentication token

Validation Errors (ERR_VALID_xxx)

{
"statusCode": 400,
"code": "ERR_VALID_001",
"message": "Component validation failed",
"relatedInfo": {
"errors": [
{ "field": "templateId", "message": "Template ID is required" }
]
}
}
CodeMessage
ERR_VALID_001Component validation failed
ERR_VALID_002Variable validation failed
ERR_VALID_003Formula validation failed

Template Errors (ERR_TMPL_xxx)

{
"statusCode": 404,
"code": "ERR_TMPL_002",
"message": "Document template not found",
"relatedInfo": {
"templateId": "tmpl_invalid"
}
}
CodeMessage
ERR_TMPL_001Template not found
ERR_TMPL_002Document template not found
ERR_TMPL_003Attachment template not found
ERR_TMPL_008Template schema validation failed

Quota Errors (ERR_QUOTA_xxx)

{
"statusCode": 429,
"code": "ERR_QUOTA_008",
"message": "Monthly document generation quota exceeded",
"relatedInfo": {
"limit": 10000,
"used": 10000,
"resetsAt": "2025-12-01T00:00:00Z"
}
}
CodeMessage
ERR_QUOTA_003Rate limit exceeded
ERR_QUOTA_008Monthly document generation quota exceeded
ERR_QUOTA_011Insufficient document credits

Implementing Error Handling

JavaScript/TypeScript

interface ApiError {
statusCode: number;
code: string;
message: string;
timestamp: string;
path: string;
relatedInfo?: Record<string, any>;
}

async function generateDocument(data: any) {
try {
const response = await fetch('https://api.renderdoc.dev/api/v1/documents/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});

if (!response.ok) {
const error: ApiError = await response.json();
throw new RenderdocError(error);
}

return await response.json();
} catch (error) {
if (error instanceof RenderdocError) {
handleRenderdocError(error);
} else {
// Network error
console.error('Network error:', error);
}
throw error;
}
}

class RenderdocError extends Error {
statusCode: number;
code: string;
relatedInfo?: Record<string, any>;

constructor(apiError: ApiError) {
super(apiError.message);
this.name = 'RenderdocError';
this.statusCode = apiError.statusCode;
this.code = apiError.code;
this.relatedInfo = apiError.relatedInfo;
}
}

function handleRenderdocError(error: RenderdocError) {
switch (error.code) {
case 'ERR_AUTH_001':
// Invalid API key
console.error('Invalid API key. Please check your credentials.');
break;

case 'ERR_QUOTA_008':
// Quota exceeded
const { limit, resetsAt } = error.relatedInfo!;
console.error(`Monthly quota of ${limit} exceeded. Resets at ${resetsAt}`);
break;

case 'ERR_VALID_001':
// Validation errors
const { errors } = error.relatedInfo!;
errors.forEach((err: any) => {
console.error(`${err.field}: ${err.message}`);
});
break;

case 'ERR_TMPL_002':
// Template not found
console.error(`Template not found: ${error.relatedInfo!.templateId}`);
break;

default:
console.error('API error:', error.message);
}
}

Python

from typing import Dict, Any, Optional
import requests

class RenderdocError(Exception):
def __init__(self, status_code: int, code: str, message: str, related_info: Optional[Dict] = None):
self.status_code = status_code
self.code = code
self.message = message
self.related_info = related_info or {}
super().__init__(self.message)

def generate_document(data: Dict[str, Any], api_key: str):
try:
response = requests.post(
'https://api.renderdoc.dev/api/v1/documents/generate',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
},
json=data
)

if not response.ok:
error_data = response.json()
raise RenderdocError(
status_code=error_data['statusCode'],
code=error_data['code'],
message=error_data['message'],
related_info=error_data.get('relatedInfo')
)

return response.json()

except RenderdocError as e:
handle_renderdoc_error(e)
raise
except requests.RequestException as e:
print(f"Network error: {e}")
raise

def handle_renderdoc_error(error: RenderdocError):
if error.code == 'ERR_AUTH_001':
print('Invalid API key. Please check your credentials.')
elif error.code == 'ERR_QUOTA_008':
limit = error.related_info.get('limit')
resets_at = error.related_info.get('resetsAt')
print(f'Monthly quota of {limit} exceeded. Resets at {resets_at}')
elif error.code == 'ERR_VALID_001':
errors = error.related_info.get('errors', [])
for err in errors:
print(f"{err['field']}: {err['message']}")
elif error.code == 'ERR_TMPL_002':
template_id = error.related_info.get('templateId')
print(f'Template not found: {template_id}')
else:
print(f'API error: {error.message}')

Retry Strategies

Exponential Backoff

async function generateDocumentWithRetry(data, maxRetries = 3) {
let lastError;

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await generateDocument(data);
} catch (error) {
lastError = error;

// Don't retry client errors (4xx except 429)
if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
throw error;
}

// Calculate delay with exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);

console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}

throw lastError;
}

Retry Conditions

Retry these errors:

  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout
  • 429 Too Many Requests (with backoff from Retry-After header)
  • Network timeouts
  • Connection errors

Don't retry these:

  • 400 Bad Request - Fix request data
  • 401 Unauthorized - Fix authentication
  • 403 Forbidden - Fix permissions
  • 404 Not Found - Resource doesn't exist
  • 409 Conflict - Fix conflict
  • 422 Unprocessable Entity - Fix business logic

Rate Limit Headers

Rate limit responses include headers:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699363200
Retry-After: 60

Using Rate Limit Headers

async function generateWithRateLimit(data) {
try {
const response = await fetch('https://api.renderdoc.dev/api/v1/documents/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});

// Check rate limit headers
const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0');

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

if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
throw new Error('Rate limited');
}

if (!response.ok) {
const error = await response.json();
throw new RenderdocError(error);
}

return await response.json();
} catch (error) {
console.error('Error generating document:', error);
throw error;
}
}

Best Practices

1. Log All Errors

function logError(error, context) {
console.error({
timestamp: new Date().toISOString(),
errorCode: error.code,
statusCode: error.statusCode,
message: error.message,
context,
relatedInfo: error.relatedInfo
});

// Send to error tracking service
errorTracker.captureException(error, { extra: context });
}

2. Provide User-Friendly Messages

function getUserMessage(error) {
const messages = {
'ERR_AUTH_001': 'Authentication failed. Please check your API key.',
'ERR_QUOTA_008': 'You have reached your monthly document limit. Please upgrade your plan or wait until next month.',
'ERR_VALID_001': 'Please check your input and try again.',
'ERR_TMPL_002': 'The requested template was not found.',
};

return messages[error.code] || 'An unexpected error occurred. Please try again later.';
}

3. Implement Circuit Breaker

class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'closed'; // closed, open, half-open
this.nextAttempt = Date.now();
}

async execute(fn) {
if (this.state === 'open') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is open');
}
this.state = 'half-open';
}

try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}

onSuccess() {
this.failureCount = 0;
this.state = 'closed';
}

onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'open';
this.nextAttempt = Date.now() + this.timeout;
}
}
}

const breaker = new CircuitBreaker();

async function generateDocumentSafely(data) {
return breaker.execute(() => generateDocument(data));
}

4. Monitor Error Rates

const errorMetrics = {
total: 0,
byCode: {},
byStatusCode: {}
};

function trackError(error) {
errorMetrics.total++;
errorMetrics.byCode[error.code] = (errorMetrics.byCode[error.code] || 0) + 1;
errorMetrics.byStatusCode[error.statusCode] = (errorMetrics.byStatusCode[error.statusCode] || 0) + 1;

// Alert if error rate is high
if (errorMetrics.total > 100 && errorMetrics.byStatusCode[500] / errorMetrics.total > 0.1) {
alertAdmin('High server error rate');
}
}

Common Errors and Solutions

ErrorCauseSolution
ERR_AUTH_001Invalid API keyCheck API key in dashboard
ERR_AUTH_003Session expiredRefresh authentication token
ERR_AUTH_005Insufficient permissionsUpdate API key permissions
ERR_VALID_001Validation failedCheck request body against API docs
ERR_TMPL_001Template not foundVerify template ID exists
ERR_TMPL_002Document template not foundCheck template type and ID
ERR_QUOTA_003Rate limit exceededImplement backoff strategy
ERR_QUOTA_008Document quota exceededUpgrade plan or wait for reset
ERR_QUOTA_011Insufficient creditsPurchase more credits
500Server errorRetry with exponential backoff
503Service unavailableCheck status page, retry later

Related: Authentication | Rate Limiting