Create an App
Create a decision app with POST /v1/apps, or draft one from a plain-language description via POST /v1/apps/proposals: name, mode, rules, and access hints.
POST /v1/apps creates an app shell: a workspace-unique slug, a display_name, a mode (rules, assisted, or external), and optionally a one-sentence description (up to 2000 chars) that the gallery card shows. The response is the app's manifest with version: null — nothing decides yet. You then save logic as a draft version (PUT /v1/apps/:id/logic), publish it, and enable the app. The slug follows the grammar [a-z0-9][a-z0-9-]{1,62}[a-z0-9] (3–64 chars) and becomes immutable at the first publish; display_name renames freely at any time.
The describe-first path is POST /v1/apps/proposals: send a plain-language description (20–2000 chars) of the decision you want automated, and the platform compiles it into a structured proposal — suggested name and slug, a mode, the policy restated as rule sentences with parameters, a drafted output contract, trigger defaults, the inputs it thinks the app needs, access_needed hints (which data products or reference tables to grant), and open_questions where your description was ambiguous. Nothing persists: the proposal is a draft on the wire, and the client turns an accepted proposal into a real app via POST /v1/apps plus PUT /:id/logic.
Requests to either endpoint need the configure tier (a workspace owner session; an operate grant or legacy write scope for keys). A duplicate slug is rejected with 409 and the message naming the taken slug, so creation is safe to retry with a new slug. Build on rules, assisted, or external: the schema also knows a fourth mode, autonomous, but its content validation admits only one narrow platform-defined logic profile, so a draft or publish declaring autonomous with any other logic is rejected with 422.
Deletion is the reverse ceremony, deliberately heavier: DELETE /v1/apps/:id requires the owner tier and is refused while the app is enabled or while other apps consume its output as an input binding (check GET /v1/apps/:id/dependents first). Deleting removes configuration only — the run journal and sealed decision records survive by design, carrying no foreign key to the app.
/v1/appsBody
curl
curl -s -X POST https://api.talonic.com/v1/apps \
-H "Authorization: Bearer tlnc_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"slug": "invoice-approval",
"display_name": "Invoice approval",
"mode": "rules",
"description": "Holds supplier invoices that drift from their purchase order."
}'Response (201)
{
"app_id": "app_8f591d2d85c5990a",
"id": "80afca3e-c66c-4f07-abbf-78913ee80dff",
"slug": "invoice-approval",
"display_name": "Invoice approval",
"description": "Holds supplier invoices that drift from their purchase order.",
"version": null,
"content_hash": null,
"mode": "rules",
"enabled": false,
"contract": null,
"output_contract": null,
"triggers": null,
"thresholds": null,
"fallback": null,
"display": null,
"endpoints": {
"mcp_tool": "app_invoice_approval",
"rest": "/v1/apps/80afca3e-c66c-4f07-abbf-78913ee80dff"
}
}/v1/apps/proposalsBody
Response (201) — proposal excerpt
{
"proposal": {
"display_name": "Invoice PO Check",
"slug": "invoice-po-check",
"mode": "rules",
"summary": "The app checks every supplier invoice against its purchase order and holds any invoice where the billed amount differs from the purchase order amount by more than 2 percent.",
"policy_sentences": [
"IF the billed amount on a supplier invoice differs from its purchase order amount by more than 2 percent THEN hold the invoice."
],
"rules": [
{
"sentence": "IF the billed amount on a supplier invoice differs from its purchase order amount by more than 2 percent THEN hold the invoice.",
"applies_to": null,
"params": { "threshold": 0.02 }
}
],
"fallback": "hold",
"output_contract": {
"type": "object",
"required": ["decision"],
"properties": { "decision": { "enum": ["approve", "hold"] }, "note": { "type": "string" } }
},
"triggers": { "manual": true, "api": true },
"inputs_needed": ["supplier invoices", "purchase orders"],
"access_needed": [
{ "resource_type": "data_product", "hint": "supplier invoices" },
{ "resource_type": "reference_table", "hint": "purchase orders" }
],
"open_questions": [
"How is the percentage difference calculated (relative to the billed or the purchase order amount)?",
"How does a purchase order match to an invoice (by PO number, supplier, or line)?"
]
}
}Answer the open_questions before you publish — they are exactly the ambiguities that would otherwise surface later as indeterminate card results or unexpected holds. Feed the answers back either by refining the rule sentences before compiling, or as guidance on POST /v1/apps/:id/logic/compile.
Export, import, and duplicate
Apps are portable. GET /v1/apps/:id/export returns an environment-free manifest of the app's active version: export_version: 1, the app's slug, display_name, and mode, the version number, and the published content unmodified — no grants, keys, install state, or endpoints, so nothing in it is tied to one workspace. POST /v1/apps/import turns such a document into a new app (it creates the app and saves the content as its draft, with mode defaulting from the content), and POST /v1/apps/:id/duplicate does both in one step inside a workspace, defaulting the copy's slug to <slug>-copy and its name to <display_name> (copy). All three need the configure tier; export and duplicate answer 409 while the app has no published version, and every copy arrives as an unpublished draft whose grants you set again.
curl — move an app between workspaces
# Export from the source workspace
curl -s https://api.talonic.com/v1/apps/$APP_ID/export \
-H "Authorization: Bearer $SOURCE_KEY" > invoice-approval.json
# → { "export_version": 1, "app": { "slug": "invoice-approval", "display_name": "Invoice approval",
# "mode": "rules" }, "version": 3, "content": { ... } }
# Import into the target workspace (body: slug, display_name, content, export_version; mode optional)
jq '{ slug: .app.slug, display_name: .app.display_name, content, export_version }' invoice-approval.json \
| curl -s -X POST https://api.talonic.com/v1/apps/import \
-H "Authorization: Bearer $TARGET_KEY" -H "Content-Type: application/json" -d @-
# → { "app": { ...manifest, "version": null }, "draft": { "version": 1, "status": "draft", "content_hash": "…" } }
# Or copy within one workspace
curl -s -X POST https://api.talonic.com/v1/apps/$APP_ID/duplicate \
-H "Authorization: Bearer tlnc_your_api_key" -H "Content-Type: application/json" \
-d '{"slug": "invoice-approval-strict"}'