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=apiThe body limit is 64 KiB. Content-Type: application/json selects JSON. Other content
types are parsed as form fields.
| Field | Required | Meaning |
|---|---|---|
title | Yes | What changed, shown on the timeline. At most 200 characters. |
service | No | Existing kogut service the change belongs to |
host | No | Existing kogut host the change was made on |
by | No | Person or system that made the change |
url | No | Link shown with the change, such as the CI run or the flag |
description | No | More 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.
| Status | Meaning |
|---|---|
201 | New change recorded |
200 | Merged with a change from the last 2 min |
400 | Unreadable body, unknown field, or JSON missing title |
401 | Missing or invalid bearer token |
403 | Token lacks the deploy scope |
413 | Body exceeds 64 KiB |
422 | Empty title, title over 200 characters, field over 500 characters, or unknown service or host |
503 | Server 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-1A 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.
#!/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