# Release Notes: tokens, a public API and Changelog for iPhone A plan for Jamie's review, 2026-10-29. Nothing is built. It was checked against `origin/main` at fb92ff8, read-only, while another agent builds the signed-out guide. The app it serves is scoped in `CHANGELOG-APP-SCOPE.md`, which was used to test every endpoint below. ## The answer in short It is one idea with two doors. A **token** is a named, scoped key the owner makes on the web. The **API** is what a token opens. **Changelog**, curl and Shortcuts are all clients of that one API. - **v1 is for adding things** (Jamie, 2026-10-29: "adding content is the primary thing", and "Upcoming should be supported as well. That is a great feature that could then power notifications"). That means notes with their files, places and tags, plus Upcoming dates and their reminders. The one read is the activity log, trimmed of words (Jamie: "a way to confirm what did the app do and what did other entities do"). Reading the archive waits until after v1. - **Build it on the MCP server, not beside it.** MCP already has: - bearer tokens, stored only as hashes and revoked at once; - scopes; - a private capture writer that commits the note, the retry receipt, the quota and the audit together; - per-connection provenance. The REST API is a second door onto the same writer, so MCP and the API can't drift apart, and no new write path is added. - **The token's name is its identity.** "Changelog on Jamie's iPhone", "Terminal". The owner types the name, so unlike an MCP client's self-reported name, it is a label the owner chose. - **Value comes early.** curl works after phase 1. After phase 2, a published Apple Shortcut on the Action Button gives one-press dictation into today's notes, before any Swift is written. Deferred (Jamie, 2026-10-29): webhooks, and anything like multiple journals. Both are covered near the end. ## What exists today - **Who can write** (AGENTS.md): a reply, a web session, an agent with `notes:write`, the standing address. "Do not add an endpoint that writes without these ownership proofs." A token becomes a fifth way, delegated in an email-verified session, as `notes:write` is. - **Tokens:** MCP's OAuth tokens are 256-bit opaque values, stored as SHA-256 hashes, checked with strong reads on every request, and revoked at once. Deleting an account blocks new writes first, then removes every grant. - **The capture writer** (`workers/capture.ts`) writes every agent note. - It handles idempotency keys, with 24-hour receipts. - It enforces daily quotas per account across connections: 20 creates, 50 appends, 50 edits and 20 attaches. - It keeps a content-free audit, writes activity rows and reports `unlocked` badges. - **Provenance:** a note carries `author` and `client`. `author: 'assistant'` shows "Added by :" in the email, on the web and in exports. - **Files:** - MCP 1.8.0 takes files as base64 in the call: at most 4 files and 4 MB together, since a synchronous Lambda invoke carries 6 MB. - The web uses signed S3 forms: 50 MB a file, 20 files a note, 200 uploads and 2 GB a day per account, each form good for five minutes. - **Upcoming:** MCP's `upcoming` scope lists, saves and deletes dates, all or nothing, at most 50 a call and 50 calls a day. Each entry has its own reminder days, 7, 1 and 0 before by default, or 14, 1 and 0 for a trip. `remindersFor` works out each morning's due lines ("Mom turns 7.0.0. A major release."). - **Rules to keep:** "Bearer tokens cannot authenticate existing web routes": tokens open `/v1` only, never `/api`. **A rule to change:** "Reminders travel only in the daily email" (see Notifications). - **Tooling:** Xcode 27.1 is installed on this Mac. - **The name:** no app live on the US App Store is called exactly "Changelog". "Changelogs" and "The Changelog - Tech News" exist. A name can be held without a live app, so only App Store Connect can say whether it is free. ## Voice and via Today one field does two jobs. `author: 'assistant'` means both "not the owner's words" and "came through a connection". A note typed into Changelog or a terminal is the owner's own words; it only came *via* Changelog. **Every token writes in the owner's voice.** The note reads, counts and searches like a web note, with a quiet "via Terminal" on the web and in exports. An "automation" kind (shown as "Added by GitHub:", left out of streaks) comes back only if webhooks ever do, and nothing in v1 blocks it. ## Architecture | Option | Verdict | |---|---| | **A. `/v1/*` on the MCP Web Function** | **Recommended.** It already authenticates bearer tokens and calls the capture writer for every write. One new CloudFront behavior, `/v1/*`, to the same origin. | | B. Bearer tokens on the web server's `/api` | No. The web role can write everything, it breaks the rule above, and `/api` is the app's private API, whose shape changes with the React app. | | C. A third Web Function | No. More infra for no separation that A lacks. | - **Address:** `https://notes.yourversionnumber.com/v1/...`. No new DNS record or certificate. - **Tokens** are a new family kind in `packages/backend/oauth.ts`, next to OAuth families: owner-named, with scopes, created, last used and an optional expiry, and no refresh. Storage, revocation and deletion work as they do for OAuth tokens. - **Format:** `rn_` and 256 random bits, shown once. The prefix makes a leaked token easy to find in a repo or a log. - **Contract first,** as MCP is: `contracts/api-surface.json`, served as OpenAPI 3.1 at `/v1/openapi.json`, with a versions ledger and tests that refuse drift. `/v1` only ever gains things; a breaking change would be `/v2`. - **Not for browsers in v1:** no CORS. - **Files need one infra change** (phase 3, flagged). Signed upload forms and the attach step after them run in the web server today, under a session, and the token server's roles were built without upload rights. The token server, or its capture function, needs to sign forms for the pending prefix and run the same attach with the same checks. That is a role change in the stack. - **OAuth for apps** (phase 5) needs two server changes: the resource can be `/v1` as well as `/mcp`, and registration accepts a native app's own callback (RFC 8252: an https app link, or a private-use scheme). It is built for any platform, so an Android client signs in the same way. ## Scopes v1 uses three of MCP's scopes, each ticked when the token is made: `notes:write`, `media:write` and `upcoming`. It adds one of its own, `activity`: the activity log with nothing that carries words. That means no email subjects, email words or other senders' addresses, and no old and new settings values. - **Always included:** `/v1/me`, the owner's tags, and the notes this token wrote. - **Not in v1:** `notes:read` and `media:read`, which read the archive. A key that lives a year in shell history and can read a whole journal is a bigger risk than MCP's hour-long access tokens. They come after v1 if a client needs them. - **As with MCP,** no token can make tokens, change settings, export or delete. The one exception is the pinned tags list (App scope, call 4). ## The API, v1 Every endpoint below is needed by Changelog (`CHANGELOG-APP-SCOPE.md`, "Checking the API against the app"). | Endpoint | What it does | Scope | Phase | |---|---|---|---| | `GET /v1/me` | today's version, local day, `tz`, `send_time`, `wrote_today`, `streak`, `paused_until`, `status`; the next 14 `days`, each with its version (so clients never port the arithmetic, and the birthday is never sent); the token's name and scopes | any | 1 | | `POST /v1/notes` | a note for today or any day back to the birthday. JSON `{text, date?, written_at?, place?, uploads?}`, or a `text/plain` body. `written_at` files a queued note to the day it was typed. Answers 201 with id, date, version, revision and `unlocked` | `notes:write` | 1 | | `POST .../{date}/{id}/append`, `PUT`, `DELETE` | append to, edit (with `revision`) or delete a note this token wrote | `notes:write` | 1 | | `GET /v1/tags` | the pinned list, then the tags used most in the last 30 days, each with its count and last use | any | 1 | | `PUT /v1/tags/pinned` | the pinned list, the same on the web and every phone | `notes:write` | 1 | | `DELETE /v1/token` | the token revokes itself (sign out) | any | 1 | | `GET /v1/activity?after=&kinds=` | the activity log, newest first, as `notes_activity` reads it but trimmed of words; token writes show `via: token` with the token's name; a cursor so a client can ask "what's new since" | `activity` | 1 | | `POST /v1/uploads` | a signed form for one file, as the web's | `media:write` | 3 | | `POST /v1/notes/{date}/{id}/media` | add uploads to a note this token wrote | `media:write` | 3 | | `GET /v1/places?q=`, `POST /v1/places/here` | city search, and the spot, its neighbourhood and up to six nearby businesses, as the web's | `notes:write` | 3 | | `GET /v1/upcoming`, `POST /v1/upcoming/batch`, `DELETE /v1/upcoming/{id}` | Upcoming, with MCP's rules: a revision on every change, `dry_run`, all or nothing, 50 a call, 50 writes a day | `upcoming` | 4 | | `GET /v1/upcoming/reminders?days=14` | each day's due reminders, with the email's own `line` and `when`, for notifications | `upcoming` | 4 | **Across all of it:** - `Idempotency-Key` on every write, with receipts kept **7 days** on `/v1` (24 hours on MCP), since a phone can be offline for a weekend. - One error shape, `{error: {code, message}}`, with codes such as `token_revoked`, `account_stopped`, `scope_required` and `rate_limited`, and `Retry-After` on a 429. - `X-Release-Notes-Version: 5.4.278` on every answer, and standard `RateLimit` headers. - Plain text in and out when asked for: ```sh echo "Fixed the kitchen faucet #house" | curl -sS --data-binary @- \ -H "Authorization: Bearer $RN_TOKEN" -H "Accept: text/plain" \ https://notes.yourversionnumber.com/v1/notes Filed in 5.4.278. ``` **Quotas:** 100 notes a day per account across tokens, separate from MCP's 20. File and Upcoming limits are the web's and MCP's. ## Notifications Jamie wants Upcoming to "power notifications". With the reminders endpoint, Changelog schedules them **on the phone**: - Release Notes keeps no device tokens and no Apple push key (a secret it would otherwise have to hold), and notifications still fire offline. - The server stays the one place that knows which reminder falls on which day, so the iPhone and Android apps can't disagree with the email. Changing the rule ("Reminders travel only in the daily email", and the calendar link's "no alarms") is App scope call 2. The detail is in `CHANGELOG-APP-SCOPE.md`, Notifications. ## Tags, on the phone and the web Pinned tags, recent tags and a sticky tag in Changelog's tag row, and the same row on the web's Today form (Jamie, 2026-10-29: "That could also come into the web interface"). Pins live on the account so they are the same everywhere. The sticky tag stays on each device. That row is the answer to multiple journals (below). ## Public docs - **`/guide/tokens/`, `/guide/api/` and `/guide/api/clients/`** go in the signed-out guide being built now. Its Markdown pages, prerender and tests that pin numbers to code constants all carry over. - **The OpenAPI file**, plus recipes to copy: curl, the Apple Shortcut, a git `post-commit` hook, cron or launchd, Python, a GitHub Actions step and Home Assistant. - **`llms.txt`** lists the API next to MCP. - **API changes** get versioned entries, as MCP's `server_version` and `changes` do. ## Clients other people build Jamie's cousin [name removed] may write an Android Changelog. That makes the API's first outside developer real, and it sets the bar for the docs. Someone with only the public docs and the public repo must be able to build a client without asking Jamie. - **A sandbox they already have.** The repo is public, and `tools/dev-server.ts` runs the real API against a fictional account with no AWS. Phase 1 makes it serve `/v1` and print a ready-made token for the fictional subscriber. An Android emulator reaches it at `10.0.2.2`. - **A client guide, not only an endpoint list.** `/guide/api/clients/` sets out how a capture client should behave, so the iPhone and Android apps act alike: - queue offline, and resend with the same idempotency key; - send `written_at`; - take versions and reminders from the server, never compute them; - handle 429 and revoked tokens; - never log the token. - **A stability promise** in the docs: `/v1` only gains things, errors have one shape, and deprecations are announced in the API's changes, with a date. - **Android sign-in.** v0 uses a pasted or scanned token, as the iPhone app does. OAuth comes later, with one catch: - An Android app link needs `/.well-known/assetlinks.json` on notes.yourversionnumber.com, listing the app's package and signing certificate. - That file is Jamie vouching for the app, so it goes only to an app Jamie calls official. - An unofficial client can use a private-use scheme callback instead. ## Changelog for iPhone Scoped in full in `CHANGELOG-APP-SCOPE.md`. It is a capture tool with three verbs, log, plan and get reminded, and it never asks to read the archive. | Version | Adds | Needs | |---|---|---| | 0.1 | Log with text and the tag row, the version header, the offline queue, today's list with edit and delete, sign-in by token or QR | phase 1 | | 0.2 | photos, the camera, voice, Here, the share extension | phase 3 | | 0.3 | Upcoming and reminder notifications, the optional daily prompt | phase 4 | | 0.4 | App Intents, the Action Button, the control and widgets | nothing new | | 1.0 | OAuth sign-in, the App Store | phase 5 | - **Build.** Its own public repo: SwiftUI, iOS 26 and later, no third-party packages, the token in the Keychain. Claude can write and build it here. Signing and TestFlight need Jamie's Apple Developer account. - **App Store.** - a privacy label: user content, linked to you, no tracking; - a real review account; - a link to `/delete/`; - no membership links in the app until the purchase rules are checked. - **Name.** "Changelog" on the Home Screen. In the store, "Changelog" if it is free, otherwise "Changelog for Release Notes". ## Badges and other touches - **Firsts shelf:** a first note from a token, and a first from Changelog. The names are Jamie's. [One sentence removed.] - **Keep a Changelog quick tags:** Added, Changed, Fixed, Removed, as plain hashtags in the tag row. - **One Connections section in Settings** lists agents, tokens and Changelog, each with its scopes and last use. ## Rules to add to AGENTS.md - **"By a token"** under Who can write: made in an email-verified session, named, scoped, stored as a hash, revocable; opens `/v1` only. - **Reminders on the phone,** in Jamie's words, if App scope call 2 is yes. - No token value in any log. The activity log gets one row for each token write ("note added via Terminal"). - Deleting an account removes tokens first. - Test with the dev server's fictional account only. Live checks are read-only: discovery, a 401 without a token, health. ## Phasing | Phase | Ships | Needs | You get | |---|---|---|---| | **0. Design** | `docs/API.md` design PR, as `docs/BADGES.md` was | | the contract agreed | | **1. Tokens and text** | Settings Connections with tokens (make, name, scopes, expiry, revoke, last used); `/v1/me` with `days`; `/v1/activity`; notes create, append, edit, delete; tags and pins; idempotency; quotas; errors; activity rows; "via" on the web, in exports and in the activity log (not the email, pending Jamie); the `/v1/*` behavior; the dev server serving `/v1`; the AGENTS.md rule | 0 | curl from the terminal; Changelog 0.1 can start | | **2. Docs and the Shortcut** | OpenAPI, its ledger and tests; the three guide pages; recipes; `llms.txt`; a published Shortcut | 1, and the guide's phase 1 machinery | one-press iPhone capture with no app; other people can build clients | | **3. Files and places** | `/v1/uploads` and attach, with the role change; city search and Here | 1 | photos and voice from scripts; Changelog 0.2 | | **4. Upcoming** | the Upcoming endpoints and reminders; the AGENTS.md reminder rule | 1 | Changelog 0.3 and its notifications | | **5. OAuth for apps** | the native-app callback and the `/v1` resource | 1 | Changelog 1.0 on the App Store, and the Android app the same way | - Changelog runs alongside, in its own repo, from the moment phase 1 merges. - Phases 3 and 4 don't depend on each other, so either can go first. - The tag row on the web's Today form can ship with phase 1, since the pins endpoint lands then. - Phase 1 touches `server/mcp/`, `oauth.ts`, the capture writer, Settings and how notes are shown. It barely overlaps the guide work, so it can start once the guide's phase 1 lands. ## Deferred: webhooks Jamie, 2026-10-29: nobody has asked for them, and they are likely to be very high volume, so they wait for a session of their own, "and maybe never". What an earlier draft had, so that session doesn't start cold: - A secret URL per hook (`/v1/hooks/`), like the calendar feed link. The CloudFront access log already leaves paths out. - Each hook an "automation" token: "Added by :", out of streaks, with its own small quota. - Later, a GitHub adapter checking `X-Hub-Signature-256`, with `X-GitHub-Delivery` as the idempotency key. - Outbound webhooks send note content out of Release Notes, which is a separate question again. Anyone can post events from their own scripts with a token and curl, within the daily limit. ## Not now: multiple journals Release Notes has one stream, about you, and that is what holds it together: - the version number is one person's life; - the email asks one question a day, and its streak, Yesterday and On this day read that one stream; - badges, the book and the yearbook gather that one stream. Journals, as most people use them, are already here as **tags**. The Day One importer turns every journal except the main one into a tag (`packages/backend/dayone.ts`), so Jamie's own Day One journals arrived as tags on 10-09. The tag row makes them one tap. If a real need ever shows up, tags can't yet: - leave something out of the email, On this day or the book (a tag filter); - limit a token to one tag; - keep a journal about someone else, with their own birthday. That is really a second account. None of these is needed now, and none is blocked. ## Jamie's calls **Agreed 2026-10-29** ("Really good. I agree with your assessment."), as recommended: - every token writes in your voice, as "via "; - expiry of 90 days, 1 year (the default) or never; - 100 notes a day across tokens; - `/v1` on this host, served by the MCP function; - a token or QR for TestFlight, OAuth for the App Store; - the product changelog stays at `/changelog/`, and the app's page goes at `/guide/changelog/`; - Keep a Changelog quick tags as plain hashtags; - two new Firsts; - the Android app starts as its builder's own client. **Changed by Jamie 2026-10-29:** v1 is for adding things, plus Upcoming, so archive reads leave v1. **Answered later on 2026-10-29** (details in `CHANGELOG-APP-SCOPE.md`): - JPEG from the app; - reminders on the phone, Jamie: "Reminders are going to show up in other places. The daily email is already doing too much work."; - the daily prompt off by default; - 7-day receipts; - the Apple Developer account is Jamie's own [email address removed]. **And then** (Jamie, 2026-10-29): - pins stay: "Pinned tags are good and then also augmented with frequently used RECENT tags." `GET /v1/tags` answers the pinned list, then the most used in the last 30 days; - Xcode on this Mac is signed in to Jamie's Apple ID, which is not yet in the paid Developer Program. That is needed for TestFlight, not to start building. **Added by Jamie later on 2026-10-29:** recent activity in Changelog, and notifications from it. In v1 these come from background refresh, and real push is a later design of its own (`CHANGELOG-APP-SCOPE.md`, call 8). ## Ready to build (Jamie, 2026-10-29) Jamie: "I'm good with your recommendations." That answered the readiness questions as recommended, and real push is deferred ("Agree on the real push deferral"). Approved, in Jamie's words: "Approved: open the Release Notes API design PR (docs/API.md) for tokens, the /v1 API and Changelog" and "Approved: create the public GitHub repo jthingelstad/changelog-ios (MIT) for the Changelog iPhone app". 1. **Who and when:** every subscriber, not only members. Tokens show only to Jamie (admin) until the guide pages are live and Jamie has used them once, then to everyone. No product update, front-page or tour mention until Jamie writes one. 2. **Reminders in the email:** build the phone side now. A web setting that takes reminders out of the email is a separate small PR, offered once the phone side works. 3. **"via"** shows on the web, in exports and in the activity log, not in the morning email. 4. **The first live note** is Jamie's: a token made in Settings and one curl command. No live writes by Claude. 5. **The app's repo:** public `jthingelstad/changelog-ios`, MIT, bundle ID `com.yourversionnumber.changelog`. 6. **The design PR** merges after Codex's review, without waiting for Jamie's read. Anything beyond the plan comes back as a question. 7. **Order:** - phase 0, then phase 1, live; - Changelog 0.1 alongside; - then phase 2 and phase 4; - then phase 3, Changelog 0.2 and 0.3. Report where the day actually lands. 8. **The other session:** wait for whatever it is still writing in `site/` (branches `rn-public-hook` and `rn-changelog` were open at 10-29 midday) before adding guide pages there. Check open PRs before each Release Notes PR. 9. **Names:** the Log button says **Commit**. **API release** is the first note through a token, and **OTA update** the first note from Changelog. 10. **Activity:** the trimmed `activity` scope and opt-in notifications, as planned. **Working rules for the build:** - worktrees off `origin/main`, never the shared checkout; - Node 24 for `npm run verify`; - Codex's review read and answered on every PR; - guide pages in the same PR once a feature is visible to subscribers; - tests and the dev server only, never live writes; - stop and ask before IAM changes outside a deploy (including the hand-applied deploy-role stack), any secret, or anything off-plan.