API Error Codes
Overview
All API errors follow a consistent format with error codes for programmatic handling.
Error Response Format
interface ApiErrorResponse {
success: false;
error: {
code: string; // Machine-readable error code
message: string; // Human-readable message
details?: object; // Additional context
validationErrors?: ValidationError[];
};
meta: {
requestId: string;
timestamp: string;
};
}
interface ValidationError {
field: string;
message: string;
code: string;
}
Error Code Categories
| Prefix | Category | HTTP Status Range |
|---|---|---|
AUTH_ | Authentication | 401 |
PERM_ | Authorization/Permission | 403 |
VAL_ | Validation | 400 |
RES_ | Resource | 404, 409 |
ENV_ | Environment | 422 |
REL_ | Release | 422 |
PROM_ | Promotion | 422 |
DEPLOY_ | Deployment | 422 |
GATE_ | Gate | 422 |
AGT_ | Agent | 422 |
INT_ | Integration | 422 |
WF_ | Workflow | 422 |
SYS_ | System | 500 |
Authentication Errors (401)
| Code | Message | Description |
|---|---|---|
AUTH_TOKEN_MISSING | Authentication token required | No token provided |
AUTH_TOKEN_INVALID | Invalid authentication token | Token cannot be parsed |
AUTH_TOKEN_EXPIRED | Authentication token expired | Token has expired |
AUTH_TOKEN_REVOKED | Authentication token revoked | Token has been revoked |
AUTH_AGENT_CERT_INVALID | Invalid agent certificate | Agent mTLS cert invalid |
AUTH_AGENT_CERT_EXPIRED | Agent certificate expired | Agent cert has expired |
AUTH_API_KEY_INVALID | Invalid API key | API key not recognized |
Permission Errors (403)
| Code | Message | Description |
|---|---|---|
PERM_DENIED | Permission denied | Generic permission denial |
PERM_RESOURCE_DENIED | Access to resource denied | Cannot access specific resource |
PERM_ACTION_DENIED | Action not permitted | Cannot perform specific action |
PERM_SCOPE_DENIED | Outside permitted scope | Action outside user’s scope |
PERM_SOD_VIOLATION | Separation of duties violation | SoD prevents action |
PERM_SELF_APPROVAL | Cannot approve own request | Self-approval not allowed |
PERM_TENANT_MISMATCH | Tenant mismatch | Resource belongs to different tenant |
Validation Errors (400)
| Code | Message | Description |
|---|---|---|
VAL_REQUIRED_FIELD | Required field missing | Field is required |
VAL_INVALID_FORMAT | Invalid field format | Field format incorrect |
VAL_INVALID_VALUE | Invalid field value | Value not in allowed set |
VAL_TOO_LONG | Field value too long | Exceeds max length |
VAL_TOO_SHORT | Field value too short | Below min length |
VAL_INVALID_UUID | Invalid UUID format | Not a valid UUID |
VAL_INVALID_DIGEST | Invalid digest format | Not a valid OCI digest |
VAL_INVALID_SEMVER | Invalid semver format | Not valid semantic version |
VAL_INVALID_JSON | Invalid JSON | Request body not valid JSON |
VAL_SCHEMA_MISMATCH | Schema validation failed | Doesn’t match schema |
Resource Errors (404, 409)
| Code | Message | HTTP | Description |
|---|---|---|---|
RES_NOT_FOUND | Resource not found | 404 | Generic not found |
RES_ENVIRONMENT_NOT_FOUND | Environment not found | 404 | Environment doesn’t exist |
RES_RELEASE_NOT_FOUND | Release not found | 404 | Release doesn’t exist |
RES_PROMOTION_NOT_FOUND | Promotion not found | 404 | Promotion doesn’t exist |
RES_TARGET_NOT_FOUND | Target not found | 404 | Target doesn’t exist |
RES_AGENT_NOT_FOUND | Agent not found | 404 | Agent doesn’t exist |
RES_CONFLICT | Resource conflict | 409 | Resource state conflict |
RES_ALREADY_EXISTS | Resource already exists | 409 | Duplicate resource |
RES_VERSION_CONFLICT | Version conflict | 409 | Optimistic lock failure |
Environment Errors (422)
| Code | Message | Description |
|---|---|---|
ENV_FROZEN | Environment is frozen | Deployment blocked by freeze window |
ENV_FREEZE_ACTIVE | Active freeze window | Cannot modify during freeze |
ENV_INVALID_ORDER | Invalid environment order | Order index conflict |
ENV_CIRCULAR_PROMOTION | Circular promotion path | Auto-promote creates cycle |
ENV_QUOTA_EXCEEDED | Environment quota exceeded | Max environments reached |
Release Errors (422)
| Code | Message | Description |
|---|---|---|
REL_ALREADY_FINALIZED | Release already finalized | Cannot modify finalized release |
REL_NOT_READY | Release not ready | Release not in ready state |
REL_DIGEST_MISMATCH | Digest mismatch | Resolved digest differs |
REL_TAG_NOT_FOUND | Tag not found in registry | Cannot resolve tag |
REL_COMPONENT_MISSING | Component not found | Referenced component missing |
REL_INVALID_STATUS_TRANSITION | Invalid status transition | Status change not allowed |
REL_DEPRECATED | Release deprecated | Cannot promote deprecated release |
Promotion Errors (422)
| Code | Message | Description |
|---|---|---|
PROM_ALREADY_EXISTS | Promotion already pending | Duplicate promotion request |
PROM_NOT_PENDING | Promotion not pending | Cannot approve/reject |
PROM_ALREADY_APPROVED | Promotion already approved | Already approved |
PROM_ALREADY_REJECTED | Promotion already rejected | Already rejected |
PROM_ALREADY_CANCELLED | Promotion already cancelled | Already cancelled |
PROM_DEPLOYING | Promotion is deploying | Cannot cancel during deploy |
PROM_INVALID_STATE | Invalid promotion state | State doesn’t allow action |
PROM_APPROVER_REQUIRED | Additional approvers required | Insufficient approvals |
PROM_SKIP_ENVIRONMENT | Cannot skip environments | Must promote sequentially |
Deployment Errors (422)
| Code | Message | Description |
|---|---|---|
DEPLOY_IN_PROGRESS | Deployment in progress | Another deployment running |
DEPLOY_NO_TARGETS | No targets available | No targets in environment |
DEPLOY_TARGET_UNHEALTHY | Target unhealthy | Target failed health check |
DEPLOY_AGENT_UNAVAILABLE | Agent unavailable | Required agent offline |
DEPLOY_ARTIFACT_MISSING | Deployment artifact missing | Required artifact not found |
DEPLOY_TIMEOUT | Deployment timeout | Exceeded timeout |
DEPLOY_PULL_FAILED | Image pull failed | Cannot pull container image |
DEPLOY_DIGEST_VERIFICATION_FAILED | Digest verification failed | Image tampered |
DEPLOY_HEALTH_CHECK_FAILED | Health check failed | Post-deploy health failed |
DEPLOY_ROLLBACK_IN_PROGRESS | Rollback in progress | Already rolling back |
DEPLOY_NOTHING_TO_ROLLBACK | Nothing to rollback | No previous deployment |
Deployment Failure Anatomy (execution failures)
The error codes above are pre-deploy 4xx API responses. Once a deploy passes gating and a deployment job runs, an execution failure is described by a structured failure anatomy attached to the job and to each failed target — not a raw exception string. It is surfaced on the deployment DTO (GET /api/v1/release-orchestrator/deployments/{id}) both job-level (failureAnatomy) and per-target (targets[].failureAnatomy), and rendered in the console deployment monitor.
interface DeploymentFailureAnatomy {
stage: string; // where it failed: artifact-generation | bundle-assembly | strategy-planning
// | traffic-gate | preflight | execution | health-probe | retry-prep | rollback
category: string; // typed cause (see below)
reason: string; // stable machine-readable slug
plainReason: string; // operator-facing one-line cause (the lead)
detail: string; // the raw technical string (exception "TypeName: message"), preserved verbatim
action: string; // plain-language remediation guidance
actionPath: string | null; // a real endpoint to act on, or null (never a dead link)
retryable: boolean; // true ONLY for genuinely transient causes
}
Categories: SecretResolution, RegistryUnreachable, AgentUnassigned, AgentOffline, Timeout, TargetNotFound, UnsupportedTarget, NoHealthyTargets, DependencyGraph, PreflightFailed, HealthCheckFailed, NoTargetsEnrolled, StrategyPlanIncomplete, StrategyGateHalted, RollbackNotEligible, Unclassified.
Honesty invariants (binding):
- No fabricated cause — an unrecognized exception/reason maps to
Unclassified(generic action), never a mislabeled specific category. - Detail is never lost — the raw exception type + message is preserved verbatim in
detail; the plain reason leads, the detail is demoted-but-reachable. The legacyfailureReason(job) /error(task) strings stay populated with the plain reason for back-compat. retryableis earned —trueonly forTimeout,RegistryUnreachable,AgentOffline; every content failure (preflight, health-check, secret, unsupported target, …) andUnclassifiedisfalse.- Absent anatomy (jobs created before this contract) → the DTO fields are
nulland the console falls back to the raw string (no fabricated stage/category).
Deployment Preflight (pre-launch readiness)
Before a deploy launches, a preflight runs a tri-state readiness checklist (GET /api/v1/release-orchestrator/releases/{releaseId}/deploy-preflight?environmentId=), and the launch chokepoint enforces it after the un-waivable security floor.
interface PreflightReport {
releaseId: string; environment: string | null;
armed: boolean; // true iff zero hard fails (unknowns do NOT dis-arm)
hasHardFail: boolean;
failedCheckKeys: string[];
checks: PreflightCheck[];
}
interface PreflightCheck {
key: string; // digest-pinned | image-resolvable | target-ready | secret-resolvable
title: string;
status: 'pass' | 'fail' | 'unknown';
detail: string;
anatomy: DeploymentFailureAnatomy | null; // present on a fail (reuses the D1 anatomy shape)
}
Honesty invariants (binding):
- Tri-state, never a false pass — a check that cannot determine its answer (registry unreachable, probe threw, GUID-keyed context unavailable) returns
unknown, neverpass.unknownis surfaced but never a lone hard block; only a definitivefailblocks the launch. - Overridable, but the floor is not — a hard
failblocks the launch (preflight_failed) unless a non-emptypreflightOverrideJustificationis supplied, which records apreflight_overrideevidence packet. The security floor runs first and is never waived by a preflight override. - Override = evidence (fail-closed) — if the
preflight_overrideevidence write fails, the deploy is blocked (preflight_override_unaudited, HTTP 503, retryable) rather than proceeding as a silent unaudited bypass.
Landing Verification & Deployment Attestation (post-deploy)
After a deploy completes, the pipeline verifies the landing and — only on a fully-verified landing — emits a deployment attestation. Both surface on the deployment DTO (landingVerification job-level + targets[].landing per-target; deploymentAttestationRef when attested) and light the console custody Deploy step.
Landing verdict (per target, honest tri-state): compares the digest the agent actually reported deploying (manifestDigest, corroborated by the EAD observed-running-digest) against the release’s gated digest.
verified— reported == gated (deploy-by-digest proven for that target).mismatch— reported != gated (a loud drift/tamper finding, carrying aDigestMismatchfailure anatomy; non-retryable). Produces no attestation.unverified— the agent reported no digest (unobserved) — never a fakeverified. Produces no attestation.
Deployment attestation (binding honesty): a deployment-kind in-toto attestation (predicate https://stellaops.dev/predicates/deployment@v1) is signed and anchored in the transparency log only when the job is Completed AND every target landed verified. The honesty gate returns before touching the signer/submitter otherwise. It lights custody Deploy = SIGNED (recorded) → PROVEN (verified via POST /api/v1/rekor/verify, the same path the Scan step uses). Absent a real attestation, Deploy stays RECORDED/MISSING — never a fabricated PROVEN. Signing/submission failure is fail-open (the deploy already succeeded): the landing verdict is still recorded, just without an attestation ref.
Deployment Strategy Plan (honesty preview)
A deployment strategy (rolling / all_at_once / blue_green / canary) is a target-batching planner. GET /api/v1/release-orchestrator/releases/{releaseId}/deploy-plan?strategy=&environmentId= previews what the requested strategy will actually do for a release’s real target count — it runs the strategy’s real plan and returns the phases verbatim plus honest caveats, so a requested strategy never implies behaviour it will not deliver.
{ "strategy": "canary", "requestedStrategy": "canary", "targetCount": 1,
"phases": [ { "index": 0, "targetCount": 1, "kind": "all-at-once" } ],
"effectiveBehaviour": "With 1 target, 'canary' runs as a single all-at-once deploy …",
"caveats": [ "single-target-collapse", "no-traffic-management" ] }
Honesty invariants (binding):
- Phases equal the real plan — copied verbatim from the strategy’s
PlanAsync; no idealized/fabricated phases. single-target-collapse— with a single target, canary/blue-green/rolling all reduce to one batch of one; the effective behaviour is reported as a single all-at-once deploy and the phase is labeledall-at-once, never a phased-canary illustration.no-traffic-management— canary/blue-green do phased target rollout, not traffic-splitting; traffic cutover is external/not automated by the adapter. The preview says so; the UI never implies an automated traffic canary/cutover.- cannot-preview — an unknown/unresolvable target count returns
{ "cannotPreview": true, "reason": … }, never a fabricated plan.
Watch / Drift (post-landing)
After landing, the deploy is watched: deploy agents report the container image digests they OBSERVE running, and the orchestrator compares them against the release’s approved/deployed digests. Tenant-scoped status, drift, and digest-location reads (GET /api/v1/release-orchestrator/estate/status|drift and GET /api/v1/release-orchestrator/estate/digest/{digest}) accept either release:read or orch:read; deviations and the other Estate reads remain orch:read-only.
// GET /estate/status
{ "deployedComponents": 8,
"drift": { "total": 1, "drift": 1, "inSync": 0, "unobserved": 0, "observingAgents": 1 },
"deviations": { "count": 0 } }
The signal surfaces in the console as a needs-you “components in drift” item, an estate-matrix drift chip, and the custody Run-stage rail.
Honesty invariants (binding):
unobservedis neverinSync— a component no agent is reporting is uncovered/unknown, never rendered green. The Run rail shows honest observing-agent coverage; it never claims “healthy” for an unobserved estate.- Drift is a real deviation, shown loudly — a running digest that is not an approved/deployed digest (
drift) is an alarm (unapproved or altered image), never suppressed. - No fabricated all-clear —
observingAgents == 0→ honest no-signal (not a fake green); a read failure → “couldn’t check”, never a fabricated in-sync or fabricated drift.
Gate Errors (422)
| Code | Message | Description |
|---|---|---|
GATE_EVALUATION_FAILED | Gate evaluation failed | Gate cannot be evaluated |
GATE_SECURITY_BLOCKED | Blocked by security gate | Security policy violation |
GATE_POLICY_BLOCKED | Blocked by policy gate | Custom policy violation |
GATE_APPROVAL_BLOCKED | Blocked pending approval | Awaiting approval |
GATE_TIMEOUT | Gate evaluation timeout | Evaluation exceeded timeout |
Agent Errors (422)
| Code | Message | Description |
|---|---|---|
AGT_REGISTRATION_FAILED | Agent registration failed | Cannot register agent |
AGT_TOKEN_INVALID | Invalid registration token | Bad or expired token |
AGT_TOKEN_USED | Registration token already used | One-time token reused |
AGT_CERTIFICATE_FAILED | Certificate issuance failed | Cannot issue certificate |
AGT_OFFLINE | Agent offline | Agent not responding |
AGT_CAPABILITY_MISSING | Missing capability | Agent lacks required capability |
AGT_TASK_FAILED | Task execution failed | Agent task failed |
AGT_HEARTBEAT_TIMEOUT | Heartbeat timeout | Agent heartbeat overdue |
Integration Errors (422)
| Code | Message | Description |
|---|---|---|
INT_CONNECTION_FAILED | Connection failed | Cannot connect to integration |
INT_AUTH_FAILED | Authentication failed | Integration auth failed |
INT_RATE_LIMITED | Rate limited | Integration rate limit hit |
INT_TIMEOUT | Integration timeout | Request timeout |
INT_INVALID_RESPONSE | Invalid response | Unexpected response format |
INT_RESOURCE_NOT_FOUND | External resource not found | Registry/SCM resource missing |
Workflow Errors (422)
| Code | Message | Description |
|---|---|---|
WF_TEMPLATE_NOT_FOUND | Workflow template not found | Template doesn’t exist |
WF_TEMPLATE_INVALID | Invalid workflow template | Template validation failed |
WF_CYCLE_DETECTED | Cycle detected in workflow | DAG contains cycle |
WF_STEP_FAILED | Workflow step failed | Step execution failed |
WF_ALREADY_RUNNING | Workflow already running | Duplicate workflow run |
WF_INVALID_STATE | Invalid workflow state | Cannot perform action |
WF_EXPRESSION_ERROR | Expression evaluation error | Bad expression |
System Errors (500)
| Code | Message | Description |
|---|---|---|
SYS_INTERNAL_ERROR | Internal server error | Unexpected error |
SYS_DATABASE_ERROR | Database error | Database operation failed |
SYS_STORAGE_ERROR | Storage error | Storage operation failed |
SYS_VAULT_ERROR | Vault error | Secret retrieval failed |
SYS_QUEUE_ERROR | Queue error | Message queue failed |
SYS_SERVICE_UNAVAILABLE | Service unavailable | Dependency unavailable |
SYS_OVERLOADED | System overloaded | Capacity exceeded |
Example Error Responses
Validation Error
{
"success": false,
"error": {
"code": "VAL_REQUIRED_FIELD",
"message": "Validation failed",
"validationErrors": [
{
"field": "releaseId",
"message": "Release ID is required",
"code": "VAL_REQUIRED_FIELD"
},
{
"field": "targetEnvironmentId",
"message": "Invalid UUID format",
"code": "VAL_INVALID_UUID"
}
]
},
"meta": {
"requestId": "req-12345",
"timestamp": "2026-01-10T14:30:00Z"
}
}
Permission Error
{
"success": false,
"error": {
"code": "PERM_SOD_VIOLATION",
"message": "Separation of duties violation: requester cannot approve their own promotion",
"details": {
"promotionId": "promo-uuid",
"requesterId": "user-uuid",
"approverId": "user-uuid",
"environmentId": "env-uuid",
"requiresSoD": true
}
},
"meta": {
"requestId": "req-12345",
"timestamp": "2026-01-10T14:30:00Z"
}
}
Gate Block Error
{
"success": false,
"error": {
"code": "GATE_SECURITY_BLOCKED",
"message": "Promotion blocked by security gate",
"details": {
"gateName": "security-gate",
"releaseId": "rel-uuid",
"targetEnvironment": "production",
"violations": [
{
"type": "critical_vulnerability",
"count": 3,
"threshold": 0
}
]
}
},
"meta": {
"requestId": "req-12345",
"timestamp": "2026-01-10T14:30:00Z"
}
}
