# REST API reference

> Versioned /api/v1 paths, auth cookies and bearer tokens, response envelopes, pagination, and implemented endpoint catalog by resource.

- Repository: Paca-AI/paca
- GitHub: https://github.com/Paca-AI/paca
- Human docs: https://grok-wiki.com/public/docs/paca-ai-paca-f238b2ab3d25
- Complete Markdown: https://grok-wiki.com/public/docs/paca-ai-paca-f238b2ab3d25/llms-full.txt

## Source Files

- `docs/api/http-design.md`
- `docs/api/README.md`
- `services/api/internal/transport/http/router/router.go`
- `services/api/internal/transport/http/handler/auth_handler.go`
- `services/api/internal/transport/http/presenter/response.go`
- `services/api/internal/transport/http/middleware/authz.go`

---

---
title: "REST API reference"
description: "Versioned /api/v1 paths, auth cookies and bearer tokens, response envelopes, pagination, and implemented endpoint catalog by resource."
---

The Paca API is served by `services/api` (Gin) and exposed at `/api` through the nginx gateway. Product routes live under `/api/v1`; infrastructure probes use `/api/healthz`. All `/api/v1` JSON responses use a standard envelope with a per-request `request_id` echoed in the `X-Request-ID` response header.

## Base URL and routing

| Surface | Path prefix | Notes |
|---|---|---|
| Health | `/api/healthz` | Minimal `{ "status": "ok" }` — no envelope |
| Product API | `/api/v1` | Standard success/error envelope |
| Plugin proxy | `/api/v1/plugins/:pluginId/*` | Forwards to WASM plugin handlers per manifest |

In Docker Compose, the gateway listens on port **80** and forwards `/api/` unchanged to the API service (`api:8080`).

<Info>
Resource paths use plural nouns and UUID identifiers (`:projectId`, `:taskId`, …). Nested resources express ownership (`/projects/:projectId/tasks/:taskId`).
</Info>

## Authentication

Paca supports three credential sources, checked in order:

1. **HttpOnly cookie** `access_token` (set by login/refresh)
2. **`Authorization: Bearer <access-jwt>`** for CLI and integrations
3. **API key** via `Authorization: ApiKey <key>` or `X-API-Key: <key>`

Refresh tokens are stored in the HttpOnly `refresh_token` cookie scoped to path `/api/v1/auth/refresh` only.

### Session auth (web UI)

:::endpoint POST /api/v1/auth/login
Validate username/password and set `access_token` + `refresh_token` cookies. Token values are never returned in the response body.
:::

<ParamField body="username" type="string" required>
Account username (not email).
</ParamField>

<ParamField body="password" type="string" required>
Plain-text password.
</ParamField>

<ParamField body="remember_me" type="boolean">
When `true`, extends refresh-token lifetime for a persistent session.
</ParamField>

<RequestExample>
```bash Login (session cookies)
curl -c cookies.txt -X POST https://your-host/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"secret","remember_me":false}'
```
</RequestExample>

<ResponseExample>
```json Success
{
  "success": true,
  "data": { "message": "logged in" },
  "request_id": "9a1d7c2b-..."
}
```
</ResponseExample>

:::endpoint POST /api/v1/auth/refresh
Read `refresh_token` from cookie, rotate both tokens, write new cookies. No request body.
:::

:::endpoint POST /api/v1/auth/logout
Requires authentication. Revokes the session family and clears both auth cookies.
:::

### Bearer token auth (API clients)

<RequestExample>
```bash Authenticated request
curl https://your-host/api/v1/users/me \
  -H "Authorization: Bearer <access-jwt>"
```
</RequestExample>

### API key auth

User-created API keys authenticate like JWTs for most routes. Create keys via session auth only (API keys cannot manage other API keys).

<RequestExample>
```bash API key request
curl https://your-host/api/v1/projects \
  -H "Authorization: ApiKey paca_xxxxxxxx"
```
</RequestExample>

<ResponseField name="key" type="string">
Returned once from `POST /api/v1/users/me/api-keys`. Not retrievable afterward.
</ResponseField>

### Agent API key mode

When using the static agent API key, pass `X-Agent-ID: <agent-uuid>` so authorization resolves agent-scoped project permissions instead of the key owner's user permissions.

### Fresh password gate

Most routes require a **fresh** access token: `must_change_password` must be `false` in the JWT. When `true`, protected routes return `403` with `AUTH_PASSWORD_CHANGE_REQUIRED`. The exception is `PATCH /api/v1/users/me/password`, which is always reachable so forced password changes can be completed.

## Authorization

Authorization is **permission-based**, enforced per route in middleware. Permissions are resolved from:

- Legacy role compatibility (`ADMIN` / `USER`)
- Assigned global roles
- Project-scoped roles (when a `:projectId` is present)

<Warning>
Permission strings in API enforcement use dotted names such as `project.members.read` and `project.roles.write`, not shortened aliases.
</Warning>

### Global permissions

| Permission | Grants |
|---|---|
| `users.read` / `users.write` / `users.delete` | Admin user management |
| `global_roles.read` / `global_roles.write` / `global_roles.assign` | Global role CRUD and assignment |
| `projects.create` | Create projects |
| `projects.read` / `projects.write` / `projects.delete` | Global project access |
| `*` | Superuser (ADMIN role default) |

### Project-scoped permissions

| Permission | Grants |
|---|---|
| `project.members.read` / `project.members.write` | Member list and mutations |
| `project.roles.read` / `project.roles.write` | Custom project roles |
| `tasks.read` / `tasks.write` | Task configuration and work items |
| `sprints.read` / `sprints.write` | Sprints and interaction views |
| `docs.read` / `docs.write` | Project documentation |
| `agents.read` / `agents.write` | AI agents and conversations |

Wildcard forms (`users.*`, `tasks.*`, `agents.*`, …) satisfy any permission in that domain.

### Public projects

Read-only project routes use optional authentication. When `is_public = true` on a project, unauthenticated callers can read permitted resources (tasks, docs, members, etc.) without credentials. Write operations always require authentication and the matching permission.

## Response envelope

### Success (`200` / `201`)

```json
{
  "success": true,
  "data": {},
  "request_id": "9a1d7c2b-..."
}
```

`204 No Content` responses (logout-adjacent deletes, some mutations) return an empty body with no envelope.

### Error

```json
{
  "success": false,
  "error_code": "TASK_NOT_FOUND",
  "error": "descriptive message",
  "request_id": "9a1d7c2b-..."
}
```

Clients should branch on `error_code`, not HTTP status or message text alone. Internal errors (`500`) return a generic message while details are logged server-side.

## Pagination and filtering

Paca uses two pagination models.

### Offset pagination

Used by `GET /api/v1/projects` and `GET /api/v1/admin/users`.

<ParamField query="page" type="integer" default="1">
1-based page number.
</ParamField>

<ParamField query="page_size" type="integer" default="20">
Items per page. Values outside `1–100` clamp to `20`.
</ParamField>

```json
{
  "success": true,
  "data": {
    "items": [],
    "total": 42,
    "page": 1,
    "page_size": 20
  },
  "request_id": "..."
}
```

### Cursor pagination (tasks)

Used by `GET /api/v1/projects/:projectId/tasks`.

<ParamField query="page_size" type="integer" default="20">
Items per page. Values outside `1–200` clamp to `20`.
</ParamField>

<ParamField query="cursor" type="string">
Opaque cursor from a previous response's `next_cursor`. Omit for the first page.
</ParamField>

```json
{
  "success": true,
  "data": {
    "items": [],
    "page_size": 20,
    "next_cursor": "encoded-cursor-or-null"
  },
  "request_id": "..."
}
```

### Task list filters

| Query param | Description |
|---|---|
| `sprint_id` | UUID, or `null` for backlog-only tasks |
| `sprint_ids` | Comma-separated UUIDs |
| `status_id` / `status_ids` | Filter by workflow status |
| `assignee_id` / `assignee_ids` | Filter by assignee; `assignee_id=null` for unassigned |
| `task_type_id` / `task_type_ids` | Filter by task type |
| `parent_task_id` | Child tasks of a parent |
| `view_id` | Enriches each task with `view_position` and `view_group_key` from manual ordering |

### View context query param

Interaction views share one path with a required context:

<ParamField query="context" type="string" default="sprint">
One of `sprint`, `backlog`, or `timeline`.
</ParamField>

<ParamField query="sprint_id" type="uuid" required>
Required when `context=sprint`.
</ParamField>

## Endpoint catalog

Paths are relative to `/api/v1`. **Auth** column abbreviations: `—` = public; `session` = cookie or Bearer JWT; `key` = API key also accepted; `fresh` = session/key + fresh password; `perm:<name>` = requires that permission (global or project-scoped as routed).

### Health

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/api/healthz` | — | Liveness probe |

### Auth

| Method | Path | Auth | Description |
|---|---|---|---|
| `POST` | `/auth/login` | — | Login; set cookies |
| `POST` | `/auth/refresh` | — | Rotate tokens from refresh cookie |
| `POST` | `/auth/logout` | session | Revoke session; clear cookies |

### Users (self-service)

| Method | Path | Auth | Description |
|---|---|---|---|
| `PATCH` | `/users/me/password` | session/key | Change own password |
| `GET` | `/users/me` | fresh | Own profile |
| `PATCH` | `/users/me` | fresh | Update `full_name` |
| `GET` | `/users/me/global-permissions` | fresh | Effective global permissions |
| `GET` | `/users/me/api-keys` | fresh + JWT only | List API keys |
| `POST` | `/users/me/api-keys` | fresh + JWT only | Create API key (raw key returned once) |
| `DELETE` | `/users/me/api-keys/:keyId` | fresh + JWT only | Revoke API key |
| `GET` | `/users/me/notifications` | fresh | Recent notifications + unread count |
| `PATCH` | `/users/me/notifications/:notificationId/read` | fresh | Mark one read |
| `POST` | `/users/me/notifications/read-all` | fresh | Mark all read |

### Admin — users and global roles

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/admin/users` | fresh + `users.read` | Paginated user list |
| `POST` | `/admin/users` | fresh + `users.write` | Create user (`must_change_password=true`) |
| `GET` | `/admin/users/:userId` | fresh + `users.read` | User by ID |
| `PATCH` | `/admin/users/:userId` | fresh + `users.write` | Update `full_name` or role |
| `PATCH` | `/admin/users/:userId/password` | fresh + `users.write` | Admin password reset |
| `DELETE` | `/admin/users/:userId` | fresh + `users.delete` | Soft-delete user |
| `GET` | `/admin/global-roles` | fresh + `global_roles.read` | List global roles |
| `POST` | `/admin/global-roles` | fresh + `global_roles.write` | Create global role |
| `PATCH` | `/admin/global-roles/:roleId` | fresh + `global_roles.write` | Update global role |
| `DELETE` | `/admin/global-roles/:roleId` | fresh + `global_roles.write` | Delete global role |
| `PUT` | `/admin/users/:userId/global-roles` | fresh + `global_roles.assign` | Replace user's global role |

### Projects

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects` | fresh | List projects (global `projects.read` → all; else member projects) |
| `POST` | `/projects` | fresh + `projects.create` | Create project (seeds backlog + timeline views) |
| `GET` | `/projects/:projectId` | optional + read perm or public | Project detail |
| `PATCH` | `/projects/:projectId` | fresh + `projects.write` | Update name/description |
| `DELETE` | `/projects/:projectId` | fresh + `projects.delete` | Delete project |

### Project members and roles

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/members` | optional + read or public | List members |
| `POST` | `/projects/:projectId/members` | fresh + `project.members.write` | Add member |
| `GET` | `/projects/:projectId/members/me/permissions` | fresh | Caller's project permissions |
| `PATCH` | `/projects/:projectId/members/:memberId` | fresh + `project.members.write` | Change member role |
| `DELETE` | `/projects/:projectId/members/:memberId` | fresh + `project.members.write` | Remove member |
| `GET` | `/projects/:projectId/roles` | optional + read or public | List project roles |
| `POST` | `/projects/:projectId/roles` | fresh + `project.roles.write` | Create project role |
| `PATCH` | `/projects/:projectId/roles/:roleId` | fresh + `project.roles.write` | Update project role |
| `DELETE` | `/projects/:projectId/roles/:roleId` | fresh + `project.roles.write` | Delete project role |

### Task configuration

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/task-types` | optional + `tasks.read` or public | List task types |
| `POST` | `/projects/:projectId/task-types` | fresh + `tasks.write` | Create task type |
| `PATCH` | `/projects/:projectId/task-types/:typeId` | fresh + `tasks.write` | Update task type |
| `DELETE` | `/projects/:projectId/task-types/:typeId` | fresh + `tasks.write` | Delete task type |
| `PUT` | `/projects/:projectId/task-types/:typeId/set-default` | fresh + `tasks.write` | Set default task type |
| `GET` | `/projects/:projectId/task-statuses` | optional + `tasks.read` or public | List statuses |
| `POST` | `/projects/:projectId/task-statuses` | fresh + `tasks.write` | Create status |
| `PATCH` | `/projects/:projectId/task-statuses/:statusId` | fresh + `tasks.write` | Update status |
| `DELETE` | `/projects/:projectId/task-statuses/:statusId` | fresh + `tasks.write` | Delete status |
| `PUT` | `/projects/:projectId/task-statuses/:statusId/set-default` | fresh + `tasks.write` | Set default status |
| `GET` | `/projects/:projectId/custom-fields` | optional + `tasks.read` or public | List custom field definitions |
| `POST` | `/projects/:projectId/custom-fields` | fresh + `tasks.write` | Create custom field |
| `GET` | `/projects/:projectId/custom-fields/:fieldId` | optional + `tasks.read` or public | Get custom field |
| `PATCH` | `/projects/:projectId/custom-fields/:fieldId` | fresh + `tasks.write` | Update custom field |
| `DELETE` | `/projects/:projectId/custom-fields/:fieldId` | fresh + `tasks.write` | Delete custom field |

### Sprints

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/sprints` | optional + `sprints.read` or public | List sprints |
| `POST` | `/projects/:projectId/sprints` | fresh + `sprints.write` | Create sprint (seeds default views) |
| `GET` | `/projects/:projectId/sprints/:sprintId` | optional + `sprints.read` or public | Sprint detail |
| `PATCH` | `/projects/:projectId/sprints/:sprintId` | fresh + `sprints.write` | Update metadata/status |
| `DELETE` | `/projects/:projectId/sprints/:sprintId` | fresh + `sprints.write` | Delete sprint |
| `POST` | `/projects/:projectId/sprints/:sprintId/complete` | fresh + `sprints.write` | Complete sprint; move incomplete tasks |

### Interaction views

All view routes accept `?context=sprint|backlog|timeline` (plus `sprint_id` when `context=sprint`).

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/views` | optional + `sprints.read` or public | List views for context |
| `POST` | `/projects/:projectId/views` | fresh + `sprints.write` | Create view |
| `PUT` | `/projects/:projectId/views/positions` | fresh + `sprints.write` | Reorder all views in context |
| `GET` | `/projects/:projectId/views/:viewId` | optional + `sprints.read` or public | Get view |
| `PATCH` | `/projects/:projectId/views/:viewId` | fresh + `sprints.write` | Update view name/config |
| `DELETE` | `/projects/:projectId/views/:viewId` | fresh + `sprints.write` | Delete view |
| `GET` | `/projects/:projectId/views/:viewId/task-positions` | optional + `tasks.read` or public | List manual task positions |
| `PUT` | `/projects/:projectId/views/:viewId/task-positions` | fresh + `tasks.write` | Bulk-upsert positions |
| `PUT` | `/projects/:projectId/views/:viewId/task-positions/:taskId` | fresh + `tasks.write` | Set single task position |

### Tasks

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/tasks` | optional + `tasks.read` or public | Cursor-paginated task list with filters |
| `POST` | `/projects/:projectId/tasks` | fresh + `tasks.write` | Create task |
| `GET` | `/projects/:projectId/tasks/by-number/:taskNumber` | optional + `tasks.read` or public | Lookup by project task number |
| `GET` | `/projects/:projectId/tasks/:taskId` | optional + `tasks.read` or public | Task detail |
| `PATCH` | `/projects/:projectId/tasks/:taskId` | fresh + `tasks.write` | Partial update |
| `DELETE` | `/projects/:projectId/tasks/:taskId` | fresh + `tasks.write` | Soft-delete task |
| `POST` | `/projects/:projectId/tasks/:taskId/write-with-ai` | fresh + `tasks.write` | AI-assisted description writing |

Task `description` is a JSON array of BlockNote block objects, not a plain string. Nullable fields use three-state PATCH semantics: absent = unchanged, `null` = cleared, value = set.

### Task activities and comments

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/tasks/:taskId/activities` | optional + `tasks.read` or public | Activity feed |
| `POST` | `/projects/:projectId/tasks/:taskId/activities/comments` | fresh + `tasks.write` | Add comment |
| `PATCH` | `/projects/:projectId/tasks/:taskId/activities/comments/:commentId` | fresh + `tasks.write` | Edit comment |
| `DELETE` | `/projects/:projectId/tasks/:taskId/activities/comments/:commentId` | fresh + `tasks.write` | Delete comment |

See the dedicated task activity page for `activity_type` values, JSONB content shapes, and diff/revert semantics.

### Task attachments

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/tasks/:taskId/attachments` | optional + `tasks.read` or public | List attachments |
| `POST` | `/projects/:projectId/tasks/:taskId/attachments/initiate-upload` | fresh + `tasks.write` | Start presigned or multipart upload |
| `POST` | `/projects/:projectId/tasks/:taskId/attachments/complete-upload` | fresh + `tasks.write` | Finalize upload |
| `GET` | `/projects/:projectId/tasks/:taskId/attachments/:attachmentId/download-url` | optional + `tasks.read` or public | Presigned download URL |
| `DELETE` | `/projects/:projectId/tasks/:taskId/attachments/:attachmentId` | fresh + `tasks.write` | Delete attachment |

### Project documentation (`/docs`)

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/docs/folders` | optional + `docs.read` or public | List folders |
| `POST` | `/projects/:projectId/docs/folders` | fresh + `docs.write` | Create folder |
| `PATCH` | `/projects/:projectId/docs/folders/:folderId` | fresh + `docs.write` | Update folder |
| `DELETE` | `/projects/:projectId/docs/folders/:folderId` | fresh + `docs.write` | Delete folder |
| `GET` | `/projects/:projectId/docs` | optional + `docs.read` or public | List documents |
| `POST` | `/projects/:projectId/docs` | fresh + `docs.write` | Create document |
| `GET` | `/projects/:projectId/docs/:docId` | optional + `docs.read` or public | Document detail |
| `PATCH` | `/projects/:projectId/docs/:docId` | fresh + `docs.write` | Update document |
| `DELETE` | `/projects/:projectId/docs/:docId` | fresh + `docs.write` | Delete document |
| `GET` | `/projects/:projectId/docs/:docId/snapshots` | optional + `docs.read` or public | Version history |
| `GET` | `/projects/:projectId/docs/:docId/snapshots/:snapshotId` | optional + `docs.read` or public | Snapshot content |
| `GET` | `/projects/:projectId/docs/:docId/activities` | optional + `docs.read` or public | Doc activity log |
| `POST` | `/projects/:projectId/docs/:docId/comments` | fresh + `docs.write` | Add doc comment |
| `PATCH` | `/projects/:projectId/docs/:docId/comments/:commentId` | fresh + `docs.write` | Edit doc comment |
| `DELETE` | `/projects/:projectId/docs/:docId/comments/:commentId` | fresh + `docs.write` | Delete doc comment |
| `POST` | `/projects/:projectId/docs/:docId/files/initiate-upload` | fresh + `docs.write` | Start doc file upload |
| `POST` | `/projects/:projectId/docs/:docId/files/complete-upload` | fresh + `docs.write` | Complete doc file upload |
| `GET` | `/projects/:projectId/docs/:docId/files/:fileId/download-url` | optional + `docs.read` or public | Doc file download URL |
| `DELETE` | `/projects/:projectId/docs/:docId/files/:fileId` | fresh + `docs.write` | Delete doc file |

### AI agents (global)

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/agents/llm-models` | fresh | Verified LLM provider/model list |
| `GET` | `/agents/skill-templates` | fresh | Available agent skill templates |

### AI agents (project-scoped)

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/agents` | fresh + `agents.read` | List project agents |
| `POST` | `/projects/:projectId/agents` | fresh + `agents.write` | Create agent |
| `GET` | `/projects/:projectId/agents/:agentId` | fresh + `agents.read` | Agent detail |
| `PATCH` | `/projects/:projectId/agents/:agentId` | fresh + `agents.write` | Update agent |
| `DELETE` | `/projects/:projectId/agents/:agentId` | fresh + `agents.write` | Delete agent |
| `GET/POST/PATCH/DELETE` | `/projects/:projectId/agents/:agentId/mcp-servers[...]` | `agents.read` / `agents.write` | MCP server config |
| `GET/POST/PATCH/DELETE` | `/projects/:projectId/agents/:agentId/skills[...]` | `agents.read` / `agents.write` | Agent skills |
| `GET` | `/projects/:projectId/agents/:agentId/chat-sessions` | fresh + `agents.read` | List chat sessions |
| `POST` | `/projects/:projectId/agents/:agentId/chat-sessions` | fresh + `agents.read` | Start chat session |
| `POST` | `/projects/:projectId/agents/:agentId/chat-sessions/:sessionId/messages` | fresh + `agents.read` | Send chat message |

### Conversations

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/projects/:projectId/conversations` | fresh + `agents.read` | List agent conversations |
| `GET` | `/projects/:projectId/conversations/:conversationId` | fresh + `agents.read` | Conversation detail |
| `GET` | `/projects/:projectId/conversations/:conversationId/events` | fresh + `agents.read` | Conversation event stream |
| `POST` | `/projects/:projectId/conversations/:conversationId/stop` | fresh + `agents.write` | Stop running conversation |
| `POST` | `/projects/:projectId/conversations/:conversationId/messages` | fresh + `agents.read` | Send message to conversation |

### Plugins

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/plugins` | optional | List installed plugins |
| `*` | `/plugins/:pluginId/*path` | per manifest | Proxy to plugin WASM routes |
| `GET` | `/admin/plugins/marketplace` | fresh + `users.write` | List marketplace catalog |
| `POST` | `/admin/plugins/marketplace/install` | fresh + `users.write` | Install from marketplace |
| `POST` | `/admin/plugins` | fresh + `users.write` | Install plugin artifact |
| `PATCH` | `/admin/plugins/:pluginId` | fresh + `users.write` | Update plugin metadata |
| `POST` | `/admin/plugins/:pluginId/upgrade` | fresh + `users.write` | Upgrade marketplace plugin |
| `DELETE` | `/admin/plugins/:pluginId` | fresh + `users.write` | Uninstall plugin |
| `PATCH` | `/admin/plugin-extension-settings` | fresh + `users.write` | System-wide extension ordering/visibility |

## Common error codes

| `error_code` | HTTP | When |
|---|---|---|
| `AUTH_INVALID_CREDENTIALS` | 401 | Wrong username/password |
| `AUTH_MISSING_TOKEN` | 401 | No credentials on protected route |
| `AUTH_TOKEN_INVALID` | 401 | Expired or malformed JWT/API key |
| `AUTH_PASSWORD_CHANGE_REQUIRED` | 403 | Must call `PATCH /users/me/password` first |
| `FORBIDDEN` | 403 | Missing required permission |
| `API_KEY_REVOKED` / `API_KEY_EXPIRED` | 401 | API key no longer valid |
| `PROJECT_NOT_FOUND` | 404 | Unknown project UUID |
| `TASK_NOT_FOUND` | 404 | Unknown task UUID |
| `VIEW_IS_LAST_VIEW` | 409 | Cannot delete the only view in a context |
| `TASK_TYPE_IS_SYSTEM` | 403 | Mutation blocked on system type (Epic, Subtask) |
| `BAD_REQUEST` | 400 | Invalid body or query parameter |
| `INTERNAL_ERROR` | 500 | Unexpected server failure |

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Log in, create a project, generate an API key, and verify `/api/healthz`.
</Card>
<Card title="Task activity API" href="/task-activity-api">
Activity feed types, comment shapes, and mutation semantics.
</Card>
<Card title="Interaction views" href="/interaction-views">
Sprint, backlog, and timeline view model with `context` query parameters.
</Card>
<Card title="Connect MCP server" href="/connect-mcp">
Wire `@paca-ai/paca-mcp` for agent-mode API access with plugin tools.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
JWT TTLs, storage backends, and service environment variables.
</Card>
</CardGroup>
