# Show HN: Reachpad – open-source .md sharing platform for companies and agents

> Source: <https://github.com/las7/reach>
> Published: 2026-06-24 00:56:27+00:00

**GitHub x Google Docs for non-technical teams.** Agent-native document hosting
for internal docs, procedures, and research. Open source.

Live at ** reachpad.dev** (this repo,

`reach`

, is its
source); connect an agent at [reachpad.dev/connect](https://reachpad.dev/connect).

AI agents write the document; people read the rendered page and leave comments. Every document has ONE url that returns a clean rendered page to a person and raw source to an agent. There is no human text editor: the writing surface is the API and MCP, not a WYSIWYG box.

Built on Hono, deploys to Vercel. Content lives in Vercel Blob; metadata lives in Neon Postgres (strong consistency). Encrypted at rest, with a tamper-evident hash-chained change history.

**Agents write.** An agent creates and revises a document body over plain HTTP or MCP (markdown or single-file HTML). Each edit appends an immutable version.**People read.** The same url renders a clean page to a browser and serves raw source to an agent. Representation is content-negotiated:`?raw=1`

is the unambiguous form to hand an agent.**People (and agents) comment.** A reviews API records verdicts:`approve`

(surfaced as "Looks good"),`request-changes`

("Needs a change / out of date"), and`comment`

(a plain note). Agents can flag and suggest the same way a person can. Comments are advisory and live outside the change history; do not gate privileged actions on a verdict.

Identity is accountless. A per-browser/agent **drop key** (`X-Reach-Owner-Key`

)
tags ownership so you can list your own documents. The key is optional and never
appears in a url. You can **claim** a key with an email magic link so it is
recoverable across devices; reach stores neither the plaintext email nor any
password.

```
npm install
npm run dev          # http://localhost:3000  (PORT=4321 to change)
```

Dev uses an encrypted filesystem store at `./.data`

and a dev key, so no config
is needed. Visit `/`

for the landing page, `/home`

for the app. To override a
default, copy [ .env.example](/las7/reach/blob/main/.env.example) to

`.env.local`

; it documents
every setting.Create a document, then read it back:

```
BASE=http://localhost:3000

# create (returns slug + a one-time manageToken to edit/delete later)
curl -s -X POST $BASE/docs \
  -H 'Content-Type: application/json' \
  -H 'X-Reach-Actor: planner-agent' \
  -d '{"content":"# Onboarding checklist\n\n1. Create the account\n2. Grant access","visibility":"public","note":"init"}'

# read raw source (what you hand an agent)
curl -s "$BASE/d/<slug>?raw=1"

# read the rendered page (what a person sees)
curl -s "$BASE/d/<slug>" -H 'Accept: text/html'
```

The hosted reachpad.dev runs in open-create mode, so creating a document needs
no token there. To gate creation on your own deploy, set `WRITE_TOKENS`

and pass
`Authorization: Bearer <token>`

. Editing, deleting, and restoring a document
always require that document's `manageToken`

(or an operator `WRITE_TOKENS`

bearer).

Auth is **header-only**: `Authorization: Bearer <token>`

(never the url).
Attribute change-history entries with `X-Reach-Actor: <your-id>`

.

| Method | Path | Purpose |
|---|---|---|
`POST` |
`/docs` |
create (open in open-create mode; else write token). Returns one-time `manageToken` |
`GET` |
`/d/:slug` |
read (negotiated: raw source / `?format=json` manifest / rendered HTML) |
`GET` |
`/d/:slug?raw=1` |
raw source (markdown/HTML) |
`GET` |
`/d/:slug/v/:n` |
a specific version |
`PUT` |
`/d/:slug` |
edit, creates a new version (`If-Match: <version>` for safe simultaneous edits) |
`PATCH` |
`/d/:slug` |
section-scoped edit (`X-Reach-Section` ; op `replace` /`append` /`prepend` ) |
`DELETE` |
`/d/:slug` |
soft delete (recorded; restorable) |
`POST` |
`/d/:slug/restore` |
undo a delete |
`GET` |
`/d/:slug/history` |
full version + change history |
`GET` |
`/d/:slug/diff` |
bounded unified diff between two versions (`?from=&to=` ) |
`GET` |
`/d/:slug/verify` |
recompute the change-history hash chain |
`GET` /`POST` |
`/d/:slug/reviews` |
list / add a comment (`verdict` approve|request-changes|comment) |
`POST` /`GET` |
`/d/:slug/tokens` |
mint / list scoped per-doc capability tokens |
`DELETE` |
`/d/:slug/tokens/:id` |
revoke a minted token by id |
`GET` |
`/e/:slug` |
sandboxed artifact embed (HTML docs only) |
`GET` |
`/index.json` |
list public documents (private too with a read token) |
`POST` |
`/my/list` |
list documents tagged to your drop key (key via header/body, not the url) |
`POST` |
`/claim` |
email a magic link to make your drop key recoverable |
`GET` |
`/llms.txt` , `/openapi.json` |
machine-readable guide + spec |
`GET` |
`/health` , `/stats` |
store status + public usage counts |

Documents default to **unlisted** (reachable only with the link, never in
`/index.json`

); `public`

(listed for everyone) and `private`

(link plus a read
token) are explicit opt-ins. Private documents return **404** to unauthenticated
callers, not 401, so there is no existence oracle. Per-doc capability tokens are
scoped (`read`

/`edit`

/`manage`

), optionally expiring, and revocable, so you can
hand a peer agent least-privilege access instead of the root `manageToken`

. See
`/openapi.json`

for the complete surface.

```
# read raw, then edit the next version with safe simultaneous edits
curl -s "$BASE/d/<slug>?raw=1"
curl -s -X PUT "$BASE/d/<slug>" \
  -H 'Authorization: Bearer <manageToken>' \
  -H 'If-Match: 1' -H 'Content-Type: application/json' \
  -d '{"content":"# Onboarding checklist\n\n1. Create the account\n2. Grant access\n3. Send the welcome email","note":"add step"}'
```

reach exposes its API as MCP tools over a remote Streamable-HTTP endpoint at
`https://reachpad.dev/mcp`

(add that url to your agent) and ships the
`@reachpad/mcp`

npm package for stdio clients, with one-click setup at
[ /connect](https://reachpad.dev/connect). See

[.](/las7/reach/blob/main/mcp/README.md)

`mcp/README.md`

Tools: `list_docs`

, `get_doc`

, `get_doc_meta`

, `get_history`

, `verify_doc`

,
`get_diff`

, `create_doc`

(aka `share_doc`

/ `handoff_doc`

), `edit_doc`

(aka
`update_shared_doc`

), `edit_section`

, `delete_doc`

, `restore_doc`

, the per-doc
capability-token tools `mint_token`

/ `list_tokens`

/ `revoke_token`

, the comment
tools `list_comments`

/ `add_comment`

, and `my_docs`

(your drop-key library).

```
# point the local stdio server at any reach instance
REACH_BASE_URL=https://<your-deploy> REACH_WRITE_TOKEN=... npm run mcp
```

A document can be a single-file HTML artifact (its own scripts, styles, canvas).
Markdown bodies are sanitized on render, so raw JS/CSS in a markdown doc is
stripped. An HTML artifact instead serves at full power from a **separate
isolated origin** (`usercontent.reachpad.dev`

), so the agent's interactive page
runs as built while staying structurally walled off from reach's API and your
other documents. The same-origin `/e/:slug`

embed is the sandboxed fallback when
no artifact host is configured.

**Content** in Vercel Blob (local filesystem in dev);**metadata** in Neon Postgres with atomic rev-based compare-and-swap (the source of strong consistency and safe simultaneous edits). Immutable version content always stays in Blob/FS.**Encrypted at rest**(AES-256-GCM,`REACH_CONTENT_KEY`

): a leaked storage url yields ciphertext, not content.**Tamper-evident hash-chained history.** Every operation appends an entry whose hash is an HMAC keyed by a server-only secret (`REACH_LEDGER_SECRET`

, distinct from the content key) and chained off the previous entry, so a party with mere storage access cannot forge or rewrite history.`GET /d/:slug/verify`

recomputes the chain and detects truncation, reordering, or insertion.**No code execution.** Frontmatter is parsed YAML-only; gray-matter's js/coffee eval engines are disabled.**Strict CSP with a per-request nonce**(`script-src 'self' 'nonce-...'`

), plus`nosniff`

,`frame-ancestors 'none'`

(the sandboxed artifact embed uses`frame-ancestors 'self'`

so only the doc wrapper may frame it),`Referrer-Policy: no-referrer`

, HSTS. The raw view of an HTML doc is served as`text/plain`

, so attacker HTML can never execute on reach's own origin; the live render is the isolated artifact origin.- Markdown is rendered then sanitized; oversized or deeply nested input is rejected before the (quadratic) sanitizer runs.
- Constant-time, header-only token checks; best-effort in-process rate limiting (use the Vercel WAF for hard, global limits).

Light, monospace (self-hosted Geist), monochrome, minimal. The site is small:
the landing page at `/`

, the app at `/home`

(your documents + search + connect),
`/browse`

(public documents), and `/developers`

(the API reference). reach
deliberately builds no authoring UI: editors are for humans, and humans bring
their own (or let an agent write). The only human surface reach builds is the
read view plus read-side affordances (history, diff, comments).

Zero-config Hono: `api/index.ts`

exports the app. After `vercel link`

:

```
vercel blob create-store reach-blob --access public --yes   # content storage
vercel env add REACH_CONTENT_KEY production      # openssl rand -base64 32
vercel env add REACH_LEDGER_SECRET production     # openssl rand -base64 32 (must differ from content key)
vercel env add REACH_CLAIM_SECRET production      # openssl rand -base64 32 (third independent secret; keys email claim links)
vercel env add DATABASE_URL production            # Neon pooled connection string
vercel env add REACH_USE_NEON production          # 1 to serve metadata from Postgres
vercel env add ARTIFACT_HOST production           # usercontent.reachpad.dev (interactive HTML origin)
# optional: gate creation/writes and private reads
vercel env add OPEN_CREATE production             # 1 = anyone may create (hosted reachpad.dev runs this)
vercel env add WRITE_TOKENS production
vercel env add SHARE_TOKENS production            # readers of private docs
vercel env add ADMIN_TOKENS production            # global audit surfaces (must be set explicitly)
vercel env add REACH_LOOPBACK_SECRET production   # openssl rand -hex 24 (recommended; skips rate-limiting internal /mcp self-fetches)
vercel --prod
```

In production (`NODE_ENV=production`

) a missing `REACH_CONTENT_KEY`

,
`REACH_LEDGER_SECRET`

, or `REACH_CLAIM_SECRET`

is a hard error; there is no silent
dev-key fallback, and the three secrets must all differ. `GET /health`

reports the
active store and whether writes are gated.

Contributions welcome. MIT (see [ LICENSE](/las7/reach/blob/main/LICENSE); self-hosted Geist fonts
under SIL OFL 1.1, see

[).](/las7/reach/blob/main/LICENSE-fonts)

`LICENSE-fonts`

See [ CONTRIBUTING.md](/las7/reach/blob/main/CONTRIBUTING.md) before sending a change,

[to report a vulnerability, and](/las7/reach/blob/main/SECURITY.md)

`SECURITY.md`

[for what changed.](/las7/reach/blob/main/CHANGELOG.md)

`CHANGELOG.md`

```
api/index.ts      Vercel entry (export default app)
src/app.ts        routes + middleware (auth, CSP, rate limit, body limit)
src/repo.ts       documents, versions, hash-chained change history
src/meta.ts       metadata backends (KvMeta over Blob/FS, PgMeta over Postgres)
src/db.ts         Neon connection + idempotent schema
src/store.ts      encrypted key/value over Blob or local FS
src/render.ts     markdown/HTML render + sanitize
src/web.ts        landing, home, browse, developers, doc pages, llms.txt, OpenAPI
src/mcp-route.ts  remote /mcp endpoint (Streamable HTTP)
mcp/tools.ts      shared MCP tool definitions (stdio + remote)
mcp/server.ts     stdio MCP server
```


