API Error Codes

Destination currency (source-verified 2026-10-07 at f9dbf896333339ed6ed3e655d4e8e3a793cc38fe): Evidence hosts attestation, canonical-record, capsule, and signing-key reads under /api/evidence/v1; OfflineKit hosts the standard export API under /api/offlinekit/v1/exports. The owner handler suffix determines the complete path. See Evidence architecture. This documentation update does not verify a live signing, promotion, or export operation.

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

PrefixCategoryHTTP Status Range
AUTH_Authentication401
PERM_Authorization/Permission403
VAL_Validation400
RES_Resource404, 409
ENV_Environment422
REL_Release422
PROM_Promotion422
DEPLOY_Deployment422
GATE_Gate422
AGT_Agent422
INT_Integration422
WF_Workflow422
SYS_System500

Authentication Errors (401)

CodeMessageDescription
AUTH_TOKEN_MISSINGAuthentication token requiredNo token provided
AUTH_TOKEN_INVALIDInvalid authentication tokenToken cannot be parsed
AUTH_TOKEN_EXPIREDAuthentication token expiredToken has expired
AUTH_TOKEN_REVOKEDAuthentication token revokedToken has been revoked
AUTH_AGENT_CERT_INVALIDInvalid agent certificateAgent mTLS cert invalid
AUTH_AGENT_CERT_EXPIREDAgent certificate expiredAgent cert has expired
AUTH_API_KEY_INVALIDInvalid API keyAPI key not recognized

Permission Errors (403)

CodeMessageDescription
PERM_DENIEDPermission deniedGeneric permission denial
PERM_RESOURCE_DENIEDAccess to resource deniedCannot access specific resource
PERM_ACTION_DENIEDAction not permittedCannot perform specific action
PERM_SCOPE_DENIEDOutside permitted scopeAction outside user’s scope
PERM_SOD_VIOLATIONSeparation of duties violationSoD prevents action
PERM_SELF_APPROVALCannot approve own requestSelf-approval not allowed
PERM_TENANT_MISMATCHTenant mismatchResource belongs to different tenant

Validation Errors (400)

CodeMessageDescription
VAL_REQUIRED_FIELDRequired field missingField is required
VAL_INVALID_FORMATInvalid field formatField format incorrect
VAL_INVALID_VALUEInvalid field valueValue not in allowed set
VAL_TOO_LONGField value too longExceeds max length
VAL_TOO_SHORTField value too shortBelow min length
VAL_INVALID_UUIDInvalid UUID formatNot a valid UUID
VAL_INVALID_DIGESTInvalid digest formatNot a valid OCI digest
VAL_INVALID_SEMVERInvalid semver formatNot valid semantic version
VAL_INVALID_JSONInvalid JSONRequest body not valid JSON
VAL_SCHEMA_MISMATCHSchema validation failedDoesn’t match schema

Resource Errors (404, 409)

CodeMessageHTTPDescription
RES_NOT_FOUNDResource not found404Generic not found
RES_ENVIRONMENT_NOT_FOUNDEnvironment not found404Environment doesn’t exist
RES_RELEASE_NOT_FOUNDRelease not found404Release doesn’t exist
RES_PROMOTION_NOT_FOUNDPromotion not found404Promotion doesn’t exist
RES_TARGET_NOT_FOUNDTarget not found404Target doesn’t exist
RES_AGENT_NOT_FOUNDAgent not found404Agent doesn’t exist
RES_CONFLICTResource conflict409Resource state conflict
RES_ALREADY_EXISTSResource already exists409Duplicate resource
RES_VERSION_CONFLICTVersion conflict409Optimistic lock failure

Environment Errors (422)

CodeMessageDescription
ENV_FROZENEnvironment is frozenDeployment blocked by freeze window
ENV_FREEZE_ACTIVEActive freeze windowCannot modify during freeze
ENV_INVALID_ORDERInvalid environment orderOrder index conflict
ENV_CIRCULAR_PROMOTIONCircular promotion pathAuto-promote creates cycle
ENV_QUOTA_EXCEEDEDEnvironment quota exceededMax environments reached

Release Errors (422)

CodeMessageDescription
REL_ALREADY_FINALIZEDRelease already finalizedCannot modify finalized release
REL_NOT_READYRelease not readyRelease not in ready state
REL_DIGEST_MISMATCHDigest mismatchResolved digest differs
REL_TAG_NOT_FOUNDTag not found in registryCannot resolve tag
REL_COMPONENT_MISSINGComponent not foundReferenced component missing
REL_INVALID_STATUS_TRANSITIONInvalid status transitionStatus change not allowed
REL_DEPRECATEDRelease deprecatedCannot promote deprecated release

Promotion Errors (422)

CodeMessageDescription
PROM_ALREADY_EXISTSPromotion already pendingDuplicate promotion request
PROM_NOT_PENDINGPromotion not pendingCannot approve/reject
PROM_ALREADY_APPROVEDPromotion already approvedAlready approved
PROM_ALREADY_REJECTEDPromotion already rejectedAlready rejected
PROM_ALREADY_CANCELLEDPromotion already cancelledAlready cancelled
PROM_DEPLOYINGPromotion is deployingCannot cancel during deploy
PROM_INVALID_STATEInvalid promotion stateState doesn’t allow action
PROM_APPROVER_REQUIREDAdditional approvers requiredInsufficient approvals
PROM_SKIP_ENVIRONMENTCannot skip environmentsMust promote sequentially

Deployment Errors (422)

CodeMessageDescription
DEPLOY_IN_PROGRESSDeployment in progressAnother deployment running
DEPLOY_NO_TARGETSNo targets availableNo targets in environment
DEPLOY_TARGET_UNHEALTHYTarget unhealthyTarget failed health check
DEPLOY_AGENT_UNAVAILABLEAgent unavailableRequired agent offline
DEPLOY_ARTIFACT_MISSINGDeployment artifact missingRequired artifact not found
DEPLOY_TIMEOUTDeployment timeoutExceeded timeout
DEPLOY_PULL_FAILEDImage pull failedCannot pull container image
DEPLOY_DIGEST_VERIFICATION_FAILEDDigest verification failedImage tampered
DEPLOY_HEALTH_CHECK_FAILEDHealth check failedPost-deploy health failed
DEPLOY_ROLLBACK_IN_PROGRESSRollback in progressAlready rolling back
DEPLOY_NOTHING_TO_ROLLBACKNothing to rollbackNo 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):

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

target-ready — current target readiness. The check reads the environment’s current topology (never a recorded deployment). The deploy engine runs only on targets whose stored health is Healthy (ITargetRegistry.ListHealthyAsync in DeployOrchestrator.StartAsync) and needs an active agent on them, so the check separates these outcomes, each with its own reason and next step. Every detail names each relevant target’s health and its last health-check time, or states that no health check is recorded.

ClassWhenStatusAnatomy category (next step)
Emptyno target is enrolled in the environmentfailNoTargetsEnrolled (enrol a target)
Inaccessibletargets are enrolled, but none is currently Healthy (unreachable, unhealthy, degraded, or never checked)failNoHealthyTargets (restore target health)
Healthy but not readya Healthy target has no assigned agent, or its assigned agent is not activefailAgentUnassigned (assign an agent) or AgentOffline (bring the agent back online)
Readya Healthy target with an active agent whose last health check is at most 15 minutes oldpassnone; the detail also names every other Healthy target that is not ready and why (no agent assigned, assigned agent not active, agent activity not confirmed, or readiness not confirmed), because the deploy engine creates a task on every Healthy target and a task on a target without an active agent fails
Not confirmeda Healthy target with an active agent whose last health check is older than 15 minutes, absent, or later than the preflight clock; or an environment, target or agent lookup that could not be resolvedunknownnone (never a pass, never a lone block)

The 15-minute freshness window matches the topology projection’s health-check TTL over the default 5-minute readiness-scheduler cadence. Before CCP-007 the check passed on any target with an active agent, regardless of stored health, while the deploy engine then refused the launch with NoHealthyTargets.

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 reported deploying (manifestDigest) against the release’s gated digest. DeploymentLandingVerifier also accepts an independently observed running digest per target as corroboration (observedDigest), but the deployment pipeline (DeploymentEnginePipeline.ApplyLandingVerificationAsync) does not supply one today, so observedDigest is null and a verified result rests on the agent report alone. That is the accepted trust model: a matching agent report is verified without an independent observation.

The verdict carries no observation timestamp.

Console presentation (deployment detail). The Web client normalizes overallStatus and each verdict status at its boundary: only verified, mismatch and unverified pass through; any other value (another spelling, a stored value such as pending, a number, or no status) becomes unknown, and the owner value is quoted rather than interpreted. Only allVerified: true counts as the all-verified claim. The badge, explanation, evidence source and attestation link all come from one state:

StateWhenRendering
Verifiedverified, a recorded gated digest, allVerified true, at least one target, every target verified with a reported or observed digest, and a recorded result for every target the deployment record listsThe only state that says the recorded target results match the gated digest. Names the source: agent report (not independently observed) or independent observation, per target. States that no observation time is recorded. Offers the Chain-of-Custody link for the deployment attestation.
MismatchmismatchAlert; per-target DigestMismatch anatomy.
UnverifiedunverifiedCaution naming the reason: no reported or observed digest, no gated digest (the target line names whether its digest was agent-reported or independently observed), or no target results.
Unknowna value outside the vocabulary, or a verified result that fails the Verified conditions (for example, a deployment target without a recorded result)No landing conclusion; the owner value or the inconsistency is named.
Not recordedthe readable deployment record carries no landingVerificationNeutral; the result is recorded when the deployment job reaches a terminal state.
Unavailablethe stored deployment record could not be read (readError)Caution; no result is shown.

In any state other than Verified, a recorded deploymentAttestationRef stays visible as a reference, but the Chain-of-Custody link is not offered.

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/evidence/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):

The preview is a current calculation, not a recorded plan. It runs over the environment’s healthy target count at request time with the default strategy options. A deployment record’s targets were recorded when that deployment started (LibraryDeploymentRequestStarter persists the environment’s targets at creation; the terminal and in-flight mirrors update those recorded targets and never re-resolve topology). The two have different identities and times.

Console presentation (deployment detail, Flight plan tab). The tab shows two separately labeled sections:

The deployment header shows the latest progress time the record holds (its start and each recorded target start or end) and how current it is: recent, stale (a non-terminal record without recorded progress for more than 12 hours, the deployment-scope bound in the runtime contract), unknown (no usable time, or a latest progress time later than the console clock; an unset owner timestamp such as 0001-01-01 is not a recorded time) or final (terminal status). A stale or unknown record keeps its owner status; no completion or failure is inferred from elapsed time. The header’s time span is measured only from recorded times: Duration for a terminal record (recorded start to recorded end), Elapsed since start otherwise (recorded start to now). When the start or a terminal record’s end is not recorded, the end is earlier than the start, or the start is later than the console clock, the header states that instead of showing a value; the Flight plan tab likewise states that no start time is recorded. A record without any target states that it delivered nothing, whatever its status, with a handoff to target enrolment.

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, "subjects": 3, "unevaluated": 1, "unknown": 0, "unbaselined": 0 } }

deviations.count is the number of outstanding estate-finding deviations: deployed (environment, release) subjects whose latest re-evaluation is more severe than the decision that authorized them. A standing regression counts once, and a release that recovered or was replaced does not count. deviations.subjects is the number of deployed (environment, release) subjects; unevaluated (not re-evaluated yet), unbaselined (no authorizing decision with a pass or failure outcome to compare against) and unknown (the re-evaluation reached neither a pass nor a failure) count the subjects whose current risk is not established.

Outstanding deviations surface in the console through the chain-of-custody read: the Watch stage of a running digest whose release has an outstanding deviation is unverifiable (estate-deviation), and the Compliance view’s assurance queue lists those digests first; a subject whose current risk is not established reads unavailable, never clean. The Doctor check check.release.environment.matches-approved fails on count or unbaselined and warns on unevaluated, unknown or an absent classification.

The drift 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):

Gate Errors (422)

CodeMessageDescription
GATE_EVALUATION_FAILEDGate evaluation failedGate cannot be evaluated
GATE_SECURITY_BLOCKEDBlocked by security gateSecurity policy violation
GATE_POLICY_BLOCKEDBlocked by policy gateCustom policy violation
GATE_APPROVAL_BLOCKEDBlocked pending approvalAwaiting approval
GATE_TIMEOUTGate evaluation timeoutEvaluation exceeded timeout

Agent Errors (422)

CodeMessageDescription
AGT_REGISTRATION_FAILEDAgent registration failedCannot register agent
AGT_TOKEN_INVALIDInvalid registration tokenBad or expired token
AGT_TOKEN_USEDRegistration token already usedOne-time token reused
AGT_CERTIFICATE_FAILEDCertificate issuance failedCannot issue certificate
AGT_OFFLINEAgent offlineAgent not responding
AGT_CAPABILITY_MISSINGMissing capabilityAgent lacks required capability
AGT_TASK_FAILEDTask execution failedAgent task failed
AGT_HEARTBEAT_TIMEOUTHeartbeat timeoutAgent heartbeat overdue

Integration Errors (422)

CodeMessageDescription
INT_CONNECTION_FAILEDConnection failedCannot connect to integration
INT_AUTH_FAILEDAuthentication failedIntegration auth failed
INT_RATE_LIMITEDRate limitedIntegration rate limit hit
INT_TIMEOUTIntegration timeoutRequest timeout
INT_INVALID_RESPONSEInvalid responseUnexpected response format
INT_RESOURCE_NOT_FOUNDExternal resource not foundRegistry/SCM resource missing

Workflow Errors (422)

CodeMessageDescription
WF_TEMPLATE_NOT_FOUNDWorkflow template not foundTemplate doesn’t exist
WF_TEMPLATE_INVALIDInvalid workflow templateTemplate validation failed
WF_CYCLE_DETECTEDCycle detected in workflowDAG contains cycle
WF_STEP_FAILEDWorkflow step failedStep execution failed
WF_ALREADY_RUNNINGWorkflow already runningDuplicate workflow run
WF_INVALID_STATEInvalid workflow stateCannot perform action
WF_EXPRESSION_ERRORExpression evaluation errorBad expression

System Errors (500)

CodeMessageDescription
SYS_INTERNAL_ERRORInternal server errorUnexpected error
SYS_DATABASE_ERRORDatabase errorDatabase operation failed
SYS_STORAGE_ERRORStorage errorStorage operation failed
SYS_VAULT_ERRORVault errorSecret retrieval failed
SYS_QUEUE_ERRORQueue errorMessage queue failed
SYS_SERVICE_UNAVAILABLEService unavailableDependency unavailable
SYS_OVERLOADEDSystem overloadedCapacity 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"
  }
}

References