Versions & Lifecycle
Version lifecycle for decision apps: immutable published versions, content-hash publishes, enable and disable, plus health and dependents inspection endpoints.
App configuration is versioned like a Spec: edits accumulate in a single draft, publish freezes the draft forever, and every run pins exactly one version. GET /v1/apps/:id/versions lists every version with its status (draft, published, superseded), content_hash, and timestamps; GET /v1/apps/:id/versions/:n returns one version including its full content. Published rows never change — the ledger can therefore always name the exact logic that decided any historical run.
POST /v1/apps/:id/versions/:n/publish publishes the current draft (answering 200). The :n must be the draft's own number — publishing anything else is a 409 naming the actual draft. Publish re-validates the content, supersedes the previously published version, activates the new one, locks the slug on the first publish, and fires the app.version.activated webhook. It is idempotent by content: a draft whose content_hash equals the active version publishes as a no-op returning the active version.
In v1, publish activates: POST /v1/apps/:id/versions/:n/activate returns 200 when :n is already the active version and 501 otherwise — re-activating a superseded version (rollback) is not implemented yet; publish a new version with the old content instead. The pre-publish [verdict diff](app-replays) exists precisely so you can see what a candidate changes before this one-way door.
POST /v1/apps/:id/enable and POST /v1/apps/:id/disable flip the run gate. Enabling requires a published version (409 before the first publish) and — when the active version declares acts (external side effects) — a human workspace owner: machine tokens and non-owner sessions get 403 Enabling an app with actions requires a workspace owner. Disabling is the kill switch and stays at plain operate with no extra ceremony: stopping an app must always be easier than starting it. A disabled app refuses live runs with 409 but still accepts [dry runs](app-runs).
/v1/apps/:id/health/v1/apps/:id/dependentscurl — publish, enable, inspect
curl -s -X POST https://api.talonic.com/v1/apps/$APP_ID/versions/2/publish \
-H "Authorization: Bearer tlnc_your_api_key"
# → { "version": 2, "status": "published", "content_hash": "aaed0a6d6e01f073..." }
curl -s -X POST https://api.talonic.com/v1/apps/$APP_ID/enable \
-H "Authorization: Bearer tlnc_your_api_key"
# → { "enabled": true }
curl -s https://api.talonic.com/v1/apps/$APP_ID/health \
-H "Authorization: Bearer tlnc_your_api_key"Response — GET /v1/apps/:id/health
{
"window_hours": 24,
"runs": {
"total": 12,
"by_status": { "completed": 11, "failed": 1 }
},
"reviews_open": 2,
"last_run_at": "2026-08-29T11:31:59.140Z"
}Version history composes with the ledger: every run records app_slug@vN, every sealed record names the deciding version, and [GET /v1/runs/:runId/verdicts/summary](app-runs) returns the deciding versionId. When behavior changes between two runs, diff the two versions' content via GET /v1/apps/:id/versions/:n — the content hash tells you instantly whether the logic differed at all.