HTTP API
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
http://localhost:4173Or 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 |
GET /api/doc | Describe one doc, by doc path or absolute path | Tree and docs |
GET /docs/<folder>/<path> | A doc's file, with Markdown rendered as HTML | Tree and docs |
GET /api/search | Docs whose title or text holds every word | Search |
GET /api/open | Show a link in the user's open Planner tab | Open and events |
GET /api/events | A live stream of new docs, edits and tree changes | Open and events |
POST /api/move | Settle or restore a task | Tasks |
GET /api/config, PUT /api/config, POST /api/config/preview | Read, save and dry-run the settings | Config |
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 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/eventsstream. - Request bodies are JSON.
- Errors reply with a
4xxstatus and{ "error": "<what is wrong>" }. POSTandPUTrequests must not carry anOriginheader from another site: a browser page elsewhere gets403 { "error": "not from the dashboard" }. Scripts,curland agents send noOrigin, so they are fine.
Data shapes
The types below are Planner's own, from
shared/types.ts, and every response is one of them.
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:
export type SearchHit = { doc: Doc; snippet: string; hits: number }