IPNS Publishing
A CID names exact bytes. The moment you fix a typo in a dataset you have a new CID, and every link to the old one points at the outdated version. That is the cost of immutability.
IPNS, the InterPlanetary Name System, is the standard answer. It gives you a stable name, derived from a keypair, that you can repoint at different CIDs over time. Share the name once and keep publishing revisions behind it.
This gives research data the pairing it needs. People who want the latest revision follow the name, and every underlying CID remains a permanent, citable address for one exact revision.
How it works here
The API asks the Kubo peer behind the cluster proxy for a fresh Ed25519 key per pointer and publishes IPNS records under it, inside the private swarm. The Postgres row that pairs the name with its current CID is the authoritative pointer. The publish happens in the background, because it walks the swarm's routing table and can take a minute. You never manage key names by hand.
Creating a pointer
The file must be one you uploaded.
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" }'{
"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. A CID that already has a pointer returns the existing record instead of creating a second one.
Publishing an update
An update is a new upload addressed to the name. The body is multipart with the revised file, and the name is a query parameter.
curl -X POST "https://api.ekayana.io/api/ipns/update?ipns_name=k51qzi5uqu5d..." \
-H "Authorization: Bearer $TOKEN" \
-F "file=@protocol-v2.pdf"The API seals and stores the new bytes exactly as POST /api/upload would, under the owner's account, then moves current_cid, increments version and republishes the name. original_cid keeps recording where the pointer started. A collaborator can publish too, if they hold a pass granting write on the original CID that the record's owner issued. They present it in the x-ucan-token and x-ucan-invocation headers like any other delegated action.
Resolving
curl https://api.ekayana.io/api/ipns/k51qzi5uqu5d.../resolve \
-H "Authorization: Bearer $TOKEN"
# -> { "ipns_name": "k51qzi5uqu5d...", "cid": "bafybeih..." }Resolution through the API is a database lookup, so it is as fast as any other call. A name the database does not know is resolved through the peer behind the proxy instead.
Every revision is retained. GET /api/ipns/{ipns_name}/versions returns the full history, so a citation of an earlier version still resolves to the exact bytes that were reviewed. The IPNS Tutorial is the full walkthrough.
What the name points at
The name resolves to a sealed CID. The record lives inside the private swarm, so it resolves through the API, or through the platform's gateway if the operator exposes one, and not on the public IPNS network. What it points at is an envelope no one but the platform can open. The file itself is read through GET /api/download/{cid} with the same ownership or capability check as any other file. IPNS gives you a stable handle rather than a public download link.
A note on the swarm
The peer publishes each record into the swarm with a 24 hour lifetime and a one hour TTL, so a gateway on the cluster sees a new version within the hour and must see a republish at least daily. The API does not depend on that. Its answer comes from the database the moment update returns. If you resolve through a gateway, cache the CID you get and refresh it in the background rather than resolving on every request.