---
title: "RFC: bundle digest format"
url: https://mdfy.app/2WqRPNiV
updated: 2026-05-14T18:15:49.480Z
source: "mdfy.app"
---
# RFC: bundle digest format

> Status: shipped 2026-04.

## What a bundle URL returns

GET `mdfy.app/b/{id}` returns plain markdown. The body is composed of (in order):

1. **Bundle metadata block** — title, description, creator, last-updated timestamp, member count.
2. **graph_data JSON block** — the synthesised analysis: nodes, edges, themes, insights, document summaries, reading order. Wrapped in a fenced code block tagged `mdfy:graph` so AI tools can ignore it if they want plain prose.
3. **Member doc stubs** — each member doc as a heading + a short summary + the URL. No full content (full content requires `?full=1`).

The whole response is < 50KB for a typical 5-7 member bundle.

## Query parameters

- `?compact` — strip low-density sections from every member stub. Default off.
- `?full=1` — inline every member doc's full body inside the response, in member order. Default off. Use when you want one giant context blob.
- `?graph=0` — omit the graph_data block. Use when the receiving AI just wants prose.
- `?recall=$Q` — filter member stubs to only the ones matching query $Q. Cheap on the server (we already have the recall index per bundle).

These compose: `?compact&recall=cross-AI&graph=0` returns only the prose stubs of members matching "cross-AI", compacted, with no graph block. ~5KB total.

## What we explicitly don't return

- Auth-protected member docs that the requester isn't authorised for. They're silently omitted from the stub list. The bundle's member count adjusts.
- Embedded media. The renderer-ready HTML for images is in the doc URL, not the bundle digest.

## Cache headers

Public bundles: `Cache-Control: public, max-age=300, stale-while-revalidate=3600`. Five minutes fresh, an hour stale-served.

Restricted / private bundles: `Cache-Control: private, no-store`. Don't cache at edge or in the browser. Auth-sensitive content shouldn't be near any cache.

## What's frozen and what's not

**Frozen:** the response is always markdown. JSON is wrapped in fenced blocks; never as `Content-Type: application/json`. This is the core of "every URL is for both humans and AIs from the same address."

**Not frozen:** the exact set of `?` params can grow. The graph_data schema can evolve (additive only — never remove a field without a major version bump). The cache headers can be tuned.

## The bet underneath this format

That `markdown-as-API-response` is the right design for the next decade. Every AI tool today fetches URLs and reads markdown as a first-class input. If that stops being true, this format stops being right. We're betting it doesn't stop being true.
