Error Handling
Learn how to properly handle errors from the CID222 API to build resilient applications.
Error Response Format
All API errors follow a consistent format:
Error Response
{"statusCode": 400,"message": "Detailed error message","error": "Bad Request","details": {// Additional context when available}}
HTTP Status Codes
| Code | Description | Action |
|---|---|---|
400 | Bad Request | Fix request body/parameters |
401 | Unauthorized | Check API key |
403 | Forbidden | Insufficient role or scope (content blocks are SSE events, not 403) |
404 | Not Found | Check endpoint URL or resource ID |
429 | Rate Limited | Retry after delay |
500 | Internal Error | Retry with backoff |
503 | Service Unavailable | Provider down, retry later |
Common Errors
Authentication Errors
401 - Invalid Token
{"statusCode": 401,"message": "Invalid or expired token","error": "Unauthorized"}
Solutions:
- Verify API key is correct
- Check if key has been rotated
- Ensure Bearer prefix is included
Content Blocked
Chat — blocked as an SSE event
data: {"type":"input_rejected","reason":"hate","details":"Your prompt was flagged by the safety filter."}data: [DONE]
Guardrails detect — action: reject (HTTP 200)
{"action": "reject","detectionCount": 1,"detectedEntities": [{ "type": "hate", "action": "reject", "confidence": 0.94, "source": "hap_detector" }],"maskedText": "..."}
In chat, blocked content arrives as an input_rejected or output_rejected SSE event (not a 403) and the prompt is never forwarded to the LLM — no tokens are consumed. The Guardrails detect endpoint returns action: reject with HTTP 200.
Rate Limiting
429 - Rate Limit
{"statusCode": 429,"message": "Rate limit exceeded","error": "Too Many Requests","details": {"retry_after": 30,"limit": 60,"remaining": 0,"reset": 1699900030}}
Handle rate limits by waiting before retrying:
Rate Limit Handler
async function handleRateLimit(response) {const retryAfter = response.headers.get('Retry-After');const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 30000;console.log(`Rate limited. Waiting ${waitTime}ms...`);await new Promise(resolve => setTimeout(resolve, waitTime));// Retry the request}
Stream Error Handling
SSE streams may encounter errors mid-stream:
Stream Error Handling
async function handleStream(response) {const reader = response.body.getReader();const decoder = new TextDecoder();try {while (true) {const { done, value } = await reader.read();if (done) break;for (const line of decoder.decode(value).split('\n')) {if (!line.startsWith('data: ')) continue;const data = line.slice(6);if (data === '[DONE]') return;const event = JSON.parse(data);// Transport/server errorif (event.type === 'error' || event.error) {throw new Error(event.error || 'Stream error');}// Policy block mid-streamif (event.type === 'input_rejected' || event.type === 'output_rejected') {throw new Error('Blocked: ' + event.reason);}if (event.type === 'content_block_delta') {processToken(event.delta.text);}}}} catch (error) {console.error('Stream error:', error);// Handle gracefully - maybe show partial response} finally {reader.releaseLock();}}
Retry Strategies
Different errors require different retry approaches:
| Error Type | Should Retry | Strategy |
|---|---|---|
| 400 Bad Request | No | Fix input and resubmit |
| 401 Unauthorized | Once | Refresh token, then retry |
| 403 Forbidden | No | Insufficient role/scope, don't retry |
| 429 Rate Limited | Yes | Wait for Retry-After header |
| 500 Server Error | Yes | Exponential backoff |
| 503 Unavailable | Yes | Exponential backoff |
Complete Error Handler
class CID222Client {async request(endpoint, body, retries = 3) {for (let attempt = 0; attempt < retries; attempt++) {try {const response = await fetch(`https://api.cid222.ai${endpoint}`, {method: 'POST',headers: {'Authorization': `Bearer ${this.apiKey}`,'Content-Type': 'application/json',},body: JSON.stringify(body)});if (response.ok) {return response.json();}const error = await response.json();// Don't retry client errorsif (response.status >= 400 && response.status < 500) {if (response.status === 429) {const retryAfter = response.headers.get('Retry-After') || '30';await this.sleep(parseInt(retryAfter) * 1000);continue;}throw new APIError(error.message, response.status);}// Retry server errors with backoffif (attempt < retries - 1) {await this.sleep(Math.pow(2, attempt) * 1000);continue;}throw new APIError(error.message, response.status);} catch (error) {if (attempt === retries - 1) throw error;}}}sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));}}class APIError extends Error {constructor(message, status) {super(message);this.status = status;}}