Projects API

Projects organize tasks, workflows, documents, and tables into workspaces. Each project can optionally point to a filesystem working directory, which agents use as their execution context.

Quick Start

Create a project, attach documents as default context, and list tasks within it — the typical setup flow:

// 1. Create a project with a working directory
interface Project {
id: string;
name: string;
description?: string;
workingDirectory?: string;
status: string;
taskCount: number;
}

const projectRes: Response = await fetch("http://localhost:3000/api/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  name: "Marketing Site Redesign",
  description: "Q2 redesign of the company marketing site",
  workingDirectory: "/Users/team/projects/marketing-site",
}),
});
const project: Project = await projectRes.json();
// → { id: "proj-8f3a-4b2c", name: "Marketing Site Redesign", status: "active", ... }

// 2. Upload documents, then bind them as project defaults
//    (any task in this project will receive these files as context)
await fetch(`http://localhost:3000/api/projects/${project.id}/documents`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  documentIds: ["doc-brand-guidelines-v3", "doc-sitemap-current"],
}),
});
// → { updated: 2, projectId: "proj-8f3a-4b2c" }

// 3. Create a task inside this project
interface Task {
id: string;
title: string;
status: string;
}

const taskRes: Response = await fetch("http://localhost:3000/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  title: "Audit current homepage for accessibility",
  projectId: project.id,
  priority: 1,
  assignedAgent: "claude-code",
}),
});
const task: Task = await taskRes.json();

// 4. List all tasks in this project to see progress
const tasksRes: Response = await fetch(`http://localhost:3000/api/tasks?projectId=${project.id}`);
const tasks: Task[] = await tasksRes.json();

console.log(`${tasks.length} tasks in ${project.name}`);
tasks.forEach((t: Task) => console.log(`  [${t.status}] ${t.title}`));

Base URL

/api/projects

Endpoints

List Projects

GET /api/projects

Retrieve all projects with task count aggregation.

Response Body (Array)

FieldTypeReqDescription
idstring (UUID)*Project identifier
namestring*Project name
descriptionstringProject description
workingDirectorystringFilesystem path for agent execution
statusenum*active, paused, or completed
taskCountnumber*Total tasks in this project
createdAtISO 8601*Creation timestamp
updatedAtISO 8601*Last modification

Fetch all projects to build a workspace selector or dashboard overview:

// List all projects with their task counts
const response: Response = await fetch("http://localhost:3000/api/projects");
const projects: Project[] = await response.json();

// Show active projects with pending work
const active: Project[] = projects.filter((p) => p.status === "active" && p.taskCount > 0);
active.forEach((p) => console.log(`${p.name}: ${p.taskCount} tasks`));

Example response:

[
  {
    "id": "proj-8f3a-4b2c",
    "name": "Marketing Site Redesign",
    "description": "Q2 redesign of the company marketing site",
    "workingDirectory": "/Users/team/projects/marketing-site",
    "status": "active",
    "taskCount": 12,
    "createdAt": "2026-03-15T09:00:00.000Z",
    "updatedAt": "2026-04-02T14:22:00.000Z"
  },
  {
    "id": "proj-c7d1-9e4f",
    "name": "API Integration Tests",
    "description": "Automated test suite for third-party API integrations",
    "workingDirectory": "/Users/team/projects/api-tests",
    "status": "active",
    "taskCount": 5,
    "createdAt": "2026-03-20T11:30:00.000Z",
    "updatedAt": "2026-04-01T16:45:00.000Z"
  }
]

Create Project

POST /api/projects

Create a new project. If a working directory is provided, an environment scan runs automatically in the background.

Request Body

FieldTypeReqDescription
namestring*Project name (1–100 characters)
descriptionstringProject description (max 500 characters)
workingDirectorystringFilesystem path (max 500 characters)

Response 201 Created — New project with status: "active"

Errors: 400 — Zod validation failure

Create a project pointing to a local codebase — Stagent automatically scans the directory for environment details (language, framework, dependencies):

// Create a project with a working directory
// The environment scan starts automatically in the background
const response: Response = await fetch("http://localhost:3000/api/projects", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  name: "Marketing Site Redesign",
  description: "Q2 redesign initiative",
  workingDirectory: "/Users/team/projects/marketing-site",
}),
});
const project: Project = await response.json();

console.log(project.id);     // "proj-8f3a-4b2c"
console.log(project.status); // "active"

Example response:

{
  "id": "proj-8f3a-4b2c",
  "name": "Marketing Site Redesign",
  "description": "Q2 redesign initiative",
  "workingDirectory": "/Users/team/projects/marketing-site",
  "status": "active",
  "taskCount": 0,
  "createdAt": "2026-04-03T10:00:00.000Z",
  "updatedAt": "2026-04-03T10:00:00.000Z"
}

Get Project

GET /api/projects/{id}

Retrieve a single project by ID.

Response 200 — Project object

Errors: 404{ "error": "Not found" }

Fetch a specific project to display its details or check status before creating tasks:

// Fetch a project and check if it's ready for work
const response: Response = await fetch("http://localhost:3000/api/projects/proj-8f3a-4b2c");
const project: Project = await response.json();

if (project.status === "active") {
console.log(`${project.name} is active with ${project.taskCount} tasks`);
}

Update Project

PATCH /api/projects/{id}

Update project fields and optionally replace document bindings. Documents are replaced atomically — the old set is removed and the new set is linked.

Request Body (all fields optional)

FieldTypeReqDescription
namestringUpdated name (1–100 chars)
descriptionstringUpdated description (max 500 chars)
workingDirectorystringUpdated path (max 500 chars)
statusenumactive, paused, or completed
documentIdsstring[]Replace all default document bindings

Response 200 — Updated project object

Errors: 400 — Validation failure, 404 — Not found

Pause a project to prevent task auto-execution while keeping all resources intact:

// Pause a project — tasks remain but won't auto-execute
await fetch("http://localhost:3000/api/projects/proj-8f3a-4b2c", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "paused" }),
});

Delete Project

DELETE /api/projects/{id}

Permanently delete a project and all related resources. Cascades through environment scans, chat data, usage records, tasks, workflows, schedules, documents, and tables.

Response 200{ "success": true }

Errors: 404 — Not found, 500 — Cascade delete failure

The cascade deletion order ensures foreign key safety:

  1. Environment data (scans, checkpoints, artifacts, sync ops)
  2. Chat data (messages, conversations)
  3. Usage ledger entries
  4. Task children (logs, notifications, documents, context)
  5. Document defaults, user tables (with rows, columns, triggers, charts)
  6. Direct children (documents, tasks, workflows, schedules)
  7. Project record

Delete a project and everything inside it — this is irreversible:

// WARNING: Cascade deletes all tasks, workflows, schedules, and documents
const res: Response = await fetch("http://localhost:3000/api/projects/proj-8f3a-4b2c", {
method: "DELETE",
});
const { success }: { success: boolean } = await res.json();
console.log(success ? "Project deleted" : "Delete failed");

List Project Documents

GET /api/projects/{id}/documents

List all default document bindings for a project with full document metadata.

Response Body (Array)

FieldTypeReqDescription
idstring (UUID)*Document ID
originalNamestring*Original filename
filenamestring*Stored filename
mimeTypestring*MIME type
sizenumber*File size in bytes
directionstring*input or output
statusstring*Processing status
categorystringDocument category

Retrieve all documents bound to a project — these are automatically available as context for every task in the project:

// List project documents to verify context is configured
const response: Response = await fetch("http://localhost:3000/api/projects/proj-8f3a-4b2c/documents");
const docs: { direction: string; originalName: string; size: number }[] = await response.json();

docs.forEach((d) => {
const sizeKB: string = (d.size / 1024).toFixed(1);
console.log(`[${d.direction}] ${d.originalName} (${sizeKB} KB)`);
});

Example response:

[
  {
    "id": "doc-brand-guidelines-v3",
    "originalName": "brand-guidelines-v3.pdf",
    "filename": "brand-guidelines-v3-1712145600.pdf",
    "mimeType": "application/pdf",
    "size": 245760,
    "direction": "input",
    "status": "processed",
    "category": "reference"
  }
]

Replace Project Documents

PUT /api/projects/{id}/documents

Replace all default document bindings for a project. Previous bindings are removed and new ones are created atomically.

Request Body

FieldTypeReqDescription
documentIdsstring[]*Array of document UUIDs to bind

Response Body

FieldTypeReqDescription
updatednumber*Count of new bindings created
projectIdstring (UUID)*Project ID

Errors: 400 — documentIds not an array, 404 — Project not found

Replace the entire set of default documents — previous bindings are removed first, then new ones are created:

// Atomically replace project document bindings
const response: Response = await fetch("http://localhost:3000/api/projects/proj-8f3a-4b2c/documents", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
  documentIds: ["doc-brand-guidelines-v3", "doc-sitemap-current"],
}),
});
const { updated }: { updated: number } = await response.json();

console.log(`Bound ${updated} documents to project`);

Project Statuses

StatusDescription
activeDefault state. Tasks can be executed.
pausedWork suspended. Tasks remain but won’t auto-execute.
completedWork finished. Project archived.