Authentication
Every request to the API carries a session token signed with ML-DSA-87 (FIPS 204). This page covers obtaining one and using it.
There are two distinct credentials in the platform, and it is worth separating them up front:
| Credential | Answers | Lifetime |
|---|---|---|
| Session token | Who are you? | 24h, whole account |
| UCAN token | What may you do to this one resource? | Minutes to days, one grant |
A session token authenticates you. A UCAN grants a narrow, delegable capability over a specific resource. You always need the first; you use the second to share.
1. Register
curl -X POST https://api.ekayana.com/api/signup \
-H "Content-Type: application/json" \
-d '{
"username": "ada",
"email": "ada@lab.example",
"password": "correct-horse-battery"
}'username must be 3–50 characters, email must parse as an address, and password has a minimum length of 8 plus a complexity rule. An optional name field carries a display name.
{ "token": "eyJhbGciOi...<payload>...<signature>" }2. Sign in
curl -X POST https://api.ekayana.com/api/signin \
-H "Content-Type: application/json" \
-d '{ "email": "ada@lab.example", "password": "correct-horse-battery" }'Returns the same { "token": ... } shape. /api/signup and /api/signin are the only unauthenticated write endpoints.
3. Call an authenticated endpoint
curl https://api.ekayana.com/api/users/me \
-H "Authorization: Bearer $TOKEN"{ "id": 42, "username": "ada", "email": "", "did": "did:bio:user:42" }The header must be exactly Authorization: Bearer <token>. A missing header, a malformed one, or a token that fails verification all return an auth error before the handler runs.
Note:/api/users/mereturns an empty
To find collaborators to share with, GET /api/users/search?q=<query> matches username or email (minimum 2 characters, capped at 10 results) and returns each user's did, which is what the sharing flow consumes.
4. Token structure
A successful signup or signin returns a session token signed with ML-DSA-87 (FIPS 204) - the same post-quantum suite used for capability tokens. The structure is JWS-shaped:
base64url(header) . base64url(claims) . base64url(signature)// header
{ "alg": "ML-DSA-87", "typ": "JWT", "nonce": "…" }
// claims
{ "sub": "42", "iat": 1753228800, "exp": 1753315200, "nonce": "…" }Present it as a bearer credential on every authenticated request:
curl https://api.ekayana.com/api/users/me \
-H "Authorization: Bearer <token>"How verification works
The entire signature is carried in the token and checked against the service public key before any claim is read. Two properties follow:
- No secret is needed to verify. Read-only replicas and external auditors can validate a token without the ability to mint one.
- Claims are tamper-evident. Editing
subto impersonate another user invalidates the signature, so the token is rejected before the claim is trusted.
Token lifetime defaults to 24 hours and is configurable via JWT_EXPIRATION_SECS. Expiry is enforced on every request; there is no refresh window, so clients re-authenticate when a token lapses.See Post-Quantum Security for the signing identity and key management.