Skip to main content
CID222Documentation

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

CodeDescriptionAction
400Bad RequestFix request body/parameters
401UnauthorizedCheck API key
403ForbiddenInsufficient role or scope (content blocks are SSE events, not 403)
404Not FoundCheck endpoint URL or resource ID
429Rate LimitedRetry after delay
500Internal ErrorRetry with backoff
503Service UnavailableProvider 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 error
if (event.type === 'error' || event.error) {
throw new Error(event.error || 'Stream error');
}
// Policy block mid-stream
if (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 TypeShould RetryStrategy
400 Bad RequestNoFix input and resubmit
401 UnauthorizedOnceRefresh token, then retry
403 ForbiddenNoInsufficient role/scope, don't retry
429 Rate LimitedYesWait for Retry-After header
500 Server ErrorYesExponential backoff
503 UnavailableYesExponential 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 errors
if (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 backoff
if (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;
}
}