kogutdocs

Deploys

Change API

Request fields, responses, and merging for changes such as migrations, flag flips, and cache purges.

POST /api/changes puts a change that is not a deploy on the timeline: a database migration, a feature flag flip, a cache purge, or a DNS change. It needs a bearer token with the deploy scope, like the Deploy API.

Request

curl -fsS -X POST https://kogut.acme.com/api/changes \
  -H "Authorization: Bearer $KOGUT_TOKEN" \
  -d title="Migrate users table" -d service=api

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

FieldRequiredMeaning
titleYesWhat changed, shown on the timeline. At most 200 characters.
serviceNoExisting kogut service the change belongs to
hostNoExisting kogut host the change was made on
byNoPerson or system that made the change
urlNoLink shown with the change, such as the CI run or the flag
descriptionNoMore detail shown with the change

Optional fields allow at most 500 characters. Empty optional fields count as absent. Unknown service or host names do not create them.

The change time is when kogut receives the request, so call it after the change takes effect.

Responses

Changes with the same title, service, and host within 2 min merge. A change without a service or host merges only with another one without them. A new change returns 201 with {"event_id": 42, "merged": false}. A merged request returns 200 with merged: true and the first change’s id.

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

Changes and deploys

Deploys and changes are stored before kogut answers and share one limit of 8 requests waiting to be stored. Changes get no deploy report.

A change counts as a likely cause of an incident that starts soon after it, under the recent_change rule. A change with neither a service nor a host only ever coincides with an incident.

Changes appear on incident timelines, on the pages of their service and host, and in the overview. A change with neither a service nor a host appears on every incident timeline in its time and in the overview. The incident and timeline MCP tools and Copy for AI include them with author and link.

Examples

A feature flag flip, as JSON, with a link to the flag:

curl -fsS -X POST https://kogut.acme.com/api/changes \
  -H "Authorization: Bearer $KOGUT_TOKEN" -H "Content-Type: application/json" \
  -d '{"title": "Turn on new checkout", "service": "web", "by": "ana", "url": "https://flags.acme.com/checkout"}'

A cache flush on one host:

curl -fsS -X POST https://kogut.acme.com/api/changes \
  -H "Authorization: Bearer $KOGUT_TOKEN" \
  -d title="Flush Redis cache" -d host=vps-1

A DNS change, with neither a service nor a host:

curl -fsS -X POST https://kogut.acme.com/api/changes \
  -H "Authorization: Bearer $KOGUT_TOKEN" \
  -d title="Move acme.com to new DNS provider" -d description="TTL 300"

Shell example

The final || keeps a failed call from failing the job.

migrate.sh
#!/bin/sh
set -eu

./manage.py migrate
curl -fsS -X POST https://kogut.acme.com/api/changes \
  -H "Authorization: Bearer $KOGUT_TOKEN" \
  -d title="Migrate database" -d service=api -d by="$USER" \
  || echo "kogut: could not mark the change" >&2