Version

CI/CD ingestion

Mint a token that only records deployments, report a deployment in one call after the fact or as it starts and ends, keep retries harmless with the run id as the idempotency key, and let GitHub deployments report themselves.

By the end of this page your pipeline will report every deployment it makes: the version, the environment, the commit and the branch, as it starts and as it ends, with a credential that can do nothing else. What the record looks like from inside the application is on CI and deployments.

Mint an ingestion token

Open Account Settings → API Tokens and press Create Token. Name it after the pipeline, confine it to the project the pipeline deploys, and tick ci:deploy and nothing else. The ability satisfies the deployment writes alone: the token can record and finish deployments, and every read, every other write and every other project is refused. The deployments it records are written as you, so mint it as the release manager or as a service account the team keeps for the purpose. Put the plaintext in the pipeline's secret store; it is shown once. API tokens covers the page in full.

You will also need the project's id and the environment's id, both in the address bar of their pages and in the GET /v1/projects/{project}/environments answer; keep them beside the token as pipeline variables.

Report a deployment in one call

A pipeline that reports after the deployment is done sends one request:

report.sh
curl -sS -X POST "$BIFROST_API/v1/projects/$BIFROST_PROJECT/deployments" \
  -H "Authorization: Bearer $BIFROST_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $PIPELINE_RUN_ID" \
  -d '{
        "environment_id": "'"$BIFROST_ENVIRONMENT"'",
        "version": "1.4.0",
        "commit_sha": "'"$COMMIT_SHA"'",
        "branch": "main",
        "pipeline_url": "'"$PIPELINE_URL"'"
      }'

The status is succeeded when left out; send "status": "failed" from the failure branch of the job and the record is kept as a failure. The answer is 201 with the deployment, and the environment now carries the version as its Current Version.

Report as the deployment starts and ends

A pipeline that reports as it goes opens the deployment first, deploys, then finishes it whichever way the job ended:

start.sh
DEPLOYMENT_ID=$(curl -sS -X POST "$BIFROST_API/v1/projects/$BIFROST_PROJECT/deployments" \
  -H "Authorization: Bearer $BIFROST_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $PIPELINE_RUN_ID-start" \
  -d '{"environment_id": "'"$BIFROST_ENVIRONMENT"'", "version": "1.4.0", "status": "running", "commit_sha": "'"$COMMIT_SHA"'", "branch": "main", "pipeline_url": "'"$PIPELINE_URL"'"}' \
  | jq -r '.data.id')
finish.sh
curl -sS -X PATCH "$BIFROST_API/v1/deployments/$DEPLOYMENT_ID" \
  -H "Authorization: Bearer $BIFROST_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $PIPELINE_RUN_ID-finish" \
  -d '{"status": "succeeded"}'

Send failed or cancelled instead when the job ended that way; a finish step that always runs, reading the job's own outcome, is the shape the ready-made snippets use. A deployment finished once is not moved again, so a second finish, from a retried job, answers 422 and changes nothing.

Keep retries harmless

Every write needs an Idempotency-Key. Use the pipeline's run id, with a suffix per step when a run makes more than one call: a retried step with the same key and the same body receives the first answer again, so a flaky network records nothing twice, while the same key with a different body is refused. The rules are on the idempotency page.

What the record carries

FieldRequiredWhat to send
environment_idyesThe environment deployed into; one of the project's.
versionyesWhat was deployed, up to 60 characters: a tag, a semantic version or a build number.
statusnosucceeded when left out; running to open a deployment you will finish, queued or failed.
commit_shanoThe commit deployed, 7 to 40 hex characters.
branchnoThe branch or tag deployed from.
pipeline_urlnoThe run's own page, so a reader can go from the record to the log.
notesnoAnything worth keeping with the record, up to 5,000 characters.
started_atnoWhen it started, ISO 8601; now when left out and the status is past queued.
finished_atnoWhen it ended, ISO 8601; now when left out and the status is final.

The full contract, the finish request and every refusal are on the Deployments reference.

What is refused

AnswerWhy
403 FORBIDDENThe token carries neither ci:deploy nor deployment:record, its owner's role lacks the permission, or it is a guest's.
404 RESOURCE_NOT_FOUNDThe environment is not the project's, or the deployment to finish is not one the token's project holds.
422 VALIDATION_FAILEDA field is malformed, the status is not one allowed here, or the deployment ended already.
400 IDEMPOTENCY_KEY_MISSINGA write came without an Idempotency-Key.

Reads are not for pipelines

An ingestion token reads nothing, by design: a credential that lives in a pipeline's secret store should not be able to list a project's work. Read the environments and the deployments with a staff token, or over MCP.

Or let GitHub deployments report themselves

A GitHub workflow that creates deployments through GitHub's own Deployments API, on a repository picked from the team's App installation, needs no step: the App hears each deployment and each of its statuses and records them on the environment whose name matches. The matching and the version rules are on CI and deployments.

Ready-made snippets

How is this guide?

Last updated on

Version

On this page

What changed recentlyEvery release of Zuse Bifrost, with the pages it touched.

For AI agents: llms.txt · llms-full.txt