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.