Deployments
Components & Releases
The component as a versioned brick and the release as an immutable bundle of component versions — creation, versioning, the immutability rule, and the associations the API exposes.
A component is one building block of your product at one version. A release is the set of component versions you ship together. The two are records: Kaiten does not build, store, or run your artifacts — it keeps the authoritative list of what a release contains, so that a deployment zone can point at a release and every instance in that zone is known to run exactly those versions.
Examples on this page use $KAITEN_API and $KAITEN_TOKEN as set up in the quickstart, and extend the seeded Kaiten Sushi Shop catalog — they create new entities next to the seeded ones rather than altering them.
Component — the versioned brick
A component is identified by its name and version together, unique within your organization: Kitchen Display 1.2.0 is one component, Kitchen Display 1.3.0 would be another. Each also has a slug, unique per organization, used in every URL. Versions are linked into a history through previousComponentId: Kaiten Sushi Shop's Delivery Service exists twice — 0.9.0 (driver dispatch, beta) and 1.0.0 (real-time position streaming), the second pointing at the first. Kitchen Display 1.2.0 has no successor yet.
| Field | Required | Constraints |
|---|---|---|
name | yes | 1–100 characters; (name, version) unique per organization |
version | yes | 1–100 characters, free-form — 2026.8.0, 1.0.0, v1.2.3 are all valid |
slug | no | 2–100 characters, ^[a-z0-9][a-z0-9-]*[a-z0-9]$, unique per organization; generated when omitted |
description | no | up to 500 characters |
previousComponentId | no | id of the version this one succeeds |
The response adds id, createdBy, and createdAt. Reading and listing require read:components; every write requires write:components.
Create a component version
Kitchen Display moves from 1.2.0 to 1.3.0. The new version is a new component that names its predecessor. Look up the predecessor's id first:
curl "$KAITEN_API/components/kitchen-display-1-2-0" \
-H "Authorization: Bearer $KAITEN_TOKEN"{
"id": "7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"name": "Kitchen Display",
"version": "1.2.0",
"slug": "kitchen-display-1-2-0",
"description": "Kitchen display system.",
"createdBy": { "id": "…", "name": "Ops Team" },
"createdAt": "2026-07-01T09:00:00Z"
}Responses on this page are illustrative — ids and timestamps depend on your stack. Every record the seeder writes names the same actor, the Ops Team service account it acts as. Keep the id:
export KDS_120_ID="7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f" # yours will differThen create the successor:
curl -X POST "$KAITEN_API/components" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Kitchen Display",
"version": "1.3.0",
"slug": "kitchen-display-1-3-0",
"description": "Order timers and allergen badges.",
"previousComponentId": "'"$KDS_120_ID"'"
}'{
"id": "9b8a7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"name": "Kitchen Display",
"version": "1.3.0",
"slug": "kitchen-display-1-3-0",
"description": "Order timers and allergen badges.",
"previousComponentId": "7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"createdBy": { "id": "…", "name": "…" },
"createdAt": "2026-09-08T10:12:00Z"
}HTTP/1.1 201 Created, and COMPONENT_CREATED is emitted. Posting Kitchen Display 1.2.0 a second time is refused with 409 Conflict — the name and version pair already exists. A previousComponentId that does not exist in your organization is a 404.
Update — in place, or as the next version
PUT /components/{slug} takes the same name, version, slug, and description fields and behaves according to one question: is this component bundled by a release?
-
Not bundled — the component is updated in place.
kitchen-display-1-3-0, just created and not yet in any release, can be corrected freely:curl -X PUT "$KAITEN_API/components/kitchen-display-1-3-0" \ -H "Authorization: Bearer $KAITEN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Kitchen Display", "version": "1.3.0", "slug": "kitchen-display-1-3-0", "description": "Order timers, allergen badges, and station routing." }'The response is the same component (
200 OK, sameid) with the new description. -
Bundled by at least one release — the component is not touched, because the release must keep meaning what it meant when it was cut. Kaiten creates the next version carrying your changes instead, chained to the one you addressed.
api-2026-8-0is bundled by2026.8.0; aPUTon it with a newversionyields a new component, and2026.8.0still lists the original. APUTthat keeps the samenameandversionis refused with400, codeUpdateComponent.LinkedComponentRequiresNewVersion.
Both outcomes emit COMPONENT_UPDATED — the catalog describes it as "updated or auto-versioned".
Delete
DELETE /components/{slug} returns 204 No Content for a component no release bundles, and 409 Conflict for one that a release lists. A release's component list is part of the release; removing a member would rewrite history, so the component stays as long as the release does. COMPONENT_DELETED is emitted on success.
Release — the immutable bundle
A release is a version — unique within your organization — with an optional slug and description, and the list of components it bundles. The list is given at creation as componentIds and returned on every read as components, fully expanded. A release is immutable: created once, read many times, never edited — the rule is spelled out below.
| Field | Required | Constraints |
|---|---|---|
version | yes | 1–100 characters, unique per organization |
slug | no | same rules as components; generated when omitted |
description | no | up to 500 characters |
componentIds | no | ids of the components to bundle — fixed once created |
Kaiten Sushi Shop has two: 2026.7.0 ("July platform release"), which bundles api-2026-7-0, web-app-2026-7-0, kitchen-display-1-2-0, and delivery-service-0-9-0, and 2026.8.0 ("Introduces real-time delivery tracking"), which bundles api-2026-8-0, web-app-2026-8-0, kitchen-display-1-2-0, and delivery-service-1-0-0. The seeder gives the components and the releases explicit slugs, the same on every stack: the two releases are r-2026-7-0 and r-2026-8-0.
Create a release
Cut 2026.8.1: the 2026.8.0 bundle with Kitchen Display moved to 1.3.0. Collect the four component ids — GET /components/{slug} for api-2026-8-0, web-app-2026-8-0, delivery-service-1-0-0, and the kitchen-display-1-3-0 created above:
export API_280_ID="<id of api-2026-8-0>"
export WEB_280_ID="<id of web-app-2026-8-0>"
export KDS_130_ID="9b8a7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d" # from the 201 above — yours will differ
export DELIVERY_100_ID="<id of delivery-service-1-0-0>"Then post them together:
curl -X POST "$KAITEN_API/releases" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"version": "2026.8.1",
"slug": "2026-8-1",
"description": "Kitchen Display 1.3.0: order timers and allergen badges.",
"componentIds": [
"'"$API_280_ID"'",
"'"$WEB_280_ID"'",
"'"$KDS_130_ID"'",
"'"$DELIVERY_100_ID"'"
]
}'{
"id": "4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a",
"version": "2026.8.1",
"slug": "2026-8-1",
"description": "Kitchen Display 1.3.0: order timers and allergen badges.",
"components": [
{ "id": "…", "name": "API", "version": "2026.8.0", "slug": "api-2026-8-0", "…": "…" },
{ "id": "…", "name": "Web App", "version": "2026.8.0", "slug": "web-app-2026-8-0", "…": "…" },
{ "id": "…", "name": "Kitchen Display", "version": "1.3.0", "slug": "kitchen-display-1-3-0", "…": "…" },
{ "id": "…", "name": "Delivery Service", "version": "1.0.0", "slug": "delivery-service-1-0-0", "…": "…" }
],
"createdBy": { "id": "…", "name": "…" },
"createdAt": "2026-09-08T10:20:00Z"
}201 Created; RELEASE_CREATED is emitted. The request names its slug, so the release is addressable as /releases/2026-8-1; without slug, the API generates one from the version with a random suffix — 2026-8-1- followed by six hexadecimal characters — to be read from the response. A second release with version: "2026.8.1" is a 409; an unknown component id is a 404. From this moment kitchen-display-1-3-0 is bundled: a PUT on it now creates a successor carrying your changes rather than editing it, and it can no longer be deleted.
The immutability rule
A release, once created, is a fact:
- No update endpoint. There is no
PUTorPATCHon/releases/{slug}— the resource exposesGET,POSTon the collection, andDELETE, nothing else; not even the description can change. Nothing writes the record after its creation, and the record carries noupdatedAtorupdatedBy: the columns were removed rather than left unused, which is why a release reads withcreatedByandcreatedAtonly, where a zone or an instance also reads with its last update. - The component list is fixed.
componentsis exactly the set given at creation. - Correcting a release means creating another one. Cut
2026.8.2with the right components; the wrong one can be deleted only if it was never deployed. - Deletion is refused once a release has been deployed.
DELETE /releases/{slug}answers409 Conflict, codeDeleteRelease.InUseConflict, as soon as the journal holds an entry for the release — whether or not a zone still runs it — and while another release names it as itsprevious_release_id. Deleting a release never removes a journal entry: while an entry names the release, the release stays. One that was created and never deployed can be deleted, andRELEASE_DELETEDis emitted.
The rule is what makes the rest of the pillar trustworthy: when the journal says 2026.8.0 landed in shared-eu, the four component versions that statement refers to are the four it referred to on the day it landed.
Associations as the API exposes them
Every link between the entities of this pillar is a field on one of them:
| Link | Where it lives | Direction |
|---|---|---|
| Component version history | Component.previousComponentId | newer → older |
| Release contents | Release.components[] (expanded Component objects) | release → components |
| Zone's current release | DeploymentZone.releaseId | zone → release |
| Journal entry | Deployment.releaseId, Deployment.deploymentZoneId | entry → release, entry → zone |
| Instance placement | Instance.deploymentZoneId and deploymentZoneSlug | instance → zone |
Two things a reader might expect are not there. A component does not list the releases that bundle it — walk GET /releases and read components instead. And releases do not chain to one another in the API: the data model shows a previous_release_id column, but no request accepts it and no response returns it. The succession 2026.7.0 → 2026.8.0 is visible in the version numbers; GET /releases/{slug} carries no link to a predecessor.
Reading a seeded release shows the shape:
curl "$KAITEN_API/releases/r-2026-8-0" \
-H "Authorization: Bearer $KAITEN_TOKEN"{
"id": "2b6f9c1e-8d3a-4f7b-9e0c-6a5d4b3c2f1e",
"version": "2026.8.0",
"slug": "r-2026-8-0",
"description": "Introduces real-time delivery tracking.",
"components": [
{ "id": "…", "name": "API", "version": "2026.8.0", "slug": "api-2026-8-0", "previousComponentId": "…", "…": "…" },
{ "id": "…", "name": "Web App", "version": "2026.8.0", "slug": "web-app-2026-8-0", "previousComponentId": "…", "…": "…" },
{ "id": "…", "name": "Kitchen Display", "version": "1.2.0", "slug": "kitchen-display-1-2-0", "…": "…" },
{ "id": "…", "name": "Delivery Service", "version": "1.0.0", "slug": "delivery-service-1-0-0", "previousComponentId": "…", "…": "…" }
],
"createdBy": { "id": "…", "name": "Ops Team" },
"createdAt": "2026-08-01T09:00:00Z"
}Lists (GET /components, GET /releases) are cursor-paginated: items, hasMore, and a nextCursor to pass back as ?cursor=; limit defaults to 50 and caps at 200. The shared conventions — pagination, problem responses, slugs — are documented on API conventions.
Events
| Event name | Event type | When |
|---|---|---|
COMPONENT_CREATED | com.kaiten.component.v1.created | A component is created |
COMPONENT_UPDATED | com.kaiten.component.v1.updated | A component is updated in place or auto-versioned |
COMPONENT_DELETED | com.kaiten.component.v1.deleted | A component is deleted |
RELEASE_CREATED | com.kaiten.release.v1.created | A release is created |
RELEASE_DELETED | com.kaiten.release.v1.deleted | A release is deleted |
The payload of each is the full object — Component or Release — as returned by the API.
Where to go next
A release does nothing until a zone runs it: Deployment Zones models the places, and Deployments & Lifecycle records the landings. Exact reference: data model.
Overview
Components, releases, deployment zones, and the append-only deployment journal — the runtime pillar of the control plane, and why it sits next to the contract.
Deployment Zones
Shared and dedicated zones — what a deployment zone models, how releases and instances relate to one, and where the dedicated model meets Kaiten's own editions.