The open-source, self-hosted alternative to Claude Managed Agents: run Claude Code, Codex, Hermes, DeepSeek Harness and Pi as managed Agents on your own infrastructure, with conversations that start and resume in seconds.
8.8 KiB
Errors
Handle the AstraBox error envelope and decide whether a failed request can be retried.
The AstraBox API returns errors in a consistent envelope. Each error response carries structured fields suitable for programmatic handling and debugging.
Error envelope
AstraBox REST resource handlers use this JSON structure:
{
"code": "SESSION_BUSY",
"message": "session is busy",
"data": null,
"error": {
"code": "SESSION_BUSY",
"status_code": 409,
"category": "state",
"retryable": true,
"owner": "session",
"user_message": "session is busy"
}
}
Every response also carries a W3C traceparent header. Unexpected server errors
include the same trace ID in data.trace_id so an operator can correlate the
response with logs.
Field descriptions
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | Yes | Stable error code for programmatic handling |
message |
string | Yes | Message safe to show to the caller |
data |
any | null | Yes | Error-specific structured data, when available |
error.code |
string | Yes | Same stable error code as the top-level code |
error.status_code |
integer | Yes | Status registered for the error code; use the actual HTTP response status for transport handling |
error.category |
string | Yes | Error category, such as request, auth, state, or persistence |
error.retryable |
boolean | Yes | Whether retrying after a delay can succeed without changing the request |
error.owner |
string | Yes | Component or party that must act: client, session, mongo, runtime, template, platform, or unknown |
error.user_message |
string | Yes | Message safe to show to the caller |
error.debug_message |
string | No | Additional diagnostic message when the error provides one |
error.evidence |
object | No | Structured evidence for diagnosis |
error.cause_code |
string | No | Lower-level cause code when one is available |
Error types
Error codes describe the specific failure; HTTP status describes how the request completed. Common status groups are:
| HTTP status | Example code |
Description |
|---|---|---|
| 400 or 422 | INVALID_REQUEST |
Invalid or missing request parameters |
| 401 | AUTH_REQUIRED, UNAUTHORIZED, TOKEN_EXPIRED |
Authentication failed or is required |
| 403 | FORBIDDEN, API_TOKEN_SCOPE_INSUFFICIENT |
Authenticated but not authorized for the operation |
| 404 | NOT_FOUND, SESSION_NOT_FOUND |
Target resource is missing or inaccessible |
| 409 | SESSION_BUSY, IDEMPOTENCY_KEY_CONFLICT |
Resource state conflicts with the operation |
| 429 | ADMISSION_DENIED |
Deployment admission policy refused the work |
| 499 | REQUEST_CANCELLED |
The caller cancelled the request |
| 5xx | PERSISTENCE_UNAVAILABLE, UNEXPECTED_SERVER_ERROR |
AstraBox, infrastructure, or an upstream dependency failed |
The table contains examples, not a complete error-code catalog. Use the
deployed release's /openapi.json and the fields returned by the failing route.
Error type details
400 or 422 — INVALID_REQUEST
The request format or parameters are invalid.
Common triggers:
- Missing required field (for example,
permission_mode) - Field type mismatch (such as a number where a string is expected)
- Parameter outside the accepted range
- Malformed JSON
{
"code": "INVALID_REQUEST",
"message": "permission_mode is required",
"data": null,
"error": {
"code": "INVALID_REQUEST",
"status_code": 400,
"category": "request",
"retryable": false,
"owner": "client",
"user_message": "permission_mode is required"
}
}
# Example trigger: missing the permission_mode field
curl --fail-with-body --silent --show-error \
-X POST "$SERVICE_URL/api/v1/sessions/$SESSION_ID/permission-mode" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
401 — authentication errors
Authentication failed.
Common triggers:
- Missing credential on a protected route
- Malformed or invalid bearer token
- Expired or revoked Access Token
- Credential issued by an untrusted issuer
{
"code": "TOKEN_EXPIRED",
"message": "bearer token expired; obtain a new one from your token issuer",
"data": null,
"error": {
"code": "TOKEN_EXPIRED",
"status_code": 401,
"category": "auth",
"retryable": false,
"owner": "client",
"user_message": "bearer token expired; obtain a new one from your token issuer"
}
}
# Example trigger: invalid token
curl --fail-with-body --silent --show-error \
"$SERVICE_URL/api/v1/agents" \
-H "Authorization: Bearer invalid-token"
403 — authorization errors
The caller is authenticated but not authorized.
Common triggers:
- The identity cannot manage the target Agent
- The Access Token does not include the scope required for the operation
- A non-administrator calls an administration route
{
"code": "API_TOKEN_SCOPE_INSUFFICIENT",
"message": "API token requires scope astrabox:admin",
"data": { "required_scope": "astrabox:admin" },
"error": {
"code": "API_TOKEN_SCOPE_INSUFFICIENT",
"status_code": 403,
"category": "auth",
"retryable": false,
"owner": "client",
"user_message": "API token requires scope astrabox:admin"
}
}
# Example trigger: a read-only token calls an administration API
curl --fail-with-body --silent --show-error \
"$SERVICE_URL/api/v1/admin/environments" \
-H "Authorization: Bearer $READ_TOKEN"
404 — not-found errors
The target resource does not exist or is not visible to the caller.
Common triggers:
- Agent, Session, or Environment ID does not exist
- The resource was deleted
- The caller is not authorized to discover another owner's resource
- URL path typo
{
"code": "SESSION_NOT_FOUND",
"message": "session not found",
"data": null,
"error": {
"code": "SESSION_NOT_FOUND",
"status_code": 404,
"category": "request",
"retryable": false,
"owner": "session",
"user_message": "session not found"
}
}
# Example trigger: nonexistent Session
curl --fail-with-body --silent --show-error \
"$SERVICE_URL/api/v1/sessions/session_nonexistent_123" \
-H "Authorization: Bearer $ACCESS_TOKEN"
409 — conflict errors
Resource state conflict prevents the operation.
Common triggers:
- Same idempotency key reused for a different conversation
- Session is already processing another turn
- Agent or Environment state does not allow the requested operation
{
"code": "SESSION_BUSY",
"message": "session is busy",
"data": null,
"error": {
"code": "SESSION_BUSY",
"status_code": 409,
"category": "state",
"retryable": true,
"owner": "session",
"user_message": "session is busy"
}
}
5xx — server and dependency errors
AstraBox, its infrastructure, or an upstream dependency failed.
Common triggers:
- Database unavailable
- Sandbox or Agent program failed to start
- Identity provider or model gateway unavailable
- Unexpected internal failure
{
"code": "PERSISTENCE_UNAVAILABLE",
"message": "mongodb timeout/unavailable, please retry",
"data": null,
"error": {
"code": "PERSISTENCE_UNAVAILABLE",
"status_code": 503,
"category": "persistence",
"retryable": true,
"owner": "mongo",
"user_message": "mongodb timeout/unavailable, please retry"
}
}
Retry when error.retryable is true, or when a 429 response provides
data.retry_after_seconds. Use bounded exponential backoff and wait at least
the provided number of seconds.
Error handling best practices
- Branch on
codeanderror.retryable, not only on the HTTP status. - Log the
traceparentresponse header,code, andmessagefor diagnostics. - Inspect
data,error.evidence, anderror.cause_codewhen present. - Do not retry when
error.retryableisfalseunless a429response providesdata.retry_after_seconds. - Use bounded exponential backoff for retryable responses.
# Request with error handling
headers=$(mktemp)
trap 'rm -f "$headers"' EXIT
response=$(curl --silent --show-error -D "$headers" -w "\n%{http_code}" \
"$SERVICE_URL/api/v1/agents" \
-H "Authorization: Bearer $ACCESS_TOKEN")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" -ge 400 ]; then
error_code=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin)['code'])")
retryable=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin)['error']['retryable'])")
traceparent=$(sed -n 's/^[Tt]raceparent: //p' "$headers" | tr -d '\r')
echo "API error: $error_code retryable=$retryable traceparent=$traceparent"
fi
Next steps
- Overview — how AstraBox fits together.