InterPlanetary Name System (IPNS)
IPNS gives a file a name that stays fixed while the CID behind it moves, so a revision can be published without handing out a new link.
Understanding IPNS
The challenge with IPFS content
Every file is referenced by its CID, and a CID is the hash of the stored bytes. When the content changes, the CID changes too.
How IPNS solves this
An IPNS name is derived from a keypair. Whoever holds the key can sign a record saying that the name now points at a given CID, and can sign a new one later. The name is stable and the record moves.
Key concepts
| Term | Description |
|---|---|
| IPNS key | An Ed25519 keypair the Kubo peer behind the cluster proxy generates and keeps in its keystore, one per pointer |
| IPNS name | The public identifier derived from that key (k51q...) |
| IPNS record | The signed statement binding the name to a CID, published into the private swarm |
| Version | A record of every publish in Postgres, where the row is the authoritative pointer. This is the platform's addition. |
1. Create a pointer
POST /api/ipns/create generates a key and publishes the first record. The CID must be one of your own uploads.
curl -X POST https://api.ekayana.io/api/ipns/create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "cid": "bafybeig...", "file_name": "protocol.pdf" }'The response wraps the record.
{
"message": "IPNS record created",
"ipns_record": {
"id": 1,
"ipns_name": "k51qzi5uqu5d...",
"ipns_key_name": "file_42_3f9c1a2b",
"current_cid": "bafybeig...",
"original_cid": "bafybeig...",
"file_name": "protocol.pdf",
"owner_user_id": 42,
"version": 1
}
}Share ipns_name. It never changes again. Calling create again for the same CID returns the existing record.
2. Publish an update
There is no separate upload step. Send the revised file to the name and the API stores it, seals it under your account like any upload, and repoints the name.
curl -X POST "https://api.ekayana.io/api/ipns/update?ipns_name=k51qzi5uqu5d..." \
-H "Authorization: Bearer $TOKEN" \
-F "file=@protocol-v2.pdf"{
"message": "IPNS content updated",
"ipns_record": {
"ipns_name": "k51qzi5uqu5d...",
"current_cid": "bafybeih...",
"original_cid": "bafybeig...",
"version": 2,
"...": "..."
}
}version increments and current_cid moves. original_cid still records where the pointer started. The stored file keeps the record's file_name.
Updating requires write access. Either you own the record, or you hold a pass granting write on the record's original CID that the owner's wallet signed. A collaborator presents it in the x-ucan-token header together with a signed invocation in x-ucan-invocation, exactly as for a delegated download. That is how a collaborator publishes a revision without owning the pointer. The new version is stored under the owner's account.
3. Read
| Operation | Endpoint |
|---|---|
| Record by name | GET /api/ipns/{ipns_name} |
| Record by CID (current or original) | GET /api/ipns/by-cid/{cid} |
| Resolve to the current CID | GET /api/ipns/{ipns_name}/resolve |
| Version history | GET /api/ipns/{ipns_name}/versions |
curl https://api.ekayana.io/api/ipns/k51qzi5uqu5d.../resolve \
-H "Authorization: Bearer $TOKEN"{ "ipns_name": "k51qzi5uqu5d...", "cid": "bafybeih..." }resolve answers from the database. For a name the platform does not hold it asks the peer to resolve it in the swarm instead.
Version history
GET /api/ipns/{ipns_name}/versions returns every CID the name has pointed to, newest first.
{
"ipns_name": "k51qzi5uqu5d...",
"total": 2,
"versions": [
{ "id": 2, "cid": "bafybeih...", "version": 2, "updated_by_user_id": 7, "created_at": "2026-08-02T09:12:44Z" },
{ "id": 1, "cid": "bafybeig...", "version": 1, "updated_by_user_id": 42, "created_at": "2026-07-24T10:00:00Z" }
]
}Superseding a dataset does not erase it. A paper citing version 1 keeps resolving to the exact bytes that were reviewed, because that CID is still pinned, while new readers following the name get the current revision. updated_by_user_id records who published each version, so a collaborator's revision is attributable.
Practical notes
- The name points at a sealed CID. The record is not on the public IPNS network, and resolving it through the platform's gateway yields the encrypted envelope. The file is read through
GET /api/download/{cid}with the usual ownership or capability check. - The database row is the pointer. The publish into the swarm runs in the background with a 24 hour lifetime and a one hour TTL, and the API's answer does not wait for it.
- Cite CIDs and publish IPNS names. Use the name where readers should track the latest revision. Use the CID where a reference must never move.
- Keys live on the peer. There is no export endpoint. A pointer is bound to the account that created it, and only that account, or a holder of a
writepass from it, can move it.