# Project Tracker API

Lightweight Kanban + value/effort planner for tracking projects. This guide is for programmatic access (curl, AI agents, scripts).

## Auth

Every endpoint except `/api/auth/login` and `/api/auth/register` requires a `Authorization: Bearer <token>` header. The token can be either:

- A **JWT** from `POST /api/auth/login` (expires in 7 days, used by the web UI)
- An **API key** created by a user in their Settings (never expires, starts with `pt_`)

API keys inherit the creating user's permissions (team access, admin flag).

## Quickstart

```bash
export TOKEN=pt_your_api_key_here
export API=https://pt.boxof.fish/api

# Verify auth
curl -H "Authorization: Bearer $TOKEN" $API/auth/me

# List projects you can see
curl -H "Authorization: Bearer $TOKEN" $API/projects

# List items in a project
curl -H "Authorization: Bearer $TOKEN" "$API/items?project=PT"
```

## Core concepts

- **Project**: a workspace with a short uppercase `key` (e.g. `PT`, `WEB`). Has an owner and optional team. Hold `repoPath` / `claudeMdPath` defaults for items.
- **Item**: a story or task. Identified by `key` like `PT-12`. Has status, priority, value (1-5), effort (1-5), desirability (1-5).
- **Team**: group of users. Projects assigned to a team are visible to all members.
- **ChangeLog**: append-only audit of what was done on an item (free text, file paths expected).

## Workflow for AI agents working on items

1. `GET /api/items/:key/context` — resolve repo path, claude.md path, branch, notes
2. `PATCH /api/items/:key/move` with `{"status":"in_progress","order":0}`
3. Do the work in the repo at the returned path
4. When done, `PATCH /api/items/:key` with `{"changeLog":"What I did. Files changed: path/a.ts, path/b.ts","status":"review"}`
5. `changeLog` strings are appended to a timestamped history; send one string per session

## Endpoints

### Items
| Method | Path                            | Description                                       |
|--------|---------------------------------|---------------------------------------------------|
| GET    | /api/items                      | List (`?project=&status=&type=&label=&search=`) |
| GET    | /api/items/:key                 | Get single item                                   |
| GET    | /api/items/:key/context         | Resolved context (repoPath, claudeMdPath, ...)    |
| GET    | /api/items/:key/activity        | Activity log                                      |
| POST   | /api/items                      | Create                                            |
| PATCH  | /api/items/:key                 | Update (partial; `changeLog` string appends)    |
| PATCH  | /api/items/:key/move            | Move status + reorder                             |
| PATCH  | /api/items/bulk                 | `{"updates":[{"key":"...","priority":"high"}]}` |
| DELETE | /api/items/:key                 | Delete                                            |
| GET    | /api/items/board                | Items grouped by status (`?project=`)           |
| GET    | /api/items/board/matrix         | 5x5 value/effort cells (`?project=`)            |
| GET    | /api/items/labels               | All unique labels                                 |
| GET    | /api/items/milestones           | All unique milestones                             |

### Projects
| Method | Path                 | Description                                      |
|--------|----------------------|--------------------------------------------------|
| GET    | /api/projects        | List visible (`?scope=mine|team&includeArchived=true`) |
| GET    | /api/projects/:key   | Get                                              |
| POST   | /api/projects        | Create (`key`, `name`, optional `teamId`)  |
| PATCH  | /api/projects/:key   | Update (`teamId: null` to unassign)            |
| DELETE | /api/projects/:key   | Delete (only if empty)                           |

### Teams
| Method | Path                              | Description                        |
|--------|-----------------------------------|------------------------------------|
| GET    | /api/teams                        | Teams current user is in           |
| POST   | /api/teams                        | Create (creator auto-joined)       |
| PATCH  | /api/teams/:id                    | Update name/description            |
| POST   | /api/teams/:id/members            | Add member by `email`            |
| DELETE | /api/teams/:id/members/:memberId  | Remove member                      |
| DELETE | /api/teams/:id                    | Delete (creator only)              |

### Auth & account
| Method | Path                       | Description                                    |
|--------|----------------------------|------------------------------------------------|
| POST   | /api/auth/register         | `{email, name}` — temp password is generated + emailed (first user can pass `password` for bootstrap) |
| POST   | /api/auth/login            | `{email, password}` → `{token, user}`       |
| GET    | /api/auth/me               | Current user                                   |
| POST   | /api/auth/change-password  | `{currentPassword, newPassword}` (min 8)     |
| GET    | /api/keys                  | List your API keys                             |
| POST   | /api/keys                  | `{name}` → returns token **once**             |
| DELETE | /api/keys/:id              | Revoke a key                                   |

### Admin (admin only)
| Method | Path                                      | Description                             |
|--------|-------------------------------------------|-----------------------------------------|
| GET    | /api/admin/users                          | List all users                          |
| POST   | /api/admin/users                          | Create user, returns temp password      |
| PATCH  | /api/admin/users/:id                      | Update name/email/isAdmin/teamIds       |
| POST   | /api/admin/users/:id/reset-password       | New temp password                       |
| DELETE | /api/admin/users/:id                      | Delete user                             |

## Item fields

```json
{
  "key": "PT-12",
  "type": "story" | "task",
  "title": "string",
  "description": "string",
  "status": "backlog" | "todo" | "in_progress" | "review" | "done",
  "priority": "low" | "medium" | "high" | "critical",
  "labels": ["string"],
  "assignee": "string",
  "parentKey": "PT-1",
  "project": "PT",
  "value": 1-5,
  "effort": 1-5,
  "desirability": 1-5,
  "dueDate": "ISO 8601 string",
  "milestone": "string",
  "blockedBy": ["PT-2"],
  "changeLog": [{"date":"ISO", "text":"..."}],
  "repoPath": "string (overrides project default)",
  "claudeMdPath": "string",
  "branch": "string",
  "notes": "string"
}
```

When updating, `changeLog` can be sent as a **string** — it will be appended with the current timestamp.

## Errors

All errors return JSON `{"error": "message"}` with an appropriate status code (400 validation, 401 auth, 403 forbidden, 404 not found, 409 conflict).

## Full machine-readable spec

`GET /api/docs/json` returns an OpenAPI 3.0 spec.
