Docs/Sharing & Delegation
Tutorial

Sharing & Delegation

Sharing a file does not copy it or make it public. It mints a UCAN capability token that names one CID, one recipient, one set of actions, and an expiry - and can be revoked.

Why not an API key?

The platform deliberately has no API keys. A key is a bearer secret that grants everything its owner can do, forever, until someone remembers to rotate it. A capability token is the opposite on all three axes:

API keyUCAN capability
ScopeWhole accountOne resource, listed actions
LifetimeUntil revoked manuallySigned expiry, always enforced
DelegationShare the secretAttenuated re-delegation, chain verified

If a UCAN leaks, the blast radius is one file until its expiry - not your account.

1. Find the recipient

bash
curl "https://api.ekayana.com/api/users/search?q=bob" \
  -H "Authorization: Bearer $TOKEN"
json
{ "users": [{ "id": 7, "username": "bob", "email": "bob@lab.example",
              "did": "did:bio:user:7" }], "total": 1 }

2. Share the file

bash
curl -X POST https://api.ekayana.com/api/share/file \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cid": "QmXyz…",
    "recipient_did": "did:bio:user:7",
    "capabilities": ["read"],
    "expiration_days": 7
  }'

expiration_days defaults to 7. capabilities is a list of actions - typically ["read"], optionally ["read","write"].

json
{
  "token": "ucan.v1.<payload>.<signature>",
  "token_hash": "3f9c…",
  "expires_at": 1753833600,
  "cid": "QmXyz…",
  "recipient_did": "did:bio:user:7",
  "capabilities": ["read"]
}

Send token to the recipient. Keep token_hash - it is the handle you revoke with.

The token is a few kilobytes: an ML-DSA-87 signature alone is 4,627 bytes. Put it in a header or request body, not a URL.

3. Redeem it

The recipient presents their own session token plus the UCAN:

bash
curl https://api.ekayana.com/api/download/QmXyz… \
  -H "Authorization: Bearer $BOB_TOKEN" \
  -H "X-UCAN-Token: $UCAN" -o shared.fasta

The server verifies the signature, confirms the token is neither expired nor revoked, and only then checks whether its capabilities cover read on ipfs://QmXyz….

4. See what was shared with you

bash
curl https://api.ekayana.com/api/shared -H "Authorization: Bearer $BOB_TOKEN"
json
{
  "files": [{
    "cid": "QmXyz…", "name": "genome.fasta", "size": 1048576,
    "shared_by": "did:bio:user:42", "capabilities": ["read"],
    "expires_at": 1753833600, "token_hash": "3f9c…"
  }],
  "total": 1
}

Expired and revoked grants are filtered out server-side.

5. Revoke

bash
curl -X DELETE https://api.ekayana.com/api/share/revoke/3f9c… \
  -H "Authorization: Bearer $TOKEN"

Revocation takes effect on the next use of the token - there is no cached allow-list to wait on. Only the issuer may revoke, and revoking a token also invalidates everything delegated beneath it: the proof chain is walked on every validation, so a revoked ancestor fails the whole chain.

Managing tokens directly

The share endpoints wrap the general UCAN API, which you can also use directly:

OperationEndpoint
IssuePOST /api/ucan/issue
ValidatePOST /api/ucan/validate (public)
RevokePOST /api/ucan/revoke
Tokens you issuedGET /api/ucan/issued
Tokens you receivedGET /api/ucan/received
Fetch by hashGET /api/ucan/token/{token_hash}

/api/ucan/validate is public by design: a verifier should be able to check a token without holding an account. See UCAN Authorization for the token format, the capability-matching rules, and attenuation.