Skip to content

Projects API

All endpoints require authentication and projects tool permissions. Routes are mounted at /api/projects.

Access LevelVisibility
employee / leadProjects where user is owner, delivery manager, or a member
managerOwn projects + squad members’ projects + direct reports’ projects
head / executiveAll projects

GET /api/projects

Permission: projects.view

Query parameters:

ParamTypeDescription
company_idstringFilter by company ID
statusstringFilter by status (comma-separated, e.g. active,completed)
type_idstringFilter by project type config ID (comma-separated)
owner_idstringFilter by owner user ID (comma-separated)
squadstringFilter by squad name (comma-separated)
searchstringSearch by project name or description

Response: { projects }

Each project includes joined fields: company_name, owner_name, delivery_manager_name, type_label, type_value, member_count, and task_count.


GET /api/projects/:id

Permission: projects.view

Response: { project, members, goals }

The project object includes: company_name, owner_name, owner_picture, delivery_manager_name, type_label, type_value, sla_policy_name, sla_response_hours, sla_resolution_hours.

Members include user_id, name, picture, job_title, and role.

Goals are ordered by position then created_at.


POST /api/projects

Permission: projects.update

Body:

{
"name": "string (required)",
"description": "string",
"company_id": "string",
"type_id": "string",
"status": "string (default: active)",
"owner_id": "string (default: current user)",
"delivery_manager_id": "string",
"squad": "string",
"start_date": "string (YYYY-MM-DD)",
"end_date": "string (YYYY-MM-DD)",
"sla_policy_id": "string"
}

Response: 201 { project }

Side effects:

  • Auto-adds the creating user as a member with role owner
  • Logs a created history entry
  • Auto-populates heatmap cell if company_id and project type are set
  • Auto-completes client onboarding milestones for project_created trigger

PUT /api/projects/:id

Permission: projects.update

Body: Any subset of the following fields:

FieldTypeDescription
namestringProject name
descriptionstringProject description
company_idstringLinked company
type_idstringProject type config item ID
statusstringProject status
owner_idstringOwner user ID
delivery_manager_idstringDelivery manager user ID
squadstringSquad name
start_datestringStart date (YYYY-MM-DD)
end_datestringEnd date (YYYY-MM-DD)
metadatastringArbitrary metadata
sla_policy_idstringSLA policy ID
auto_ticket_from_emailstringAuto-ticket email address

Response: { project }

Side effects:

  • Logs an updated history entry
  • Auto-populates heatmap cell on status or company change
  • When status is set to completed and project has a company: auto-creates a draft client win and notifies the assigned delivery manager

DELETE /api/projects/:id

Permission: projects.manage

Response: { ok: true }

Returns 404 if the project does not exist.


GET /api/projects/:id/members

Permission: projects.view

Response: { members }

Each member includes id, role, created_at, user_id, name, picture, and job_title.

POST /api/projects/:id/members

Permission: projects.update

Body:

{
"user_id": "string (required)",
"role": "string (default: member)"
}

Response: 201 { ok: true }

Uses INSERT OR IGNORE so adding an existing member is a no-op. Sends a project_member_added notification to the new member (unless they are the current user).

DELETE /api/projects/:id/members/:userId

Permission: projects.update

Response: { ok: true }


GET /api/projects/:id/goals

Permission: projects.view

Response: { goals }

Goals are ordered by position then created_at.

POST /api/projects/:id/goals

Permission: projects.update

Body:

{
"title": "string (required)",
"description": "string",
"due_date": "string (YYYY-MM-DD)",
"position": "number (default: 0)"
}

Response: 201 { goal }

PUT /api/projects/:id/goals/:goalId

Permission: projects.update

Body: Any subset of:

FieldTypeDescription
titlestringGoal title
descriptionstringGoal description
statusstringGoal status
due_datestringDue date (YYYY-MM-DD)
positionnumberSort position

Response: { goal }

DELETE /api/projects/:id/goals/:goalId

Permission: projects.update

Response: { ok: true }


GET /api/projects/:id/tasks

Permission: projects.view

Response: { tasks }

Returns top-level tasks only (parent_task_id IS NULL) linked to this project via resource_type = 'project'. Each task includes joined fields: assignee_name, assignee_picture, status_label, status_color, status_value, priority_label, priority_color.

Tasks are ordered by sort_order, due_date, then created_at DESC.


GET /api/projects/:id/decisions

Permission: projects.view

Response: { decisions }

Each decision includes decided_by_name. Ordered by decided_at DESC, then created_at DESC.

POST /api/projects/:id/decisions

Permission: projects.update

Body:

{
"title": "string (required)",
"decision": "string (required)",
"rationale": "string",
"decided_by": "string (user ID)",
"decided_at": "string (ISO date)",
"status": "string (default: agreed)"
}

Response: 201 { decision }

PUT /api/projects/:id/decisions/:decisionId

Permission: projects.update

Body: Any subset of: title, decision, rationale, decided_by, decided_at, status.

Response: { decision }

DELETE /api/projects/:id/decisions/:decisionId

Permission: projects.update

Response: { ok: true }


GET /api/projects/:id/profitability

Permission: projects.view

Returns a financial and time summary for the project by aggregating budgets, time entries, and invoices.

Response:

{
"budget_total": 0,
"total_hours": 0,
"billable_hours": 0,
"team_size": 0,
"invoiced": 0,
"collected": 0,
"time_by_member": [
{
"id": "string",
"name": "string",
"picture": "string",
"total_hours": 0,
"billable_hours": 0
}
]
}

GET /api/projects/:id/ai-summary

Permission: projects.view

Response: { summary } where summary is null if no summary has been generated, or:

{
"id": "string",
"project_id": "string",
"summary_text": "string",
"recommendations": ["string", "string", "string"],
"generated_at": "ISO datetime"
}
POST /api/projects/:id/ai-summary

Permission: projects.view

Generates a new AI summary using Workers AI (Kimi K2.5). Gathers project details, members, tasks, and goals to produce a 3-5 sentence summary and 2-3 actionable recommendations. The result is cached in project_ai_summaries (upserted on subsequent calls).

Response: { summary } (same shape as GET)

Returns 503 if the AI binding is not configured. Returns 502 if AI generation fails.


GET /api/projects/:id/deal

Permission: projects.view

Returns the deal linked to this project via the deal_project_links table.

Response: { deal_link } where deal_link is null if no deal is linked, or includes deal_title and deal_status from the joined deal record.