# API overview

A running Planner has a small local HTTP API, the same one its web app and command line use. Scripts and agents can
read every plan, search them, watch for new ones, show one to the user, settle tasks and change settings.

## Base URL

```text
http://localhost:4173
```

Or your configured `port`. Planner only listens on `localhost`, and there is no authentication: anything that can reach
the port can use the API.

## Endpoints

| Endpoint                                                         | Does                                              | Page                                      |
| ---------------------------------------------------------------- | ------------------------------------------------- | ----------------------------------------- |
| `GET /api/tree`                                                  | Every folder, task, phase and doc                 | [Tree and docs](api-tree-and-docs.md)     |
| `GET /api/doc`                                                   | Describe one doc, by doc path or absolute path    | [Tree and docs](api-tree-and-docs.md)     |
| `GET /docs/<folder>/<path>`                                      | A doc's file, with Markdown rendered as HTML      | [Tree and docs](api-tree-and-docs.md)     |
| `GET /api/search`                                                | Docs whose title or text holds every word         | [Search](api-search.md)                   |
| `GET /api/open`                                                  | Show a link in the user's open Planner tab        | [Open and events](api-open-and-events.md) |
| `GET /api/events`                                                | A live stream of new docs, edits and tree changes | [Open and events](api-open-and-events.md) |
| `POST /api/move`                                                 | Settle or restore a task                          | [Tasks](api-tasks.md)                     |
| `GET /api/config`, `PUT /api/config`, `POST /api/config/preview` | Read, save and dry-run the settings               | [Config](api-config.md)                   |

## Names you will see

Everything is named by its folder, so several docs folders never clash. A **docs folder name** is the `name` in the
config, such as `plans`.

| Name          | Form                        | Example                                                       |
| ------------- | --------------------------- | ------------------------------------------------------------- |
| Doc path      | `<folder>/<path inside it>` | `plans/active/search-rewrite/plans/phase-2-query-api/plan.md` |
| Task key      | `<folder>:<task folder>`    | `plans:search-rewrite`                                        |
| Workspace key | `<task key>[/<sub-folder>]` | `plans:search-rewrite/phase-2-query-api`                      |

A workspace is a task's own top-level plans, a phase, or a group such as `research/`. Every doc's `ws` is the
workspace it belongs to, or `""` for docs outside any task. [Links](links.md) use the same names but leave out the
first folder's name.

## Requests and responses

- Replies are JSON (`Content-Type: application/json`), except `/docs/…` files and the `/api/events` stream.
- Request bodies are JSON.
- Errors reply with a `4xx` status and `{ "error": "<what is wrong>" }`.
- `POST` and `PUT` requests must not carry an `Origin` header from another site: a browser page elsewhere gets
  `403 { "error": "not from the dashboard" }`. Scripts, `curl` and agents send no `Origin`, so they are fine.

## Data shapes

The types below are Planner's own, from
[`shared/types.ts`](https://github.com/limyuquan/planner/blob/main/shared/types.ts), and every response is one of them.

```ts
import type { Color } from './config'
import type { Hotkeys } from './hotkeys'

export type Status = 'active' | 'done'

// One file the dashboard can show. `path` is "<folder>/<path inside it>" (see
// shared/keys.ts) and is also its id. `ws` is the workspace it belongs to
// ("" for loose files).
export type Doc = { path: string; file: string; kind: string; title: string; ws: string }

// A folder whose files open together as one workspace: a phase, a group like
// research/, or a task's own top-level plans.
export type Folder = { key: string; name: string; label: string; num: string; dir: string; docs: Doc[] }

export type Task = {
  // Also the workspace key of the task's own plans: "<folder>:<task>".
  key: string
  folder: string
  name: string
  label: string
  icon?: string
  status: Status | null
  dir: string
  docs: Doc[]
  phases: Folder[]
  groups: Folder[]
  mtime: number
}

export type Collection = { folder: string; label: string; docs: Doc[] }

export type Kind = { id: string; label: string; color: Color; icon?: string }

// One configured docs folder, as the browser sees it.
export type DocsFolderInfo = {
  name: string
  icon?: string
  // The real path, and the same as shown to people (~ for the home folder).
  root: string
  label: string
  // Whether its tasks can be settled, i.e. it has status folders.
  settle: boolean
  // Set when the folder cannot be read, e.g. it does not exist.
  problem?: string
}

export type Tree = {
  // In the order of the config. The first one's links leave out its name.
  folders: DocsFolderInfo[]
  tasks: Task[]
  collections: Collection[]
  kinds: Kind[]
  // Every action's combos, defaults filled in.
  hotkeys: Hotkeys
}

// Where a doc sits, as the "new plan" toast names it.
export type Place = { task: string; phase: string | null }

// Pushed from the server over /api/events.
export type ServerEvent =
  | { type: 'tree' }
  | { type: 'added'; doc: Doc; place: Place }
  | { type: 'changed'; path: string }
  | { type: 'open'; ws: string; plan: string; doc: string }

// What one folder's layout finds, for the settings page and `config check`.
export type FolderPreview = {
  name: string
  label: string
  problem?: string
  tasks: number
  phases: number
  docs: number
  byKind: Record<string, number>
  // Phase folders whose names the phase pattern does not match.
  unnumbered: string[]
  // Extensions found next to plans but not listed in fileTypes, with counts.
  skipped: Record<string, number>
}

// The settings page's dry run: how a draft config reads its folders.
export type Preview = { ok: boolean; error?: string; folders: FolderPreview[] }

// /api/tree. No tree means no folder is configured yet. configProblem is set
// when the config file has an error, and the last good settings are in use.
export type TreeResponse = { tree: Tree | null; configProblem?: string }
```

`SearchHit`, from [`shared/search.ts`](https://github.com/limyuquan/planner/blob/main/shared/search.ts):

```ts
export type SearchHit = { doc: Doc; snippet: string; hits: number }
```
