See Canvas and Masonry for how logs are arranged on a board, and the Markdown Editor for what the body field can contain.
Every route on this page is under /api/workspaces/:workspaceId/logs and requires Workspace Member.
GET /api/workspaces/:workspaceId/logs
GETWorkspace Member
Lists logs for a workspace. See Conventions for how day changes pagination.
Query Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
day |
string | No | Only logs on this date (YYYY-MM-DD) — unpaginated, capped at 100 |
from |
string | No | Only logs on or after this date |
to |
string | No | Only logs on or before this date |
tag |
string | No | Only logs carrying this tag |
page |
number | No | Page number, used when day is omitted |
Response
{
"logs": [
{ "id": "01HXYZ...", "workspaceId": "...", "authorId": "...", "day": "2026-09-06", "title": "Meeting notes", "body": "...", "createdAt": "...", "updatedAt": "...", "posX": 24, "posY": 24, "width": null, "height": null, "zIndex": 0, "authorName": "Demo", "tags": ["postgres"] }
],
"hasMore": false,
"page": 1,
"limit": 10
}POST /api/workspaces/:workspaceId/logs
POSTWorkspace Member
Creates a log. day is set once here and never changes afterward — that’s what keeps a log’s mirrored file in one place forever.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
day |
string | Yes | YYYY-MM-DD |
body |
string | Yes | Markdown, up to 2,000,000 characters |
title |
string | null | No | |
posX |
integer | No | Canvas X coordinate |
posY |
integer | No | Canvas Y coordinate |
Response: 201 with the log itself (not wrapped) — the author, workspace, parsed tags, and parsed tasks are included, same shape as GET /:logId below.
GET /api/workspaces/:workspaceId/logs/:logId
GETWorkspace Member
Response
{
"id": "...", "workspaceId": "...", "authorId": "...", "day": "2026-09-06", "title": "...", "body": "...",
"createdAt": "...", "updatedAt": "...", "posX": 24, "posY": 24, "width": null, "height": null, "zIndex": 0,
"author": { "id": "...", "name": "Demo" },
"workspace": { "id": "...", "slug": "personal-demo", "name": "Personal" },
"tags": ["postgres", "perf"],
"tasks": [{ "id": "...", "logId": "...", "text": "Ship the release notes", "done": false, "dueDate": "2026-09-07", "lineNo": 1, "completedAt": null }]
}PATCH /api/workspaces/:workspaceId/logs/:logId
PATCHWorkspace Member
Updates a log’s content. Re-parses tags and tasks from the new body on save.
Request body — both optional:
| Field | Type | Notes |
|---|---|---|
title |
string | null | |
body |
string |
Response: the updated log, same shape as GET /:logId.
PATCH /api/workspaces/:workspaceId/logs/:logId/position
PATCHWorkspace Member
Moves a log on the infinite canvas. This is board-arrangement state, not content — it skips the markdown mirror and updatedAt entirely, so dragging a card never rewrites its file.
Request body
| Field | Type | Required |
|---|---|---|
posX |
integer | Yes |
posY |
integer | Yes |
Response: { "ok": true }
PATCH /api/workspaces/:workspaceId/logs/:logId/size
PATCHWorkspace Member
Resizes a log’s card on the canvas. Same board-arrangement treatment as position — no mirror write.
Request body
| Field | Type | Required |
|---|---|---|
width |
integer | Yes |
height |
integer | Yes |
Response: { "ok": true }
PATCH /api/workspaces/:workspaceId/logs/:logId/zindex
PATCHWorkspace Member
Brings a log to front (or sends it back) on the canvas. Same board-arrangement treatment — no mirror write.
Request body
| Field | Type | Required |
|---|---|---|
zIndex |
integer | Yes |
Response: { "ok": true }
POST /api/workspaces/:workspaceId/logs/:logId/duplicate
POSTWorkspace Member
Copies a log onto the same day, offset 24px down and right, stacked above the original.
Response: 201 with the new log, same shape as GET /:logId.
POST /api/workspaces/:workspaceId/logs/:logId/move/:targetWorkspaceId
POSTWorkspace Member
Moves a log (and its attachments) to a different workspace, keeping its day and content. Requires membership in both :workspaceId and :targetWorkspaceId — checked separately, since they’re two different workspaces.
Response: the log, now pointing at the target workspace.
DELETE /api/workspaces/:workspaceId/logs/:logId
DELETEWorkspace Member
Soft-deletes a log (sets deletedAt) and removes its markdown mirror file.
Response: { "ok": true }