Tasks API
Tasks are the unit of execution inside ainative-business. Each task moves through explicit lifecycle states and can be executed by any configured agent runtime. The Tasks API supports CRUD operations, fire-and-forget execution, real-time log streaming, and workflow-aware sibling queries.
Quick Start
Create a task, queue it, execute it, and stream the logs — a typical integration flow:
// 1. Create a task — starts in "planned" state
interface Task {
id: string;
title: string;
status: string;
priority: number;
projectId?: string;
assignedAgent?: string;
agentProfile?: string;
}
const taskRes: Response = await fetch("http://localhost:3000/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Analyze Q4 revenue trends",
description: "Review revenue data and produce a summary report with charts",
projectId: "proj-8f3a-4b2c",
priority: 1,
assignedAgent: "claude-code",
agentProfile: "data-analyst",
}),
});
const task: Task = await taskRes.json();
// → { id: "task-9d4e...", status: "planned", ... }
// 2. Queue the task, then execute it
await fetch(`http://localhost:3000/api/tasks/${task.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "queued" }),
});
const exec: Response = await fetch(`http://localhost:3000/api/tasks/${task.id}/execute`, { method: "POST" });
// → 202 Accepted — agent is now running in the background
// 3. Stream logs in real time
const logs = new EventSource(`http://localhost:3000/api/tasks/${task.id}/logs`);
logs.onmessage = (e: MessageEvent) => {
const entry: { eventType: string; message: string } = JSON.parse(e.data);
console.log(`[${entry.eventType}] ${entry.message}`);
};
// 4. When done, fetch the result
interface TaskOutput {
taskId: string;
status: string;
result: string;
contentType: string;
}
const outputRes: Response = await fetch(`http://localhost:3000/api/tasks/${task.id}/output`);
const output: TaskOutput = await outputRes.json();
// → { taskId: "task-9d4e...", status: "completed", result: "...", contentType: "markdown" } Base URL
/api/tasks
Endpoints
List Tasks
/api/tasks Retrieve all tasks with optional filtering by project and status. Results are ordered by priority, then newest first.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| projectId | string | — | Filter tasks by project UUID |
| status | enum | — | Filter by lifecycle state (e.g. running) |
Response 200 — Array of task objects
Task Object
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Task identifier |
| title | string | * | Task title |
| description | string | — | Detailed task brief |
| projectId | string (UUID) | — | Associated project |
| status | enum | * | Lifecycle state: planned, queued, running, completed, failed, cancelled |
| priority | number (0–3) | * | 0 = P0 Critical, 3 = P3 Low |
| assignedAgent | string | — | Agent runtime ID |
| agentProfile | string | — | Profile ID for execution |
| createdAt | ISO 8601 | * | Creation timestamp |
| updatedAt | ISO 8601 | * | Last modification timestamp |
Fetch all running tasks for a specific project — useful for building a dashboard or monitoring view:
// Fetch running tasks for a project
const response: Response = await fetch("http://localhost:3000/api/tasks?projectId=proj-8f3a&status=running");
const tasks: Task[] = await response.json();
console.log(`${tasks.length} running tasks`);
tasks.forEach((t: Task) => console.log(` [${t.priority}] ${t.title}`)); Example response:
[
{
"id": "task-9d4e-a1b2",
"title": "Analyze Q4 revenue trends",
"status": "running",
"priority": 1,
"projectId": "proj-8f3a-4b2c",
"assignedAgent": "claude-code",
"agentProfile": "data-analyst",
"createdAt": "2026-04-03T10:30:00.000Z",
"updatedAt": "2026-04-03T10:31:15.000Z"
}
] Create Task
/api/tasks Create a new task. The task starts in planned state. Optionally link documents and assign an agent profile.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| title | string | * | Task title (1–200 characters) |
| description | string | — | Task brief (max 2000 characters) |
| projectId | string (UUID) | — | Project to associate with |
| priority | number | — | Priority level 0–3(default: 2) |
| assignedAgent | enum | — | Agent runtime: claude-code, openai-codex-app-server, anthropic-direct, openai-direct, ollama |
| agentProfile | string | — | Profile ID for agent configuration |
| documentIds | string[] | — | Document UUIDs to attach as context |
Response 201 Created — The created task object with a generated UUID
Create a task with documents attached — the agent will receive these files as context during execution:
// Create a task with attached documents
const response: Response = await fetch("http://localhost:3000/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Summarize meeting notes",
description: "Extract action items and decisions from the uploaded transcript",
projectId: "proj-8f3a-4b2c",
priority: 1,
assignedAgent: "claude-code",
agentProfile: "technical-writer",
documentIds: ["doc-meeting-transcript-001"],
}),
});
const task: Task = await response.json();
console.log(task.id); // "task-a7c2-..."
console.log(task.status); // "planned" Errors: 400 — Zod validation failure or runtime/profile incompatibility
{
"error": {
"formErrors": [],
"fieldErrors": {
"title": ["String must contain at least 1 character(s)"]
}
}
} Get Task
/api/tasks/{id} Retrieve a single task with resolved relationship names and aggregated usage data from the cost ledger.
Response 200 — Task object with enriched fields
Additional Fields (beyond standard task object)
| Field | Type | Req | Description |
|---|---|---|---|
| projectName | string | — | Resolved project name |
| workflowName | string | — | Parent workflow name (if step task) |
| scheduleName | string | — | Parent schedule name (if scheduled) |
| usage.inputTokens | number | — | Total input tokens consumed |
| usage.outputTokens | number | — | Total output tokens consumed |
| usage.totalTokens | number | — | Combined token count |
| usage.costMicros | number | — | Total cost in microdollars (divide by 1,000,000 for USD) |
| usage.modelId | string | — | Model used for execution |
| usage.startedAt | ISO 8601 | — | Execution start time |
| usage.finishedAt | ISO 8601 | — | Execution end time |
The usage field is only present if the task has execution records in the cost ledger. Use costMicros / 1_000_000 to get USD.
// Fetch task with usage data to check cost
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2");
const task: Task & { usage?: { costMicros: number; modelId: string; totalTokens: number } } = await response.json();
if (task.usage) {
const costUSD: number = task.usage.costMicros / 1_000_000;
console.log(`Model: ${task.usage.modelId}`);
console.log(`Tokens: ${task.usage.totalTokens.toLocaleString()}`);
console.log(`Cost: $${costUSD.toFixed(4)}`);
} Example response for a completed task:
{
"id": "task-9d4e-a1b2",
"title": "Analyze Q4 revenue trends",
"status": "completed",
"projectName": "Marketing Analysis",
"usage": {
"inputTokens": 12450,
"outputTokens": 3200,
"totalTokens": 15650,
"costMicros": 48200,
"modelId": "claude-sonnet-4-6-20250514",
"startedAt": "2026-04-03T10:31:15.000Z",
"finishedAt": "2026-04-03T10:32:44.000Z"
}
}Errors: 404 — Task not found
Update Task
/api/tasks/{id} Update task fields. Status changes are validated against the transition table. Document bindings can be replaced atomically.
Request Body (all fields optional)
| Field | Type | Req | Description |
|---|---|---|---|
| title | string | — | Updated title (1–200 chars) |
| description | string | — | Updated brief (max 2000 chars) |
| status | enum | — | New lifecycle state (validated against transitions) |
| priority | number | — | Updated priority (0–3) |
| assignedAgent | enum | — | New agent runtime |
| agentProfile | string | — | New profile ID |
| result | string | — | Execution result text |
| documentIds | string[] | — | Replace all document bindings atomically |
Promote a planned task to the queue and escalate its priority:
// Escalate and queue a task for immediate execution
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "queued", priority: 0 }),
}); Errors: 400 — Invalid status transition (e.g. planned → completed) or validation failure, 404 — Not found
Delete Task
/api/tasks/{id} Permanently remove a task and its associated data.
Response 200 — { "success": true }
// Permanently remove a task
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2", { method: "DELETE" }); Errors: 404 — Task not found
Execute Task
/api/tasks/{id}/execute Start agent execution. Returns 202 immediately — the agent runs asynchronously in the background. Task must be in queued status.
Response 202 Accepted — { "message": "Execution started" }
The agent runtime is selected from the task’s assignedAgent field. If no profile is set, ainative-business auto-classifies one based on the task description. Budget guardrails are enforced before execution begins.
// Fire-and-forget execution — poll status or stream logs to track progress
const res: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/execute", {
method: "POST",
});
if (res.status === 202) {
console.log("Agent is running — stream /logs to follow progress");
} else if (res.status === 429) {
console.log("Budget exceeded — increase budget in Settings");
} Errors:
400— Task not inqueuedstate, or runtime/profile incompatibility404— Task not found429— Budget limit exceeded
Resume Task
/api/tasks/{id}/resume Resume a failed or cancelled task from its last session checkpoint. Requires a previous execution session. Maximum 3 resumes per task.
Response 202 Accepted — { "message": "Resume started" }
Resume picks up the agent conversation from the last checkpoint, preserving context. If the resume limit (3) is reached, re-queue the task for a fresh start instead.
// Resume a failed task — the agent continues where it left off
const res: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/resume", {
method: "POST",
});
if (res.status === 400) {
const err: { error: string } = await res.json();
if (err.error.includes("Resume limit")) {
// Re-queue for fresh execution instead
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "queued" }),
});
}
} Errors:
400— No session to resume, resume limit reached (max 3), or task not in failed/cancelled state404— Task not found429— Budget limit exceeded
Cancel Task
/api/tasks/{id}/cancel Cancel a running or queued task. Sends a cancellation signal to the active agent runtime.
Response 200 — { "success": true }
// Cancel a runaway task
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/cancel", { method: "POST" }); Errors: 404 — Task not found
Respond to Permission Request
/api/tasks/{id}/respond Allow or deny a pending permission request from an executing agent. Optionally save an always-allow rule so the agent won't ask again.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| notificationId | string | * | ID of the pending notification |
| behavior | enum | * | allow or deny |
| message | string | — | Optional message back to the agent |
| updatedInput | object | — | Modified tool input (keys validated against original) |
| alwaysAllow | boolean | — | Save as permanent permission rule |
| permissionPattern | string | — | Glob pattern for the always-allow rule |
Allow a file write permission and remember the decision for future runs:
// Auto-approve file writes to the reports directory
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/respond", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
notificationId: "notif-456",
behavior: "allow",
alwaysAllow: true,
permissionPattern: "write:/reports/*",
}),
}); Response 200 — { "success": true }
Errors: 400 — Validation failure or disallowed input keys, 404 — Notification not found, 409 — Already responded
Get Task Output
/api/tasks/{id}/output Retrieve the execution result with auto-detected content type. The contentType field tells you how to render it.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| taskId | string | * | Task identifier |
| status | string | * | Current task status |
| result | string | * | Execution output text |
| contentType | enum | * | Auto-detected: text, markdown, code, json, or unknown |
// Fetch output and render based on detected content type
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/output");
const output: { taskId: string; status: string; result: string; contentType: string } = await response.json();
switch (output.contentType) {
case "markdown": renderMarkdown(output.result); break;
case "json": renderJSON(JSON.parse(output.result)); break;
case "code": renderCode(output.result); break;
default: renderPlainText(output.result);
} Example response:
{
"taskId": "task-9d4e-a1b2",
"status": "completed",
"result": "## Q4 Revenue Analysis\n\nRevenue grew 23% QoQ...",
"contentType": "markdown"
}Errors: 404 — Task not found
Stream Task Logs
/api/tasks/{id}/logs Real-time Server-Sent Events stream of agent log entries. Polls every 500ms with 15-second keepalive heartbeats.
Response — text/event-stream with JSON log objects
Each event contains a serialized log entry with eventType, message, and timestamp. The stream stays open until the client disconnects or the task completes.
// Stream agent logs in real time
const source = new EventSource("http://localhost:3000/api/tasks/task-9d4e-a1b2/logs");
source.onmessage = (event: MessageEvent) => {
const log: { eventType: string; message: string; timestamp: string } = JSON.parse(event.data);
const time: string = new Date(log.timestamp).toLocaleTimeString();
console.log(`[${time}] [${log.eventType}] ${log.message}`);
};
source.onerror = () => {
console.log("Stream ended — task may have completed");
source.close();
}; Example stream events:
data: {"eventType":"tool_use","message":"Reading file: src/data/revenue.csv","timestamp":"2026-04-03T10:31:16Z"}
data: {"eventType":"assistant","message":"Analyzing quarterly trends...","timestamp":"2026-04-03T10:31:18Z"}
data: {"eventType":"tool_use","message":"Writing report to /reports/q4-analysis.md","timestamp":"2026-04-03T10:31:22Z"} Get Task Provenance
/api/tasks/{id}/provenance Retrieve the execution provenance graph — shows how the task was created, what triggered it, and its lineage chain.
Response 200 — Provenance graph object
Provenance traces the origin of a task: was it created manually, spawned by a workflow, triggered by a schedule, or promoted from AI Assist?
// Trace where a task came from
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/provenance");
const prov: { sourceType: string; parentId: string } = await response.json();
console.log(`Source: ${prov.sourceType}`); // "workflow", "schedule", "manual"
console.log(`Parent: ${prov.parentId}`); Errors: 404 — Task not found
Get Sibling Tasks
/api/tasks/{id}/siblings List other tasks in the same workflow run. Returns an empty array if the task is not part of a workflow.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Sibling task ID |
| title | string | * | Task title |
| status | string | * | Current lifecycle state |
| createdAt | ISO 8601 | * | Creation timestamp |
Useful for building workflow progress views — shows all steps alongside the current task:
// Show workflow progress alongside current task
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/siblings");
const siblings: { id: string; title: string; status: string }[] = await response.json();
siblings.forEach((s) => {
const icon: string = s.status === "completed" ? "✓" : s.status === "running" ? "⟳" : "○";
console.log(`${icon} ${s.title} (${s.status})`);
}); Status Transitions
Tasks follow a strict lifecycle. Invalid transitions return 400.
| From | Allowed Targets |
|---|---|
| planned | queued, cancelled |
| queued | running, planned, cancelled |
| running | completed, failed, cancelled |
| completed | planned |
| failed | planned, queued, running |
| cancelled | planned, running |
Error Format
All validation errors follow the Zod flattened format:
{
"error": {
"formErrors": [],
"fieldErrors": {
"title": ["String must contain at least 1 character(s)"],
"priority": ["Number must be less than or equal to 3"]
}
}
}
Constants
| Constant | Value | Description |
|---|---|---|
| MAX_RESUME_COUNT | 3 | Maximum resumes per task |
| DEFAULT_MAX_TURNS | 50 | Default agent conversation turns |
| DEFAULT_MAX_BUDGET_USD | 2.00 | Default per-task budget |
| WORKFLOW_STEP_MAX_BUDGET_USD | 5.00 | Budget cap for workflow step tasks |