Chanl

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:

FieldWhat it tells you
error.codeMachine-readable error type. Use this in code to branch on error kind.
error.messageHuman-readable explanation. Often includes the resource type and ID.
error.detailsExtra context when available -- field-level validation errors, billing limits, dependency lists.

Error Codes

HTTPCodeCommon CauseFix
400VALIDATION_ERRORMissing required field, wrong type, malformed JSONCheck field types and required fields against the API docs
401UNAUTHORIZEDExpired or missing API key, invalid JWTRe-authenticate with chanl login or set a new key with chanl config set apiKey <key>
402BILLING_LIMIT_EXCEEDEDWorkspace hit a tier limit (agents, tools, KB entries) or exhausted usage quotaUpgrade your plan or wait for the usage period to reset
403FORBIDDENWrong workspace, insufficient role, or resource owned by another workspaceVerify workspace with chanl auth whoami and check your role
404NOT_FOUNDResource was deleted, ID is wrong, or it belongs to a different workspaceVerify the ID with chanl <resource> list
409CONFLICTDuplicate name, or resource has active dependencies that prevent the operationRename, or delete dependent resources first. Some endpoints accept --force
429RATE_LIMIT_EXCEEDEDToo many requests in a short periodWait and retry. The SDK handles this automatically with exponential backoff
500INTERNAL_ERRORServer-side bug or transient infrastructure issueRetry 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.000Z

Use --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_ERROR

Type 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:

  1. Capture the request ID -- include --verbose or check error.requestId in the SDK. This helps us trace the exact request.
  2. File an issue on github.com/chanl-ai/chanl-sdk with the error code, message, and request ID.
  3. Check API status with chanl health to rule out platform-wide issues.

On this page