Skip to content

A REST API and a real data export - #14

Merged
BleakMidwinter90 merged 1 commit into
mainfrom
feat/api-and-export
Aug 10, 2026
Merged

A REST API and a real data export#14
BleakMidwinter90 merged 1 commit into
mainfrom
feat/api-and-export

Conversation

@BleakMidwinter90

Copy link
Copy Markdown
Owner

Two things the research kept surfacing. Donetick — the strongest open source competitor — ships a REST API, webhooks and long-lived tokens, and self-hosters ask for data portability specifically and repeatedly. This also gives a native client something to talk to.

Endpoints

Under /api/v1:

GET /agenda what needs doing, with assignees
GET /balance the score and per-member load
POST /occurrences/:id {"action":"complete"|"skip"}
GET /export everything, as one JSON file

Responses are flattened shapes rather than raw table rows, so the schema can move without breaking somebody's script.

Two ways in

A bearer token for scripts, wall tablets and native clients, or the ordinary session cookie so the browser needs no second credential.

Tokens are stored only as a SHA-256 hash — the same treatment as sessions, because a leaked database backup must not hand over working credentials. The plaintext is shown exactly once, and the UI says so plainly rather than leaving it to be discovered after the panel closes.

One decision worth reviewing

A token identifies a household, not a person, so completing a chore through one requires an explicit memberId. It returns a 400 explaining that rather than guessing.

Silently crediting nobody would quietly corrupt the fairness score, which is the entire point of the app — so this is a case where being annoying is correct.

The export

Deliberately leaves secrets out. Join codes, session tokens, API token hashes and push subscriptions are credentials, not content, and an export is a file people email to themselves. There's a verification step confirming none of them appear in the output.

It sets Content-Disposition so it downloads as a dated file rather than filling a browser tab with JSON.

A bug avoided

The lastUsedAt write is fire-and-forget, and now explicitly .catch()ed. An unhandled promise rejection would take the entire Node server down over a bookkeeping write nobody is waiting for.

Verified end to end

Against the running server: 401 unauthenticated on every endpoint; working bearer auth returning the right household; 401 on a forged token; a helpful 400 when memberId is missing; correct crediting when it's supplied; lastUsedAt recorded; and an export containing zero secrets.

197 tests still green, lint and typecheck clean.

Two things the research kept surfacing. Donetick ships a REST API,
webhooks and long-lived tokens, and self-hosters ask for data portability
specifically and repeatedly. This also gives a native client something to
talk to.

Endpoints under /api/v1: agenda, balance, complete-or-skip an occurrence,
and export. Responses are flattened shapes rather than raw table rows, so
the schema can move without breaking somebody's script.

Two ways to authenticate. A bearer token for scripts, wall tablets and
native clients, or the ordinary session cookie so the browser needs no
second credential. Tokens are stored only as a SHA-256 hash, like sessions
- a leaked backup must not hand over working credentials - and the
plaintext is shown exactly once, which is stated plainly rather than left
to be discovered after the panel closes.

A token identifies a household, not a person, so completing a chore
through one requires memberId. The ledger has to credit somebody real, and
silently crediting nobody would quietly corrupt the fairness score that is
the entire point of the app.

The export leaves secrets out on purpose. Join codes, session tokens, API
token hashes and push subscriptions are credentials, not content, and an
export is a file people email to themselves. There is a check confirming
none of them appear.

The lastUsedAt write is fire-and-forget but explicitly caught. An
unhandled rejection would take the whole server down over a bookkeeping
write nobody is waiting for.

197 tests still green. Verified end to end against the running server:
401 unauthenticated on every endpoint, working bearer auth, 401 on a
forged token, a helpful 400 when memberId is missing, correct crediting,
lastUsedAt recorded, and an export whose Content-Disposition downloads a
dated file containing no secrets.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@BleakMidwinter90
BleakMidwinter90 merged commit 397502a into main Aug 10, 2026
2 checks passed
@BleakMidwinter90
BleakMidwinter90 deleted the feat/api-and-export branch August 10, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant