# Deploy API

Request fields, authentication, and responses for recording deploys.

`POST /api/deploys` records a deploy for an existing service. It needs a bearer token
with the `deploy` scope, created in **Settings → Tokens**. The token is shown once; keep
it in your CI secrets. Record other changes, such as migrations, with the
[Change API](https://docs.kogut.dev/deploys/changes/).

## Request

```sh
curl -fsS -X POST https://kogut.acme.com/api/deploys \
  -H "Authorization: Bearer $KOGUT_TOKEN" \
  -d service=api -d version="$GITHUB_SHA"
```

The body limit is 64 KiB. `Content-Type: application/json` selects JSON. Other content
types are parsed as form fields.

| Field | Required | Meaning |
|---|---|---|
| `service` | Yes | Existing kogut service name |
| `version` | Yes | Commit hash, tag, or version string |
| `by` | No | Person or system that deployed |
| `url` | No | Link shown with the deploy |
| `description` | No | Description shown on the timeline |

Each field allows at most 500 characters. Empty optional fields count as absent.
Unknown service names do not create services.

The deploy time is when kogut receives the request, so call it after the version goes
live. Send one request per service. Deploys of a dependency also count in
[correlation](https://docs.kogut.dev/incidents/correlation/).

The agent records a deploy, with source `agent`, when a container starts from a new
image; see [Container images](https://docs.kogut.dev/deploys/containers/).

## Responses

Deploys with the same service and version within 2 min merge, including events from
[GitHub](https://docs.kogut.dev/deploys/github/). A new deploy returns `201` with
`{"event_id": 42, "merged": false}`. A merged request returns `200` with `merged: true`.

| Status | Meaning |
|---|---|
| `201` | New deploy recorded |
| `200` | Merged with a deploy from the last 2 min |
| `400` | Unreadable body, unknown field, or JSON missing `service` or `version` |
| `401` | Missing or invalid bearer token |
| `403` | Token lacks the `deploy` scope |
| `413` | Body exceeds 64 KiB |
| `422` | Empty required value, field over 500 characters, or unknown service |
| `503` | Server busy or storage wait exceeded. A retry can merge with a stored deploy. |

## Shell example

The final `||` keeps a failed call from failing the deploy.

```sh title="deploy.sh"
#!/bin/sh
set -eu

version=$(git rev-parse --short HEAD)
docker compose pull api && docker compose up -d api
curl -fsS -X POST https://kogut.acme.com/api/deploys \
  -H "Authorization: Bearer $KOGUT_TOKEN" \
  -d service=api -d version="$version" \
  || echo "kogut: could not mark the deploy" >&2
```
