Vetrix Docs

CI/CD

Reference for the continuous-integration endpoints under /api/v1/repos/{owner}/{repo}: pipelines, the jobs they run, the artifacts and coverage those jobs produce, and the per-branch pipeline variables that feed them.

Resource overview

A pipeline is one run of a repository's CI configuration against a single commit. Every pipeline belongs to a repository and is addressed by its UUID. Repo-scoped operations hang off /api/v1/repos/{owner}/{repo}/pipelines/...; one cross-repository convenience listing lives at /api/v1/pipelines. This page covers:

  • pipelines — list, fetch (with jobs), trigger, cancel, retry, and delete;
  • jobs — the per-stage units a pipeline runs, returned inside the pipeline detail, plus a Server-Sent Events stream of a job's logs;
  • artifacts — files a job uploads: list, fetch metadata, download the raw bytes, read the durable coverage report, and attach, list, or download SBOM attestations;
  • CI/CD variables — per-branch pipeline variables (secret values sealed at rest) and their log-disclosure flag.

The pipeline definition is read from the first file found on the search path .ci/pipeline.yml.vetrix/pipeline.ymlvetrix-ci.yml at the triggered ref.

Two related surfaces are documented elsewhere. The job-side endpoints the runner itself calls (artifact push, build-cache URLs) authenticate with a job token rather than a user credential and are not part of the user-facing surface here. Fleet administration — CI runners, runner hosts, cluster CI defaults, and CI usage — lives in admin.md. The deployments and environments feature — environments and reviewers, blue/green traffic split, deployments and their statuses, and deployment approvals — is a separate, feature-gated endpoint family covered in Deployments, environments & approvals at the end of this page.

Pipeline and job lifecycle

A pipeline's state and each job's state are drawn from the same closed set:

State Meaning
pending Created and queued; no job has started, or jobs are awaiting a runner.
running At least one job is executing.
success Every job that gates the pipeline finished successfully.
failed A gating job failed.
cancelled The pipeline was cancelled before completing.

success, failed, and cancelled are terminal. A pipeline's trigger field records how it started — push, merge, or manual (a trigger or retry through the API).

A finished job carries an exit_code (the process exit status; absent while the job is still pending or running). A job whose allow_failure flag is set stays in state failed when it fails but does not fail the pipeline — the pipeline can still roll up to success. When a job ends for a specific reason beyond the coarse state, status_reason names it (for example timed_out for a job the runner killed for overrunning its resolved timeout).

Auth & scopes

See conventions.md for the accepted credential types and how scopes are enforced. Every pipeline, job, artifact, and variable endpoint on this page, and the commit-status endpoints, require a bearer credential — there is no anonymous CI read, even on a public repository. Authorization is then per repository: reads require CI read access on the repo, and mutations require CI write access.

Operation OAuth2 scope PAT scope
Read (list/fetch pipelines, jobs, logs, artifacts, coverage, variables, attestations, commit statuses) read:pipeline ci:read
Write (trigger/cancel/retry/delete pipelines, manage variables, attach attestations, publish commit statuses) write:pipeline ci:write

Scope is enforced additively with the repository access check: the caller must hold both the scope and CI access on the repository. A read against a private repository the caller may not read returns 404 Not Found, never 403, so the repository's existence is not disclosed (see the 404-not-403 rule in errors.md). A pipeline, job, or artifact UUID that belongs to a different repository than the one named in the path is likewise an enumeration-resistant 404.

The deployments and environments family at the end of this page authorizes differently — on the caller's repository role rather than a CI scope. See its Authorization note for that model.

Endpoints

Method Path Summary
GET /api/v1/repos/{owner}/{repo}/pipelines List a repository's pipelines
GET /api/v1/repos/{owner}/{repo}/pipelines/{id} Fetch a pipeline with its jobs
POST /api/v1/repos/{owner}/{repo}/pipelines Trigger a pipeline on a ref
POST /api/v1/repos/{owner}/{repo}/pipelines/{id}/cancel Cancel a running pipeline
POST /api/v1/repos/{owner}/{repo}/pipelines/{id}/retry Retry a failed or cancelled pipeline
DELETE /api/v1/repos/{owner}/{repo}/pipelines/{id} Delete a pipeline and its data
GET /api/v1/pipelines List the caller's recent pipelines across owned repos
GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/jobs/{jid}/logs Stream a job's logs (SSE)
GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/artifacts List a pipeline's artifacts
GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/artifacts/{name} Fetch one artifact's metadata
GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/artifacts/{artifactId}/content Download an artifact's bytes
GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/coverage Read the durable coverage report
GET /api/v1/repos/{owner}/{repo}/artifacts/{id}/attestations List an artifact's attestations
POST /api/v1/repos/{owner}/{repo}/artifacts/{id}/attestations Attach an attestation to an artifact
GET /api/v1/repos/{owner}/{repo}/artifacts/{id}/attestations/{attestation_id}/payload Download an attestation's stored payload bytes
GET /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses List a commit's statuses
POST /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses Publish a commit status
GET /api/v1/repos/{owner}/{repo}/pipelines/variables List a branch's pipeline variables
POST /api/v1/repos/{owner}/{repo}/pipelines/variables Create a pipeline variable
PUT /api/v1/repos/{owner}/{repo}/pipelines/variables/{id} Rotate a variable's value
PATCH /api/v1/repos/{owner}/{repo}/pipelines/variables/{id}/disclose_in_logs Set a variable's log-disclosure flag
DELETE /api/v1/repos/{owner}/{repo}/pipelines/variables/{id} Delete a pipeline variable

Pipelines

GET /api/v1/repos/{owner}/{repo}/pipelines

List the repository's pipelines, most recent first.

Path parameters

Name Type Description
owner string Owner username.
repo string Repository name.

Query parameters

page and per_page follow the Pagination convention (page defaults to 1; per_page defaults to 25, and a value outside [1, 100] falls back to that default). This endpoint returns a bare JSON array of pipeline objects, not the { items, total, ... } page envelope — request the next page when a full page is returned. The optional filters below narrow the list:

Name Type Description
state string One of pending, running, success, failed, cancelled. A value outside this set yields an empty list.
trigger string One of push, merge, manual. A value outside this set yields an empty list.
ref string Match pipelines for a specific branch or tag ref. Matched verbatim; an unknown ref yields an empty list.

Response

[
  {
    "id": "9f1c2e7a-...",
    "repo_id": "3b8e...",
    "commit_sha": "1a2b3c4d...",
    "ref": "refs/heads/main",
    "trigger": "push",
    "state": "success",
    "created_at": "2026-02-19T09:31:00Z",
    "finished_at": "2026-02-19T09:36:12Z",
    "has_coverage": true,
    "config_path": "vetrix-ci.yml",
    "config_source_sha": "1a2b3c4d..."
  }
]

has_coverage reports whether a durable coverage record exists for the pipeline. config_path and config_source_sha record which configuration file the jobs were parsed from and the commit it was read at; both are omitted on older rows that predate the fields. finished_at is omitted while the pipeline is not terminal.

Status codes

Status When
200 OK Listing returned (possibly empty).
401 Unauthorized No valid credential.
404 Not Found Repository missing, or private and not readable.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -H "Authorization: Bearer <token>" \
  "https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines?state=failed&per_page=50"

GET /api/v1/repos/{owner}/{repo}/pipelines/{id}

Fetch one pipeline together with its constituent jobs.

Path parameters

Name Type Description
owner string Owner username.
repo string Repository name.
id string (uuid) Pipeline UUID.

Response

The pipeline fields above, plus a jobs array:

{
  "id": "9f1c2e7a-...",
  "repo_id": "3b8e...",
  "commit_sha": "1a2b3c4d...",
  "ref": "refs/heads/main",
  "trigger": "push",
  "state": "failed",
  "created_at": "2026-02-19T09:31:00Z",
  "finished_at": "2026-02-19T09:36:12Z",
  "has_coverage": false,
  "jobs": [
    {
      "id": "c0ffee00-...",
      "pipeline_id": "9f1c2e7a-...",
      "name": "test",
      "stage": "test",
      "image": "node:22-bookworm",
      "state": "failed",
      "runner_id": "runner-1",
      "exit_code": 1,
      "started_at": "2026-02-19T09:31:40Z",
      "finished_at": "2026-02-19T09:35:02Z",
      "allow_failure": false,
      "status_reason": "timed_out"
    }
  ]
}

Each job carries state, and once terminal an exit_code. allow_failure, status_reason, and resolved_timeout_seconds appear when set. A job may also carry runner-produced execution metadata when the runner records it — host_id, outer_image, cgroup_limits, network_mode, cache_hit, cache_key, and cold_start; each is omitted when the runner did not set it.

Status codes

Status When
200 OK Pipeline returned.
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
404 Not Found Pipeline or repository missing, not readable, or belonging to another repository.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-...

POST /api/v1/repos/{owner}/{repo}/pipelines

Trigger a new pipeline on a ref. Requires CI write access. The handler resolves the ref to its tip commit, reads the pipeline configuration from the search path, and schedules the jobs the ref selects.

Request body

{
  "ref": "main"
}
Field Type Required Description
ref string yes Branch name, tag name, or fully-qualified ref to schedule against.

Response

201 Created with the scheduled pipeline (the same shape as a list item).

Status codes

Status When
201 Created Pipeline scheduled.
400 Bad Request Malformed body, or ref missing.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Repository missing or not readable.
422 Unprocessable Entity No configuration found, invalid pipeline YAML, the ref cannot be resolved to a commit, or every job is gated out for the ref.
429 Too Many Requests The per-repository / per-user trigger rate limit was exceeded.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"ref":"main"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines

POST /api/v1/repos/{owner}/{repo}/pipelines/{id}/cancel

Cancel a pipeline that has not yet reached a terminal state. Requires CI write access.

Status codes

Status When
204 No Content Cancellation accepted.
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Pipeline or repository missing, not readable, or belonging to another repository.
409 Conflict The pipeline is already in a terminal state.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-.../cancel

POST /api/v1/repos/{owner}/{repo}/pipelines/{id}/retry

Re-run a finished pipeline. Only failed and cancelled pipelines are retryable. The retry re-reads the configuration at the original pipeline's commit and schedules a new pipeline against the same ref. Requires CI write access.

Response

201 Created with the new pipeline.

Status codes

Status When
201 Created A new pipeline was scheduled as a retry.
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Pipeline or repository missing or not readable.
409 Conflict The pipeline is not in a retryable (failed or cancelled) state.
422 Unprocessable Entity The configuration no longer parses, or every job is gated out for the ref.
429 Too Many Requests The per-repository / per-user trigger rate limit was exceeded.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-.../retry

DELETE /api/v1/repos/{owner}/{repo}/pipelines/{id}

Delete a pipeline run and all of its dependent data (jobs, artifacts, commit statuses, logs, and stored artifact bytes). Requires CI write access.

Status codes

Status When
204 No Content Pipeline deleted.
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Pipeline or repository missing, not readable, or belonging to another repository.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -X DELETE -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-...

GET /api/v1/pipelines

List the 50 most recent pipelines across every repository the caller owns, newest first. This convenience listing is not paginated and is scoped to the caller's own repositories. Returns a bare JSON array; each item is a pipeline object plus owner and repo_name.

Response

[
  {
    "id": "9f1c2e7a-...",
    "repo_id": "3b8e...",
    "commit_sha": "1a2b3c4d...",
    "ref": "refs/heads/main",
    "trigger": "push",
    "state": "success",
    "created_at": "2026-02-19T09:31:00Z",
    "finished_at": "2026-02-19T09:36:12Z",
    "has_coverage": false,
    "owner": "alice",
    "repo_name": "widgets"
  }
]

Status codes

Status When
200 OK Listing returned (possibly empty).
401 Unauthorized No valid credential.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/pipelines

Jobs

Jobs are returned inside the pipeline detail (see GET .../pipelines/{id} above); there is no standalone job-fetch endpoint. The one job-specific endpoint streams a job's logs.

GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/jobs/{jid}/logs

Stream a job's logs as Server-Sent Events (text/event-stream). Historical frames are replayed first, then live frames follow while the job runs; the stream ends when the job is terminal. This endpoint also accepts the browser session cookie so the in-app log console can open it with EventSource.

Path parameters

Name Type Description
owner string Owner username.
repo string Repository name.
id string (uuid) Pipeline UUID.
jid string (uuid) Job UUID; must belong to the named pipeline and repository.

Request headers

Header Description
Last-Event-ID Optional. Resume the stream after the last event the client received, so a reconnect does not replay already-seen frames.

Status codes

Status When
200 OK SSE stream opened.
400 Bad Request Malformed pipeline or job UUID.
401 Unauthorized No valid credential.
404 Not Found Pipeline, job, or repository missing, not readable, or belonging to another repository.
503 Service Unavailable The log broker or CI engine is not wired on this deployment.

Example

curl -N -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-.../jobs/c0ffee00-.../logs

Artifacts

Artifacts are files a job uploads. List and metadata reads return the artifact record; a separate content route streams the raw bytes. The coverage report is a durable summary that survives artifact-byte pruning.

GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/artifacts

List all artifacts produced by a pipeline's jobs. Returns a bare JSON array.

Response

[
  {
    "id": "a17fac70-...",
    "pipeline_id": "9f1c2e7a-...",
    "job_id": "c0ffee00-...",
    "name": "web/coverage/coverage-summary.json",
    "size_bytes": 4096,
    "sha256": "e3b0c44298fc1c14...",
    "created_at": "2026-02-19T09:35:30Z",
    "retention_expires_at": "2026-03-21T09:35:30Z",
    "paths": ["web/coverage/"],
    "download_url": "/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-.../artifacts/a17fac70-.../content"
  }
]

download_url is the relative path to the content route for the artifact's bytes, addressed by id (artifact names routinely contain /, which is not routable by name). retention_expires_at and paths are omitted on older artifacts that lack them.

Status codes

Status When
200 OK Listing returned (possibly empty).
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
404 Not Found Pipeline or repository missing, not readable, or belonging to another repository.
503 Service Unavailable The CI engine is not wired on this deployment.

GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/artifacts/{name}

Fetch one artifact's metadata by its name. {name} is a single path segment, so this route resolves artifacts whose name has no /; use download_url from the list for the bytes of any artifact.

Response

A single artifact object, the same shape as a list item.

Status codes

Status When
200 OK Artifact returned.
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
404 Not Found Artifact, pipeline, or repository missing, not readable, or belonging to another repository.
503 Service Unavailable The CI engine is not wired on this deployment.

GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/artifacts/{artifactId}/content

Stream an artifact's raw bytes, addressed by artifact id. The response Content-Type is application/json for a .json artifact and application/octet-stream otherwise.

Path parameters

Name Type Description
id string (uuid) Pipeline UUID.
artifactId string (uuid) Artifact UUID; must belong to the named pipeline and repository.

Status codes

Status When
200 OK Bytes streamed.
400 Bad Request Malformed pipeline or artifact UUID.
401 Unauthorized No valid credential.
404 Not Found Artifact or pipeline missing, not readable, belonging to another repository, or its bytes are no longer stored.
503 Service Unavailable The CI engine, or artifact-byte storage, is not wired on this deployment.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-.../artifacts/a17fac70-.../content

GET /api/v1/repos/{owner}/{repo}/pipelines/{id}/coverage

Read the durable coverage report for a pipeline. This reads the persisted coverage record (the top-line totals collected at the end of the run), so it keeps returning after the underlying coverage artifact bytes have been pruned.

Response

{
  "pipeline_id": "9f1c2e7a-...",
  "commit_sha": "1a2b3c4d...",
  "total_pct": 94.86,
  "total_covered_stmts": 8123,
  "total_stmts": 8563,
  "package_count": 42,
  "generated_at": "2026-02-19T09:35:25Z",
  "collected_at": "2026-02-19T09:35:31Z"
}

generated_at is omitted when the source summary did not record it.

Status codes

Status When
200 OK Coverage report returned.
400 Bad Request Malformed pipeline UUID.
401 Unauthorized No valid credential.
404 Not Found No coverage record for the pipeline, or the pipeline / repository is missing, not readable, or belonging to another repository.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/9f1c2e7a-.../coverage

GET /api/v1/repos/{owner}/{repo}/artifacts/{id}/attestations

List the attestations attached to an artifact. Note the base path: attestations hang off /artifacts/{id} directly, where {id} is the artifact UUID — not under /pipelines/.

The response is a bare top-level JSON array, not the { items, total, page, per_page } envelope described under Pagination. This endpoint takes no page or per_page parameter and returns every attestation on the artifact in one response; an artifact with no attestations returns []. Read the array directly — indexing into an items key yields nothing.

Response

[
  {
    "id": "a77e57ed-...",
    "artifact_id": "a17fac70-...",
    "kind": "sbom",
    "format": "cyclonedx-json",
    "predicate_type": "https://cyclonedx.org/bom",
    "payload_cid": "bafy...",
    "payload_sha256": "9f86d081884c7d65...",
    "payload_meta": { "component_count": "212" },
    "signature_required": true,
    "signer_subject": "ci@alice/widgets",
    "created_at": "2026-02-19T09:36:00Z"
  }
]

format, payload_meta, and signer_subject are omitted when not set.

Status codes

Status When
200 OK Listing returned (possibly empty).
400 Bad Request Malformed artifact UUID.
401 Unauthorized No valid credential.
403 Forbidden The repository is public or internal and the caller lacks CI read access on it.
404 Not Found Artifact or repository missing; the repository is private and not readable by the caller; or the artifact belongs to another repository.
503 Service Unavailable The attestation service is not configured on this deployment.

POST /api/v1/repos/{owner}/{repo}/artifacts/{id}/attestations

Attach an attestation to an artifact. Requires CI write access. This endpoint accepts SBOM attestations (kind is sbom).

{id} must be an artifact that is already registered against a pipeline in this repository — attach never creates one. A running job registers the artifacts it produced through the job-side endpoint POST /api/v1/jobs/{id}/artifacts, specified in ../openapi/openapi-cicd-v2.yaml; its 201 Created body is the artifact record, and the id in that record is the artifact UUID you attach against. That endpoint authenticates with the per-job token the runner is issued rather than a user credential, and the token must carry the registry:write permission and name the same job, pipeline, and repository as the row it is pushing to. The per-job token is not accepted on this attach endpoint or on any other endpoint on this page: they sit behind the user-credential gate, which rejects a job token as an invalid credential and answers 401. Attach with a user bearer credential holding CI write access on the repository.

The usual order of operations is therefore: register the artifact (job-side, job token) → attach the attestation (this endpoint, user credential) → list the artifact's attestations → download an attestation's payload bytes.

Request body

{
  "kind": "sbom",
  "format": "cyclonedx-json",
  "payload": "<base64-encoded bytes>",
  "unsigned": false
}
Field Type Required Description
kind string yes Attestation kind. Only sbom is accepted here.
format string no SBOM format; defaults to cyclonedx-json.
payload string yes Base64-encoded attestation bytes.
unsigned boolean no When true, store the attestation without requiring a signature.

Response

201 Created with the attestation record (the same shape as a list item).

Status codes

Status When
201 Created Attestation recorded.
400 Bad Request Malformed body, payload not valid base64, the SBOM payload is empty, or kind is missing.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Artifact or repository missing, not readable, or belonging to another repository.
422 Unprocessable Entity An unsupported kind, or an unsupported SBOM format. The body lists the supported set.
503 Service Unavailable The attestation service is not configured on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"kind":"sbom","payload":"'"$(base64 -w0 sbom.json)"'"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/artifacts/a17fac70-.../attestations

GET /api/v1/repos/{owner}/{repo}/artifacts/{id}/attestations/{attestation_id}/payload

Download the stored payload bytes of one attestation. Requires CI read access on the repository, the same gate the attestation listing uses. The response body is the exact byte sequence that was stored at attach time — for an SBOM attestation, the SBOM document itself, byte-identical to the base64-decoded payload that was attached. Nothing is re-serialised, re-signed, or wrapped.

Take {attestation_id} from the id of an entry in the attestation listing for the same artifact.

Path parameters

Name Type Description
id string (uuid) Artifact UUID; must belong to a pipeline in the named repository.
attestation_id string (uuid) Attestation UUID; must belong to that artifact.

Response

The raw stored bytes, served as a download rather than an inline document. Payload bytes are supplied by the caller who attached them, so the response is always forced to an attachment and is never rendered inline by a browser.

Header Value
Content-Type application/json, always — including for an attestation stored in a non-JSON SBOM format such as spdx-tag-value. Determine the real format from the format field of the attestation record, not from this header.
Content-Disposition attachment; filename="attestation-<attestation_id>.json"
X-Content-Type-Options nosniff
X-Payload-SHA256 Hex SHA-256 of the returned bytes; identical to the record's payload_sha256. Hash what you received and compare to verify the download end to end.
Content-Length Byte length of the payload.

Status codes

Status When
200 OK Payload streamed.
400 Bad Request Malformed artifact UUID or attestation UUID.
401 Unauthorized No valid credential. A per-job token is not a valid credential here.
403 Forbidden The repository is public or internal and the caller lacks CI read access on it.
404 Not Found Repository, artifact, or attestation missing; the repository is private and not readable by the caller; the artifact belongs to another repository; the attestation belongs to another artifact; or the record exists but its stored bytes are gone.
503 Service Unavailable The attestation service is not configured on this deployment, or its blob store cannot serve payload reads.

A private repository the caller may not read, a cross-repository artifact UUID, and a cross-artifact attestation UUID are all indistinguishable from a genuinely missing record: each answers 404.

Example

curl -H "Authorization: Bearer <token>" \
  -D headers.txt -o sbom.json \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/artifacts/a17fac70-.../attestations/a77e57ed-.../payload

Verify the download against the digest the server echoed:

grep -i '^x-payload-sha256:' headers.txt
sha256sum sbom.json

Commit statuses

A commit status is a per-context verdict recorded against a commit SHA — a (context, state) pair with an optional description and target URL. The merge-request UI reads them to render the per-context status pills on a commit. Statuses come from two sources: the CI engine writes one per pipeline as it runs, and an external integration can publish its own against any commit in a repository it has CI write access to. The upsert is keyed on the (repository, commit SHA, context) triple, so re-publishing the same context overwrites the prior status rather than appending a second row.

GET /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses

List the statuses recorded against a commit SHA. Requires CI read access. Returns a bare JSON array, most-recent state per context; an empty array when the commit has no statuses (the SHA is not validated against the object store, so an unknown SHA returns [] rather than 404).

Path parameters

Name Type Description
owner string Owner username.
repo string Repository name.
sha string Commit SHA the statuses were written against. Matched verbatim.

Response

[
  {
    "id": "7c0b9d21-...",
    "commit_sha": "1a2b3c4d...",
    "context": "vetrix/ci/pipeline:9f1c2e7a-...",
    "state": "success",
    "description": "All jobs passed",
    "target_url": "https://ci.example.com/runs/42",
    "updated_at": "2026-02-19T09:36:12Z",
    "soft_failed": true
  }
]
Field Type Description
id string (uuid) Status row identifier.
commit_sha string The commit the status applies to.
context string The status context (a label namespacing one source of verdict, e.g. vetrix/ci/pipeline:<uuid> for an engine-written status).
state string One of pending, running, success, failure, cancelled.
description string Optional human-readable summary. Omitted when empty.
target_url string Optional link to the producing run. Omitted when empty.
updated_at string (date-time) When the status was last written.
soft_failed boolean Derived warning flag. true only for an engine-written status whose pipeline rolled up to success because its only failing jobs were marked allow_failure; lets the UI show a soft-warning pill instead of a clean one. Omitted when false, and always false for an externally published status (it has no originating pipeline).

Status codes

Status When
200 OK Listing returned (possibly empty).
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks CI read access on it.
404 Not Found Repository missing, or private and not readable.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/commits/1a2b3c4d.../statuses

POST /api/v1/repos/{owner}/{repo}/commits/{sha}/statuses

Publish a commit status against a SHA. Requires CI write access. This is the surface an external CI system uses to render its own verdict on a commit; the status carries no originating Vetrix pipeline, so its soft_failed is always false. Re-posting the same context against the same SHA overwrites the existing status.

Path parameters

Name Type Description
owner string Owner username.
repo string Repository name.
sha string Commit SHA to record the status against. Stored verbatim.

Request body

{
  "state": "success",
  "context": "ci/external-build",
  "description": "Build #128 passed",
  "target_url": "https://ci.example.com/runs/128"
}
Field Type Required Description
state string yes One of pending, running, success, failure, cancelled.
context string yes Label that namespaces this verdict. Required — the upsert key includes it, so an empty context is rejected rather than collapsed onto a default.
description string no Human-readable summary.
target_url string no Link to the producing run.

Response

201 Created with the created (or updated) status, the same shape as a list item.

Status codes

Status When
201 Created Status published.
400 Bad Request Malformed body, state missing or not one of the allowed values, or context missing.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access. This also covers a private repository the caller cannot read at all: unlike the GET above (which 404s an unreadable private repo for enumeration resistance), the POST resolves the repository without visibility filtering and then gates on CI write, so an existing-but-inaccessible repo returns 403, not 404.
404 Not Found Repository (owner/repo) not found.
503 Service Unavailable The CI engine is not wired on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"state":"success","context":"ci/external-build","target_url":"https://ci.example.com/runs/128"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/commits/1a2b3c4d.../statuses

CI/CD variables

Pipeline variables are per-branch secret values the runner injects into a job's environment. Values are sealed at rest and are never returned by any endpoint — list, create, rotate, and flag responses carry metadata only. By default a variable's value is masked in pipeline logs; the disclose_in_logs flag opts a single variable out of that masking.

GET /api/v1/repos/{owner}/{repo}/pipelines/variables

List the variables defined for one branch. Returns metadata only. Requires CI read access.

Query parameters

Name Type Required Description
branch string yes The branch whose variables to list.

Response

[
  {
    "id": "5ec00000-...",
    "scope": "branch",
    "scope_id": "3b8e...",
    "branch_name": "main",
    "name": "DEPLOY_TOKEN",
    "created_by": "7f000000-...",
    "created_at": "2026-02-10T12:00:00Z",
    "last_used_at": "2026-02-19T09:31:40Z",
    "disclose_in_logs": false
  }
]

Status codes

Status When
200 OK Listing returned (possibly empty).
400 Bad Request branch query parameter missing.
401 Unauthorized No valid credential.
404 Not Found Repository missing or not readable.
503 Service Unavailable Branch pipeline variables are not wired on this deployment.

Example

curl -H "Authorization: Bearer <token>" \
  "https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/variables?branch=main"

POST /api/v1/repos/{owner}/{repo}/pipelines/variables

Create a variable on a branch. Requires CI write access. The value is sealed before storage and is not echoed back.

Request body

{
  "branch": "main",
  "name": "DEPLOY_TOKEN",
  "value": "s3cr3t",
  "disclose_in_logs": false
}
Field Type Required Description
branch string yes Target branch. Non-empty, at most 255 bytes.
name string yes Environment-variable identifier: a letter or underscore followed by letters, digits, or underscores.
value string yes The secret value. At most 64 KiB.
disclose_in_logs boolean no When true, the runner does not mask this value in pipeline logs. Defaults to false.

Response

201 Created with the variable's metadata:

{
  "id": "5ec00000-...",
  "branch": "main",
  "name": "DEPLOY_TOKEN",
  "created_by": "7f000000-...",
  "disclose_in_logs": false
}

Status codes

Status When
201 Created Variable created.
400 Bad Request Malformed body, missing/oversized branch, invalid name, or oversized value.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Repository missing or not readable.
503 Service Unavailable Branch pipeline variables are not wired on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"branch":"main","name":"DEPLOY_TOKEN","value":"s3cr3t"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/variables

PUT /api/v1/repos/{owner}/{repo}/pipelines/variables/{id}

Rotate a variable's value in place, keeping its branch, name, and disclose_in_logs flag. Requires CI write access.

Request body

{
  "value": "n3w-s3cr3t"
}
Field Type Required Description
value string yes The replacement value. At most 64 KiB.

Response

200 OK with the variable's metadata (same shape as the create response).

Status codes

Status When
200 OK Value rotated.
400 Bad Request Malformed body, malformed variable UUID, or oversized value.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Variable or repository missing, not readable, or belonging to another repository.
503 Service Unavailable Branch pipeline variables are not wired on this deployment.

PATCH /api/v1/repos/{owner}/{repo}/pipelines/variables/{id}/disclose_in_logs

Set a variable's log-disclosure flag without changing its value. Requires CI write access.

Request body

{
  "disclose_in_logs": true
}
Field Type Required Description
disclose_in_logs boolean yes The target flag value. When true, the runner stops masking this variable's value in pipeline logs.

Response

200 OK with the variable's metadata, reflecting the new flag.

Status codes

Status When
200 OK Flag updated.
400 Bad Request Malformed body, malformed variable UUID, or disclose_in_logs omitted.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Variable or repository missing, not readable, or belonging to another repository.
503 Service Unavailable Branch pipeline variables are not wired on this deployment.

DELETE /api/v1/repos/{owner}/{repo}/pipelines/variables/{id}

Delete a variable. Requires CI write access.

Status codes

Status When
204 No Content Variable deleted.
400 Bad Request Malformed variable UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks CI write access.
404 Not Found Variable or repository missing, not readable, or belonging to another repository.
503 Service Unavailable Branch pipeline variables are not wired on this deployment.

Example

curl -X DELETE -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/pipelines/variables/5ec00000-...

Deployments, environments & approvals

The deployments and environments feature is a separate endpoint family from the CI engine above. It models where a repository's builds are released and the controls around those releases: named environments (with optional protection rules and reviewers), deployments to an environment and their status history, reviewer approvals, and an optional blue/green traffic split per environment. Every path is repo-scoped under /api/v1/repos/{owner}/{repo}/....

Feature availability

These endpoints are served from the repository's Postgres-backed stores, which a standard server constructs at boot (the database connection is mandatory), so on a standard deployment the whole family is present. Two gating behaviors differ from the CI engine's uniform 503:

  • Environments, deployments, and approvals routes are mounted only when their backing store is wired. A deployment that does not wire them never registers the routes, so a request returns 404 Not Found (the path is unknown), not 503. The deployments routes additionally need the environments store; if a deployment wires the deployments service without the environments store, POST .../deployments answers 503 Service Unavailable (environments store not configured).
  • Blue/green routes are always mounted and answer 503 Service Unavailable (bluegreen not configured) when the blue/green service or the environments store is not wired. The three mutations (split, flip, rollback) also answer 503 when no traffic-router driver is registered for the environment's router kind — the driver set is an operator choice (nginx and shell are available; kubernetes is not).

Authorization

Unlike the pipeline endpoints above, this family does not gate on a CI OAuth2 or PAT scope. Authorization is the caller's effective repository role (see conventions.md for credential types). A valid bearer credential is required on every endpoint; the role then carries the permission the operation needs:

Operation Permission Lowest role that carries it
Read environments, deployments, approvals, blue/green state environment:read repo read
Trigger a deployment deployment:trigger repo write
Create an environment environment:create repo admin
Update / delete an environment, manage reviewers, drive blue/green environment:update / environment:delete repo admin
Add a deployment status, approve / reject a deployment deployment:approve repo admin

The repository owner and instance admins hold every permission; collaborator and team-group grants contribute their role's permissions.

Read and write paths differ in how they treat a repository the caller cannot see:

  • Reads apply the repository's three-tier visibility model. A public or internal repository is readable by any authenticated caller that holds environment:read; a private repository is readable only by a member (owner, instance admin, or a collaborator / team-group grant). A non-member's read of a private repository's environments or deployments returns 404 Not Found, never 403, so the repository's existence is not disclosed. An authenticated caller who can see a public / internal repository but lacks environment:read gets 403 Forbidden.
  • Writes resolve the repository and then check the permission directly: a missing repository is 404, and a caller that lacks the required permission is 403 Forbidden (requires <permission>).

A deployment id is global, not repo-scoped; every deployment endpoint binds the id to the {owner}/{repo} in the path through the deployment's environment. A deployment whose environment belongs to a different repository is an enumeration-resistant 404.

Endpoints

Method Path Summary
GET /api/v1/repos/{owner}/{repo}/environments List environments
POST /api/v1/repos/{owner}/{repo}/environments Create an environment
GET /api/v1/repos/{owner}/{repo}/environments/{name} Fetch one environment
PATCH /api/v1/repos/{owner}/{repo}/environments/{name} Update an environment
DELETE /api/v1/repos/{owner}/{repo}/environments/{name} Delete an environment
POST /api/v1/repos/{owner}/{repo}/environments/{name}/reviewers Add a reviewer
DELETE /api/v1/repos/{owner}/{repo}/environments/{name}/reviewers/{reviewer_id} Remove a reviewer
GET /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen Fetch blue/green state
PATCH /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen/split Set the traffic split
POST /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen/flip Flip blue/green
POST /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen/rollback Roll back the split
POST /api/v1/repos/{owner}/{repo}/deployments Create a deployment
GET /api/v1/repos/{owner}/{repo}/deployments List deployments
GET /api/v1/repos/{owner}/{repo}/deployments/{id} Fetch a deployment with its statuses
POST /api/v1/repos/{owner}/{repo}/deployments/{id}/statuses Append a deployment status
POST /api/v1/repos/{owner}/{repo}/deployments/{id}/approve Approve a deployment
POST /api/v1/repos/{owner}/{repo}/deployments/{id}/reject Reject a deployment
GET /api/v1/repos/{owner}/{repo}/deployments/{id}/approvals List a deployment's approvals

Environments

An environment is a named release target (production, staging, a per-PR preview, …) scoped to one repository. A name is unique per repository and must match [A-Za-z0-9][A-Za-z0-9._/-]{0,63}. An environment carries an optional URL template, a deployment tier, a set of protection rules (enforced when a deployment is approved), an allow-list of deploy branches, and an optional auto-stop interval for transient environments.

The environment object is:

{
  "id": "e1a2b3c4-...",
  "repo_id": "3b8e...",
  "name": "production",
  "url_template": "https://{branch}.example.com",
  "deployment_tier": "production",
  "protection_rules": {
    "required_reviewers": ["7f000000-..."],
    "required_review_count": 1,
    "wait_minutes": 5,
    "deployment_branches": ["main", "release/*"],
    "prevent_self_review": true
  },
  "deploy_branches": ["main"],
  "auto_stop_after_interval": 3600000000000,
  "transient": false,
  "created_at": "2026-02-19T09:31:00Z",
  "updated_at": "2026-02-19T09:31:00Z"
}

deployment_tier is one of production, staging, development, preview. protection_rules is always present (an empty object when no rules are set); its sub-fields are omitted when zero. Note the asymmetry on auto_stop_after_interval: a request supplies it as a duration string (5m, 2h, 1d), while the response returns it as an integer of nanoseconds (and omits it when unset). url_template is omitted when unset.

GET /api/v1/repos/{owner}/{repo}/environments

List every environment defined on the repository. Requires environment:read. Returns a bare JSON array of environment objects (not the page envelope); the list is not paginated.

Status codes

Status When
200 OK Listing returned (possibly empty).
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks environment:read.
404 Not Found Repository missing, or private and not readable.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/environments

POST /api/v1/repos/{owner}/{repo}/environments

Create an environment. Requires environment:create.

Request body

{
  "name": "production",
  "url_template": "https://{branch}.example.com",
  "deployment_tier": "production",
  "protection_rules": {
    "required_review_count": 1,
    "deployment_branches": ["main"],
    "prevent_self_review": true
  },
  "deploy_branches": ["main"],
  "auto_stop_after_interval": "2h",
  "transient": false
}
Field Type Required Description
name string yes Unique per repository; must match [A-Za-z0-9][A-Za-z0-9._/-]{0,63}.
url_template string no Display URL template for the deployed environment.
deployment_tier string no One of production, staging, development, preview.
protection_rules object no Approval / branch / wait rules; see Deployment approvals.
deploy_branches array of string no Branch allow-list for this environment.
auto_stop_after_interval string no Duration form 5m, 2h, 1d. Required when transient is true.
transient boolean no Marks a short-lived environment. Requires auto_stop_after_interval.

Response

201 Created with the environment object.

Status codes

Status When
201 Created Environment created.
400 Bad Request Malformed body.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:create.
404 Not Found Repository missing or not readable.
409 Conflict An environment with that name already exists for the repository.
422 Unprocessable Entity Invalid name, unknown deployment_tier, an unparseable auto_stop_after_interval, or transient: true without auto_stop_after_interval.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"name":"production","deployment_tier":"production"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/environments

GET /api/v1/repos/{owner}/{repo}/environments/{name}

Fetch one environment by name. Requires environment:read.

Status codes

Status When
200 OK Environment returned.
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks environment:read.
404 Not Found Environment or repository missing, or repository private and not readable.

PATCH /api/v1/repos/{owner}/{repo}/environments/{name}

Update an environment in place. Requires environment:update. Every field is optional; an omitted field is left unchanged. Sending "auto_stop_after_interval": "" clears the interval.

Request body

{
  "url_template": "https://{branch}.staging.example.com",
  "deployment_tier": "staging",
  "protection_rules": { "required_review_count": 2 },
  "deploy_branches": ["main", "release/*"],
  "auto_stop_after_interval": "1d",
  "transient": true
}
Field Type Description
url_template string Replace the URL template.
deployment_tier string Replace the tier (production / staging / development / preview).
protection_rules object Replace the protection rules.
deploy_branches array of string Replace the deploy-branch allow-list.
auto_stop_after_interval string Duration form; empty string clears it.
transient boolean Replace the transient flag.

Response

200 OK with the updated environment object.

Status codes

Status When
200 OK Environment updated.
400 Bad Request Malformed body.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:update.
404 Not Found Environment or repository missing or not readable.
422 Unprocessable Entity Unknown deployment_tier or an unparseable auto_stop_after_interval.

DELETE /api/v1/repos/{owner}/{repo}/environments/{name}

Delete an environment. Requires environment:delete.

Status codes

Status When
204 No Content Environment deleted.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:delete.
404 Not Found Environment or repository missing or not readable.

Environment reviewers

Reviewers gate a deployment to an environment whose protection rules require approval. A reviewer entry names either a single user or a group, never both. There is no list-reviewers endpoint; manage reviewers by their returned id.

The reviewer object is:

{
  "id": "5e1ec70r-...",
  "environment_id": "e1a2b3c4-...",
  "user_id": "7f000000-...",
  "prevent_self_review": true,
  "created_at": "2026-02-19T09:31:00Z"
}

Exactly one of user_id / group_id is present.

POST /api/v1/repos/{owner}/{repo}/environments/{name}/reviewers

Add a reviewer to an environment. Requires environment:update. Supply exactly one of user_id or group_id.

Request body

{
  "user_id": "7f000000-...",
  "prevent_self_review": true
}
Field Type Required Description
user_id string (uuid) one of A single reviewer user.
group_id string (uuid) one of A reviewer group; its members may approve.
prevent_self_review boolean no Whether this reviewer may approve a deployment they created. Defaults to true. The 403 self_review_blocked gate on approve / reject is driven by the environment's protection_rules.prevent_self_review.

Response

201 Created with the reviewer object.

Status codes

Status When
201 Created Reviewer added.
400 Bad Request Malformed body.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:update.
404 Not Found Environment or repository missing or not readable.
422 Unprocessable Entity Neither or both of user_id / group_id supplied.

DELETE /api/v1/repos/{owner}/{repo}/environments/{name}/reviewers/{reviewer_id}

Remove a reviewer. Requires environment:update.

Status codes

Status When
204 No Content Reviewer removed.
400 Bad Request Malformed reviewer UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:update.
404 Not Found Reviewer, environment, or repository missing or not readable.

Blue/green traffic split

Each environment may carry a blue/green traffic-split state: a traffic_split percentage routed to the green (candidate) side, a drain timeout, the blue and green deployment ids, and the router kind that fronts them. split adjusts the percentage, flip promotes the green candidate to blue (routing 100% to it, then swapping the pointers and persisting traffic_split: 0), and rollback returns the split to 0.

The blue/green state object is:

{
  "environment_id": "e1a2b3c4-...",
  "blue_deployment_id": "d1...",
  "green_deployment_id": "d2...",
  "traffic_split": 25,
  "drain_timeout_sec": 30,
  "last_flipped_at": "2026-02-19T09:40:00Z",
  "router_kind": "nginx",
  "router_config": {},
  "updated_at": "2026-02-19T09:41:00Z"
}

traffic_split is the percentage routed to green, in [0, 100]. router_kind is one of nginx, kubernetes, shell. router_config is always present and defaults to {}. blue_deployment_id, green_deployment_id, and last_flipped_at are omitted when unset. An environment that has never had blue/green configured has no state row — the read returns 404 and callers treat that as "blue/green not configured" for the environment.

GET /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen

Fetch the blue/green state for an environment. Requires environment:read.

Status codes

Status When
200 OK State returned.
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks environment:read.
404 Not Found Environment or repository missing or not readable, or no blue/green state is configured for the environment.
503 Service Unavailable Blue/green or the environments store is not wired on this deployment.

PATCH /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen/split

Set the percentage of traffic routed to the green side. Requires environment:update. traffic_split is required; drain_timeout_sec is optional and keeps the persisted value when omitted.

Request body

{
  "traffic_split": 25,
  "drain_timeout_sec": 30
}
Field Type Required Description
traffic_split integer yes Percentage routed to green, 0100.
drain_timeout_sec integer no Seconds to drain the previous side; must be >= 0. Defaults to the persisted value.

Response

200 OK with the updated blue/green state object.

Status codes

Status When
200 OK Split applied.
400 Bad Request Malformed body.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:update.
404 Not Found Environment or repository missing or not readable, or no blue/green state is configured.
422 Unprocessable Entity traffic_split missing or outside [0, 100], or a negative drain_timeout_sec.
503 Service Unavailable Blue/green or the environments store is not wired, or no driver is registered for the environment's router kind.

POST /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen/flip

Promote the green candidate to blue: route 100% to green, drain the previous side, swap the blue/green pointers, and persist traffic_split: 0 with a fresh last_flipped_at. Requires environment:update.

Response

200 OK with the updated blue/green state object.

Status codes

Status When
200 OK Flip applied.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:update.
404 Not Found Environment or repository missing or not readable, or no blue/green state is configured.
503 Service Unavailable Blue/green or the environments store is not wired, or no driver is registered for the environment's router kind.

POST /api/v1/repos/{owner}/{repo}/environments/{name}/bluegreen/rollback

Return the split to 0 (back to the blue side) using an immediate drain. The pointers are not swapped, so a subsequent flip can promote green again. Requires environment:update.

Response

200 OK with the updated blue/green state object.

Status codes

Status When
200 OK Rollback applied.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks environment:update.
404 Not Found Environment or repository missing or not readable, or no blue/green state is configured.
503 Service Unavailable Blue/green or the environments store is not wired, or no driver is registered for the environment's router kind.

Deployments

A deployment records a release of one commit to one environment, plus the ordered history of its status transitions. A deployment's latest_state is drawn from this closed set:

State Meaning
queued Created and awaiting promotion (e.g. protection checks).
pending Accepted; not yet executing.
in_progress Executing.
success Released; marked the environment's current deployment.
failure The release failed.
error The release errored (including a protection block).
inactive Superseded or stopped.

Status transitions are validated against this graph; an illegal move is 409:

queued      → pending | in_progress | failure | error | inactive
pending     → in_progress | failure | error | inactive
in_progress → success | failure | error | inactive
success     → inactive
failure     → inactive
error       → inactive
inactive    → (terminal)

A transition to success marks the deployment as the environment's current deployment and demotes any prior current row. Self-transitions are not legal.

The deployment object is:

{
  "id": "d1a2b3c4-...",
  "environment_id": "e1a2b3c4-...",
  "workflow_run_id": "00000000-0000-0000-0000-000000000000",
  "ref": "refs/heads/main",
  "sha": "1a2b3c4d...",
  "artifact_id": "a17fac70-...",
  "creator_id": "7f000000-...",
  "created_at": "2026-02-19T09:31:00Z",
  "current": false,
  "latest_state": "queued",
  "statuses": [
    {
      "id": "57a70000-...",
      "deployment_id": "d1a2b3c4-...",
      "state": "queued",
      "created_at": "2026-02-19T09:31:00Z",
      "creator_id": "7f000000-..."
    }
  ]
}

statuses carries the full transition history and is returned only by the single-deployment fetch — the list endpoint omits it. workflow_run_id is always present and is the nil UUID when the deployment was not tied to a workflow run. artifact_id is omitted when unset; each status's description, log_url, environment_url, and creator_id are omitted when unset.

POST /api/v1/repos/{owner}/{repo}/deployments

Create a deployment against an existing environment. Requires deployment:trigger. The environment named in the body must already exist. A new deployment starts in queued with a seeded queued status.

Request body

{
  "environment": "production",
  "ref": "refs/heads/main",
  "sha": "1a2b3c4d...",
  "workflow_run_id": "9f1c2e7a-...",
  "artifact_id": "a17fac70-..."
}
Field Type Required Description
environment string yes Name of an existing environment.
ref string yes The ref being deployed.
sha string yes The commit SHA being deployed.
workflow_run_id string (uuid) no Associate the deployment with a workflow run.
artifact_id string (uuid) no Associate a built artifact.

Response

201 Created with the deployment object (latest_state: "queued").

Status codes

Status When
201 Created Deployment created.
400 Bad Request Malformed body, or environment / ref / sha missing.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks deployment:trigger.
404 Not Found Repository not readable, or the named environment does not exist.
503 Service Unavailable The environments store is not wired on this deployment.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"environment":"production","ref":"refs/heads/main","sha":"1a2b3c4d"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/deployments

GET /api/v1/repos/{owner}/{repo}/deployments

List a repository's deployments. Requires environment:read. Returns a bare JSON array of deployment objects (without statuses).

Query parameters

Name Type Description
environment string Filter by environment name. An unknown name is ignored (the filter is dropped, so all deployments are returned).
state string Filter by deployment state. An unknown value is 400.
current string true returns only each environment's current deployment.
limit integer Maximum rows. Defaults to 50; a non-integer value falls back to the default.
offset integer Zero-based offset. Defaults to 0; a non-integer value falls back to the default.

This endpoint uses the limit / offset form (see conventions.md) but returns a bare array rather than a count envelope.

Status codes

Status When
200 OK Listing returned (possibly empty).
400 Bad Request Unknown state filter value.
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks environment:read.
404 Not Found Repository missing, or private and not readable.

GET /api/v1/repos/{owner}/{repo}/deployments/{id}

Fetch one deployment together with its full status history. Requires environment:read.

Status codes

Status When
200 OK Deployment returned (with statuses).
400 Bad Request Malformed deployment UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks environment:read.
404 Not Found Deployment or repository missing or not readable, or the deployment's environment belongs to another repository.

POST /api/v1/repos/{owner}/{repo}/deployments/{id}/statuses

Append a status transition to a deployment. Requires deployment:approve (this is the operator surface for driving a deployment forward; the engine writes statuses internally). The new state must be a legal transition from the deployment's current latest_state.

Request body

{
  "state": "in_progress",
  "description": "rolling out",
  "log_url": "https://logs.example.com/abc",
  "environment_url": "https://app.example.com"
}
Field Type Required Description
state string yes Target state; must be a legal transition.
description string no Human-readable note.
log_url string no Link to the release log.
environment_url string no Link to the live environment.

Response

201 Created with the created status object.

Status codes

Status When
201 Created Status appended.
400 Bad Request Malformed deployment UUID, malformed body, or an unknown state value.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks deployment:approve.
404 Not Found Deployment or repository missing or not readable.
409 Conflict The transition from the current state to state is not legal.

Deployment approvals

When an environment's protection rules require review, reviewers approve or reject a queued deployment. Each vote is upserted, so a reviewer may switch between approve and reject until the deployment is promoted or blocked. After a vote is recorded the protection rules are re-evaluated against the full vote set, which can move the deployment: a passing evaluation drives it to in_progress, and a blocking evaluation drives it to failure.

Protection rules are evaluated in priority order: any rejection blocks; a deployment branch outside the allow-list blocks; an unexpired wait timer waits; an unmet required-review count waits; otherwise the deployment is promoted.

The approve / reject response carries the recorded vote and the resulting decision. These two objects use Go field-name casing (the fields are capitalized), unlike the snake_case used elsewhere on this page:

{
  "approval": {
    "ReviewerID": "7f000000-...",
    "Action": "approved",
    "Comment": "ship it",
    "CreatedAt": "2026-02-19T09:45:00Z"
  },
  "decision": {
    "State": "promote",
    "Reason": "",
    "WaitUntil": null,
    "MissingReviewers": 0,
    "OutstandingActors": null
  }
}

decision.State is one of promote, wait, block. decision.Reason is a stable discriminator — rejected_by_reviewer, branch_not_allowed, waiting_timer, or awaiting_review — and is empty on promote. WaitUntil is set when the decision is a timer wait; MissingReviewers is set when the decision is awaiting more approvals.

POST /api/v1/repos/{owner}/{repo}/deployments/{id}/approve

Record the caller's approval of a deployment, then re-evaluate protection rules. Requires deployment:approve.

Request body

{
  "comment": "ship it"
}
Field Type Required Description
comment string no Optional note recorded with the vote.

Response

201 Created with the { "approval": ..., "decision": ... } object above.

Status codes

Status When
201 Created Vote recorded and rules re-evaluated.
400 Bad Request Malformed deployment UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller lacks deployment:approve, or self-review is blocked (self_review_blocked) because the caller created the deployment and the environment prevents self-review.
404 Not Found Deployment or repository missing or not readable, or the deployment's environment belongs to another repository.

Example

curl -X POST -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"comment":"ship it"}' \
  https://api.gitvetrix.com/api/v1/repos/alice/widgets/deployments/d1a2b3c4-.../approve

POST /api/v1/repos/{owner}/{repo}/deployments/{id}/reject

Record the caller's rejection of a deployment, then re-evaluate. A rejection blocks the deployment (driving it to failure). Requires deployment:approve. Same request body, response shape, and status codes as approve.

GET /api/v1/repos/{owner}/{repo}/deployments/{id}/approvals

List the votes recorded for a deployment, ordered oldest-first. Requires environment:read. Returns a bare JSON array of approval objects (the same Go-cased fields as the approval object above):

[
  {
    "ReviewerID": "7f000000-...",
    "Action": "approved",
    "Comment": "ship it",
    "CreatedAt": "2026-02-19T09:45:00Z"
  }
]

Status codes

Status When
200 OK Listing returned (possibly empty).
400 Bad Request Malformed deployment UUID.
401 Unauthorized No valid credential.
403 Forbidden Caller can see the repository but lacks environment:read.
404 Not Found Deployment or repository missing or not readable, or the deployment's environment belongs to another repository.

Errors

These endpoints use the shared error envelope and status-code conventions in errors.md. A 503 Service Unavailable on this page means the named feature — the CI engine, artifact-byte storage, branch pipeline variables, the attestation service, or (for the deployments and environments family) the blue/green service, its environments store, or a traffic-router driver — is staged but not wired on this deployment, not that the request was wrong; the response body names the missing component. For the deployments and environments family specifically, an unwired environments / deployments / approvals route is absent rather than 503, so its requests return 404.

Rate limits

These endpoints are metered under the standard scopes described in rate-limits.md: reads against api.read and mutations against api.write. Triggering and retrying a pipeline are additionally subject to a per-repository / per-user trigger limit that answers 429 Too Many Requests with Retry-After when exceeded.