API errors
Every Ciresk API answers an error as application/problem+json with a stable code. The codes every API shares mean the same in every product, and their type is https://docs.ciresk.com/errors/<code>, a section on this page. A product's own codes are documented with the product: Ciresk CI's are under Ciresk CI codes on this page, and Ciresk dev's are on its troubleshooting page, where their type points (https://docs.ciresk.com/docs/dev/troubleshooting#<code>). The other products do not document codes of their own yet; their type is https://docs.ciresk.com/errors/<code> too, and title and detail say what to do.
How an error looks
Errors are application/problem+json (RFC 9457). code is stable and is the last segment of type. title is written for the person or agent reading it; detail says what to do. Some codes carry extra fields, named in their section.
{
"type": "https://docs.ciresk.com/errors/rate_limited",
"title": "Too many requests",
"status": 429,
"code": "rate_limited",
"detail": "Slow down and retry after the indicated delay",
"retry_after": 12,
"request_id": "req_0192f3c1d2e84b7a9c0d1e2f3a4b5c6d"
}Retry a command only with the same Idempotency-Key: the stored result comes back and nothing runs twice. See Idempotency. Every response carries an X-Request-Id header, and every error repeats it as request_id. Include it when you report a problem.
Codes every API shares
These codes come from the API standard every Ciresk API follows, and the ciresk ci and ciresk dev CLIs map them to the same exit codes. Where a product or its CLI does more with a code, its section says so under the product's name. A missing resource is 404 <resource>_not_found (job_not_found, session_not_found), documented by the product that owns the resource; not-found and forbidden are the same answer for a resource the caller cannot see.
| Status | code | CLI exit |
|---|---|---|
| 400 | invalid_json, invalid_body, invalid_query, idempotency_key_required, invalid_idempotency_key, invalid_cursor | 2 |
| 401 | unauthenticated | 3 |
| 403 | forbidden, insufficient_scope | 4 |
| 413 | payload_too_large | 2 |
| 415 | unsupported_media_type | 2 |
| 421 | wrong_region | 3 |
| 422 | validation_failed, idempotency_key_reused | 2 |
| 429 | rate_limited | 5 |
| 500 | internal | 8 |
| 503 | unavailable | 8 |
invalid_json
Status 400. The body is not valid JSON. Sending it again unchanged fails the same way.
invalid_body
Status 400. The body could not be read. A JSON body of the wrong shape (a missing required field, a wrong type) is validation_failed instead.
invalid_query
Status 400. The query string does not fit the operation's parameters, for example a limit that is not a number; detail names the parameter. Sending it again unchanged fails the same way.
idempotency_key_required
Status 400. The request is a command and has no Idempotency-Key header. Add one derived from your own records and send the request again. See Idempotency.
invalid_idempotency_key
Status 400. The Idempotency-Key is empty, longer than allowed or not printable ASCII. Use 1 to 255 printable ASCII characters.
invalid_cursor
Status 400. The cursor is not one this list issued. Start the list again without a cursor and follow next_cursor.
unauthenticated
Status 401. There is no credential, or it is invalid, expired or revoked. A revoked key stops within 60 s. Sign in again with ciresk ci auth login (ciresk dev uses the same sign-in), or create a key on platform.ciresk.com under API tokens. See API keys.
Ciresk CI. A token from POST /v1/ci/auth/exchange lasts an hour: exchange a fresh OIDC token for a new one.
forbidden
Status 403. The credential is valid but cannot do this: it names an organization or a resource it does not belong to, or the resource is one only another kind of credential may touch. title or detail says which.
Ciresk dev. This API key has no dev scopes means the key was created without the product Ciresk Dev; see forbidden in Ciresk dev.
insufficient_scope
Status 403. The credential lacks the scope the endpoint needs; required_scope names it. Create a key with that scope, or ask an admin for a role that holds it. See Scopes.
Ciresk CI. An exchanged token asks for the scope in requested_scopes, within what your organization's trust rule grants.
payload_too_large
Status 413. The request body is over the endpoint's limit.
Ciresk CI. For tree sync, send the blob through a presigned upload instead of inline.
Ciresk dev. A body is at most 1 MiB, and the dev file itself at most 256 KiB.
unsupported_media_type
Status 415. The Content-Type is not one the endpoint takes. Send application/json, or the content type the endpoint names.
wrong_region
Status 421. The credential belongs to an organization pinned to another region; region in the problem names the right one. Send the request there. See Regions.
Ciresk CI. ciresk ci retries once against the indicated region on its own, and exits 3 when that fails too.
Ciresk dev. ciresk dev does not retry in the other region: it exits 3. Pass --region <region> or set CIRESK_REGION. See wrong_region in Ciresk dev.
validation_failed
Status 422. The body is well-formed but a value is wrong: out of its pattern or range, an id with the wrong prefix, a missing required field or a wrong type. detail names the field. Sending it again unchanged fails the same way.
idempotency_key_reused
Status 422. The same Idempotency-Key was sent with a different body. Use a new key for a new request; reuse a key only for a retry of the same one.
rate_limited
Status 429. Too many requests from this credential, or from this address, in the window. retry_after and the Retry-After header say how many seconds to wait; then retry, a command with the same Idempotency-Key.
Ciresk CI. Three limits answer it: tree sync requests (/v1/ci/trees/..., per principal), unauthenticated requests per address, and the long-polls one credential holds open at once; the numbers are on Limits. ciresk ci waits retry_after (at most 30 s) and sends the request again, up to three times, before it exits 5.
Ciresk dev. ciresk dev and its MCP tools send a GET, PUT or DELETE, or a POST with an Idempotency-Key, again by themselves at most twice, and only when the wait is at most 5 s; otherwise the command exits 5 and prints how long to wait. ciresk dev up, tickets, device grants and needs are never sent again on their own. See how ciresk dev retries a 429 and its rate limits.
internal
Status 500. Something failed inside the cell. Nothing in the request caused it. Retry once after a pause with the same idempotency key; then report the request_id to [email protected].
unavailable
Status 503. The API cannot reach a service it depends on right now. Retry with backoff and the same idempotency key.
Ciresk CI. The cell cannot reach its database or object store, or it is draining for an update (/ready answers 503 for a few seconds before a restart). A running task keeps running on its worker through a short outage.
Ciresk CI codes
Ciresk CI's own codes, which only its API returns. Their type is https://docs.ciresk.com/errors/<code> as well. In ciresk ci, a code this page does not list maps to exit 8 when its status is 5xx and to exit 2 otherwise.
{
"type": "https://docs.ciresk.com/errors/budget_exceeded",
"title": "Budget exceeded",
"status": 403,
"code": "budget_exceeded",
"detail": "A hard budget cap is reached",
"budget": { "estimate_units": 40, "day": { "spent_units": 20000, "jobs": 112, "hard_units": 20000, "hard_jobs": 500 }, "session": { "spent_units": 1296, "jobs": 9, "hard_units": 2000, "hard_jobs": 50 } },
"resets_at": "2026-10-21T00:00:00Z",
"request_id": "3f1c9a52-8e0b-4d7a-9c61-0b5e2f7d4a18"
}A worker sees a few more on its own protocol (leases, logs, artifacts, cache); ciresk ci never does. They are under Worker protocol codes.
enrolment_invalid
Status 401. The enrolment secret a worker presented on join is unknown, already used or expired. Create a new one with ciresk ci workers enrol; a secret is valid once.
oidc_rejected
Status 401. The OIDC token sent to POST /v1/ci/auth/exchange was refused; reason says why: malformed, unsupported_algorithm, no_key_id, missing_claim, unknown_issuer, unknown_key, bad_signature, wrong_audience (the cell expects ci.ciresk.com), expired, or unknown_repo when no trust rule matches the token's repository and ref.
policy_denied
Status 403. Your organization's policy refuses the job: a full build without allow_full_build, a kind that is neither in allowed_kinds nor a task of the manifest, or a budget_units above max_units_per_job. policy_field names the field and trust the tier the job would have run as. Run on the affected set instead, or change the policy with PUT /v1/ci/policy.
tenant_not_provisioned
Status 403. The organization behind the credential has no place in this cell yet. Finish the organization's setup on platform.ciresk.com, or contact Ciresk if the organization should already be here.
cache_read_only
Status 403. The task's trust tier may read the cache namespace but not write it. Nothing to fix on the client: an untrusted job writes to its own isolated namespace only.
budget_exceeded
Status 403. The actor's session or day hard cap is spent, or the organization's monthly budget is spent and set as a hard limit. budget carries the spend and limits and resets_at says when the window resets. Stop and report; do not retry before resets_at. next says the same and names ciresk ci budget for the spend and ciresk ci policy set for an admin who raises the cap. See Budgets.
job_not_found
Status 404. No job with that id is visible to this credential. Not-found and forbidden are the same answer across organizations. The other resources of Ciresk CI answer the same way: artifact_not_found, blob_not_found, bundle_not_found, chunk_not_found, commit_not_found, image_not_found, oidc_issuer_not_found, repo_not_found, schedule_not_found, task_not_found, tenant_not_found, tree_not_found, webhook_not_found, worker_not_found and workspace_not_found. For a sharded task detail names the valid shards.
base_ref_not_found
Status 422, at submit. The job names a base commit but carries no tree, no worker's mirror has that commit, and the cell cannot fetch the repository (a private repository is never fetched: workers hold no git credential); nothing was queued. base_digest names the commit and next the command that runs it: ciresk ci run <kind> --json from a checkout of that commit uploads the tree instead. A worker that still misses the commit after a job was admitted ends that task as an infrastructure error with this code.
job_terminal
Status 409. The job is already succeeded, failed, cancelled, timed_out or infra_error; state carries it. A cancel of a finished job changes nothing.
approval_forbidden
Status 403, on POST /v1/ci/jobs/{id}/approvals. The caller may not decide this approval gate: only a person decides one, never an agent, a csk_ key or a service; the person must have signed in within the last 10 minutes (ciresk ci auth login again) and, when the gate names approvers, be one of them. detail names which rule refused it, and the refusal is an approval.refused audit row.
separation_of_duties
Status 403, on POST /v1/ci/jobs/{id}/approvals. The person deciding requested the job, or is accountable for the agent or key that did. Someone else decides it, unless the organisation's policy sets approval_self: record, which accepts the decision and audits it as approval.sod_exception.
approval_not_found
Status 404, on POST /v1/ci/jobs/{id}/approvals. No gated task of the job matches task (a task name, or a matrix cell selector such as deploy[region=eu]). ciresk ci approvals --job <job> lists the job's gates.
approval_decided
Status 409, on POST /v1/ci/jobs/{id}/approvals. Every gate task selects is already approved or rejected; a decision is single use. ciresk ci status <job> shows what came of it.
approval_expired
Status 409, on POST /v1/ci/jobs/{id}/approvals. The gate's timeout passed before anyone decided it: the task was cancelled (approval_expired) and its dependants skipped. Run the job again to ask again.
blob_too_large
Status 413. A single blob is over max_blob_bytes; limit carries it in bytes. Add the file to .ciresk/ignore if it is build output, or shrink it; an admin raises the limit with ciresk ci policy set max_blob_bytes=<bytes>. next says the same. See Limits.
manifest_missing
Status 422. The job names a manifest_path that is not in the tree or the commit; path carries it. Run ciresk ci init to create .ciresk/ci.yaml, commit it, and run again.
manifest_invalid
Status 422. The manifest does not parse: an unknown field, a version other than 1, a name in both env and pass_env, a task that does not exist in depends_on. path and reason say where. See Manifest.
manifest_cycle
Status 422. Two or more tasks depend on each other through depends_on; reason names the cycle. Break it and run again.
resources_exceed_workers
Status 422. A task's resources ask for more memory, CPUs or scratch space than any online worker of your organization has, so no worker could ever lease it. task, dimension, asked and limit (the largest worker's capacity on that axis) say which. Lower the value in .ciresk/ci.yaml, or bring a larger worker online. See Manifest.
resources_exceed_policy
Status 422. A task's resources ask for more than your organization's policy allows one task (max_task_memory, max_task_cpus). task, dimension, asked and cap say which. Lower the value, or have an admin raise the cap with ciresk ci policy set.
blob_digest_mismatch
Status 422. The bytes that arrived for a blob do not hash to the digest they were declared under; expected and actual carry both. Hash the file again and declare it under the right digest.
webhook_limit
Status 422. The organization already has as many webhooks as it may (20); limit carries the number. List them with GET /v1/ci/webhooks, delete one you no longer need with POST /v1/ci/webhooks/{id}/delete, then create this one again; next says the same.
concurrency_limited
Status 429. The actor, the session or the tenant already runs as many jobs at once as the policy's max_concurrent_per_actor, max_concurrent_per_session or max_concurrent_per_tenant allows; scope names which (actor, session, tenant), limit and running carry the numbers and retry_after the seconds to wait. Nothing is queued: submit the job again once one finishes. ciresk ci waits retry_after and submits again, up to three times, before it exits 5. The default caps follow your fleet; see Limits.
not_implemented
Status 501. The endpoint exists in the API but this cell does not serve it yet: today the affected preview (ci.repos.affected) and syncing a repository that has a remote URL.
store_unsupported
Status 501. The request needs something this cell's object store cannot do, for example a multipart upload on a store without it. A retry fails the same way; report it with the request_id.
product_not_licensed
Status 402. The organization holds no licence for Ciresk CI; product names it. New jobs and worker registrations are refused; jobs, logs, artifacts and exports stay readable. Stop and report: an owner of the organization checks its plan on platform.ciresk.com.
licence_not_yet_valid
Status 402. The organization's licence starts later; starts_at says when. New work is accepted from then on; everything else keeps working.
licence_expired
Status 402. The organization's licence ended at expired_at. New jobs and worker registrations are refused; reads, exports and recovery keep working. Stop and report.
licence_ceiling_reached
Status 402. The licence allows no more of what was asked; ceiling is seats or workers and limit the number. A worker that registers again under its own name is not counted twice. Remove a worker with ciresk ci workers remove, or have an owner change the plan.
licence_unavailable
Status 503. The cell cannot read the organization's licence and its last answer is too old, so new work waits; reads keep working. Retry after retry_after seconds (30); if it persists, report it as an infrastructure problem.
policy_download_denied
Status 403. The organization's policy denies downloads for the job's requester, or for the caller of a tree or blob download: no raw log, artifact or signed URL leaves the cell. next names the bounded views that still work: ciresk ci summary <job>, ciresk ci tests <job> and ciresk ci logs <job> (ci__job_summary, ci__job_tests, ci__job_logs). Do not retry.
restricted_source_required
Status 422, at submit. The job needs source under a path the requester may not read, so nothing was queued. reason is hidden_prefix (a path the job needs is hidden from you) or graph_not_recorded (the cell has no package graph of this tree yet, so it cannot tell). part and owner name the part and its owner when the repository declares one, needed_by lists your targets or the job's tasks that need it, and next says what to do: depend on the part's published version, ask its owner to publish one, or ask someone who may read the source to run the tree once. The answer never names a hidden path.
restricted_write_required
Status 422. Only someone who may write a part's source publishes it, and the requester may only read it. part and owner name them when the repository declares the part, and next names whom to ask.
publish_recipe_outside_prefix
Status 422. A publish recipe is read only from <prefix>.ciresk/publish.yaml of a restricted prefix, never from .ciresk/ci.yaml, so only that prefix's writers change how its part is built. Move the recipe there.
publish_recipe_invalid
Status 422. The publish recipe does not parse, names a kind there is none of, or asks for something this cell cannot publish yet; detail says which. Fix the recipe and run again.
parts_registry_unavailable
Status 503. The parts registry is not reachable from the cell, so no version could be registered and nothing was published. Retry later.
device_login_unavailable
Status 404, on GET /v1/ci/auth/config. This cell has no browser sign-in. ciresk ci auth login says so and exits 3: create an API key on platform.ciresk.com and set CIRESK_TOKEN.
request_timeout
Status 408. The request body did not arrive within 30 seconds. Send it again, a command with the same Idempotency-Key; send bytes through a presigned upload, not in the body.
tree_incomplete
Status 422, at submit. Your organization's store lacks blobs the tree references. root names the tree, missing lists the blobs, each with its presigned upload, and truncated says whether the list was cut. ciresk ci uploads them and submits again on its own (up to five rounds); an API client uploads missing and sends the same request again.
tree_not_committed
Status 422, at submit. The job names a tree_root that is not committed in your organization's store: never uploaded, partly uploaded, or another organization's, which all get the same answer. root names it and next the fix: run ciresk ci check (or the kind you wanted) in the worktree that holds the tree, which uploads it.
tree_too_large
Status 413. The tree is over the organization's max_tree_entries or max_tree_bytes; field names which, limit the limit, and entries and bytes the tree. Add build output and other generated files to .ciresk/ignore, or have an admin raise the field with ciresk ci policy set, up to the cell's ceiling. See Limits.
tree_invalid
Status 422. An entry of the uploaded tree is malformed; path (or digest) and why say which and why. Sending it again unchanged fails the same way.
tree_missing_too_many
Status 413. One tree sync request named more digests than a request may carry; count and limit (10,000) carry both. Split the digests over several requests.
checkpoint_too_large
Status 413. The job names a tree_root that would take more than limit objects, bytes or rounds (one per directory level) from the organization's dev checkpoints in one request; field names which. next: upload the tree with ciresk ci check from a checkout of it. What the request already took is kept, so sending the same root again continues.
not_ready
Status 503, from GET /ready: the cell is up but cannot serve. detail names the probe that failed, and db, store and listener carry each probe's result. Load balancers read it; an API call in the same moment answers unavailable.
Worker protocol codes
A worker meets these on its own protocol and handles each by itself; ciresk ci never sees them.
task_not_leased
Status 409. No active lease holds the task; task_id and state say where it stands. The worker stops reporting on it.
lease_not_held
Status 409. The lease the worker named does not hold the task: another attempt does. task_id and state carry it. The worker stops reporting on it.
worker_lease_expired
Status 409. The lease expired: the worker did not renew it within the lease TTL (Limits). task_id names the task and state is expired; the cell requeues the task.
worker_offline
Status 409. The worker asked for work while offline; state carries its state. It sends a heartbeat first.
worker_deregistered
Status 409. The worker was deregistered, for example when ciresk ci workers undrain names a removed worker. It comes back only by registering with a fresh enrolment: ciresk ci workers enrol.
log_sequence_gap
Status 409. A log chunk arrived out of order; next_seq is the sequence number the cell expects. The worker resends from there.
log_sequence_lost
Status 409. The cell cannot place the worker's log chunks after many gaps; next_seq carries where it stands. The worker stops uploading this attempt's log, and the task keeps running.
log_chunk_too_large
Status 413. A log chunk is over 64 KiB, or the task's log is over the organization's max_log_bytes; limit carries the limit in bytes.
artifact_too_large
Status 413. An artifact is over the organization's max_artifact_bytes; limit carries it in bytes. Upload less, or have an admin raise the field with ciresk ci policy set.
cache_key_conflict
Status 409. The cache key already holds another digest; existing_digest names it. The entry is not overwritten.