This document is the source of truth for the node's HTTP contract. The fragment
manifest shape is defined in fragment.schema.json.
All responses are UTF-8. The node is single-tenant: one publisher, one owner.
A fragment has exactly one canonical location on its home node:
GET /fragments/{id}
{id} is the URL path segment, format yyyy-mm-dd-slug (see
fragment.schema.json). Its machine views are the two sub-resources of that
location: /fragments/{id}/sphere.json (the manifest) and
/fragments/{id}/content.md (the body). The fragment's ABSOLUTE canonical URL is
therefore:
{node_base}/fragments/{id}
A reference to a fragment is exactly one of:
- same-node — the bare
id(2026-01-15-sample-fragment), resolved against this node, or - external — an absolute URL to another node's canonical fragment URL
(
https://other.node/fragments/2026-01-15-their-fragment).
This single scheme is used identically everywhere a fragment is referenced:
relations[].target (see fragment.schema.json), inline links inside
content.md, and the machine views above. There is no second addressing form;
an id is just the same-node short form of the absolute canonical URL.
Publisher discovery document. ALWAYS returns 200, even when the node has no
fragments. Served from the KV cache, falling back to D1 when the cache is cold.
The publisher object carries attribution: name, an optional summary, the
publisher's canonical url, and an icon URL (the publisher mark). icon
defaults to this node's own served mark (/assets/sphere-mark.svg); url is
omitted when unset.
{
"sphere_version": "1.0",
"publisher": {
"name": "Example Publisher",
"summary": "Agent-readable publishing.",
"url": "https://example.com",
"icon": "https://node.example/assets/sphere-mark.svg"
},
"default_license": "CC-BY",
"fragment_count": 1,
"fragments": [
{
"id": "2026-01-15-sample-fragment",
"title": "Sample Fragment",
"policy": "free",
"manifest": "/fragments/2026-01-15-sample-fragment/sphere.json",
"content": "/fragments/2026-01-15-sample-fragment/content.md"
}
]
}Ledger event: discovery.
Fragment manifest. Metadata is always available regardless of access policy.
404 if the fragment is unknown.
The node attaches two fields to the manifest response, both additive — no authored manifest field changes meaning:
canonical: the fragment's own absolute canonical URL ({node_base}/fragments/{id}), so an agent reading a fragment in isolation knows its home. The node is authoritative for this value and always sets it, overriding any authoredcanonicalfield.publisher: a compact publisher reference ({ name, url, icon }), so attribution travels with a single fragment read, not only via the node index.
The authored relations array is served through unchanged in its authored JSON
position. Each edge is { type, target } where target is a canonical fragment
reference per the scheme above (a same-node id or an absolute external fragment
URL). The node validates this shape on ingest (publish) against
fragment.schema.json; it does not rewrite or resolve targets at read time.
The node's canonical publisher mark (an SVG), served with image/svg+xml and a
cache header. This is the default target of publisher.icon above.
Ledger event: manifest.
- Policy
free: full content,200,text/markdown. - Policy
paidormetered: HTTP402with a JSON body carrying the preview (firstaccess.preview_charscharacters of the content) and the payment challenge built from the manifestaccess.paymentblock. AWWW-Authenticate: Paymentheader is also set.
HTTP/1.1 402 Payment Required
Content-Type: application/json
WWW-Authenticate: Payment profile="MPP", endpoint="https://pay.example.com/mpp"
{
"policy": "paid",
"preview": "First N characters of the content ...",
"challenge": {
"profile": "MPP",
"method": "PaymentAuth",
"endpoint": "https://pay.example.com/mpp",
"price_per_access": 0.02,
"currency": "USD"
}
}In v1 the challenge is RETURNED but NOT verified. Payment verification is a
dormant stub. Ledger event: access (free) or payment_required (gated).
404 if the fragment is unknown.
The node also renders a minimal human-readable surface. It is selected purely by
content negotiation: a request whose Accept header includes text/html gets
HTML; everything else is unaffected. The machine routes above are byte-for-byte
unchanged regardless of Accept — a browser hitting
/fragments/{id}/content.md still gets the 402/markdown machine response. The
human routes are additive: their paths returned 404 before and still 404
(as JSON) for non-HTML clients.
A browser never sees a raw 402 or raw Markdown.
Publisher index: the publisher name and optional summary
(SPHERE_PUBLISHER_NAME, SPHERE_PUBLISHER_SUMMARY), a list of fragments
(title, summary, and a policy badge, each linking to its reading page), and a
footer crediting the Sphere project. Driven entirely by node data and config.
Ledger event: discovery.
Fragment reading page, rendered in the shared template.
- Policy
free/sponsored: the fullcontent.mdrendered to HTML. - Policy
paid/metered: only the preview (firstaccess.preview_charscharacters) rendered to HTML, plus a short line explaining the rest is gated and pointing at the machinecontent.mdroute. Payment is never performed from a browser, so a gated page is a preview-only read.
404 (HTML) if the fragment or its content is unknown.
Ledger event: access (free) or preview (gated).
All owner endpoints require Authorization: Bearer <SPHERE_OWNER_TOKEN> and are
read-only. Missing or wrong token returns 401. Owner requests do NOT append
ledger events.
{
"publisher": "Example Publisher",
"fragment_count": 1,
"events": { "total": 12, "by_type": { "discovery": 4, "manifest": 5, "access": 3 } },
"top_fragments": [{ "id": "2026-01-15-sample-fragment", "title": "Sample Fragment", "events": 8 }],
"revenue": { "total": 0, "currency": "USD", "payments": 0 }
}Revenue is always zero in v1 (payments are dormant).
Event series over time for one fragment, bucketed by day.
{
"fragment_id": "2026-01-15-sample-fragment",
"points": [{ "day": "2026-01-15", "event_type": "manifest", "count": 3 }]
}Payment ledger. Empty in v1; the shape is present for forward compatibility.
{ "payments": [], "total": 0 }Every public request appends exactly one row: ts, fragment_id, event_type, ua_family, ref_source. event_type is one of: discovery, manifest,
preview, access, payment_required, unlock. unlock is dormant in v1
(emitted only once payment verification is implemented); preview is emitted
when a browser views the human page of a gated fragment (a preview-only read).
Human and agent traffic share these event types and are distinguished by
ua_family (e.g. browser vs agent).
The ledger NEVER stores IP address, full user-agent, or any other PII.
ua_family is a coarse bucket; ref_source is a normalized referrer origin.