Apps API
Apps are composed views — each one is a YAML manifest under ~/.ainative/apps/ that bundles agent profiles, workflow blueprints, tables, and schedules into a kit-typed surface (tracker, coach, inbox, research, ledger, or workflow-hub). The Apps API exposes the registry that other surfaces (the /apps page, the chat App Builder, the workflow runner) read from. Apps are built from the chat composer; the API today is read-only with one cascading delete.
Quick Start
Discover the available apps, look up a specific one’s full manifest, and remove an app along with every primitive it uniquely owns — the typical shell of an app-management surface:
// 1. List every app in the registry (lightweight summary)
interface AppSummary {
id: string;
name: string;
description: string | null;
primitivesSummary: string;
profileCount: number;
blueprintCount: number;
tableCount: number;
scheduleCount: number;
scheduleHuman: string | null;
rootDir: string;
createdAt: number;
files: string[];
}
const apps: AppSummary[] = await fetch("http://localhost:3000/api/apps").then((r) => r.json());
// → [{ id: "tracker-revenue", name: "Revenue Tracker", primitivesSummary: "1 profile · 2 blueprints · 1 table · daily 9am", ... }, ...]
// 2. Pick one and load the full manifest detail
const targetId: string = apps[0].id;
interface AppDetail extends AppSummary {
manifest: {
id: string;
name: string;
description?: string;
persona?: string;
profiles: Array<{ id: string; source?: string }>;
blueprints: Array<{ id: string; source?: string; trigger?: { kind: "row-insert"; table: string } }>;
tables: Array<{ id: string; source?: string }>;
schedules: Array<{ id: string; source?: string }>;
view?: { kit: "auto" | "tracker" | "coach" | "inbox" | "research" | "ledger" | "workflow-hub" };
};
}
const detail: AppDetail = await fetch(`http://localhost:3000/api/apps/${targetId}`).then((r) => r.json());
console.log(`${detail.manifest.name} → kit: ${detail.manifest.view?.kit ?? "auto"}`);
// 3. Remove the app and every uniquely-owned primitive (cascade)
const cascade: { success: true; filesRemoved: number; projectRemoved: boolean; profilesRemoved: number; blueprintsRemoved: number } =
await fetch(`http://localhost:3000/api/apps/${targetId}`, { method: "DELETE" }).then((r) => r.json());
console.log(`Removed ${cascade.filesRemoved} files, ${cascade.profilesRemoved} profiles, ${cascade.blueprintsRemoved} blueprints`); Base URL
/api/apps
Endpoints
List Apps
/api/apps List every app installed in the local registry (~/.ainative/apps/). Returns a lightweight AppSummary for each — full manifest detail comes from the per-id endpoint.
Response Body (Array of AppSummary)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string | * | App identifier (manifest `id` field — slug-style, e.g., "tracker-revenue") |
| name | string | * | Human-readable app name |
| description | string | null | — | Optional manifest description |
| rootDir | string | * | Absolute filesystem path to the app directory |
| primitivesSummary | string | * | Compact human summary, e.g., "1 profile · 2 blueprints · 1 table · daily 9am" |
| profileCount | number | * | Number of profiles bound by this app |
| blueprintCount | number | * | Number of blueprints bound by this app |
| tableCount | number | * | Number of tables bound by this app |
| scheduleCount | number | * | Number of schedules bound by this app |
| scheduleHuman | string | null | — | Humanized cron string from the first schedule, if any (e.g., "daily 9am", "Monday 7:30am") |
| createdAt | number (epoch ms) | * | Manifest creation timestamp |
| files | string[] | * | Filenames inside the app directory (manifest.yaml plus any sidecar artifacts) |
Errors: 500 — Registry read failure (corrupt manifest, missing apps directory)
Build the App Library page or a kit picker — sort by recency and group by kit:
// List apps and surface the most recent ones first
const apps: AppSummary[] = await fetch("http://localhost:3000/api/apps").then((r) => r.json());
const recent = [...apps].sort((a, b) => b.createdAt - a.createdAt).slice(0, 5);
recent.forEach((a) => {
console.log(`${a.name} — ${a.primitivesSummary}`);
}); Example response:
[
{
"id": "tracker-revenue",
"name": "Revenue Tracker",
"description": "Daily revenue rollup with month-over-month comparison.",
"rootDir": "/Users/team/.ainative/apps/tracker-revenue",
"primitivesSummary": "1 profile · 2 blueprints · 1 table · daily 9am",
"profileCount": 1,
"blueprintCount": 2,
"tableCount": 1,
"scheduleCount": 1,
"scheduleHuman": "daily 9am",
"createdAt": 1746028800000,
"files": ["manifest.yaml", "profiles/analyst.md", "blueprints/daily-rollup.yaml"]
},
{
"id": "coach-onboarding",
"name": "New-Hire Coach",
"description": "Two-week onboarding workflow with weekly check-ins.",
"rootDir": "/Users/team/.ainative/apps/coach-onboarding",
"primitivesSummary": "1 profile · 1 blueprint · Monday 9am",
"profileCount": 1,
"blueprintCount": 1,
"tableCount": 0,
"scheduleCount": 1,
"scheduleHuman": "Monday 9am",
"createdAt": 1745510400000,
"files": ["manifest.yaml", "profiles/coach.md", "blueprints/weekly-checkin.yaml"]
}
] Get App
/api/apps/{id} Return the full AppDetail for a single app — same fields as the list endpoint, plus the parsed `manifest` object with profile/blueprint/table/schedule references and the kit-typed view spec.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| id | string (path) | * | App identifier from the list endpoint |
Response 200 — AppDetail (extends AppSummary with a manifest object)
manifest (subset of fields most clients use)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string | * | Manifest id (matches path id) |
| name | string | * | Display name |
| description | string | — | Manifest description |
| persona | string | — | Optional persona description for the kit-rendered view |
| profiles | AppArtifactRef[] | * | Bound agent profiles — each ref carries an id and an optional `source` filename |
| blueprints | AppBlueprintRef[] | * | Bound workflow blueprints — refs may carry a `trigger: { kind: "row-insert", table: string }` |
| tables | AppArtifactRef[] | * | Bound tables |
| schedules | AppArtifactRef[] | * | Bound schedules |
| permissions | object | — | Optional `{ preset: string }` referencing a permission preset |
| view | ViewSpec | — | Kit-typed view spec — discriminated by `kit: "auto" | "tracker" | "coach" | "inbox" | "research" | "ledger" | "workflow-hub"` |
Errors: 404 — App not found
Render the app detail surface — pick a kit-specific component based on the manifest’s view.kit:
// Render the right kit component for the app
type Kit = "auto" | "tracker" | "coach" | "inbox" | "research" | "ledger" | "workflow-hub";
const detail = await fetch("http://localhost:3000/api/apps/tracker-revenue").then((r) => r.json());
const kit: Kit = detail.manifest.view?.kit ?? "auto";
// Discriminate on kit to pick the renderer (kit-specific binding shapes live in each resolver)
switch (kit) {
case "tracker":
console.log(`Render TrackerKit for ${detail.manifest.name}`);
break;
case "coach":
console.log(`Render CoachKit for ${detail.manifest.name}`);
break;
default:
console.log(`Render generic AppView for ${detail.manifest.name}`);
} Example response:
{
"id": "tracker-revenue",
"name": "Revenue Tracker",
"description": "Daily revenue rollup with month-over-month comparison.",
"rootDir": "/Users/team/.ainative/apps/tracker-revenue",
"primitivesSummary": "1 profile · 2 blueprints · 1 table · daily 9am",
"profileCount": 1,
"blueprintCount": 2,
"tableCount": 1,
"scheduleCount": 1,
"scheduleHuman": "daily 9am",
"createdAt": 1746028800000,
"files": ["manifest.yaml", "profiles/analyst.md", "blueprints/daily-rollup.yaml"],
"manifest": {
"id": "tracker-revenue",
"name": "Revenue Tracker",
"description": "Daily revenue rollup with month-over-month comparison.",
"profiles": [{ "id": "analyst", "source": "profiles/analyst.md" }],
"blueprints": [
{ "id": "daily-rollup", "source": "blueprints/daily-rollup.yaml", "trigger": { "kind": "row-insert", "table": "revenue-events" } },
{ "id": "month-compare", "source": "blueprints/month-compare.yaml" }
],
"tables": [{ "id": "revenue-events" }],
"schedules": [{ "id": "daily-9am" }],
"permissions": { "preset": "git-safe" },
"view": { "kit": "tracker" }
}
} Delete App (Cascade)
/api/apps/{id} Remove an app and every primitive it uniquely owns. The cascade deletes the app's files, the matching project (if any), and any profiles or blueprints that aren't shared with another app. Returns counts so the UI can confirm what was actually removed.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| id | string (path) | * | App identifier to remove |
Response 200 — Cascade summary
Cascade summary
| Field | Type | Req | Description |
|---|---|---|---|
| success | true | * | Always `true` when at least one artifact was removed |
| filesRemoved | number | * | Files deleted from the app root directory |
| projectRemoved | boolean | * | Whether the matching project (id === app id) was deleted |
| profilesRemoved | number | * | Profiles deleted because no other app referenced them |
| blueprintsRemoved | number | * | Blueprints deleted because no other app referenced them |
Errors: 400 — App id missing · 404 — Nothing to remove (app does not exist or already cleaned up) · 500 — Cascade failure (one or more removals threw)
Confirm the cascade in the UI before calling — once removed, an app’s manifest and uniquely-owned profiles cannot be restored without re-installing:
// Cascading delete — confirm what got removed for the toast
const result: { success: true; filesRemoved: number; projectRemoved: boolean; profilesRemoved: number; blueprintsRemoved: number } =
await fetch("http://localhost:3000/api/apps/tracker-revenue", { method: "DELETE" }).then((r) => r.json());
const parts: string[] = [];
if (result.filesRemoved) parts.push(`${result.filesRemoved} files`);
if (result.projectRemoved) parts.push("project");
if (result.profilesRemoved) parts.push(`${result.profilesRemoved} profiles`);
if (result.blueprintsRemoved) parts.push(`${result.blueprintsRemoved} blueprints`);
console.log(`Removed: ${parts.join(", ")}`); Example response:
{
"success": true,
"filesRemoved": 3,
"projectRemoved": true,
"profilesRemoved": 1,
"blueprintsRemoved": 2
} Kit Reference
The view.kit field on the manifest determines which renderer the app surface uses. Kit selection drives layout, not data — bindings (tables, blueprints, schedules) live alongside view, not inside it.
| Kit | Use case | Typical bindings |
|---|---|---|
tracker | KPI dashboard with rolling rollups | 1 table (events) + 1-2 blueprints + 1 schedule |
coach | Multi-step coaching workflow | 1 profile + 1 blueprint + 1 schedule (weekly cadence) |
inbox | Triage queue with HIL approvals | 1 table (queue) + 1 blueprint (intake) + permissions preset |
research | Long-running investigation with documents | 1 profile + 1 blueprint + tables for hypotheses/findings |
ledger | Append-only entries with totals | 1 table (entries) + computed columns |
workflow-hub | Multi-blueprint launcher | 0 tables, 3+ blueprints |
auto | Default — generic AppView with no kit-specific layout | any |
Manifest Authoring
Manifests are authored from the chat App Builder, not via API — there is no POST /api/apps endpoint. The builder writes a YAML file under ~/.ainative/apps/{id}/manifest.yaml and refreshes the registry cache. The Apps API endpoints listed above only read and delete; for creation flows, use the chat conversation builder documented in the Chat API.