Planner /Docs

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: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

EndpointDoesPage
GET /api/treeEvery folder, task, phase and docTree and docs
GET /api/docDescribe one doc, by doc path or absolute pathTree and docs
GET /docs/<folder>/<path>A doc's file, with Markdown rendered as HTMLTree and docs
GET /api/searchDocs whose title or text holds every wordSearch
GET /api/openShow a link in the user's open Planner tabOpen and events
GET /api/eventsA live stream of new docs, edits and tree changesOpen and events
POST /api/moveSettle or restore a taskTasks
GET /api/config, PUT /api/config, POST /api/config/previewRead, save and dry-run the settingsConfig

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.

NameFormExample
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/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, 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 }