Docs/File Storage API
Tutorial

File Storage API

Files are stored on IPFS and addressed by CID - a hash of the content itself. Two consequences shape this whole API:

  • Identical bytes produce an identical CID. Uploading the same file twice yields one CID with two owners, which is why deletion is reference-counted.
  • A CID is not a permission. Knowing a CID does not grant access; every read goes through an ownership or capability check.

Upload

POST /api/upload takes multipart/form-data. Filenames are sanitised server-side.

bash
curl -X POST https://api.ekayana.com/api/upload \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@genome.fasta"
json
{
  "cid": "QmXyz…",
  "name": "genome.fasta",
  "size": 1048576,
  "timestamp": "2026-07-24T10:00:00Z",
  "user_id": 42
}

The CID is also returned in an X-CID response header, so a client can read it without parsing the body.

Large files: upload asynchronously

Add ?async=true and the call returns immediately with a task handle instead of blocking until IPFS has the bytes.

bash
curl -X POST "https://api.ekayana.com/api/upload?async=true" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@wgs-run.tar"
json
{
  "task_id": "3f9c…",
  "status": "pending",
  "cid": null,
  "error": null,
  "progress": 0.0,
  "started_at": "2026-07-24T10:00:00Z"
}

Poll GET /api/upload/status/{task_id} until status becomes completed (with cid populated) or failed (with error).

Concurrency is bounded server-side by MAX_CONCURRENT_UPLOADS (default 50); work beyond that queues rather than being rejected.

Download

bash
curl https://api.ekayana.com/api/download/QmXyz… \
  -H "Authorization: Bearer $TOKEN" -o genome.fasta

Access is granted two ways:

  • Ownership - you uploaded it.
  • Delegation - you hold a UCAN granting read on ipfs://{cid}, presented in an additional header:
bash
curl https://api.ekayana.com/api/download/QmXyz… \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-UCAN-Token: $UCAN"

The session token still identifies you; the UCAN carries the grant. See Sharing & Delegation.

Payloads written through the encrypted path are sealed with AES-256-GCM under an ML-KEM-wrapped content key and are transparently decrypted on read - a legacy plaintext object is detected and passed through unchanged, so both coexist. See Privacy Controls.

Metadata, listing, deletion

OperationEndpoint
File metadataGET /api/metadata/{cid}
List your pinned CIDsGET /api/pins
DeletePOST /api/delete with { "cid": "Qm…" }

GET /api/metadata/{cid} accepts X-UCAN-Token for delegated reads, same as download.

Deletion is reference-counted

POST /api/delete removes your metadata row, then unpins from IPFS only if no other user still holds that CID. This is a direct consequence of content addressing: your upload and a colleague's identical upload are the same object, so one party deleting must not destroy the other's data.

GDPR note: unpinning removes the platform's copy. Content already fetched by third parties cannot be recalled - which is exactly why personal data is encrypted before it reaches IPFS rather than relying on deletion.