Cloud API
Mindwtr Cloud exposes a small bearer-token API for sync, task automation, and attachment transfer. It is designed for self-hosted deployments and uses the same token namespace as the self-hosted cloud backend.
Authentication
Send a bearer token on every /v1/* request:
Authorization: Bearer <token>Use MINDWTR_CLOUD_AUTH_TOKENS or MINDWTR_CLOUD_AUTH_TOKENS_FILE in production. MINDWTR_CLOUD_ALLOW_ANY_TOKEN=true is only for controlled automation and caps new namespaces with MINDWTR_CLOUD_ANY_TOKEN_MAX_NAMESPACES.
Health
GET /healthReturns server health without authentication.
Snapshot Sync
GET /v1/data
PUT /v1/dataGET /v1/data returns the authenticated namespace snapshot. If the namespace does not exist and writes are allowed, the server creates an empty snapshot.
PUT /v1/data validates the uploaded AppData, merges it with the existing namespace using the core sync algorithm, validates the merged result, and writes it back. It is not a forced overwrite. A successful response returns { ok: true, stats, clockSkewWarning }, where stats is the same merge-stats shape used by local sync diagnostics.
Tasks
GET /v1/tasks
POST /v1/tasks
GET /v1/tasks/:id
PATCH /v1/tasks/:id
DELETE /v1/tasks/:id
POST /v1/tasks/:id/complete
POST /v1/tasks/:id/archiveList query parameters:
| Parameter | Purpose |
|---|---|
query | Case-insensitive text search across task title and metadata. |
status | One task status: inbox, next, waiting, someday, reference, done, or archived. |
all=1 | Include completed tasks. |
deleted=1 | Include soft-deleted tasks. |
isFocusedToday | true/1 returns only tasks starred for today's focus; false/0 returns only the rest. Omit for no filter. |
limit, offset | Page size and start offset. |
Create accepts either title or quick-add input, plus optional props. Patch accepts task fields supported by the cloud validation layer and bumps sync revision metadata.
On create, props accepts these task fields: status, projectId, sectionId, areaId, description, priority, dueDate, startTime, relativeStartOffset, reviewAt, recurrence, showFutureRecurrence, contexts, tags, checklist, attachments, assignedTo, location, energyLevel, timeEstimate, timeSpentMinutes, taskMode, textDirection, isFocusedToday, pushCount, repeatReminderMinutes, suppressMindwtrReminders, and viewSectionIds. title is a top-level field, not a prop. Patch accepts the same fields plus title, order, orderNum, boardOrder, and focusOrder. Server-managed fields such as id, createdAt, updatedAt, completedAt, deletedAt, and rev are never client-writable. The lists come from the cloudWrite flag in packages/core/src/task-sync-schema.ts, so that file is the current reference.
assignedTo is a single person's name as a string, for example "Alex", not an array of person IDs. Use null or "" to clear it. REST task creation and updates reject arrays, objects, numbers, and booleans with HTTP 400. Search ignores malformed non-string assignments already in storage without rewriting them.
Mindwtr Cloud 1.2.8 and later return a strong ETag on GET /v1/tasks/:id and GET /v1/projects/:id. To update the version you read, send that tag in If-Match on the corresponding PATCH. The server checks the current record inside its write lock and returns 412 Precondition Failed without writing if the record changed, including an attachment-only change. Read the record again and rebuild your update before retrying. Omitting If-Match keeps the existing unconditional patch behavior.
For automation, put status inside props and omit dueDate when no deadline is set. attachments must be an array of attachment objects, not a URL string. For a link attachment, supply an ID, kind: "link", title, URI, and creation/update timestamps as shown below. Generate a unique ID for each new attachment and keep it when editing that link. You can also put a URL in props.description if you only need it in the task notes.
{
"title": "JIRA-123 - Follow up",
"props": {
"status": "next",
"attachments": [
{
"id": "890b7cda-e1ec-43de-a0d1-60b5385b7f8d",
"kind": "link",
"title": "JIRA-123",
"uri": "https://jira.example.com/browse/JIRA-123",
"createdAt": "2026-09-13T12:00:00.000Z",
"updatedAt": "2026-09-13T12:00:00.000Z"
}
]
}
}Capture
POST /v1/captureCapture creates an Inbox task from a transcription, an audio recording, or both. The body can be multipart form data, JSON, or plain text, but only multipart form data can carry audio. See Capture webhook for the fields, the responses, and the Pebble Index 01 setup.
POST /v1/capture-tokens
GET /v1/capture-tokens
DELETE /v1/capture-tokens/:idPOST /v1/capture-tokenscreates a capture-only token for the account. The body is{ "label": "Pebble ring" }, and the label is optional. It returns201withid,token,labelandcreatedAt. The token is shown once.GET /v1/capture-tokenslists the account's capture-only tokens withid,labelandcreatedAt. No secrets.DELETE /v1/capture-tokens/:idrevokes one.
All three need the full bearer token. A capture-only token is accepted only on POST /v1/capture and is rejected with 403 on every other route.
Projects, Areas, and Sections
GET /v1/projects
POST /v1/projects
GET /v1/projects/:id
PATCH /v1/projects/:id
DELETE /v1/projects/:id
GET /v1/areas
POST /v1/areas
GET /v1/areas/:id
PATCH /v1/areas/:id
DELETE /v1/areas/:id
GET /v1/sections
POST /v1/sections
GET /v1/sections/:id
PATCH /v1/sections/:id
DELETE /v1/sections/:idAll list endpoints accept limit, offset, and deleted=1. Sections also accept projectId.
Reference fields must point to live records. A project areaId must reference a live area. Use areaId: null to clear a project area; areaId: "" is invalid. A section projectId must reference a live project.
Deleting areas, projects, and sections uses tombstones and server-side repair to keep the snapshot valid for sync.
Search
GET /v1/search?query=<text>Search returns live tasks and projects in separate arrays. It supports the shared limit and offset parameters, plus independent cursors:
| Parameter | Purpose |
|---|---|
taskLimit, taskOffset | Page the task result set. |
projectLimit, projectOffset | Page the project result set. |
The response includes taskTotal, projectTotal, and the effective cursor values.
Attachments
GET /v1/attachments/:path
HEAD /v1/attachments/:path
PUT /v1/attachments/:path
DELETE /v1/attachments/:path
POST /v1/attachments/orphans
DELETE /v1/attachments/orphansAttachment paths are resolved inside the authenticated token namespace. Uploads enforce the configured byte limit and the core attachment validation rules.
The orphan cleanup endpoint scans the namespace for files no longer referenced by data.json. It skips files modified in the last five minutes so an upload racing with a later snapshot write is not removed.
Calendar Feed
GET /v1/calendar/feed
POST /v1/calendar/feed
DELETE /v1/calendar/feed
GET /v1/calendar/:token.icsThe first three require the bearer token and read, rotate, and revoke the namespace's feed token. They return { "feed": { "token", "path", "createdAt" } }, or { "feed": null } when nothing is published. POST returns 404 for a namespace that has never synced data.
GET /v1/calendar/:token.ics takes no Authorization header — the token in the URL is the credential — and returns text/calendar for that namespace. An unknown token is a 404.
The feed carries one event per scheduled task (startTime) and one per deadline (dueDate), matching what the app's Calendar view shows: completed, archived, reference, deleted, and non-active-project tasks are excluded, and a task scheduled and due on the same day appears once. Events carry a stable UID and the task title only — no description, checklist, attachments, tags, or project metadata.
MCP Adapter
The published mindwtr-mcp helper can use a self-hosted Cloud endpoint as a backend. Configure it with --cloud-url and --cloud-token or the MINDWTR_MCP_CLOUD_URL / MINDWTR_MCP_CLOUD_TOKEN environment variables.
Cloud-backed MCP mode reads /v1/data and exposes read tools for tasks, projects, sections, areas, and people. With --write, it routes task, project, section, and area mutations through the per-resource REST endpoints above; it stays read-only by default and does not turn Mindwtr Cloud itself into a hosted MCP service.
cancelledAt
cancelledAt is an optional ISO timestamp with a timezone on Task and Project. Setting it archives the commitment without marking it completed. A cancelled task has no completedAt and generates no next recurring occurrence. A project cancellation preserves completed steps and cancels its remaining actions. Upgrade every writer before using this field with sync. See Cancelling a commitment.
Use PATCH /v1/tasks/:id or PATCH /v1/projects/:id with { "cancelledAt": "2026-09-07T12:00:00.000Z" }. An explicit active status clears cancellation and reactivates the record; sending cancelledAt: null alone only clears the outcome marker. Creation accepts the field in props.