Error Codes
Every error response from the Chronary API follows a consistent shape, making it straightforward to handle errors programmatically.
Error response format
Section titled “Error response format”All errors return a JSON body with a single error object:
{ "error": { "type": "validation_error", "message": "Invalid request body", "param": "start_time", "request_id": "req_abc123" }}| Field | Type | Description |
|-------|------|-------------|
| type | string | Machine-readable error type (see table below) |
| message | string | Human-readable explanation of what went wrong |
| param | string \| null | The specific parameter that caused the error, when applicable |
| request_id | string | Unique identifier for the request, useful for debugging |
Error types
Section titled “Error types”validation_error — 400 Bad Request
Section titled “validation_error — 400 Bad Request”The request body, query parameters, or path parameters failed validation.
Common causes:
- Missing required fields
- Invalid date format in
start_timeorend_time - Value out of allowed range (e.g., negative duration)
- Unknown fields in the request body
How to fix: Check the param field in the error response to identify which field is invalid. Refer to the API reference for the expected format.
authentication_error — 401 Unauthorized
Section titled “authentication_error — 401 Unauthorized”The API key is missing, malformed, or revoked.
Common causes:
- Missing
Authorizationheader entirely - Using
Authorization: chr_sk_...instead ofAuthorization: Bearer chr_sk_... - Using a revoked or rotated API key
How to fix: Verify the Authorization: Bearer <your_api_key> header is present and uses a valid, active key from the console.
forbidden — 403 Forbidden
Section titled “forbidden — 403 Forbidden”The authenticated key does not have permission to perform the requested action.
Common causes:
- Attempting to modify an event with
source: "ical_subscription"(external events are read-only) - Accessing a resource that belongs to a different organization
- Responding to a scheduling proposal (
spr_) as an agent that is not a listed participant - Performing an action not allowed on your current plan
How to fix: Check that the resource belongs to your organization and that the source field allows modification. External events synced via iCal subscriptions cannot be edited through the API. For proposals, confirm the responding agent’s id is in the proposal’s participant_agent_ids list.
not_found — 404 Not Found
Section titled “not_found — 404 Not Found”The requested resource does not exist or is not accessible from your organization.
Common causes:
- Typo in the resource ID (e.g.,
agt_vscal_prefix mismatch) - The resource was deleted
- The resource belongs to a different organization
- The parent calendar for an event operation does not exist (“Calendar not found”)
- The organizer or a listed participant on a proposal does not exist (“Organizer agent not found” / “One or more participant agents not found”)
How to fix: Verify the ID is correct and uses the right prefix (agt_ for agents, cal_ for calendars, evt_ for events, spr_ for scheduling proposals). Confirm the resource belongs to the same organization as your API key. For proposals, verify the organizer_agent_id and every participant_agent_ids entry reference existing agents.
conflict — 409 Conflict
Section titled “conflict — 409 Conflict”The request conflicts with the current state of a resource.
Common causes:
- Creating an agent with an email address already in use
- Creating a hold that overlaps an existing hold with equal or higher priority (“An overlapping hold with equal or higher priority already exists”)
- Calling
PUT /v1/events/:id/confirmor/releaseon an event whosestatusis nothold(“Event is not in hold status”) - Calling
PUT /v1/events/:id/confirmon a hold whosehold_expires_athas passed (“Hold has already expired”) - Responding to or resolving a scheduling proposal that is no longer in the
pendingstate (“Proposal is not in a pending state”) - Submitting a second response from the same agent to the same proposal (“Agent has already responded to this proposal”)
- Creating a duplicate webhook subscription for the same URL and event type
How to fix: Inspect the error message to identify the specific conflict. For hold conflicts, raise hold_priority above the existing hold or create the event at a non-overlapping time. For stale proposals, re-fetch the proposal to see its current status before retrying. For duplicate responses, update the existing response rather than creating a new one.
rate_limit_error — 429 Too Many Requests
Section titled “rate_limit_error — 429 Too Many Requests”You have exceeded the per-key rate limit of 10 requests per second.
Common causes:
- Sending bursts of requests without throttling
- Running parallel operations without a concurrency limit
How to fix: Respect the Retry-After header in the response. Implement exponential backoff with jitter. See the Rate Limits page for a retry code example.
quota_exceeded — 429 Too Many Requests
Section titled “quota_exceeded — 429 Too Many Requests”Your organization has exhausted its monthly quota for a specific resource.
Common causes:
- Hitting the monthly event creation limit on the Free plan (2,500/mo)
- Exceeding the monthly API call limit (50,000/mo on Free)
How to fix: Check your current usage with GET /v1/usage. Upgrade your plan in the console or wait for the automatic monthly reset. See Rate Limits for quota details by plan.
internal_error — 500 Internal Server Error
Section titled “internal_error — 500 Internal Server Error”Something went wrong on the Chronary server.
Common causes:
- Transient infrastructure issue
- Unexpected edge case in request processing
How to fix: Retry the request with exponential backoff. If the error persists after multiple retries, contact support with the request_id from the error response.
Handling errors with the Python SDK
Section titled “Handling errors with the Python SDK”The Python SDK maps each error type to a specific exception class. All exceptions inherit from ChronaryError:
ChronaryError└── APIError ├── APIConnectionError │ └── APITimeoutError └── APIStatusError ├── BadRequestError (400) ├── AuthenticationError (401) ├── PermissionDeniedError (403) ├── NotFoundError (404) ├── RateLimitError (429) ├── QuotaExceededError (429) └── InternalServerError (500+)Catching specific errors
Section titled “Catching specific errors”from chronary import ( Chronary, NotFoundError, RateLimitError, QuotaExceededError, AuthenticationError, BadRequestError, APIError,)
client = Chronary()
try: agent = client.agents.get("agt_nonexistent")except NotFoundError as e: print(f"Not found: {e.message}") print(f"Request ID: {e.request_id}")except RateLimitError as e: print(f"Rate limited: {e.message}")except QuotaExceededError as e: print(f"Quota exceeded: {e.message}")except AuthenticationError as e: print(f"Invalid API key: {e.message}")except BadRequestError as e: print(f"Validation error on '{e.param}': {e.message}")except APIError as e: # Catch-all for any API error print(f"API error ({e.status_code}): {e.message}")Error attributes
Section titled “Error attributes”APIStatusError exceptions (and all subclasses) expose:
| Attribute | Type | Description |
|-----------|------|-------------|
| status_code | int | HTTP status code |
| message | str | Human-readable error message |
| request_id | str \| None | Unique request ID for debugging |
| error_type | str \| None | Machine-readable error type |
| param | str \| None | The parameter that caused the error |
Configuring retries
Section titled “Configuring retries”The SDK automatically retries on 429 and 5xx errors with exponential backoff:
# Increase retries for reliability-critical codeclient = Chronary(max_retries=5, timeout=30.0)
# Disable retries entirelyclient = Chronary(max_retries=0)The Retry-After header is respected when present. See Rate Limits for more details.
Using request_id for debugging
Section titled “Using request_id for debugging”Every API response — successful or not — includes a request_id in the response body and as the X-Request-Id response header. This ID uniquely identifies your request in Chronary’s logging infrastructure.
When contacting support about an error:
- Include the
request_idfrom the error response - Note the approximate time the error occurred
- Include the endpoint and method you called
This allows the support team to trace the exact request through the system and diagnose the issue quickly.
# The request_id appears in both the response body and headerscurl -v https://api.chronary.ai/v1/agents/agt_nonexistent \ -H "Authorization: Bearer chr_sk_your_key_here"
# Response header: X-Request-Id: req_abc123# Response body:# {# "error": {# "type": "not_found",# "message": "Agent not found",# "param": null,# "request_id": "req_abc123"# }# }