---
title: "Builds and deployments"
description: "Connect a repository, follow builds, deploy and roll back services, and read the deployment history."
order: 4
---

Connect a repository, follow builds, deploy and roll back services, and read the deployment history.

## `GET /api/v1/github/app`

The App and the URL that installs it. `installations` is how many owners it is on, so the page can say "configured but installed nowhere" rather than guessing from an empty repository list.

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [GitHubAppResponse](#githubappresponse) |
| 503 | GitHub App not configured |  |

## `GET /api/v1/github/repos`

Every repository the App's installations can see, by full name (the ConnectRepo picker).

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [Repo](#repo) |
| 503 | GitHub App not configured |  |

## `GET /api/v1/projects/{slug}/builds`

Newest first, for one environment (default `production`), optionally one service.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` (optional) | query | string or null | Default `production`. |
| `service` (optional) | query | string or null |  |
| `limit` (optional) | query | integer or null | 1 to 500, default 50. |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [BuildResponse](#buildresponse) |
| 404 | No such project or environment |  |

## `POST /api/v1/projects/{slug}/builds`

Queue a manual build of a service from the head of a branch: the spec's ref, else the environment's branch, else the source's default branch.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` (optional) | query | string or null | Default `production`. |
| `service` (optional) | query | string or null |  |
| `limit` (optional) | query | integer or null | 1 to 500, default 50. |

**Body:** [CreateBuildRequest](#createbuildrequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [BuildResponse](#buildresponse) |
| 404 | No such project, environment, or branch |  |
| 409 | App not installed on the owner |  |
| 422 | Unknown service, or a service without a source |  |
| 503 | GitHub App not configured |  |

## `GET /api/v1/projects/{slug}/builds/{id}`

One build with its latest deployment, its step timings and the GitHub ids.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [BuildDetailResponse](#builddetailresponse) |
| 404 | No such project or build |  |

## `POST /api/v1/projects/{slug}/builds/{id}/cancel`

Cancel a queued build (200, now `cancelled`) or a running one (202, it stops at its next step boundary and then reads `cancelled`).

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Was queued, now cancelled | [BuildResponse](#buildresponse) |
| 202 | Running, stops at its next step boundary | [BuildResponse](#buildresponse) |
| 404 | No such project or build |  |
| 409 | Already finished, or its rollout has started |  |

## `GET /api/v1/projects/{slug}/builds/{id}/log`

The log lines after `after` (default the start), at most `limit` (default 500, max 5000); `live` while the build still runs.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |
| `after` (optional) | query | integer or null | Lines after this sequence number; default the start. |
| `limit` (optional) | query | integer or null | 1 to 5000, default 500. |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [LogResponse](#logresponse) |
| 404 | No such project or build |  |

## `POST /api/v1/projects/{slug}/builds/{id}/retry`

A fresh queued build of the same commit and service (trigger `retry`), of the same kind: a publish run's retry resumes it, skipping what already published.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [BuildResponse](#buildresponse) |
| 404 | No such project or build |  |

## `GET /api/v1/projects/{slug}/deployments`

The timeline of one environment (default `production`), optionally one service, newest first.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` (optional) | query | string or null | Default `production`. |
| `service` (optional) | query | string or null |  |
| `limit` (optional) | query | integer or null | 1 to 500, default 50. |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [TimelineRow](#timelinerow) |
| 404 | No such project or environment |  |

## `POST /api/v1/projects/{slug}/resources/service/{name}/deploy`

Roll the service to a digest, given directly or as the image a build pushed. Refused unsigned (422) and while a build of the service is between `deploying` and its verdict (409).

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null | Default `production`. |
| `service` (optional) | query | string or null |  |
| `limit` (optional) | query | integer or null | 1 to 500, default 50. |

**Body:** [DeployRequest](#deployrequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [DeploymentResponse](#deploymentresponse) |
| 404 | No such project, environment, service or build |  |
| 409 | A rollout is in flight, or the registry is not configured |  |
| 422 | Not signed by the ops key, no digest, or a build without an image |  |

## `POST /api/v1/projects/{slug}/resources/service/{name}/rollback`

Roll the service back: to `digest`, else to the newest healthy digest that is not the current one. The body is optional.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null | Default `production`. |
| `service` (optional) | query | string or null |  |
| `limit` (optional) | query | integer or null | 1 to 500, default 50. |

**Body:** [RollbackRequest](#rollbackrequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [DeploymentResponse](#deploymentresponse) |
| 404 | No such project, environment or service, or nothing to roll back to |  |
| 409 | A rollout is in flight |  |
| 422 | The digest is not signed by the ops key |  |

## `GET /api/v1/projects/{slug}/sources`

The project's repositories, by repo, each with the services bound to it.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [SourceResponse](#sourceresponse) |
| 404 | No such project |  |

## `POST /api/v1/projects/{slug}/sources`

Connect a repository. The installation is the one the App has on the repo's owner unless given; the default branch is what GitHub reports for the repo.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

**Body:** [CreateSourceRequest](#createsourcerequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [SourceResponse](#sourceresponse) |
| 404 | No such project |  |
| 409 | Repository already connected, project archived, or App not installed on the owner |  |
| 422 | Not an owner/repo |  |
| 503 | GitHub App not configured |  |

## `GET /api/v1/projects/{slug}/sources/saves/{id}`

One save: its verdict, its pull request, and what the evaluator printed.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [SaveResponse](#saveresponse) |
| 404 | No such project or save |  |

## `POST /api/v1/projects/{slug}/sources/saves/{id}/merge`

Merge the save's pull request. Only an `open` save merges; GitHub's refusal (a conflict, a required check not green, a repository that allows squash only) is a 409 carrying GitHub's reason.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [SaveResponse](#saveresponse) |
| 404 | No such project or save |  |
| 409 | Not an open pull request, or GitHub refuses the merge |  |
| 503 | GitHub App not configured |  |

## `POST /api/v1/projects/{slug}/sources/scan`

What a repository deploys. Any other repository is scanned inline as it always was: every Dockerfile, its exposed port and healthcheck, plus a release entry per `migrate` or `release` stage. Reads through the project's source when the repo is connected, else through the installation on the repo's owner.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

**Body:** [ScanSourceRequest](#scansourcerequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Guessed from the Dockerfiles | array of [DetectedService](#detectedservice) |
| 202 | The repository declares itself; poll the scan | [ScanQueuedResponse](#scanqueuedresponse) |
| 404 | No such project, or GitHub knows no such repo or ref |  |
| 409 | App not installed on the owner |  |
| 422 | Not an owner/repo |  |
| 503 | GitHub App not configured |  |

## `GET /api/v1/projects/{slug}/sources/scans/{id}`

One scan: its verdict, the declaration it produced, and the evaluator's output.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ScanResponse](#scanresponse) |
| 404 | No such project or scan |  |

## `DELETE /api/v1/projects/{slug}/sources/{id}`

Disconnect a repository. Builds keep their history; a Service that still builds from the repo blocks the disconnect.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | string (uuid) |  |

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | No such project or source |  |
| 409 | A Service still builds from the repository |  |

## `GET /api/v1/projects/{slug}/sources/{id}/file`

One file's text (or a binary's base64) and blob sha at a ref, through the App.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | string (uuid) |  |
| `path` (optional) | query | string |  |
| `ref` (optional) | query | string | Branch, tag or sha; default the source's default branch. |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [StudioFileResponse](#studiofileresponse) |
| 404 | No such project, source or ref |  |
| 409 | Source not installed |  |
| 413 | Text over 1 MiB, or any file over 5 MiB |  |
| 422 | Not a repository path, or a directory |  |
| 503 | GitHub App not configured |  |

## `GET /api/v1/projects/{slug}/sources/{id}/saves`

A source's saves, newest first (the last 20).

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | string (uuid) |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [SaveResponse](#saveresponse) |
| 404 | No such project or source |  |

## `POST /api/v1/projects/{slug}/sources/{id}/saves`

Poll `GET …/sources/saves/{id}`.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | string (uuid) |  |

**Body:** [CreateSaveRequest](#createsaverequest)

| Status | Means | Body |
| --- | --- | --- |
| 202 | Accepted, and carried on in the background | [SaveQueuedResponse](#savequeuedresponse) |
| 404 | No such project or source |  |
| 409 | Source not installed, or project archived |  |
| 422 | No message, a path that is not the repository's, a path twice, text and base64 both or base64 that is not, more than 200 changes, 8 MiB of content or 5 MiB in one binary file |  |

## `GET /api/v1/projects/{slug}/sources/{id}/tree`

The whole tree at a ref, and the refs the studio offers.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `id` | path | string (uuid) |  |
| `ref` (optional) | query | string | Branch, tag or sha; default the source's default branch. |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [StudioTree](#studiotree) |
| 404 | No such project, source or ref |  |
| 409 | Source not installed |  |
| 503 | GitHub App not configured |  |

## `GET /api/v1/signing/public-key`

The ops cosign public key as PEM, for out of band `cosign verify --key`.

| Status | Means | Body |
| --- | --- | --- |
| 200 | PEM | `text/plain` bytes |
| 404 | Image signing not configured |  |

## Types

### GitHubAppResponse

| Field | Type | About |
| --- | --- | --- |
| `html_url` | string |  |
| `install_url` | string | `<html_url>/installations/new`, where a person installs it on an owner. |
| `installations` | integer | How many owners the App is installed on right now. |
| `slug` | string |  |

### Repo

| Field | Type | About |
| --- | --- | --- |
| `default_branch` | string |  |
| `full_name` | string | `owner/repo`. |
| `private` | boolean |  |

### BuildResponse

| Field | Type | About |
| --- | --- | --- |
| `author` | string | The commit author's GitHub login. |
| `branch` (optional) | string or null |  |
| `cache_hit` (optional) | number or null | 0 to 1, from the build's progress lines. |
| `commit_sha` | string |  |
| `environment` | string |  |
| `error` (optional) | string or null |  |
| `failure_kind` (optional) | string or null | `checks`, `build`, `memory` (killed for memory), `scan`, `push`, `sign`, `deploy`, `health`, `publish` or `internal`. |
| `finished_at` (optional) | string or null |  |
| `github_check_run_id` (optional) | integer or null |  |
| `id` | integer |  |
| `image_digest` (optional) | string or null |  |
| `image_ref` (optional) | string or null |  |
| `image_size_bytes` (optional) | integer or null |  |
| `kind` | string | `image`, a service's image build, or `publish`, a tag's packages published (steps `clone`, `plan`, `crates`, `layers`, `locales`). |
| `message` | string | First line of the commit message. |
| `project` | string |  |
| `queued_at` | string |  |
| `repo` | string |  |
| `requested_by` | string | `github` for a push, the operator email otherwise. |
| `service` | string |  |
| `source_id` (optional) | string (uuid) or null |  |
| `started_at` (optional) | string or null |  |
| `status` | string | `queued`, `cloning`, `checks`, `building`, `scanning`, `pushing`, `signing`, `deploying`, `publishing`, `healthy`, `published`, `failed`, `rolled_back` or `cancelled`. |
| `step_timings` | array of object | `[{step, started_at, finished_at?, ok?, note?}]` in run order. |
| `trigger` | string | `push`, `manual` or `retry`. |

### CreateBuildRequest

| Field | Type | About |
| --- | --- | --- |
| `ref` (optional) | string or null | The branch to build the head of; default the spec's ref, else the environment's branch, else the source's default branch. |
| `service` | string |  |

### BuildDetailResponse

| Field | Type | About |
| --- | --- | --- |
| `build` | [BuildResponse](#buildresponse) |  |
| `deployment` (optional) | null or [DeploymentResponse](#deploymentresponse) |  |
| `github` | [GithubIds](#githubids) |  |
| `timings` | array of object | `build.step_timings`, the timeline's own copy. |

### LogResponse

| Field | Type | About |
| --- | --- | --- |
| `lines` | array of [LogLineResponse](#loglineresponse) |  |
| `live` | boolean | The build is still running: poll again. |
| `next` | integer | Pass as `after` for the next page: the last sequence returned, or the `after` given when there was nothing new. |

### TimelineRow

One line of the Deployments page: a build with its latest deployment, or a deployment without a build (a digest deployed by hand).

| Field | Type | About |
| --- | --- | --- |
| `at` | string |  |
| `author` | string |  |
| `build` (optional) | null or [BuildSummary](#buildsummary) |  |
| `commit` | string | The commit sha; the digest for a hand deployed image without one. |
| `deployment` (optional) | null or [DeploymentSummary](#deploymentsummary) |  |
| `failure_kind` (optional) | string or null | The timeline chip: `checks`, `build`, `scan`, `push`, `sign`, `deploy`, `health` or `internal`. |
| `message` | string |  |
| `service` | string |  |
| `state` | string | `queued`, `building`, `checks`, `scanning`, `pushing`, `signing`, `deploying`, `healthy`, `unhealthy`, `rolling_back`, `rolled_back`, `failed`, `refused` or `cancelled`. |
| `state_text` | string |  |

### DeployRequest

`{digest}` or `{build_id}`: the manifest digest to roll to, or the build whose pushed image it is.

| Field | Type | About |
| --- | --- | --- |
| `build_id` (optional) | integer or null |  |
| `digest` (optional) | string or null |  |

### DeploymentResponse

| Field | Type | About |
| --- | --- | --- |
| `author` | string |  |
| `build_id` (optional) | integer or null |  |
| `commit_sha` (optional) | string or null |  |
| `created_at` | string |  |
| `created_by` | string |  |
| `environment` | string |  |
| `finished_at` (optional) | string or null |  |
| `github_deployment_id` (optional) | integer or null |  |
| `github_status_id` (optional) | integer or null |  |
| `id` | integer |  |
| `image_digest` | string |  |
| `message` | string |  |
| `previous_digest` (optional) | string or null |  |
| `project` | string |  |
| `rollout` | string |  |
| `rollout_note` (optional) | string or null |  |
| `service` | string |  |
| `trigger` | string |  |

### RollbackRequest

| Field | Type | About |
| --- | --- | --- |
| `digest` (optional) | string or null | The digest to return to; default the newest healthy one before the current. |

### SourceResponse

| Field | Type | About |
| --- | --- | --- |
| `connected_at` | string |  |
| `declared` (optional) | null or [DeclaredNames](#declarednames) |  |
| `default_branch` | string |  |
| `id` | string (uuid) |  |
| `installation_id` | integer | The App installation the repo is reached through; 0 until installed. |
| `last_event_at` (optional) | string or null | When the last webhook delivery for this repo arrived; null = never. |
| `repo` | string | GitHub `owner/repo`. |
| `services` | array of string |  |

### CreateSourceRequest

| Field | Type | About |
| --- | --- | --- |
| `installation_id` (optional) | integer or null | Resolved from the App's installations by the repo's owner when absent. |
| `repo` | string |  |

### SaveResponse

| Field | Type | About |
| --- | --- | --- |
| `branch` (optional) | string or null |  |
| `commit_sha` (optional) | string or null |  |
| `created_at` | string |  |
| `error` (optional) | string or null |  |
| `files` | array of [SaveFileResponse](#savefileresponse) | What the save changes, by path. |
| `finished_at` (optional) | string or null |  |
| `id` | integer |  |
| `log` | string | What the evaluator printed. |
| `merge_sha` (optional) | string or null |  |
| `merged_at` (optional) | string or null |  |
| `merged_by` (optional) | string or null |  |
| `message` | string |  |
| `pull_number` (optional) | integer or null |  |
| `pull_url` (optional) | string or null |  |
| `ref` (optional) | string or null | The branch the edit was made on; null = the default branch. |
| `repo` | string |  |
| `requested_by` | string |  |
| `scan_id` (optional) | integer or null | The scan the merge queued of the default branch. |
| `source_id` (optional) | string (uuid) or null |  |
| `status` | string | `queued`, `running`, `open` (the pull request exists), `merged`, `closed` (on GitHub, unmerged) or `failed`. |

### ScanSourceRequest

| Field | Type | About |
| --- | --- | --- |
| `ref` (optional) | string or null | Branch, tag or sha; default the repo's default branch. |
| `repo` | string | GitHub `owner/repo`; a connected source of the project, or any repo an installation can see. |

### DetectedService

| Field | Type | About |
| --- | --- | --- |
| `args` (optional) | map of string | Build arguments; a declaration carries them, a Dockerfile scan cannot know them. |
| `context` | string | The build context, relative to the repository root. |
| `dockerfile` | string |  |
| `healthcheck` (optional) | string or null | The path a `HEALTHCHECK` probes, when it names a URL. |
| `kind` | string | `service` (long running) or `release` (a `migrate`/`release` stage). |
| `name` | string |  |
| `port` (optional) | integer or null | The last `EXPOSE`d port. |
| `target` (optional) | string or null | A named stage to build (`--target`); null = the final stage. |
| `volumes` (optional) | array of [DeclaredVolume](#declaredvolume) |  |

### ScanQueuedResponse

What `POST /sources/scan` answers for a repository that declares itself: the scan to poll.

| Field | Type | About |
| --- | --- | --- |
| `scan_id` | integer |  |
| `status` | string | `queued`. |

### ScanResponse

| Field | Type | About |
| --- | --- | --- |
| `error` (optional) | string or null |  |
| `git_ref` | string |  |
| `hooks` | any | Event → the steps it fires. |
| `id` | integer |  |
| `log` | string | What the evaluator printed. |
| `reconciled` (optional) | null or [Reconciled](#reconciled) |  |
| `repo` | string |  |
| `services` | array of [DetectedService](#detectedservice) | The declared services, in the shape the Dockerfile scan has always answered. |
| `status` | string | `queued`, `running`, `succeeded` or `failed`. |
| `steps` | array of [DeclaredStepResponse](#declaredstepresponse) |  |

### StudioFileResponse

| Field | Type | About |
| --- | --- | --- |
| `base64` (optional) | string or null | A binary file's content, base64 (standard alphabet, padded); null for text and for a missing file. A binary over 5 MiB answers 413 instead. |
| `binary` | boolean | A NUL byte or invalid UTF-8 in the first 8 KiB, or anywhere: the editor shows it read only, `text` is empty and `base64` holds the content. |
| `exists` | boolean | False: no file at the path on the ref (a new file saves with `base_sha` null). |
| `html_url` | string | `https://github.com/<repo>/blob/<ref>/<path>`, for the link beside a field the file owns. |
| `path` | string |  |
| `ref` | string |  |
| `sha` | string | The blob sha, sent back with a save so a file changed since is refused; empty when the file is missing. |
| `text` | string | Empty when the file is missing or binary. |

### CreateSaveRequest

| Field | Type | About |
| --- | --- | --- |
| `changes` | array of [SaveChange](#savechange) | 1 to 200 changes, 8 MiB of content at most (text and decoded bytes together), 5 MiB per binary file, each path once. |
| `message` | string | The commit message and the pull request's title; required. |
| `ref` (optional) | string or null | The branch the editor opened the files at; default the default branch. The changes are checked and evaluated over this branch's head and the commit's parent is that head, so a save on an open save's `ops/studio-<n>` carries that save's change with it. |

### SaveQueuedResponse

| Field | Type | About |
| --- | --- | --- |
| `save_id` | integer |  |
| `status` | string | `queued`. |

### StudioTree

| Field | Type | About |
| --- | --- | --- |
| `default_branch` | string |  |
| `entries` | array of [StudioTreeEntry](#studiotreeentry) | Every file and directory, in GitHub's order. A submodule is left out: its files are another repository's. |
| `ref` | string | The ref the tree was read at. |
| `refs` | array of [StudioRef](#studioref) | The refs a file opens at and a save builds on: the default branch first, then each open save's branch, newest first. |
| `sha` | string | The commit the ref names. |
| `truncated` | boolean | GitHub cut the recursive listing short (past 100 000 entries): `entries` is a prefix of the tree, and a save of a path it does not list is refused. |

### GithubIds

The ids GitHub handed back for this build: its check run, and the deployment and status of its latest rollout.

| Field | Type | About |
| --- | --- | --- |
| `check_run_id` (optional) | integer or null |  |
| `deployment_id` (optional) | integer or null |  |
| `status_id` (optional) | integer or null |  |

### LogLineResponse

| Field | Type | About |
| --- | --- | --- |
| `at` | string |  |
| `level` | string | `info`, `warn` or `error`. |
| `line` | string |  |
| `seq` | integer |  |
| `step` | string |  |

### BuildSummary

| Field | Type | About |
| --- | --- | --- |
| `branch` (optional) | string or null |  |
| `cache_hit` (optional) | number or null |  |
| `error` (optional) | string or null |  |
| `finished_at` (optional) | string or null |  |
| `id` | integer |  |
| `image_digest` (optional) | string or null |  |
| `kind` | string | `image` or `publish`. |
| `queued_at` | string |  |
| `requested_by` | string |  |
| `status` | string |  |
| `trigger` | string |  |

### DeploymentSummary

| Field | Type | About |
| --- | --- | --- |
| `created_at` | string |  |
| `created_by` | string |  |
| `finished_at` (optional) | string or null |  |
| `id` | integer |  |
| `image_digest` | string |  |
| `previous_digest` (optional) | string or null |  |
| `rollout` | string |  |
| `rollout_note` (optional) | string or null |  |
| `trigger` | string |  |

### DeclaredNames

| Field | Type | About |
| --- | --- | --- |
| `domains` | map of array of string | Environment → the domains the file gives a hostname there (`""` as above). |
| `hooks` | map of array of string | Event → the steps it fires. |
| `operational` | map of map of array of string | Environment → service → the operational keys (`replicas`, `limits`, `env_group`, `address`) the file SETS there, each locked in that environment's form. `""` is every environment without a `$<name>` block. |
| `services` | array of string |  |
| `steps` | array of string |  |

### SaveFileResponse

| Field | Type | About |
| --- | --- | --- |
| `action` | [SaveAction](#saveaction) |  |
| `path` | string |  |

### DeclaredVolume

| Field | Type | About |
| --- | --- | --- |
| `database` | string |  |
| `path` (optional) | string |  |

### DeclaredStepResponse

| Field | Type | About |
| --- | --- | --- |
| `name` | string |  |
| `spec` | any |  |

### Reconciled

| Field | Type | About |
| --- | --- | --- |
| `created` | array of [ReconciledRow](#reconciledrow) |  |
| `refused` | array of [ReconciledRow](#reconciledrow) | Writes a type refused, each with its reason. |
| `undeclared` | array of [ReconciledRow](#reconciledrow) | Rows this repository's file no longer declares: left in place, never destroyed. |
| `updated` | array of [ReconciledRow](#reconciledrow) |  |

### SaveChange

| Field | Type | About |
| --- | --- | --- |
| `base64` (optional) | string or null | The new content of a binary file, base64 (standard alphabet, padded), in place of `text`: never both. |
| `base_sha` (optional) | string or null | The `sha` the file answered when the editor opened it; null for a new file. |
| `moved_from` (optional) | string or null | For the add of a move or rename: the path it was moved from, whose git mode the file keeps (a script stays executable). Null otherwise. |
| `path` | string | Repository relative: no leading `/`, no `..`, never under `.git/`. |
| `text` (optional) | string or null | The new text; null with `base64` null deletes the file. |

### StudioRef

| Field | Type | About |
| --- | --- | --- |
| `name` | string |  |
| `pull_number` (optional) | integer or null |  |
| `save_id` (optional) | integer or null | The open save whose branch this is; null for the default branch. |

### StudioTreeEntry

| Field | Type | About |
| --- | --- | --- |
| `path` | string |  |
| `sha` | string | The blob or tree sha: a file's is what a save sends back as `base_sha`. |
| `size` (optional) | integer or null | Bytes, for a file; null for a directory. |
| `type` | [StudioEntryType](#studioentrytype) |  |

### SaveAction

What a change does to its file, in the words the console lists it with.

One of: `"add"` or `"modify"` or `"delete"`.

### ReconciledRow

| Field | Type | About |
| --- | --- | --- |
| `environment` (optional) | string or null | null for a step (project scoped). |
| `kind` | string | `service`, `domain`, `step` or `docs-reader`. |
| `name` | string |  |
| `reason` (optional) | string or null |  |

### StudioEntryType

One of: `"blob"` or `"tree"`.
