Protocol Labs Ecosystem Integration
The storage and authorization layers of the platform are built from specifications that came out of Protocol Labs and the ecosystem around it. These are content addressing, IPFS itself, IPNS, the DAG-CBOR codec and UCAN. This page lists what the platform runs, where each piece sits, and what you would observe if you went looking for it. Every claim on this page can be checked against the API, the cluster or the published crates.
| Specification | Where it runs | What it does here |
|---|---|---|
| IPFS | a private cluster of Kubo peers, reached through its proxy | Stores every sealed payload and every public metadata document. Pins on every peer on write. |
| CID (multiformats) | every file, document and token | CIDv1, SHA-256, base32. The address of a stored object is the hash of its bytes. |
| IPNS | the keystore of the peer behind the proxy | A stable name for a dataset that gets revised. The version history lives in Postgres. |
| DAG-CBOR (IPLD) | pq-ucan and the sealed envelope | The canonical encoding of every UCAN delegation and invocation, and of the encryption envelope around each payload. A token's CID is its handle. |
| UCAN 1.0 | pq-ucan, inside the API | Delegations a wallet signs and invocations a recipient signs, validated as a chain with the service as executor. |
| Multikey / multibase | did:bio documents | Ed25519 verification keys as publicKeyMultibase strings with a multicodec prefix, and ML-KEM-1024 encapsulation keys as multibase strings in a service entry. |
IPFS as a private cluster
IPFS runs as a private cluster. Several Kubo peers share a swarm key, so they connect only to each other and never to the public DHT, and IPFS Cluster peers keep one shared pinset and replicate every pin across them. The API speaks the Kubo RPC dialect to a single endpoint, IPFS_API_URL. In the cluster deployment that endpoint is the cluster's IPFS proxy, which pins and unpins on every peer and forwards everything else to the peer it fronts.
| RPC call | Used for |
|---|---|
add with pin=true and cid-version=1 | Storing a payload or a metadata document. The proxy pins it on every peer as part of the write. |
cat | Reading a payload back from the fronted peer before it is opened for the caller |
pin/rm | Releasing a CID no record references any more, on every peer |
key/gen (Ed25519) | Creating the key behind an IPNS name, in the fronted peer's keystore |
name/publish | Pointing an IPNS name at a CID |
name/resolve | Resolving a name the database does not know |
GET /api/ready reports whether the endpoint answers, next to the database check. Two kinds of object reach the cluster.
- Every file uploaded through
POST /api/uploadis a sealed payload. It is encrypted under a fresh content key beforeaddis called, and the key is wrapped to the service's ML-KEM-1024 key inside the same envelope. The CID names the envelope. See Privacy Controls. - Every DID has a public metadata document. When a DID is created or its metadata changes, the API writes a
metadata.jsonholding the title, researchers, keywords, license, related identifiers, funding, the DID, its kind and its DOI, and pins it. The previous version is unpinned. The DID document points at the current one through a service entry of typeBioMetadatawhose endpoint isipfs://<cid>.
An upload returns the CID of the envelope.
curl -X POST https://api.ekayana.io/api/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@results.csv"{ "cid": "bafkrei...", "name": "results.csv", "size": 20480,
"content_type": "text/csv", "timestamp": "2026-07-24T10:00:00Z", "user_id": 42 }The peers never join the public network, so a public IPFS gateway cannot fetch that CID at all. The platform's own gateway, if the operator exposes one, returns the envelope rather than the file. GET /api/download/{cid} is the only call that opens it, for the owner or for someone presenting a valid pass.
Content identifiers
Objects are addressed by CIDv1 with SHA-256, rendered in base32, so every identifier the API returns starts with baf. A single block, such as a metadata document, is bafkrei..., and a file large enough to be chunked is bafybei.... The address is the hash of the stored bytes. A fetched object can be checked against its own name, and a tampered block does not match.
Sealing changes one thing IPFS users expect. Two uploads of the same file produce two different CIDs, because each upload gets a fresh content key and a fresh nonce and the envelopes differ even when the plaintext does not. A CID therefore has exactly one owner, and GET /api/pins lists the CIDs of your uploads and no one else's. Metadata documents are added in the clear, so two identical documents would share a CID. In practice each carries its own DID and timestamps.
IPNS
A CID names exact bytes. For a dataset that gets revised, POST /api/ipns/create asks the peer behind the proxy for a new Ed25519 key and publishes the first record under it. Later revisions are published under the same name through POST /api/ipns/update. The peer publishes into the private swarm in the background with a 24 hour record lifetime and a one hour TTL. The Postgres row is the authoritative pointer. GET /api/ipns/{name}/resolve answers from the database and falls back to the peer's name/resolve only for names the database does not hold. Every version is retained. The name is published inside the private swarm, so it resolves through the API or the platform's gateway and not on the public IPNS network. See IPNS Publishing.
DAG-CBOR and IPLD
The platform uses one part of IPLD. DAG-CBOR is the encoding of its signed and sealed objects.
pq-ucanencodes every UCAN delegation and invocation as DAG-CBOR, using thecidandipld-corecrates. Thesigning_bytesthatPOST /api/share/file/preparereturns are the DAG-CBOR payload the wallet signs. Thetoken_hashreturned afterwards is the CID of the token, abafyrei...identifier under the dag-cbor codec. Revocation names the token by that CID.- Each stored payload is a DAG-CBOR map with four keys.
vis the envelope version, currently 1.ivis the AES-GCM nonce.ctis the ciphertext.keyis the wrapped content key in the IPLD form thatpq-spacedefines. Decoding the bytes fromipfs catshows that structure and nothing readable.
The platform does not build a general IPLD graph of research objects. Papers, datasets and authors are linked through DID documents, DOIs and the knowledge graph rather than through IPLD links. Knowledge Graph Integration describes how those links are stored.
UCAN
UCAN is the authorization model. A share is a UCAN 1.0 delegation the owner's wallet signs. A read is an invocation the recipient's browser session key signs. pq-ucan validates both with the service's did:bio as executor. The envelope format, the DAG-CBOR encoding and the varsig headers are the ones the wider ecosystem uses, with ML-DSA principals added behind a feature flag. The format, the policy language and the validation rules are on UCAN Authorization. The flow a user sees is on Sharing & Delegation.
Multikey and multibase
did:bio documents encode Ed25519, X25519 and secp256k1 verification keys as Multikey methods. The publicKeyMultibase value is the multicodec prefix naming the key type, followed by the key, rendered in base58btc with the z prefix, so a wallet's key reads z6Mk.... did-bio-core produces and parses these, and that lets a resolver verify a signature from the identifier alone. ML-DSA-87 keys take the other form the DID data model allows, a JsonWebKey method with an AKP JWK, because no final multicodec mapping exists for them yet. The same multibase convention carries an ML-KEM-1024 encapsulation key, multicodec mlkem-1024-pub with code 0x120d, in a service entry typed SpaceEncapsulationKey. That entry is how pq-space publishes the key a content key can be wrapped to.
Checking it yourself
GET /api/readyshows theipfscheck next todatabase.- Resolve any anchored DID with
GET /api/did/resolve/{did}, take the#metadataservice endpoint, and fetch its CID through the platform's gateway. You get the public JSON document. A public IPFS gateway cannot reach it, because the peers never join the public network. - Fetch a file CID through the same gateway. You get DAG-CBOR bytes with the keys
v,iv,ctandkey, and nothing else. - Decode a
tokenfromGET /api/ucan/token/{cid}as DAG-CBOR and hash it. The CID is thetoken_hashyou were given.