Skip to main content

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).

GET/v1/apps/:id/health
GET/v1/apps/:id/dependents

curl — 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"
}
Publishing and activating fire one webhook, app.version.activated, carrying app_id, version, and content_hash — the push-side twin of the catalog ETag. Agents holding a cached tool schema for the app should re-read GET /v1/apps on this event.

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.

Frequently asked questions

Why did publish return the previous version untouched?+
The draft's content hash matched the active version, so publish no-opped — this is the idempotent content-hash publish. Byte-identical logic never mints a new version, which keeps version numbers meaningful and lets clients retry publish safely.
How do I roll back to an earlier version?+
Direct re-activation of a superseded version returns 501 in v1. Fetch the old version's content via GET /v1/apps/:id/versions/:n, save it as the draft with PUT /v1/apps/:id/logic, and publish. The content hash will match the old version's, and the ledger stays linear.
Why can't my API key enable the app?+
The active version declares acts — actions with external side effects — and enabling such an app is an owner decision made by a human session. Keys and non-owner roles get 403. Apps with an empty acts list enable at the operate tier.
What happens to in-flight runs when I disable an app?+
Disable flips the gate for new runs (409 on trigger); it does not cancel work already executing. Because rules and assisted runs complete synchronously within the trigger call, in practice the gate is the whole story for those modes; external runs already parked at awaiting_decision remain until decided or expired.