Skip to content

Local API Server ​

Mindwtr includes an optional local REST API server for scripting and integrations. On desktop it runs inside the app binary and uses the same local storage paths as the app. The repository also includes a Bun helper for development and advanced scripting.


Desktop Toggle ​

Desktop builds can start the local REST API without running source code:

  • Open Settings -> Advanced.
  • Enable Local API server.
  • Keep the default port 3456 or choose another localhost port.
  • Copy the generated bearer token from the same settings card.

The app binds to 127.0.0.1 only and requires Authorization: Bearer <token> on every request. Mobile binaries do not expose a local REST API surface.

Development Helper ​

The repo helper is still available when you want to run the API outside the desktop app or point it at explicit files.


Quick Start ​

From the desktop app:

text
Settings -> Advanced -> Enable local API server

Default URL:

text
http://127.0.0.1:3456

From the repo root with Bun:

bash
bun install
MINDWTR_API_TOKEN=replace-with-a-strong-token bun run mindwtr:api -- --port 4317 --host 127.0.0.1

Options ​

OptionDefaultDescription
--port <n>4317Server port
--host <host>127.0.0.1Bind address
--data <path>Platform defaultOverride data.json location
--db <path>Platform defaultOverride mindwtr.db location
--dangerously-disable-authoffRun with no token at all — unsafe, and not browser-safe even on loopback

Environment Variables ​

VariableDescription
MINDWTR_DATAOverride data.json location (if --data is omitted)
MINDWTR_DB_PATHOverride mindwtr.db location (if --db is omitted)
MINDWTR_API_TOKENRequired. Bearer token the helper demands on every request; it refuses to start without one unless --dangerously-disable-auth is passed
MINDWTR_API_CORS_ORIGINOptional. One exact http(s) origin allowed to call the API; no CORS headers are sent when unset

By default, the API resolves both data.json and mindwtr.db using Mindwtr's platform paths (preferring XDG data on Linux).


Authentication ​

Desktop Local API requests always require the bearer token shown in Settings -> Advanced:

Authorization: Bearer <token>

The Bun helper requires a token too: it exits immediately unless MINDWTR_API_TOKEN is set, or you explicitly pass --dangerously-disable-auth (intended only for isolated compatibility testing).


Endpoints ​

MethodEndpointDescription
GET/healthHealth check → { ok: true }
GET/tasksList tasks
GET/tasks?status=nextFilter by status
GET/tasks?query=@workSearch tasks
GET/tasks?isFocusedToday=trueDesktop: filter starred tasks, including queued future starts
GET/tasks?all=1Include done/archived
GET/tasks?deleted=1Include soft-deleted
POST/tasksCreate task
GET/tasks/:idGet single task
PATCH/tasks/:idUpdate task
DELETE/tasks/:idSoft delete task
POST/tasks/:id/completeMark as done
POST/tasks/:id/archiveMark as archived
POST/tasks/:id/restoreRestore a soft-deleted task
GET/projectsList projects
POST/projectsDesktop: create project
GET/projects/:idDesktop: get one project
PATCH/projects/:idDesktop: update project
DELETE/projects/:idDesktop: soft-delete project
POST/projects/:id/restoreDesktop: restore project
GET/areasList areas
GET/areas/:idDesktop: get one Area
PATCH/areas/:idDesktop: update an Area
GET/v1/areasCompatibility alias for areas
GET/sectionsHelper: list sections, optionally ?projectId=
POST/sectionsHelper: create section
GET/sections/:idHelper: get single section
PATCH/sections/:idHelper: update section
DELETE/sections/:idHelper: delete section
GET/search?query=...Search tasks + projects

The desktop Local API returns 409 (recurrence_requires_app) when completing a fixed-schedule task with a start date and no due date would require skipping missed occurrences. The task stays unchanged; complete it in the app. Start-only tasks whose next occurrence is still current or in the future remain supported.

Rows marked Desktop exist only in the desktop app's Local API. Rows marked Helper exist only in the Bun helper, which also answers them under a /v1/ prefix (/v1/sections).

The desktop Local API accepts isFocusedToday=true/1 and isFocusedToday=false/0. With true or 1, it returns starred tasks, including future-start Next actions queued for Focus. With false or 0, it returns unmarked tasks, including older records without the field. Omit the parameter to leave the list unfiltered. Other values return 400.

query here is plain-text matching: the value is lowercased and tested as a substring against the task's title, description, status, tags, contexts, project and area ids, and support notes. It is not the operator language. status:, context:, tag:, due:<=7d, quoted phrases and -negation belong to the app's search box, the CLI's --query, and the MCP server's task listing; sent here they match as literal characters and will usually return nothing.

Response Shapes ​

Task (partial)

json
{
  "id": "uuid",
  "title": "Task title",
  "status": "inbox",
  "projectId": "uuid",
  "dueDate": "2026-01-25T12:00:00.000Z",
  "tags": ["#work"],
  "contexts": ["@email"],
  "createdAt": "2026-01-25T10:00:00.000Z",
  "updatedAt": "2026-01-25T10:00:00.000Z",
  "deletedAt": null
}

Project (partial)

json
{
  "id": "uuid",
  "title": "Project name",
  "status": "active",
  "color": "#94a3b8",
  "createdAt": "2026-01-25T10:00:00.000Z",
  "updatedAt": "2026-01-25T10:00:00.000Z",
  "deletedAt": null
}

Area

json
{
  "id": "uuid",
  "name": "Area name",
  "color": "#94a3b8",
  "icon": "briefcase",
  "order": 0,
  "createdAt": "2026-01-25T10:00:00.000Z",
  "updatedAt": "2026-01-25T10:00:00.000Z"
}

Create Task Body ​

json
{
  "input": "Call Alice",
  "title": "Alternative title",
  "props": {
    "status": "next",
    "contexts": ["@phone"],
    "tags": ["#errands"]
  }
}

Desktop uses title when present, otherwise input, and applies explicit props. The Bun helper additionally runs parseQuickAdd for input.

Desktop project writes ​

The built-in desktop API supports creating, reading, updating, soft-deleting, and restoring projects. Project creation requires title; areaId, color, status, isSequential, and order are optional. Updates accept the same editable fields. Create, read, update, and restore responses contain the saved project as { "project": { ... } }; delete returns { "ok": true }.

Send creation options inside props, for example { "title": "Plan the move", "props": { "isSequential": true } }; PATCH fields go directly in the request body. sequentialScope accepts project or section. Project status accepts active, someday, waiting, or archived. Set areaId to null to remove the area. Archiving and reactivating a project follow the app’s child-task and section rules. For PATCH, missing projects return 404, while deleted or purged projects return 409.

GET /projects/:id also returns a stored soft-deleted project, which lets a script inspect it before restoring it. DELETE /projects/:id soft-deletes the project and its live sections, and detaches live tasks instead of deleting them or changing their status. POST /projects/:id/restore restores the project and any children deleted by the same legacy cascade, but it does not reattach tasks detached by a current delete. Repeating delete or restore is safe and leaves the current state unchanged. Lifecycle changes to a purged project return 409.

Create a sequential project:

bash
curl -s -X POST "http://127.0.0.1:3456/projects" \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"title":"Plan the move","props":{"isSequential":true}}' | jq .

Change an existing project to parallel:

bash
curl -s -X PATCH "http://127.0.0.1:3456/projects/$PROJECT_ID" \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"isSequential":false}' | jq .

Edit Areas on desktop ​

GET /areas/:id and PATCH /areas/:id return { "area": { ... } }. Both return 404 for unknown or deleted Areas. These endpoints belong to the built-in desktop API, not the Bun helper.

PATCH accepts a nonempty object containing only name, color, icon, and order. name must be a nonblank string and is trimmed. color and icon accept strings or null to clear the field. order must be a finite number; fractional and negative values are allowed. Unknown fields, invalid types, blank names, and an empty patch return 400.

A name matching another live Area after trimming and case-insensitive comparison returns 409 without changing anything. The API never merges Areas implicitly. Renames and color changes preserve the app’s related-project metadata behavior and are saved together. Clearing an Area’s color also resets its linked projects to the default color (#94a3b8). Setting order updates this Area’s numeric position; it is not a batch reorder operation. Area creation and deletion are not included.

bash
curl -s -X PATCH "http://127.0.0.1:3456/areas/$AREA_ID" \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Work","color":"#94a3b8","order":2}' | jq .

Desktop task triage ​

Use PATCH /tasks/:id with status to move an active task between inbox, next, waiting, someday, and reference. Continue using the dedicated /complete, /archive, and /restore actions for those operations. Terminal status values are not accepted by PATCH.

PATCH cannot reopen a done or archived task (409); reopen it in the app first. Restore a soft-deleted task with /restore before triaging it. Purged tasks cannot be triaged.

Moving a task to inbox, next, waiting, or someday inside an archived project returns 409. First reactivate the project with PATCH /projects/:id and {"status":"active"}, or move the task out of that project in the same task PATCH.

Move an Inbox task to Next Actions:

bash
curl -s -X PATCH "http://127.0.0.1:3456/tasks/$TASK_ID" \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"status":"next"}' | jq .

Examples ​

List next actions:

bash
curl -s 'http://127.0.0.1:3456/tasks?status=next' \
  -H 'Authorization: Bearer <token>' | jq .

Create with explicit props:

bash
curl -s -X POST 'http://127.0.0.1:3456/tasks' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"title":"Call Alice","props":{"status":"next","contexts":["@phone"],"tags":["#errands"]}}' | jq .

Complete a task:

bash
curl -s -X POST "http://127.0.0.1:3456/tasks/$TASK_ID/complete" \
  -H 'Authorization: Bearer <token>' | jq .

CLI Tool ​

A simpler command-line interface is also available:

bash
# Add a task
bun mindwtr:cli -- add "Call mom @phone #family"

# List active tasks
bun mindwtr:cli -- list

# List with filters
bun mindwtr:cli -- list --status next --query "due:<=7d"

# Read or update a task
bun mindwtr:cli -- get <taskId>
bun mindwtr:cli -- update <taskId> '{"status":"next"}'

# Complete a task
bun mindwtr:cli -- complete <taskId>

# Archive, delete, or restore
bun mindwtr:cli -- archive <taskId>
bun mindwtr:cli -- delete <taskId>
bun mindwtr:cli -- restore <taskId>

# Search
bun mindwtr:cli -- search "@work"

# List projects
bun mindwtr:cli -- projects

CLI Reference ​

CommandExampleNotes
addmindwtr:cli -- add "Call mom @phone"Uses quick-add parsing
listmindwtr:cli -- list --status nextSupports --all, --deleted, --query
getmindwtr:cli -- get <taskId>Prints full task JSON
updatemindwtr:cli -- update <taskId> '{"status":"next"}'Applies a JSON patch
searchmindwtr:cli -- search "@work due:<=7d"Searches tasks/projects
completemindwtr:cli -- complete <taskId>Marks task as done
archivemindwtr:cli -- archive <taskId>Marks task as archived
deletemindwtr:cli -- delete <taskId>Soft-deletes task
restoremindwtr:cli -- restore <taskId>Restores a deleted task
projectsmindwtr:cli -- projectsLists active projects

Security Notes ​

  • The server is intended to run on 127.0.0.1 (localhost). Don't expose it publicly unless you understand the risks.
  • Desktop API access requires the generated bearer token from Settings. Keep that token private.
  • If you need remote access to the Bun helper, set MINDWTR_API_TOKEN and place the server behind an authenticated reverse proxy.

See Also ​

Mindwtr is free, open source, and local-first.
Getting Things Done and GTD are registered trademarks of the David Allen Company. Mindwtr is not affiliated with, endorsed by, or sponsored by the David Allen Company.