# PostMD — Changelog

**Covers** — what changed in the service and its API, newest first.

**Not here** — current behaviour is in [/docs/service](/docs/service) and
[/docs/api](/docs/api). Read those to find out how something works now; read this only to find
out what moved. The map is [/llms.txt](/llms.txt).

The API version appears in the path (`/api/v1`). A change that breaks an existing
endpoint will be released under a new version path. Anything listed as **Added**
is safe to adopt without touching your existing calls.

## Unreleased

The service has been rebuilt on a new backend. Existing accounts, documents and
groups carry over unchanged.

### Added

**Images come from the web.**
A markdown file published on its own has nothing beside it, so a relative image path
resolves to nothing. Use a web URL or an inline `data:` URI. PostMD stores no files of
its own; an earlier draft of this release accepted image uploads and that was dropped
before release.

**Edit mode.**
A published document can be revised without changing what readers see. `POST /documents/{docCode}/edit/start`
takes the document for an hour, `/edit/content` saves a working copy as many times as needed, and
`/edit/publish` replaces the published content in one step. The viewer shows the working copy to
the caller holding the document and the published content to everyone else, so an agent can work
on a document while a person watches and applies it when it is right, or applies it itself when
that is what it was asked to do. While a document is held, an
ordinary update from another caller is refused with the new `E_DOC_0011`. Working copies are
deleted after seven days untouched. See [Edit mode](/docs/api).

**Graph data in documents.**
A document can carry graph data in an HTML comment, and the viewer draws it in a panel beside the
text. Thirteen types are available, each drawn in a space of its own: prerequisite order on
numbered rows, a timeline on a time axis, a knowledge graph as a network, a taxonomy as a tree, a
concept map, a procedure as a flowchart, concepts against chapters as a grid, cause and effect as a
fishbone, a comparison as a table, a quadrant as cells or a plane on two axes, an argument map, discourse structure and a requirements trace.
An agent can also define a type of its own by supplying the legend with the data. There is no new
endpoint; the data is part of the markdown and travels with it. During this release the first six
types gained a layout of their own and the other seven were added, along with the optional fields
`question`, `attributes` and `axes` on a block and `date`, `role`, `group`, `terms` and `values` on a node;
nothing was renamed or removed. See [Graph data](/docs/graph).

**A document password in place of a control token.**
Publishing without a credential returns `anonymous` and `retainedUntil` alongside
`docCode`. A document published without a `password` can be updated or deleted by anyone
holding its `docCode`. Setting a `password` puts both behind the `X-Document-Password`
header. An earlier draft of this release issued a per-document `controlToken` sent as
`X-Document-Token`; that token was dropped before release, and sending the header now has no
effect.

**Note visibility now comes from ownership.**
On your own document you still choose between `PRIVATE` and `SHARED`. On someone
else's document every note is `SHARED`; sending `PRIVATE` there returns the new
`E_NOTE_0004` rather than automatically publishing it as shared. Documents nobody owns — anonymous
uploads and service-owned pages — no longer take notes at all, and `E_NOTE_0003`
now reports that instead of "private only". Existing notes were cleared, because the
rules above cannot describe some of the rows that had accumulated.

**MCP server on npm and the official registry.**
`postmd-mcp-server` is on npm and listed in the MCP registry as
`io.github.reinlainer/postmd-mcp-server`. `npx -y postmd-mcp-server` runs it with no
configuration. Publishing through it needs no key; `POSTMD_API_KEY` unlocks the
management tools. It wraps `/api/v1` and adds no endpoints of its own.

**Finished addresses in the upload answer.**
`POST /api/v1/documents` and the update endpoint return `shareUrl` and `viewerUrl`
next to `docCode`. Callers no longer build the address themselves, which removes the
chance of handing out the viewer path where the share path belongs. `docCode` is
unchanged, and the bulk endpoint still returns codes only.

**Notes and highlights.**
A signed-in member can attach notes to a document, anchored to a quoted passage,
and mark passages with one of four highlight colours — a highlight is a note
without text. Notes are `PRIVATE` by default; `SHARED` notes act as comments on
documents owned by a person. Available through the API under
`/api/v1/documents/{docCode}/notes`, with `/api/v1/notes` listing your notes
across documents.

**Notes are listed newest first.**
`GET /api/v1/documents/{docCode}/notes` returned oldest first while this reference said
newest first. The endpoint now matches what was written, and both note listings agree.

**Highlight colour names.**
Each member can name the four highlight colours in account settings. Names are
per member: a shared note carries its colour to every reader, but each reader
sees their own name for it.

**Documents through the API.**
Upload, read, update and delete documents, and fetch the original markdown back.
A document belongs to one group and can be moved to another. Bulk upload accepts
many files in one request and reports each file separately, so one rejected file does not fail
the whole batch. An earlier draft of this release took a `shareEndDate` on upload and update to
stop sharing on a given date; it was dropped before release, and sending the field now has no
effect.

**Groups.**
Create, rename and delete groups, and file documents into one. A group is a way to
sort your own documents and nothing more: it has no members, no invite link and no
password, and it grants no access to the documents in it.

**Link previews.**
Sharing a document link in a chat app or on social media now shows the document
title. Password-protected documents do not reveal their title.

**Documentation endpoints.**
`/llms.txt`, `/docs` and `/docs/{slug}` describe the service in a form both
people and agents can read. The machine-readable spec stays at `/api-docs`.

**Account withdrawal.**
A member can close their account. Nothing changes for one month. After that, documents without a
password move to the anonymous account so their links keep working, documents with a password are
deleted, and personal data is masked. See [/docs/service](/docs/service).

**Ordering for group document lists.**
`GET /api/v1/groups/{groupId}/documents` accepts a `sort` parameter — by update
time, upload time or title, ascending or descending. The whole result set is
ordered before it is split into pages, so paging through a sorted list never
repeats or skips a document. Existing calls keep the previous behaviour: without
`sort`, the most recently updated document comes first.

**Password change.**
`POST /api/v1/auth/me/password` changes the password after checking the current
one. Every session on every device ends, so the client signs in again.

### Changed

**Every document is kept for 30 days after it was last read or changed.**
The retention deadline used to apply to anonymous documents only. It now applies to members'
documents too; official documents are the only exception. Reading the body extends it, at most once
a day, and changing the document resets it. Every upload answer carries `retainedUntil`. See
[/docs/service](/docs/service).

**Deleting a group moves its documents to your default group.**
Deleting a group used to leave its documents attached to it. They stayed readable at their
addresses but no longer appeared in any list. They now move to the default group, and documents
already left behind that way have been moved there too.

**Edit mode can be picked up again after the hour runs out.**
When nobody else has entered in the meantime, the caller who held the document can save to it
or call `/edit/start` again and keeps the same session token, so the address handed to the person
watching keeps working. Before this, the save was refused with `E_DOC_0012`.

**A share link to a missing document opens the viewer.**
`/share/{docCode}` for a code that does not exist answered with the JSON error, which a browser
showed as it was. It now answers `404` with the same short page as any share link, and the viewer
says the document was not found.

**The OpenAPI specification marks credentials as optional where they are.**
Updating, deleting and edit mode were marked as needing an API key, although a document without a
password needs none. They now list no credential and an API key as alternatives. The endpoints of
the administration screens are no longer part of the public specification.

**Sessions extend while you are using the service.**
The condition for extending a session depended on a header no client sent, so it
never became true and a session expired a fixed six hours after signing in, even
while in use. The server now measures activity itself: an authenticated request
reaching it counts. Renewal calls do not count, so a client that only refreshes on
a timer cannot keep a session alive forever.

**Reading a document's metadata no longer reveals a withheld title.**
`GET /documents/{docCode}/meta` returned the title and filename even when the body
was refused for a password. It now leaves both empty for anyone but the owner, while
still reporting `hasPassword` so the caller can tell why.

**A malformed request no longer answers `500`.**
Sending the wrong `Content-Type`, an unreadable body, or an unsupported method used
to produce a server error. These are caller mistakes and now answer `400`.

**Closing an account also revokes its API keys.**

**Closing an account now checks the password.**
`POST /api/v1/auth/withdraw` reads a `password` field and verifies it. The screen
already asked for the password but the server did not read it, so a signed-in
session alone was enough to close an account.

**Timestamps now say which zone they are in, and dates are read as Korean dates.**
Timestamps used to be returned without a zone marker, for example
`2026-08-21T05:03:59`. The value was UTC but nothing said so, and a client reading
it as local time saw a document that had just been updated as hours old. Timestamps
now end with `Z`.

A `yyyyMMdd` date has no time of day, so the service has to choose a zone to read
it in. It now reads dates in `Asia/Seoul`, where the service runs. A date of
`20260819` used to end at `2026-08-19T23:59:59` compared against UTC, which was
9 hours later than the date suggested.

**A document now belongs to exactly one group.**
A document used to be placeable in several groups at once, and uploading with
`groupId` placed it in that group *and* in the uploader's default group. A document
in two groups is one document, not a copy, so deleting it to tidy one group removed
it from the other as well, with no way to undo it.

Uploading with `groupId` now files the document in that group only. Omit `groupId`
and it goes into your default group as before. `POST /documents/{docCode}/groups`
and `POST /documents/{docCode}/groups/{groupId}/remove` are replaced by
`POST /documents/{docCode}/group`, which moves a document to another group. In the
MCP server the two tools become one, `postmd_move_document_to_group`.

**Sessions last longer and renew themselves.**
Signing in issues a short-lived access token plus a refresh token held in an
HttpOnly cookie. The session extends while it is in use. It expires after six
hours without activity, or seven days on a device marked as trusted.

**Refresh tokens are replaced on every use.**
Sending a refresh token that was already replaced is treated as a stolen
credential and revokes the tokens descended from that sign-in. Signing in on
another device starts a separate session, which stays signed in. Run one refresh
at a time from a client.

**Errors identify the exact cause.**
Alongside the HTTP status, `resultCode` may now carry a specific code such as
`E_DOC_0002` (password required). `message` is always English and written for
logs — branch on `resultCode` and supply your own wording.

**Documents are encrypted on disk.**
Content is encrypted before being written and decrypted when served. API clients
see no difference.

**API keys must have an expiry date.**
The date is required and may be at most one year away. Previously a key could be
issued with no expiry.
