CI
Console calls for runners and runs. The pipeline file is CI runner. This page does not repeat that key table.
pipelineSnapshot on a run is the stored pipeline as a JSON object. Keys for .kataflax/ci.yaml are on CI runner.
| Caller | Header | Paths |
|---|---|---|
| Console | Authorization: Bearer <platform JWT or kfx_ PAT> |
/api/projects/:projectId/git/repos/:repoId/ci/... |
| Runner process | Authorization: Bearer kfxr_… |
/api/ci/runner/heartbeat, /claim, and /api/ci/runner/runs/:runId/... |
Runner-process routes sit outside console auth. An invalid kfxr_ token is 401 "Invalid runner token". Heartbeat, claim, log, complete, the claim payload, and .kataflax/ci.yaml are on CI runner.
Missing or non-Bearer auth on console paths is 401 { "success": false, "message": "Authentication required" }. A bad platform token is 401 "Invalid or expired token". A service token, a kfxr_ token, or any other non-platform credential on console paths is 403 "Platform authentication required".
Register a runner
Section titled “Register a runner”POST /api/projects/:projectId/git/repos/:repoId/ci/runners
Console auth. Permission repos.push. 404 "Repository not found" when that check fails.
The body must be a JSON object (400 "JSON object is required") with string name (400 "name is required"). Name length is 400 "name must be 1–100 characters".
| Key | Default | Notes |
|---|---|---|
scope |
repository |
repository, project, or organization. Anything else is 400 "scope must be repository, project, or organization". |
tags |
[] |
List of non-empty strings. 400 "tags must be a list of non-empty strings". |
runUntagged |
true |
Bool. |
protected |
false |
Bool. |
paused |
false |
Bool. |
A non-bool for runUntagged, protected, or paused is 400 "<key> must be a boolean".
Organization scope when the member is not an organization owner or admin is 403 "Only an organization owner or admin can register an organization runner".
Cap messages are A repository may have at most <limit> runners, A project may have at most <limit> project runners, and A project may have at most <limit> organization runners.
{ "name": "laptop", "scope": "repository", "tags": [], "runUntagged": true, "protected": false, "paused": false}201. token is present only on this response. prefix is the first 12 characters and starts with kfxr_. Copy token now. Later responses only show prefix and lastFour.
{ "success": true, "data": { "id": "<uuid>", "repositoryId": "<repo uuid>", "projectId": "<project uuid>", "organizationId": "<org uuid>", "scope": "repository", "tags": [], "runUntagged": true, "protected": false, "paused": false, "createdByUserId": "<user uuid>", "name": "laptop", "prefix": "kfxr_xxxxxxx", "lastFour": "abcd", "online": false, "createdAt": "<ISO-8601>", "token": "kfxr_…" }}projectId, organizationId, and createdByUserId are omitted when null. New rows set them. lastHeartbeatAt is omitted until a heartbeat exists. online is always present.
List is data.runners and omits token.
429 limit_key max_ci_runners_per_repo, with limit and current. message is the plan-catalog text. limit and current in the sample below stand in for those numbers.
{ "success": false, "message": "<plan-limit text>", "code": "quota_exceeded", "limit_key": "max_ci_runners_per_repo", "limit": 0, "current": 0}PATCH may send tags, runUntagged, protected, and paused. Sending scope is 400 "scope cannot be changed".
List runs
Section titled “List runs”GET /api/projects/:projectId/git/repos/:repoId/ci/runs
Console auth. Permission repos.read. No body. Optional query: runnerId, shas (comma-separated). The default limit is 50, or 200 when shas is set.
200
{ "success": true, "data": { "runs": [ { "id": "<uuid>", "repositoryId": "<repo uuid>", "branch": "main", "sha": "<commit sha>", "event": "branch", "status": "queued", "pipelineSnapshot": {}, "createdAt": "<ISO-8601>" } ] }}Those keys are always present. Optional keys: runnerId, mergeRequestId, startedAt, finishedAt. reason is present only when status is skipped, and the value is quota_exceeded. attempt is on jobs. The list does not include jobs or steps.
GET /api/projects/:projectId/git/repos/:repoId/ci/runs/:runId returns data.run (the same object), data.jobs, and data.steps.
Job keys always present: id, runId, name, stage, status, allowFailure, when, needs, tags, interruptible, attempt, createdAt. Optional job keys: runnerId, runnerName, timeout (from timeoutMinutes), resourceGroup, artifactKey, artifactExpiresAt, junitPassed, junitFailed, junitSkipped, startedAt, finishedAt.
Step keys: id, runId, name, kind, stepIndex, status, logText, plus optional jobId, startedAt, finishedAt.
404 "Repository not found" on list and "Run not found" on get when unauthorized or missing.
Cancel
Section titled “Cancel”POST /api/projects/:projectId/git/repos/:repoId/ci/runs/:runId/cancel
Console auth. No body. Permission repos.push.
200 { "success": true, "data": { "run": { ... } } } with status cancelled when the run was queued or running. An already cancelled run returns the same shape. Any other finished status is 409 "Run is already finished". Missing or unauthorized is 404 "Run not found".
POST /api/projects/:projectId/git/repos/:repoId/ci/runs/:runId/rerun
Console auth. No body. Permission repos.push.
201 { "success": true, "data": { "run": { ... } } }.
404 "Run not found". 400 "No CI pipeline at this commit", "Pipeline does not run on this branch", "Pipeline does not run on pull requests", "Pipeline does not run on this tag", "Pipeline does not run on this schedule", or "Pipeline has no steps for this event".
A quota block is 429 with code quota_exceeded and limit_key max_ci_runs_per_day. A billing block is 402 with code payment_required. message is the plan-catalog text. limit and current are omitted when the service does not pass them.
Manual play
Section titled “Manual play”POST /api/projects/:projectId/git/repos/:repoId/ci/runs/:runId/jobs/:jobId/play
Console auth. No body. Permission repos.push. This plays a job whose status is manual: the job moves to waiting, then queues when the stage or needs already allow it.
200
{ "success": true, "data": { "run": {}, "job": {} }}run is the run object from List runs. job is one job object (keys listed there).
404 "Job not found" when the caller lacks repos.push or the run, repo, or job does not match. 409 "Job is not waiting for manual play" when job.status is not manual.