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 key | UCAN capability | |
|---|---|---|
| Scope | Whole account | One resource, listed actions |
| Lifetime | Until revoked manually | Signed expiry, always enforced |
| Delegation | Share the secret | Attenuated re-delegation, chain verified |
If a UCAN leaks, the blast radius is one file until its expiry - not your account.
1. Find the recipient
curl "https://api.ekayana.com/api/users/search?q=bob" \
-H "Authorization: Bearer $TOKEN"{ "users": [{ "id": 7, "username": "bob", "email": "bob@lab.example",
"did": "did:bio:user:7" }], "total": 1 }2. Share the file
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"].
{
"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:
curl https://api.ekayana.com/api/download/QmXyz… \
-H "Authorization: Bearer $BOB_TOKEN" \
-H "X-UCAN-Token: $UCAN" -o shared.fastaThe 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
curl https://api.ekayana.com/api/shared -H "Authorization: Bearer $BOB_TOKEN"{
"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
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:
| Operation | Endpoint |
|---|---|
| Issue | POST /api/ucan/issue |
| Validate | POST /api/ucan/validate (public) |
| Revoke | POST /api/ucan/revoke |
| Tokens you issued | GET /api/ucan/issued |
| Tokens you received | GET /api/ucan/received |
| Fetch by hash | GET /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.