Skip to main content

max / makenotwork

exorcise: developer public docs Batch B4. Replaced 17 prose em-dashes with context-appropriate punctuation (colons for list separators, periods/commas/parens for clauses). Code fences, JSON, URLs, en-dash ranges, compound hyphens untouched. sse.md left as-is (only em-dash is in a JS code comment). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-12 15:38 UTC
Commit: cbb2e8eb8ae2ef0b89ac13aad2d87481e456b969
Parent: 250282b
5 files changed, +19 insertions, -19 deletions
@@ -74,11 +74,11 @@ A machine-readable OpenAPI 3 spec is served at:
74 74 GET /api/openapi.json
75 75 ```
76 76
77 - An interactive Swagger UI viewer for the same spec is available at [`/api/docs`](/api/docs) — send test requests, browse schemas, and copy example payloads without leaving the browser.
77 + An interactive Swagger UI viewer for the same spec is available at [`/api/docs`](/api/docs): send test requests, browse schemas, and copy example payloads without leaving the browser.
78 78
79 79 The spec covers the SyncKit and License Key endpoints (the surfaces with frozen request/response shapes). Point any OpenAPI-aware client generator, Postman-style tool, or IDE plugin at the JSON URL. The spec's `version` matches the server version, so a hard-refresh after a deploy picks up any schema changes.
80 80
81 - OAuth, OTA, and RSS feeds are documented on their own pages rather than in the spec — OAuth is fully described by [discovery metadata](./oauth.md#discovery-metadata), OTA follows Tauri's protocol, and RSS is an XML surface.
81 + OAuth, OTA, and RSS feeds are documented on their own pages rather than in the spec: OAuth is fully described by [discovery metadata](./oauth.md#discovery-metadata), OTA follows Tauri's protocol, and RSS is an XML surface.
82 82
83 83 ## API Reference
84 84
@@ -1,12 +1,12 @@
1 1 # Public Feeds API
2 2
3 - Makenot.work publishes creator and project content as RSS 2.0 feeds. The feeds are unauthenticated, uncapped by application, and stable — aggregators can rely on them without registering an app or requesting a key.
3 + Makenot.work publishes creator and project content as RSS 2.0 feeds. The feeds are unauthenticated, uncapped by application, and stable, so aggregators can rely on them without registering an app or requesting a key.
4 4
5 5 The fan-facing walkthrough lives at [RSS Feeds](../guide/rss.md). This page is for aggregators, feed readers, and anyone building on top of MNW content.
6 6
7 7 ## Open by Policy
8 8
9 - These feeds are a permanent, first-class integration surface. Any aggregator — including ones that compete with Balanced Breakfast (our own aggregator) — can consume them on equal footing. There is no privileged internal path; BB reads the same URLs as everyone else.
9 + These feeds are a permanent, first-class integration surface. Any aggregator, including ones that compete with Balanced Breakfast (our own aggregator), can consume them on equal footing. There is no privileged internal path; BB reads the same URLs as everyone else.
10 10
11 11 Concretely, this means:
12 12
@@ -35,7 +35,7 @@ Each `<item>` element carries:
35 35 | Field | Source |
36 36 |-------|--------|
37 37 | `<title>` | Item title |
38 - | `<link>` | `{host}/i/{item_id}` — the canonical item page |
38 + | `<link>` | `{host}/i/{item_id}`: the canonical item page |
39 39 | `<description>` | Item description (empty string if unset) |
40 40 | `<pubDate>` | Item `created_at` |
41 41 | `<guid>` | Item ID (UUID). Stable for the life of the item. |
@@ -58,7 +58,7 @@ Only published posts appear. Drafts and scheduled-future posts are omitted.
58 58
59 59 ### Personal Feed (`/feed/{user_id}`)
60 60
61 - The personal feed aggregates new items from creators and projects the user follows. Unlike the other endpoints, it is per-user and private, so it is authenticated by an HMAC signature in the query string — no cookie or bearer token, which lets any RSS reader fetch it.
61 + The personal feed aggregates new items from creators and projects the user follows. Unlike the other endpoints, it is per-user and private, so it is authenticated by an HMAC signature in the query string (no cookie or bearer token), which lets any RSS reader fetch it.
62 62
63 63 The signed URL is minted for the user in the dashboard. Users can rotate it (the URL carries a version `v`; when the user regenerates, the old version's signatures are rejected). Aggregators should treat a `403` on this endpoint as a signal that the user needs to paste in a fresh URL.
64 64
@@ -78,9 +78,9 @@ There is no JSON directory endpoint yet, but three existing mechanisms cover the
78 78
79 79 - **Sitemap.** `GET /sitemap.xml` enumerates every active public creator (`/u/{username}`) and every public item (`/i/{item_id}`). An aggregator can walk the sitemap and construct `/u/{username}/rss` from each creator URL. The sitemap is cached and rate-friendly to crawl.
80 80 - **Autodiscovery `<link>` tags.** Three page types advertise their feed via `<link rel="alternate" type="application/rss+xml">` in the HTML `<head>`:
81 - - `/u/{username}` — the creator's item feed
82 - - `/p/{slug}` — the project's item feed
83 - - `/p/{slug}/blog` — the project's blog feed
81 + - `/u/{username}`: the creator's item feed
82 + - `/p/{slug}`: the project's item feed
83 + - `/p/{slug}/blog`: the project's blog feed
84 84 Standard feed-reader auto-detection picks these up.
85 85 - **Direct construction.** If you have a username or project slug already, build the URL from the endpoint table above.
86 86
@@ -92,17 +92,17 @@ Feeds are cheap to generate but not free. Recommended cadence:
92 92
93 93 - **Item and blog feeds**: poll no more than every 15 minutes per URL.
94 94 - **Changelog feed**: hourly is plenty.
95 - - **Personal feed**: poll on the same cadence the user's reader would — typically 15–60 minutes.
95 + - **Personal feed**: poll on the same cadence the user's reader would: typically 15–60 minutes.
96 96
97 97 There is no per-endpoint rate limit distinct from the platform baseline described in [API Overview](./api-overview.md). Feeds share the general read-path budget. If you are polling thousands of URLs, stagger requests rather than firing them in a burst.
98 98
99 - Conditional requests (`If-Modified-Since`, `ETag`) are not currently honored — every request re-renders the feed. Assume you will download the full body each poll and de-duplicate on GUID. This may change; watch the changelog feed.
99 + Conditional requests (`If-Modified-Since`, `ETag`) are not currently honored. Every request re-renders the feed. Assume you will download the full body each poll and de-duplicate on GUID. This may change; watch the changelog feed.
100 100
101 101 ## Content and Licensing
102 102
103 103 The feeds carry titles, descriptions, publication dates, and canonical links back to makenot.work. They do not carry the underlying media (audio, video, downloads, or paid content), which remains gated behind the item page.
104 104
105 - Redistribution of feed metadata (titles, descriptions, links) for the purpose of aggregation, indexing, or notification is expressly permitted. Attribution back to the creator's makenot.work page via the `<link>` element is required and is included in every entry by default — do not strip it.
105 + Redistribution of feed metadata (titles, descriptions, links) for the purpose of aggregation, indexing, or notification is expressly permitted. Attribution back to the creator's makenot.work page via the `<link>` element is required and is included in every entry by default. Do not strip it.
106 106
107 107 Republishing feed content as if it were your own, or scraping full media through the feed's link elements, is not permitted and is not what these feeds are for. When in doubt, link back.
108 108
@@ -113,7 +113,7 @@ Response:
113 113
114 114 For apps that need to work offline, use the verification endpoint. It validates and activates the key, then returns a signed JWT token valid for 7 days. The token acts as a time-boxed offline grace credential: your app caches it and honors it until the `exp` claim passes, without contacting the server in between.
115 115
116 - The token is signed with HS256, a symmetric scheme keyed on the server's secret. That secret is never distributed, so your app cannot cryptographically verify the signature locally — genuine signature verification happens server-side on the next re-verify. Treat the cached token as a trusted receipt of a recent successful verification (read its `exp` to know when to re-verify), not as something you can independently authenticate offline. As with any client-side licensing, offline enforcement is a soft grace window, not a tamper-proof gate.
116 + The token is signed with HS256, a symmetric scheme keyed on the server's secret. That secret is never distributed, so your app cannot cryptographically verify the signature locally. Genuine signature verification happens server-side on the next re-verify. Treat the cached token as a trusted receipt of a recent successful verification (read its `exp` to know when to re-verify), not as something you can independently authenticate offline. As with any client-side licensing, offline enforcement is a soft grace window, not a tamper-proof gate.
117 117
118 118 This endpoint requires the project to have license verification enabled (configured by the creator in project settings).
119 119
@@ -159,7 +159,7 @@ The token contains:
159 159
160 160 1. Call `/api/v1/license/verify` on app launch (when online)
161 161 2. Cache the returned JWT locally
162 - 3. On subsequent offline launches, read the `exp` claim and honor the license until it passes (the signature is not locally verifiable — see above)
162 + 3. On subsequent offline launches, read the `exp` claim and honor the license until it passes (the signature is not locally verifiable, see above)
163 163 4. Re-verify with the server when the token nears expiry or when connectivity returns
164 164
165 165 ### Deactivation via Verify API
@@ -10,7 +10,7 @@ Endpoints, supported scopes, and grant types are published at the standard RFC 8
10 10 GET /.well-known/oauth-authorization-server
11 11 ```
12 12
13 - Most OAuth libraries auto-configure from this URL — pass the base host and they will fetch the metadata. Manual configuration values are documented in the sections below.
13 + Most OAuth libraries auto-configure from this URL; pass the base host and they will fetch the metadata. Manual configuration values are documented in the sections below.
14 14
15 15 ## Overview
16 16
@@ -351,14 +351,14 @@ Configured per app. Apps linked to a free item (or no item) have unrestricted sy
351 351
352 352 SyncKit bills the developer, not your end users. You buy a storage budget for the whole app and allocate it however you like; how you charge your own users (flat, freemium, usage-based, nothing) is your concern.
353 353
354 - One rate: **$0.03 per GB per month**. Ingress and egress are included (absorbed in the rate's margin). API requests are unmetered. Every invoice is floored at **$0.31/month** — the smallest charge that clears Stripe's per-transaction fee.
354 + One rate: **$0.03 per GB per month**. Ingress and egress are included (absorbed in the rate's margin). API requests are unmetered. Every invoice is floored at **$0.31/month**, the smallest charge that clears Stripe's per-transaction fee.
355 355
356 356 Billing is configured from the app's dashboard panel, in one of two modes:
357 357
358 - - **Bulk** — set one knob, `storage_gb_cap` (total GB). Price = `storage_gb_cap × $0.03`. At the cap, **uploads are refused** (`402`, `reason: "storage_limit_reached"`); existing data still reads.
359 - - **Per-key** — set `key_cap` (max active SDK keys) and `gb_per_key` (allotment per key). Price = `key_cap × gb_per_key × $0.03`. A `claim_key` past the cap returns `402 key_limit_reached`; a key over its allotment returns `402 storage_limit_reached` with `dimension: "storage_per_key"`.
358 + - **Bulk**: set one knob, `storage_gb_cap` (total GB). Price = `storage_gb_cap × $0.03`. At the cap, **uploads are refused** (`402`, `reason: "storage_limit_reached"`); existing data still reads.
359 + - **Per-key**: set `key_cap` (max active SDK keys) and `gb_per_key` (allotment per key). Price = `key_cap × gb_per_key × $0.03`. A `claim_key` past the cap returns `402 key_limit_reached`; a key over its allotment returns `402 storage_limit_reached` with `dimension: "storage_per_key"`.
360 360
361 - Caps are bounded: each mode's priced total must be between 1 GB and 10 TB (10240 GB). The bill is fixed for the period — there are no overages. Increasing a cap takes effect immediately; decreasing it applies at the next billing cycle. Cancellation takes effect at period end, and a standard export is available at any time.
361 + Caps are bounded: each mode's priced total must be between 1 GB and 10 TB (10240 GB). The bill is fixed for the period. There are no overages. Increasing a cap takes effect immediately; decreasing it applies at the next billing cycle. Cancellation takes effect at period end, and a standard export is available at any time.
362 362
363 363 First-party apps use a separate end-user subscription model and are not configured here.
364 364