A Hypermedia link names a thing by its owner. The server that holds it does not appear. An hm:// URL starts with an account's public key and continues with a path the owner chose. It can pin an exact version, and even a single paragraph or a range of words inside it. Any web site that runs Seed can serve the same document under an ordinary https:// URL, and the two forms convert into each other exactly.
The grammar
hm://<uid>[/<path>][?v=<version>[&l]][#<block>[+|[<start>:<end>]]]part | meaning | example |
|---|---|---|
| the principal of the space, in its string form |
|
| zero or more |
|
| an exact version: the head Change CIDs, sorted, joined with |
|
| present with |
|
| a block id inside the document |
|
| the block and all of its children |
|
| a range of text inside the block, in Unicode code points |
|
So hm://z6Mkfz…/notes/sushi?v=bafyreid3…#Zr1kAbQ2[12:40] means "characters 12 to 40 of block Zr1kAbQ2, in the version whose single head is bafyreid3…, of the document at /notes/sushi in the space z6Mkfz…". Without v the URL means the latest version the reader knows about. With v alone it is an immutable reference. With v and l it asks for the newest version but records the one the author saw, so the link still works as an exact citation if the document is deleted. Today the daemon returns the latest version when l is present.
Paths must start with /, must not end with /, and may not contain single or double quotes, backslashes, NUL, tab, CR or LF. The daemon rejects any Ref that breaks these rules. Two segment shapes have a special meaning. A path that is a single segment decoding as a TSID (14 or 15 base58 characters) is a comment or contact record. The apps use a leading - for local draft paths that are never published.
Versions, blocks and ranges
A version pins the whole history, because every head Change links its dependencies by CID. The same v= always replays to the same content on any node that has the blobs. Without v, a block reference #id follows the block wherever it moves in later versions. Combine both when you need a quotation that cannot drift. Ranges count Unicode code points. They do not count UTF-16 units or bytes, so an emoji counts once.
Links inside documents use the same grammar. A link annotation, an embed block, a button, and a mention all carry an hm:// URL. The daemon indexes each of them as a citation of the target; see Comments for backlinks. Web URLs of Seed sites are converted to hm:// when pasted into the editor.
View suffixes
A path segment beginning with : after the document path selects a view of the same document. The apps and the web server understand all of them. The daemon and the agents runtime understand the ones marked.
suffix | shows | notes |
|---|---|---|
| the documents under this path | listing via |
| the discussion |
|
| the activity feed |
|
| who may write here | |
| the metadata as attributes |
|
| the schema this document defines | |
| the account's profile | also accepted by daemon discovery |
| contact views of a space | optionally |
| site-level views | app and web only |
The agents runtime's read verb accepts /:directory, /:attributes, /:profile and /:comments on an hm:// address. The daemon's discovery request also understands the wildcards hm://<uid>/<path>/* (one level) and hm://<uid>/<path>/** (everything beneath).
Comments and other records
A comment's identity is <author uid>/<tsid>, and its address is hm://<author uid>/<tsid>. The record lives in its author's space. The document it is about may be in another space. On the web it is usually shown in context as https://<site>/<doc path>/:comments/<author uid>/<tsid>, with ?v=<comment cid> pinning one edit of the comment. Contacts are addressed the same way by TSID. Capabilities have no URL, and the API lists them. The daemon's link indexer also understands hm://c/<cid>, a link to one exact comment blob, but the apps do not produce that form today.
Web URLs and hm URLs
Every Seed web server, whether it is hyper.media or a site published at its own domain, serves documents at two kinds of URL:
web form | meaning |
|---|---|
| the gateway form: any document of any space, convertible 1:1 to |
| the site form: the document at |
Query parameters and fragments carry over unchanged, so https://hyper.media/hm/z6Mkfz…/notes/sushi?v=…#Zr1kAbQ2+ and hm://z6Mkfz…/notes/sushi?v=…#Zr1kAbQ2+ are the same reference. This keeps web links durable. If the hyper.media server is offline, a reader who knows the rule can keep using the /hm/ URLs as hm:// URLs on the peer-to-peer network. See broken links.
The site form needs one extra fact: which space the host is registered to. Three mechanisms provide it:
GET https://<host>/hm/api/config returns {registeredAccountUid, peerId, addrs, …}. The daemon uses this to resolve any https:// URL you hand to it. One Seed node also learns this way which peer serves a site; see Sites.
Any document page answers OPTIONS (and GET) with X-Hypermedia-Id, X-Hypermedia-Version, X-Hypermedia-Title, X-Hypermedia-Type (Document or Comment), X-Hypermedia-Authors and, for comments, X-Hypermedia-Target. The id, title, authors and target are URL-encoded. The response is 200 with only CORS headers if the URL does not resolve.
The same values are in the HTML as <meta name="hypermedia_id">, hypermedia_version, hypermedia_title, hypermedia_type, hypermedia_authors and hypermedia_target.
Add .md or .json to the document segment of a site URL to export it: https://<host>/notes/sushi.md, /notes/sushi.json/:directory, /hm/<uid>/<path>.md, with ?v= and ?l honoured.
Reserved first segments
On a web host:
Under /hm/, the segments download, connect, register, profile, contact, agents, auth, create-site, notifications, embed and inspect are pages. None of them is a space. In the hm:// scheme itself, hm://connect/<payload> carries a peer-connection invitation and hm://inspect/<uid>/<path> opens the app's blob inspector. Neither is a document address.
Working with URLs
In the Seed app
In the Seed app, Copy Link on a document, a block or a selection produces the site form when the space has a site, and the gateway form otherwise. It uses #block+ for a whole block and #block[start:end] for a selection. The version graph's "Exact version" link adds v=. Pasting an hm:// or a Seed web URL into the editor makes a link. The app registers hm:// as an operating-system scheme, so such links open the desktop app.
CLI
Every CLI command that takes an id accepts hm:// and Seed web URLs alike: seed-cli document get https://hyper.media/hm/<uid>/<path> and seed-cli document get hm://<uid>/<path>?v=<version> both work, and document get <url>/:directory lists children. To turn an unknown web URL into an hm:// one from a shell:
curl -s -X OPTIONS -I https://example.org/some/page | grep -i x-hypermediaSDK
In the SDK, unpackHmId(url) parses hm:// URLs and gateway https://<host>/hm/… URLs into {id, uid, path, version, latest, blockRef, blockRange, hostname, scheme}. It returns null for a site-form URL. packHmId(id) writes the hm:// form, and parseFragment / serializeBlockRange handle the # part. resolveHypermediaUrl(url) resolves an arbitrary web URL through an optional cached domain resolver and then the OPTIONS headers. These are in @seed-hypermedia/client; see the SDK guide. The web-form builders createWebHMUrl and createSiteUrl live in @shm/shared, a package internal to the apps.
Web API
The Resource request takes the packed id as its id parameter: GET /api/Resource?id=hm://<uid>/<path>?v=… (URL-encoded). The daemon's GetResource accepts hm://, http:// and https:// and does the /hm/api/config resolution itself. GET /api/DiscoveryStatus?uid=&path=&v= reports whether a node has fetched a document it did not have. See the Seed API guide.
Agents
Seed Agents address everything by URL: read hm://<uid>/<path> (with ?v=, /:directory, /:attributes, /:profile, /:comments, or a comment id hm://<uid>/<tsid>), read https://… (tried as Hypermedia first, then as a web page), and write hm://<uid>/<path>. An external agent should convert web URLs with the OPTIONS trick above before calling the CLI. See Agents.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime