Apps Overview
The Talonic Apps surface: named, versioned decision apps over governed data, with an ETag-cached catalog, dual auth heads, and a read/run/operate/owner ladder.
Apps are named, versioned, publishable decision units. An app reads a governed set of inputs — published data products, reference tables, connected databases, or other apps — and produces schema-typed decisions with a replayable, provenance-carrying ledger: an append-only run journal, a queryable run projection, and an immutable sealed decision record per run. Every app runs in one of three autonomy modes: rules (deterministic policy cards decide), assisted (rules decide, a draft-only agent proposes review resolutions), or external (an outside agent decides inside Talonic's contract and ledger).
The REST surface spans six route families: /v1/apps (lifecycle, logic, versions, acceptance, grants), /v1/runs (run projection, verdict matrix, journal, sealed record), /v1/replays, /v1/reviews (the Human Review inbox), /v1/rule-candidates, and /v1/review-teams. The Talonic web UI is a client of exactly these endpoints — it holds no private capabilities — and every enabled app is additionally published as a dynamic MCP tool (apps/<slug>), so an external agent given only a token discovers and operates the same surface a person sees.
Authentication is dual-headed: tlnc_ API keys and OAuth tokens on one head, platform session JWTs on the other, both landing on the same authorization layer. Authorization is a four-tier ladder — read < run < operate < owner — mapped for humans from the workspace role (viewer → read, member → run, senior_member → operate, owner → owner). Machine callers are decided by per-app grants: a read grant admits reads, an operate grant admits read/run/operate, and owner-tier routes (grant management, deletion, pickable clients) always require a human workspace owner — they are never machine-grantable.
A compatibility rule keeps pre-Apps keys working: a key with zero app-grant rows falls back to its legacy scopes — read scope for read-tier routes, write for run/operate. The moment the first grant row lands on a key, that key switches to strict per-app enforcement: it can only touch apps it holds grants for, at the granted level. OAuth clients and routes without an app id in the path (the workspace review inbox, /v1/runs/:runId) always use the scope path, with every read tenant-scoped by the service.
The whole surface requires a concrete workspace. A key or session resolving to the cross-tenant master view is rejected with 403 and the message A concrete workspace is required for the Apps surface — there are no cross-tenant app reads or writes on /v1.
/v1/appsQuery parameters & headers
The response carries an etag both as an ETag header and in the body: a hash across the visible set's id:content_hash:enabled triples. It changes exactly when an app is created, deleted, enabled, disabled, or gets a new active version — which makes it the cache key for tool catalogs. MCP clients re-list tools when it moves; your integration can poll GET /v1/apps with If-None-Match cheaply and rebuild only on 200.
curl
curl -s https://api.talonic.com/v1/apps \
-H "Authorization: Bearer tlnc_your_api_key"
# Conditional re-fetch: 304 when nothing changed
curl -s -o /dev/null -w "%{http_code}" https://api.talonic.com/v1/apps \
-H "Authorization: Bearer tlnc_your_api_key" \
-H 'If-None-Match: "e3b0c44298fc1c149afbf4c8996fb924"'Response (200)
{
"apps": [
{
"app_id": "app_8f591d2d85c5990a",
"id": "80afca3e-c66c-4f07-abbf-78913ee80dff",
"slug": "load-auto-billing",
"display_name": "Load auto-billing",
"description": "Rules app: Load auto-billing",
"version": 1,
"content_hash": "9df5a562cff6d2dfda738f4bf8f88b0b...",
"mode": "rules",
"enabled": true,
"created_at": "2026-08-29T11:31:36.698Z",
"updated_at": "2026-08-29T11:32:32.724Z",
"contract": { "reads": [], "acts": [] },
"output_contract": {
"type": "object",
"required": ["decision"],
"properties": { "decision": { "enum": ["approve", "hold"] } }
},
"input_schema": {
"type": "object",
"properties": {
"input": { "type": "object" },
"idempotency_key": { "type": "string", "maxLength": 64 },
"dry_run": { "type": "boolean" }
}
},
"triggers": { "api": true, "manual": true },
"thresholds": { "auto_execute_above_confidence": 0.9 },
"fallback": "hold",
"display": null,
"endpoints": {
"mcp": "apps/load-auto-billing",
"rest": "/v1/apps/80afca3e-c66c-4f07-abbf-78913ee80dff"
}
}
],
"etag": "\"e51b28d3552a4f39a4c88b1e02e7a3d1\""
}/v1/apps/:idWebhook events
Apps emit workspace webhooks through the standard HMAC-signed delivery pipeline: app.run.completed and app.run.failed when a triggered run reaches a terminal state, app.review.raised and app.review.resolved around Human Reviews, app.version.activated when a publish lands, app.action.execute when a decision executes an action (the payload carries the decision, its evidence locators, rationale, and the action's idempotency key), and app.exception.raised for system-raised exceptions. Subscribe via [POST /v1/webhooks](create-webhook-config) exactly as for document events.