Pipelines API
Lists the API routes that save pipeline definitions, start runs and read their status and logs.
Last updated
On this page
- The run document
- The pipeline document
- Volume checks
- POST /v1/pipelines/{owner}
- PUT /v1/pipelines/{owner}/{name}
- POST /v1/pipelines/{owner}/{name}/webhook/rotate
- POST /v1/webhooks/pipelines/{owner}/{name}/{secret}
- GET /v1/pipelines/{owner}
- GET /v1/pipelines/{owner}/{name}
- GET /v1/pipelines/{owner}/{name}/versions
- DELETE /v1/pipelines/{owner}/{name}
- DELETE /v1/pipelines/{owner}/{name}/versions/{n}
- POST /v1/pipelines/{owner}/{pipeline}/runs
- GET /v1/pipelines/{owner}/{pipeline}/runs
- GET /v1/pipelines/{owner}/{pipeline}/runs/{id}
- POST /v1/pipelines/{owner}/{pipeline}/runs/{id}/cancel
- GET /v1/pipelines/{owner}/{pipeline}/runs/{id}/jobs/{index}/logs
The run document
Every run route returns this object. An optional field with no value is null.
{ "id": "1760400000000-0a1b2c", "number": 14, "pipeline": "test", "owner": "acme", "pipelineVersion": 3, "definitionSha256": "5e2b8c0d4f6a1e3b7c9d0f2a4b6c8e0a1c3e5f7b9d1f3a5c7e9b0d2f4a6c8e0a", "event": "manual", "inputs": { "target": "staging" }, "environment": { "clone": "staging" }, "requestedBy": "karthik", "requestedAtMs": 1760400000000, "state": "running", "cancelRequested": false, "claimedBy": "worker-3f2a", "leaseExpiresMs": 1760400124000, "startedAtMs": 1760400005000, "finishedAtMs": null, "detail": null, "jobs": [ { "key": "test (postgres16)", "job": "test", "matrix": { "db": "postgres16" }, "needs": [], "state": "running", "sandbox": "sbx-5a6b7c8d9e0f1a2b", "startedAtMs": 1760400006000, "finishedAtMs": null, "exitCode": null, "failedStep": null, "detail": null, "home": { "volume": "ws-0a1b2c3d4e5f6a7b", "name": "go-base", "version": 3, "snapshot": "ws-0a1b2c3d4e5f6a7b-8a7b6c91" } } ], "after": null, "triggered": [], "definition": null}| Field | Type | Meaning |
|---|---|---|
id | string | 13 digits of creation time in milliseconds, a dash and 6 hex digits. Sorts by creation time |
number | integer | per owner and pipeline, from 1 |
pipeline | string | the definition's name |
owner | string | the person or team the run acts as |
pipelineVersion | integer | the version the run was planned from. A version cannot change, so the run always does what that version says |
definitionSha256 | string | the SHA-256 of that version's definition |
event | string | manual, schedule, webhook or pipeline |
inputs | object | the resolved inputs: the value you gave, else the default |
environment | string, object or null | as the request gave it: an environment, or {"clone": "name"}. null means the pipeline's own default |
requestedBy | string | the handle that asked, or the event's actor |
requestedAtMs | integer | milliseconds since the epoch |
state | string | queued, running, success, failure or cancelled. The last three are final |
cancelRequested | boolean | a cancel was asked; a running run stops at its next report |
claimedBy, leaseExpiresMs | string or integer, null | which worker holds the run and when its claim lapses. Bookkeeping; every report renews the claim for 120 seconds |
startedAtMs, finishedAtMs | integer or null | milliseconds since the epoch |
detail | string or null | why it stopped, when it did not succeed |
jobs | object[] | the planned jobs, fixed when the run was created |
after | object or null | for event pipeline: the pipeline and run whose end started this run, and the depth of the chain |
triggered | object[] | the runs that this run's end started: pipeline, and run or the error that stopped it |
definition | object or null | the version's definition. Only reading one run returns it, and only while the version exists. A list returns null |
A job is:
| Field | Type | Meaning |
|---|---|---|
key | string | unique within the run: test (postgres16) for one matrix instance of test |
job | string | the name in the definition. Other jobs' needs name this |
matrix | object | this instance's matrix values; {} without a matrix |
needs | string[] | the names of the jobs that must end first; a matrix job counts once for all its instances |
state | string | pending, running, success, failure, skipped or cancelled |
sandbox | string or null | the sandbox id that ran or is running it |
startedAtMs, finishedAtMs | integer or null | milliseconds since the epoch |
exitCode | integer or null | the exit code the job ended with |
failedStep | string or null | the step that failed |
detail | string or null | a sentence about why the job stopped |
home | object | absent for an empty home. What the job started from: volume (the volume id), name and version when the volume is named and the snapshot is a numbered version, and snapshot |
saved | object | what the job saved: volume, name, version and snapshot. Absent until the save is ready, and for a job that does not save |
The index in the log route is the position of a job in jobs.
The pipeline document
The definition routes return this object. A new version is saved only when the definition changes.
{ "owner": "acme", "name": "test", "version": 2, "latestVersion": 2, "createdBy": "karthik", "createdAtMs": 1760400000000, "definitionSha256": "9f2c0b7e5a41d3c8a6b05e7f1d2c4a3b8e9f0a1b2c3d4e5f60718293a4b5c6d7", "definition": { "name": "test", "jobs": { "test": { "steps": [{ "run": "make test" }] } } }, "triggers": { "schedule": [], "webhook": false }, "webhook": { "set": false }}| Field | Type | Meaning |
|---|---|---|
version, latestVersion | integer, integer | the version shown, and the newest one |
definitionSha256 | string | the hash of the canonical definition |
definition | object | the definition as saved |
triggers | object | schedule, webhook and after of the version shown |
webhook.set | boolean | whether the pipeline has a webhook secret |
webhookUrl | string | the webhook URL with its secret. Only in the answer of the save that made the secret; no later answer shows it |
Volume checks
A save and a run both check saveTo and fromSnapshot. Each refusal is one line, prefixed with job {key}: .
saveTo {name}: …: the name breaks the rule, or is reserved.saveTo is a literal volume name:saveTohas${{ }}.saveTo cannot be used with matrix.fromSnapshot and saveTo name different volumes ({a}, {b}).volume {name} does not exist and no job before it saves to it.volume {name} has no version {n}.{name} is an environment volume; a job starts only from a workspace volume.{id} is not a push: a sync point is not a start point.jobs {a} and {b} both save to {name} and neither needs the other(without the job prefix).
A fromSnapshot with ${{ }} is checked at run creation, after the inputs are filled in.
POST /v1/pipelines/{owner}
Creates a pipeline. The body is the definition, and its name is the pipeline's name. Response: 201, the pipeline document, version 1.
Only you, or an admin of the team, can write. A team member gets 403 only a team admin can change team pipelines.
Errors:
409—{owner} has 50 pipelines; delete one first;pipeline {name} exists.422—pipeline {name} is invalid:and one line per error. The checks include that every referenced secret exists, thattriggers.afternames an existing pipeline, and the volume checks.503— a secret or pipeline listing failed.
PUT /v1/pipelines/{owner}/{name}
Replaces the definition. The name in the body must equal {name}. A definition equal to the latest version saves nothing and returns the latest. Any other saves the next version. To roll back, send an old definition: it becomes a new version.
Response: 200, the pipeline document.
Errors:
404— the pipeline does not exist.409—pipeline {name} changed; retry;pipeline {name} has 100 versions; delete an old version.422— as for create, or the names differ.
When the latest version sets triggers.webhook to true and the pipeline has no secret, the save makes one and returns webhookUrl once. A version that sets triggers.webhook to false deletes the secret.
POST /v1/pipelines/{owner}/{name}/webhook/rotate
Makes a new webhook secret. The old URL stops at once. Only you, or an admin of the team, can rotate. Response: 200.
{ "webhookUrl": "https://kloudlite.io/v1/webhooks/pipelines/acme/test/Q2hhbmdlTWUtdGhpcy1pcy1hbi1leGFtcGxlLW9ubHk" }Keep the URL secret. No later answer shows it.
Errors:
403—only a team admin can change team pipelines.404— the pipeline does not exist.409—the webhook is off; turn it on in a new version.
POST /v1/webhooks/pipelines/{owner}/{name}/{secret}
Starts a run of the newest version with event webhook and every input at its default. The call needs no token: the secret in the URL is the credential. Anyone with the URL can start a run, and that run gets the pipeline's secrets.
The body is optional. A JSON body of at most 256 KiB is webhook.body in env and if. Any other body runs with no webhook.body. Query parameters are ignored.
Response: 202.
{ "id": "1760400000000-0a1b2c" }Errors:
404, with no body — the owner, the pipeline or the secret is wrong, or the webhook is off. The answer is the same for each.413— the body is larger than 256 KiB.422—pipeline {name} cannot run:and one line per error.429, with no body — the pipeline had 30 webhook calls in this minute (UTC).503—webhooks need an object store.
GET /v1/pipelines/{owner}
Lists the pipelines of an owner, sorted by name. Response: 200.
[{ "name": "test", "latestVersion": 2, "updatedAtMs": 1760400000000, "triggers": { "schedule": [], "webhook": false } }]GET /v1/pipelines/{owner}/{name}
Reads a pipeline. Query ?version=N reads version N; absent means the latest. Response: 200, the pipeline document.
Errors:
404— the pipeline does not exist, orversion {n} of {name} is gone.
GET /v1/pipelines/{owner}/{name}/versions
Lists the versions of a pipeline, oldest first. Response: 200, an array of { version, createdBy, createdAtMs, definitionSha256 }.
DELETE /v1/pipelines/{owner}/{name}
Deletes a pipeline and all its versions. Finished runs stay, with their records. Response: 204.
Errors:
409—pipeline {name} has a queued or running run.
DELETE /v1/pipelines/{owner}/{name}/versions/{n}
Deletes one version. Response: 204. A finished run of a deleted version still reads, with definition: null.
Errors:
404—version {n} of {name} is gone.409—version {n} is the latest; it cannot be deleted;version {n} is used by run {id}, for a queued or running run.
POST /v1/pipelines/{owner}/{pipeline}/runs
Starts a run by hand. The API plans the run, not a worker. It reads one stored version and checks the inputs and the secrets. It expands the jobs and records the plan before the call returns. A version that cannot be planned is refused with every reason at once and nothing is recorded. Any active member of the owner can start a run.
| Field | Type | Required | Rule |
|---|---|---|---|
version | integer | no | The version to run. Default: the newest version when the call arrives |
inputs | object | no | String values, keyed by input name. Only inputs the pipeline declares. An input with a default can be left out |
environment | string or object | no | An environment, or {"clone": "name"} for a per-run copy. Overrides the environment of every job that does not name its own. Must not be empty |
{ "version": 3, "inputs": { "target": "staging" }, "environment": { "clone": "staging" } }Unknown fields are refused, repo and ref included. A job gets code with git clone in a step, see Code in a job.
Response: 202, the run document in queued.
{ "id": "1760400000000-0a1b2c", "number": 14, "pipeline": "test", "owner": "acme", "pipelineVersion": 3, "definitionSha256": "5e2b8c0d4f6a1e3b7c9d0f2a4b6c8e0a1c3e5f7b9d1f3a5c7e9b0d2f4a6c8e0a", "event": "manual", "inputs": { "target": "staging" }, "environment": { "clone": "staging" }, "requestedBy": "karthik", "requestedAtMs": 1760400000000, "state": "queued", "cancelRequested": false, "claimedBy": null, "leaseExpiresMs": null, "startedAtMs": null, "finishedAtMs": null, "detail": null, "jobs": [ { "key": "lint", "job": "lint", "matrix": {}, "needs": [], "state": "pending", "sandbox": null, "startedAtMs": null, "finishedAtMs": null, "exitCode": null, "failedStep": null, "detail": null } ], "after": null, "triggered": [], "definition": null}Errors:
400—environment is empty.403—your access to {owner} is paused;you are no longer a member of {owner}.404— the owner is not yours; there is no such pipeline;version {n} of {pipeline} is gone.422— an unknown field; orpipeline {pipeline} cannot run:and one line per error. The lines includeunknown input(naming the declared ones),input … is required,input …: … is not one of …, andsecret {name} does not exist, and the volume checks.503— the secrets cannot be read right now.
GET /v1/pipelines/{owner}/{pipeline}/runs
Lists the runs of one pipeline, newest first.
| Query | Meaning |
|---|---|
limit | how many. Default 50, at most 200; a larger number is clamped |
Response: 200, an array of run documents.
Errors:
404— the owner is not yours, or the pipeline name is malformed. Alimitthat is not a number reads as the default.
GET /v1/pipelines/{owner}/{pipeline}/runs/{id}
Reads one run. Response: 200, the run document, with definition set to the definition of pipelineVersion while that version exists. After the version is deleted the run still reads, with definition null.
Errors:
404— the owner is not yours; the run belongs to another pipeline;{id}is not shaped like a run id; there is no such run.
POST /v1/pipelines/{owner}/{pipeline}/runs/{id}/cancel
Asks a run to stop. A queued run is cancelled at once, with its pending jobs. A running run is flagged, and its worker stops every job at its next report. Takes no body.
Response: 200, the run document; a queued run is already cancelled, a running one has cancelRequested true and is still running until the worker stops it.
Errors:
404— as for reading a run.409—this run has already finished.
GET /v1/pipelines/{owner}/{pipeline}/runs/{id}/jobs/{index}/logs
Reads the combined output of one job from a byte offset. A running job is followed by calling again with next.
| Query | Meaning |
|---|---|
offset | the byte to start from. Default 0, and so is a value that is not a number; one past the end reads from the end |
Response: 200. A page is at most 1 MiB and never ends inside a character.
{ "data": "Run lint\n$ cargo clippy\n", "next": 24, "complete": false, "state": "running" }| Field | Type | Meaning |
|---|---|---|
data | string | the output from offset |
next | integer | the offset to ask for next |
complete | boolean | true once the job has finished and everything it wrote was returned |
state | string | the job's state |
Job logs are kept for 90 days. After that a finished job's log is the single line log expired: job logs are kept for 90 days, with complete true. Before the job writes anything the log is empty, not an error.
Errors:
404— as for reading a run, or{index}is not a job of the run.502—the log could not be read.503—logs are unavailable: no object store.