The Proton API uses a session fork protocol to create child sessions for
service-specific apps (Lumo, Drive, Pass, etc.) from a parent account
session. This is how the web client navigates between account.proton.me
and service apps like lumo.proton.me.
The fork protocol has two phases:
- Push — The parent (account) app creates a fork selector on its host.
- Pull — The child (service) app consumes the selector on its own host.
The selector is a shared credential that bridges the two hosts. The Proton
backend correlates push and pull via the Session-Id cookie (domain
.proton.me, shared across all subdomains).
The parent app sends an authenticated POST to create the fork:
POST /api/auth/v4/sessions/forks
Host: account.proton.me
Cookie: AUTH-<uid>=<token>; Session-Id=<sid>; ...
x-pm-uid: <uid>
x-pm-appversion: web-account@5.0.367.1
Content-Type: application/json
{
"Payload": "<encrypted-fork-blob>",
"ChildClientID": "web-lumo",
"Independent": 0
}
Key details:
- Auth is cookie-based (
AUTH-<uid>=<token>cookie), not Bearer token. x-pm-uidheader is present.- No
Authorization: Bearerheader. ChildClientIDdetermines which scopes the child session receives.Independent: 0means the child session is linked to the parent.Payloadcontains an encrypted blob with the key password.
Response:
{"Code": 1000, "Selector": "<selector>"}The child app consumes the selector with an unauthenticated GET:
GET /api/auth/v4/sessions/forks/<selector>
Host: lumo.proton.me
Cookie: Session-Id=<sid>; iaas=...; Tag=default
x-pm-appversion: web-lumo@1.3.3.4
Key details:
- No auth headers — no
x-pm-uid, noAuthorization, noAUTH-*cookie. - Only session cookies:
Session-Id,iaas,Tag,zId,aId. x-pm-appversionmust match the target service.- The
Session-Idcookie correlates the pull with the push.
Response:
{
"Code": 1000,
"Payload": "<encrypted-fork-blob>",
"LocalID": 1,
"ExpiresIn": 86399,
"TokenType": "Bearer",
"Scope": "full locked self organization payments keys parent user loggedin paid nondelinquent drive docs verified settings lumo",
"Scopes": ["full", "locked", "self", "organization", "payments", "keys", "parent", "user", "loggedin", "paid", "nondelinquent", "drive", "docs", "verified", "settings", "lumo"],
"UID": "<child-uid>",
"UserID": "<user-id>",
"AccessToken": "<child-access-token>",
"RefreshToken": "<child-refresh-token>"
}After the pull, the child app establishes its session:
GET /api/core/v4/users— fetch user dataPUT /api/auth/v4/sessions/local/key— set local session keyPOST /api/core/v4/auth/cookies— establish cookie-based authGET /api/core/v4/addresses— fetch addresses for key unlock
All post-fork calls use x-pm-appversion matching the service host
(e.g., web-lumo@1.3.3.4 for lumo.proton.me).
The lumo scope (and other service-specific scopes) is granted only
when the push uses cookie-based auth. Bearer token auth on the push
results in a restricted scope set that excludes service-specific scopes.
This is the critical difference between the browser flow and a CLI client: the browser naturally uses cookies, while API clients typically use Bearer tokens.
| Cookie | Domain | Push | Pull | Purpose |
|---|---|---|---|---|
AUTH-<uid> |
.proton.me |
Yes | No | Parent session auth |
Session-Id |
.proton.me |
Yes | Yes | Correlates push/pull |
iaas |
.proton.me |
Yes | Yes | Session metadata |
Tag |
.proton.me |
Yes | Yes | Session metadata |
For a CLI client to use the fork protocol with full scopes (including lumo):
-
The session must use cookie-based auth, not Bearer tokens. The
go-proton-apiResty client is Bearer-only and cannot be used for Lumo. A separate cookie-based session handler is required. -
The auth flow must replicate the browser:
- SRP login → get Bearer tokens
POST /core/v4/auth/cookies→ transitions to cookie auth- After this call, Bearer tokens are invalid. New credentials are
delivered as
AUTH-<uid>=<token>andREFRESH-<uid>=<token>cookies. - All subsequent API calls use cookie auth (no Bearer header).
-
The fork push uses the
AUTH-*cookie from step 2. Without it, the server grants restricted scopes excludinglumo. -
The fork pull is unauthenticated — only
Session-Idcookie. -
After the fork pull, the child session calls
auth/cookiesagain to establish cookie auth on the child (entry 86 in the HAR).
The go-proton-api library uses Bearer token auth exclusively via its
Resty HTTP client. The auth/cookies endpoint invalidates Bearer tokens
and transitions to cookie-only auth. After calling auth/cookies:
- The Resty client's Bearer tokens are invalid → 401 on all requests
- The Resty client's token refresh fails → 422 "Invalid input"
- Only cookie-based requests (via
http.Clientwith cookie jar) work
This means Lumo requires a cookie-based session handler independent of
go-proton-api. Drive and other services can continue using the Resty
client with Bearer auth.
WebClients.git/packages/shared/lib/authentication/fork/produce.ts— pushWebClients.git/packages/shared/lib/authentication/fork/consume.ts— pullWebClients.git/packages/shared/lib/api/auth.ts—pushForkSession,pullForkSessiontmp/fork-debug-findings.md— raw debug session notes