Tutorial

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

TermDescription
IPNS keyAn Ed25519 keypair the Kubo peer behind the cluster proxy generates and keeps in its keystore, one per pointer
IPNS nameThe public identifier derived from that key (k51q...)
IPNS recordThe signed statement binding the name to a CID, published into the private swarm
VersionA 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.

bash
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.

json
{
  "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.

bash
curl -X POST "https://api.ekayana.io/api/ipns/update?ipns_name=k51qzi5uqu5d..." \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@protocol-v2.pdf"
json
{
  "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

OperationEndpoint
Record by nameGET /api/ipns/{ipns_name}
Record by CID (current or original)GET /api/ipns/by-cid/{cid}
Resolve to the current CIDGET /api/ipns/{ipns_name}/resolve
Version historyGET /api/ipns/{ipns_name}/versions
bash
curl https://api.ekayana.io/api/ipns/k51qzi5uqu5d.../resolve \
  -H "Authorization: Bearer $TOKEN"
json
{ "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.

json
{
  "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 write pass from it, can move it.