Authentication in depth
Technical details of V3 Custody authentication: magic link sign-in, sessions, Bearer and cookies, errors, and the access model.
This page expands on the introductory Authentication page. It covers magic link sign-in, sessions, how Bearer and cookie authentication work, and edge cases. Both authentication methods are built on a session: a Bearer token for services and mobile apps, and an httpOnly cookie for the web.
The sign-in flow, from magic link to session and the two ways to present it:
Magic link sign-in
Users sign in with a magic link, not a password:
POST /api/v1/auth/magic-linksends a magic link to the email address (via Cloudflare Email binding). Only existing users and invited users (with an active invitation) can sign in; registration without an invitation is blocked. After clicking the link, the user is redirected to the frontend (FRONTEND_URL).
curl -X POST "[BASE_URL]/api/v1/auth/magic-link" \
-H "Content-Type: application/json" \
-d '{ "email": "trader@acme.com" }'
await fetch("[BASE_URL]/api/v1/auth/magic-link", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "trader@acme.com" }),
});
import requests
requests.post(
"[BASE_URL]/api/v1/auth/magic-link",
json={"email": "trader@acme.com"},
)
See the magic link page in the API Reference for the exact request body schema. The fields in this example are for illustration only.
Session
A session is created after sign-in. The web gets an httpOnly cookie, while services and mobile apps use the session token as a Bearer token.
Presentation methods: Bearer and cookie
Both methods use the same session, just a different transport:
| Bearer | Cookie | |
|---|---|---|
| for | services, mobile | web console |
| how it's sent | Authorization: Bearer <token> | httpOnly cookie, automatically |
| accessible from JS | yes (you store the token) | no (httpOnly, by design) |
| server↔server | yes | no |
Bearer: for services and mobile
Pass the session token in the Authorization: Bearer <token> header.
Cookie: for the web
When you sign in through the web console, the platform sets an httpOnly session cookie. The browser sends it automatically, and by design it isn't accessible from JavaScript.
For server-to-server integrations, use Bearer rather than cookies.
Authentication errors
Without a valid token or session, the API returns 401:
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
| Code | When | Meaning |
|---|---|---|
401 | no valid token or session | not authenticated: "who you are" is unknown |
403 | authenticated, but lacking permission | group membership doesn't allow the action (default-deny) |
An authenticated but insufficiently authorized request returns 403. Access is determined by group membership (default-deny: without allow policies, actions are forbidden).
Access model
Authentication answers "who are you," while access answers "what are you allowed to do." In V3 Custody, permissions come from policy group membership; tokens have no separate scopes. Visibility is isolated by default: a member sees their own data, while an administrator sees the entire Vault. Visibility can be expanded through visibility-grants. For details, see Authentication and the Invite your team guide.
Common mistakes
| Symptom | Cause |
|---|---|
401 even though a token is sent | the session expired or the Authorization header format is wrong |
| registration fails | no active invitation; sign-in is only for existing or invited users |
| cookie can't be read from JS | it's httpOnly by design; use Bearer for server↔server |
403 for a new user | the user hasn't been added to any group (default-deny) |
| magic link doesn't arrive | check the email address and Email binding; only invited users can sign in |