Authentication
How sign-in and access work across Jetsyn's surfaces: the web app session cookie (email magic link or Google), the CLI device-authorization grant with a locally stored token, and the server-side entitlement gate that resolves every request to your one cell.
Jetsyn has one account behind two surfaces. You sign in to the web app with an email link or Google, and you sign in to the CLI once from your browser. Either way you land in the same, single cell, and every request is authorized on the server before it touches your data.
One account, two surfaces, one cell#
Whether you come in from the browser or the terminal, Jetsyn resolves your email to exactly one Jetsyn Cloud cell. The web app and the CLI are two doors into that one cell, never two accounts and never two copies of your data. Sign in on either and you are working the identical files.
Authentication is split cleanly by surface. The web app carries a browser session cookie. The CLI runs a device-authorization grant: you approve the terminal once from a browser, and an opaque token lives on your machine after that. Both resolve to the same account and the same cell.
Available now
Two sign-in methods in the web app, email magic link and Google, plus one browser-approved device sign-in for the CLI. All three map to the same Jetsyn account and the same cell.
Web app sign-in#
The web app signs you in two ways, and both end at the same session. There is no password to store.
Email magic link. You enter your email and Jetsyn sends a one-time link. POST /api/auth/request with { email } mints a single-use, signed, time-boxed login token and emails you a link back to the app. Clicking it hits GET /api/auth/verify?token=..., which verifies the token (single-use, signature-checked, expiring), starts your session, and redirects you into the product.
Google. Sign in with Google sends you through GET /api/auth/google/start to Google's consent screen (OAuth 2.0 authorization-code flow) and back to GET /api/auth/google/callback. Jetsyn exchanges the code server-side, requires a verified email, and mints the exact same session the email link does.
| Endpoint | What it does |
|---|---|
POST /api/auth/request | Take { email }, mint a single-use signed login token, email the magic link. |
GET /api/auth/verify?token=... | Verify the link, set the jetsyn_session cookie, redirect into the app. |
GET /api/auth/google/start | Hand off to Google's consent screen (authorization-code flow). |
GET /api/auth/google/callback | Exchange and verify server-side, set the same jetsyn_session cookie. |
The session cookie
Your web session is a stateless, signed token in a jetsyn_session cookie. There is no server-side session table to look up. The cookie itself carries the signed claim, and the backend verifies the signature on every request. Magic link and Google mint the identical cookie, so the two methods are interchangeable.
HttpOnly, so page scripts can never read it.Secure, so it only travels over HTTPS.SameSite=Lax, so an email or OAuth redirect still carries it on the first navigation.- A 7-day lifetime, after which you sign in again.
- Scoped to the Jetsyn web domain, and separate from the token your CLI holds, so a browser cookie can never reach a tenant cell directly.
CLI sign-in (device authorization)#
A browser session cookie must not live inside a long-running terminal, so the CLI does not reuse it. Instead it runs an OAuth 2.0 device-authorization grant: you approve the terminal once in a browser, and the CLI receives its own long-lived token to store locally. One command starts it.
jetsyn login # or just `jetsyn`, which signs you in on first connect- 1
Start the grant
The CLI calls
POST /api/cli/device-startand gets back a CLI-held secret (device_code), a short human code (user_code), averification_uri, a pollinterval, and an expiry. Nothing is signed in yet. - 2
Approve in the browser
Your browser opens to the Jetsyn sign-in page with the code prefilled, so there is nothing to paste. Sign in with your email link or Google if you are not already, then approve the terminal. The page binds the code to your account via
POST /api/cli/device-claim, authorized by your web session. - 3
The CLI collects its token
While you approve, the CLI polls
POST /api/cli/device-tokenwith itsdevice_code. It sees{ "status": "pending" }until you finish, then receives a long-lived, opaqueaccess_tokenand your cell'sworkspace_slug, returned exactly once. - 4
Token stored locally
The CLI writes the token to
~/.jetsyn/credentialswith600permissions. The server keeps only a SHA-256 hash of it at rest, never the plaintext, and the CLI passes it through an environment variable, never on the command line. - 5
Connected
Every later run presents the token as an
Authorization: Bearerheader and drops you straight into your cell with no re-prompt.jetsyn logoutdeletes the local token.
| Endpoint | Purpose | Auth |
|---|---|---|
POST /api/cli/device-start | Begin the grant; returns device_code, user_code, verification_uri, interval, expires_in. | None |
POST /api/cli/device-claim | Browser binds the user_code to your signed-in account. | Web session |
POST /api/cli/device-token | CLI polls; pending until approved, then returns access_token + workspace_slug. | device_code |
GET /api/cli/me | Confirm the token; returns email, super_admin, workspaces. | Bearer token |
token=<opaque token, presented as a Bearer>
email=you@company.com
workspace=your-cellTip
The device grant is invite-friendly and cross-device: if the browser does not open, or you approve on another machine, visit the verification_uri and type the short user_code shown in your terminal.
One account, one cell#
The first time you sign in on the CLI, Jetsyn ensures you own a cell and hands its slug back in the token response. The web app resolves the same cell for the same account. It is one resolver keyed on your identity, so your terminal and your browser open the identical cell, never a second store.
# CLI, right after you approve in the browser
POST /api/cli/device-token { "device_code": "..." }
-> { "access_token": "<opaque token>", "workspace_slug": "your-cell" }
# Confirm the token and read your identity
GET /api/cli/me (Authorization: Bearer <token>)
-> { "email": "you@company.com",
"super_admin": false,
"workspaces": [ { "slug": "your-cell" } ] }Same cell, either surface
A file you edit from the CLI shows up in the web app immediately, because both surfaces read the same cell. Sign-in only decides how you reach that cell, never which cell you get.
How access is gated#
Signing in proves who you are. A second, separate check decides what you may do. Entitlement is resolved on the server for every gated call, and never trusted from the client.
The backend is the single authority. GET /api/me returns your identity and your entitlement together, so a frontend or an integration reads one place to know what to allow.
email,user_id,role,super_adminidentify you.workspaceslists the cell slugs you can reach.planandstatusare your subscription truth;entitledis the derived unlock.onboarding_completedtells the app whether to route you into setup or straight to work.
The unlock rule is binary: you are entitled when your subscription status is active or trialing, and a super-admin is always allowed. A gated action you are not entitled to run returns HTTP 402 with { "error": "billing_required" }, the app's signal to open checkout. See Surfaces & tiers for what each plan unlocks over the same cell.
The CLI is gated the same way, one layer deeper: a cell only opens for a member of that cell (or a super-admin), and the membership check fails closed on any uncertainty. Your token proves identity; the cell mount re-checks that this identity may open this slug on every connect.
Never gate on the client
A client-side plan or tier value is for display only. Always re-check entitlement server-side (via /api/me or the backend gate) before running a paid action. A new route that skips the check can silently ship ungated.
Where to go next#
The CLI
The command-line product in full: install it, sign in once with the device grant, and reach your cell from the terminal.
Read →Surfaces & tiers
What each plan unlocks over the same cell, and how entitlement maps to what you can do.
Read →HTTP API
The control-plane REST surface behind sign-in: how routes self-authenticate on your session and scope reads to you.
Read →Architecture
How a cell is built and isolated, and why your credentials for outside tools never live in the employee's hands.
Read →