Error Codes
Every error response uses a consistent envelope:
{ "success": false, "message": "Human-readable description", "code": "machine_readable_code", "errors": []}The code field is stable. Use it for programmatic error handling. The message field may change between versions.
HTTP status codes
Section titled “HTTP status codes”| Status | Meaning | Retryable? |
|---|---|---|
| 400 | Bad request. Invalid input | No |
| 401 | Unauthorized. Missing or invalid API key | No |
| 403 | Forbidden. CSRF failure (session auth only) | No |
| 404 | Not found. Resource doesn’t exist | No |
| 409 | Conflict. Resource state prevents the action | No (fix state first) |
| 422 | Validation error. Request body/query failed schema validation | No (fix input) |
| 429 | Rate limited. Too many requests | Yes (after Retry-After seconds) |
| 500 | Internal error. Unexpected server failure | Yes |
Global errors
Section titled “Global errors”These can occur on any endpoint.
| Code | Status | Description |
|---|---|---|
bad_request | 400 | Request is malformed or semantically invalid. |
unauthorized | 401 | No valid API key provided, or key has been revoked. |
forbidden | 403 | Access denied. |
not_found | 404 | Resource does not exist or URL does not match any route. |
conflict | 409 | Resource state prevents the requested action. |
validation_error | 422 | Request body, query, or path params failed schema validation. See errors array for details. |
rate_limit_exceeded | 429 | Exceeded 100 requests per 10 seconds. The Retry-After header tells you when to retry. |
internal_error | 500 | Unexpected server error. |
error | 500 | Fallback code when no specific code applies. |
Schedule errors
Section titled “Schedule errors”| Code | Status | When |
|---|---|---|
schedule_not_found | 404 | Schedule ID doesn’t exist or doesn’t belong to your account. |
schedule_archived | 409 | Attempting to update or archive an already-archived schedule. |
schedule_completed | 409 | Attempting to update a completed schedule (reached runs limit or stop_at). |
handler_required_for_pull | 400 | Updating a schedule to pull delivery without setting a handler. |
schedule_status_changed | 409 | Concurrent update detected. The schedule’s status changed between your read and write. Retry the operation. |
Job errors
Section titled “Job errors”| Code | Status | When |
|---|---|---|
job_not_found | 404 | Job ID doesn’t exist or doesn’t belong to your account. |
idempotency_key_conflict | 409 | A job with the same idempotency_key already exists on your account. |
Execution errors
Section titled “Execution errors”| Code | Status | When |
|---|---|---|
execution_not_found | 404 | Execution ID doesn’t exist or doesn’t belong to your account. |
execution_not_running | 409 | Reporting a result for an execution that isn’t in running status. The execution may have already timed out or been reported by another process. |
Signing key errors
Section titled “Signing key errors”| Code | Status | When |
|---|---|---|
signing_key_not_found | 404 | No signing key exists for your account. Create one in the dashboard. |
signing_key_grace_window_active | 409 | A key rotation is already in progress. Wait 24 hours for the grace window to close before rotating again. |
Validation error details
Section titled “Validation error details”When the code is validation_error, the errors array contains per-field details:
{ "success": false, "message": "request body failed validation", "code": "validation_error", "errors": [ { "field": "cron", "message": "cron must be a valid cron expression" }, { "field": "payload", "message": "payload must be 64KB or smaller" } ]}Common validation messages:
| Message | Cause |
|---|---|
provide either cron or interval, not both | Schedule creation/update with both timing fields |
provide either delay or scheduled_for, not both | Job creation with both timing options |
payload must be 64KB or smaller | Payload exceeds size limit |
cron must be a valid cron expression | Unparseable cron syntax |
must be a valid IANA timezone | Invalid timezone string |
url must use https in production | HTTP URL (HTTPS required in production) |
url must not point to a private or reserved IP address | Localhost or private network URL |
stop_at must be a future date | Past date for schedule termination |
scheduled_for must be a future date | Past date for job execution |
size must be between 1 and 100 | Pagination page size out of range |
at least one field must be provided | Empty update body |
Handling errors in code
Section titled “Handling errors in code”With the SDK
Section titled “With the SDK”The SDK wraps API errors in ChronosApiError:
import { ChronosApiError } from '@chronos.sh/sdk';
try { await chronos.worker.start();} catch (err) { if (err instanceof ChronosApiError) { console.error(`${err.code} (${err.status}): ${err.message}`); if (err.code === 'rate_limit_exceeded') { // back off and retry } }}With direct API calls
Section titled “With direct API calls”Check the code field for programmatic handling:
const res = await fetch('https://api.chronos.sh/v1/schedules', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(schedule),});
if (!res.ok) { const error = await res.json(); switch (error.code) { case 'validation_error': console.error('Invalid input:', error.errors); break; case 'rate_limit_exceeded': const retryAfter = res.headers.get('Retry-After'); await sleep(Number(retryAfter) * 1000); break; case 'unauthorized': throw new Error('Check your API key'); break; }}