Error Reference
Error codes, response formats, and troubleshooting for the Chanl CLI and SDK
Every error from the Chanl API follows a consistent format. This page documents every error code, how errors appear in the CLI and SDK, and how to fix the most common ones.
Error Response Format
When an API request fails, the response body always has this shape:
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Agent with ID 695d6957e21c0ceb325c394d not found",
"details": null
},
"timestamp": "2026-03-21T14:30:00.000Z"
}Three fields matter for debugging:
| Field | What it tells you |
|---|---|
error.code | Machine-readable error type. Use this in code to branch on error kind. |
error.message | Human-readable explanation. Often includes the resource type and ID. |
error.details | Extra context when available -- field-level validation errors, billing limits, dependency lists. |
Error Codes
| HTTP | Code | Common Cause | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Missing required field, wrong type, malformed JSON | Check field types and required fields against the API docs |
| 401 | UNAUTHORIZED | Expired or missing API key, invalid JWT | Re-authenticate with chanl login or set a new key with chanl config set apiKey <key> |
| 402 | BILLING_LIMIT_EXCEEDED | Workspace hit a tier limit (agents, tools, KB entries) or exhausted usage quota | Upgrade your plan or wait for the usage period to reset |
| 403 | FORBIDDEN | Wrong workspace, insufficient role, or resource owned by another workspace | Verify workspace with chanl auth whoami and check your role |
| 404 | NOT_FOUND | Resource was deleted, ID is wrong, or it belongs to a different workspace | Verify the ID with chanl <resource> list |
| 409 | CONFLICT | Duplicate name, or resource has active dependencies that prevent the operation | Rename, or delete dependent resources first. Some endpoints accept --force |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests in a short period | Wait and retry. The SDK handles this automatically with exponential backoff |
| 500 | INTERNAL_ERROR | Server-side bug or transient infrastructure issue | Retry once. If persistent, report it with the requestId from the response |
Billing Limit Details
When you hit a BILLING_LIMIT_EXCEEDED error, the details field tells you exactly what limit was reached:
{
"error": {
"code": "BILLING_LIMIT_EXCEEDED",
"message": "You've reached the agent limit (5) on your starter plan. Upgrade to create more.",
"details": {
"entityType": "Agent",
"current": 5,
"limit": 5,
"tier": "starter"
}
}
}For usage quotas (chat messages, voice minutes, evaluations), the details include the metric and period usage:
{
"error": {
"code": "BILLING_LIMIT_EXCEEDED",
"message": "Usage quota exceeded for voice_minutes. Please add credits or upgrade your plan.",
"details": {
"type": "usage_quota",
"metricKey": "voice_minutes",
"periodUsed": 500,
"periodLimit": 500
}
}
}Validation Error Details
Validation errors can include field-level information when available:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"fieldErrors": {
"name": ["name must be a string", "name should not be empty"],
"type": ["type must be one of: http, js, system"]
}
}
}
}CLI Error Format
The CLI translates API errors into colored terminal output.
Default (table) output:
✗ Error: Agent with ID 695d6957e21c0ceb325c394d not found
Code: NOT_FOUND (404)With --json:
chanl agents get 695d6957e21c0ceb325c394d --json{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Agent with ID 695d6957e21c0ceb325c394d not found"
}
}With --verbose:
✗ Error: Agent with ID 695d6957e21c0ceb325c394d not found
Code: NOT_FOUND (404)
Request ID: req-8f3a2b1c
Timestamp: 2026-03-21T14:30:00.000ZUse --json in scripts and CI/CD pipelines. The exit code is non-zero on any error, so set -e or if checks work as expected.
SDK Error Handling
The SDK provides typed error classes for every error code. All errors extend ChanlError.
Basic try/catch
import { Chanl, ChanlError, NotFoundError, ValidationError } from '@chanl/sdk';
const chanl = new Chanl({ apiKey: process.env.CHANL_API_KEY });
try {
const agent = await chanl.agents.get('nonexistent-id');
} catch (error) {
if (error instanceof NotFoundError) {
console.log(error.resourceType); // "Agent"
console.log(error.resourceId); // "nonexistent-id"
} else if (error instanceof ValidationError) {
// Field-level errors
console.log(error.fieldErrors); // { name: ["name must be a string"] }
console.log(error.getFieldErrors('name')); // ["name must be a string"]
console.log(error.hasFieldError('name')); // true
} else if (error instanceof ChanlError) {
// Any other API error
console.log(error.code); // "CONFLICT", "FORBIDDEN", etc.
console.log(error.statusCode); // 409, 403, etc.
console.log(error.message); // Human-readable message
console.log(error.requestId); // For support requests
}
}Error Class Hierarchy
ChanlError (base)
├── NotFoundError 404 NOT_FOUND
├── ValidationError 400 VALIDATION_ERROR
├── AuthenticationError 401 AUTHENTICATION_ERROR
├── AuthorizationError 403 AUTHORIZATION_ERROR
├── ConflictError 409 CONFLICT_ERROR
├── RateLimitError 429 RATE_LIMIT_ERROR
├── ServerError 5xx SERVER_ERROR
├── NetworkError --- NETWORK_ERROR
└── TimeoutError --- TIMEOUT_ERRORType Guards
The SDK exports type guard functions for cleaner conditional logic:
import {
isChanlError,
isNotFoundError,
isValidationError,
isAuthenticationError,
} from '@chanl/sdk';
try {
await chanl.tools.execute(toolId, { orderId: '12345' });
} catch (error) {
if (isNotFoundError(error)) {
// TypeScript narrows to NotFoundError
console.log(`${error.resourceType} ${error.resourceId} not found`);
} else if (isAuthenticationError(error)) {
// Re-authenticate
chanl.updateAuth({ apiKey: getNewApiKey() });
} else if (isChanlError(error)) {
// Generic Chanl error
console.error(`[${error.code}] ${error.message}`);
}
}Serialization
Every ChanlError is JSON-serializable for logging:
try {
await chanl.agents.create({ name: '' });
} catch (error) {
if (isChanlError(error)) {
// Structured logging
logger.error(error.toJSON());
// {
// name: "ValidationError",
// code: "VALIDATION_ERROR",
// message: "Validation failed",
// statusCode: 400,
// details: { fieldErrors: { name: ["name should not be empty"] } },
// requestId: "req-8f3a2b1c"
// }
}
}Retry Configuration
The SDK retries failed requests automatically. By default:
- Max retries: 3
- Initial delay: 1000ms
- Backoff multiplier: 2x (1s, 2s, 4s)
Only retryable errors are retried: network failures, timeouts, and 429/5xx responses. Client errors (400, 401, 403, 404, 409) are never retried.
Custom retry config
const chanl = new Chanl({
apiKey: process.env.CHANL_API_KEY,
retry: {
maxRetries: 5, // More attempts
retryDelay: 500, // Start faster
backoffMultiplier: 3, // Back off more aggressively
},
});Disable retries
const chanl = new Chanl({
apiKey: process.env.CHANL_API_KEY,
retry: {
maxRetries: 0,
retryDelay: 0,
backoffMultiplier: 1,
},
});Per-request retry control
// Disable retry for a specific request
const result = await chanl.request('POST', '/api/v1/agents', data, {
retry: false,
});Common Scenarios
Getting Help
If you hit an error that is not listed here or that persists after following the fix:
- Capture the request ID -- include
--verboseor checkerror.requestIdin the SDK. This helps us trace the exact request. - File an issue on github.com/chanl-ai/chanl-sdk with the error code, message, and request ID.
- Check API status with
chanl healthto rule out platform-wide issues.