Native flows
The Connect browser flow assumes you can redirect a browser to via.sudomimus.com and receive a callback. That works for web applications but is awkward for desktop apps, games, and headless tools.
A native client has three flows available, depending on what it can do. Two are Native API (native-api.sudomimus.com) one-shot direct-issue endpoints — Steam direct-issue and AccessKey direct-issue. The third, browser polling, is a Connect API flow (/establish → /status-poll → /redeem) that a native client drives itself; it does not run on Native API.
If your client is public and should not receive an application client-auth private key or a pre-issued AccessKey secret, use Device authorization instead. Device authorization is the code-confirmation flow for CLIs, launchers, and similar public clients.
| Flow | When to use | Round-trips | Endpoint |
|---|---|---|---|
| Browser polling | Any native client with a system browser available | 3+ (establish + N×poll + redeem) | connect POST /establish → /status-poll → /redeem |
| Steam direct-issue | Game client running inside Steam, with access to Steamworks SDK | 1 | native-api POST /direct-issue/steam-ticket |
| AccessKey direct-issue | CLI / headless service / launcher pre-provisioned with an application-scoped credential for a known existing account | 1 | native-api POST /direct-issue/access-key |
Browser polling
Section titled “Browser polling”When your native client can open the user’s system browser but cannot easily receive a callback URL, use the polling flow:
- The client backend calls
connect POST /establish(signed with the application’s client-auth JWT) declaring aSTATUS_POLLreturn method, and receives{ exposureKey, hiddenKey }. - The client opens the system browser pointed at
https://via.sudomimus.com/?exposure-key=<exposureKey>. - The user completes the passkey or email-OTP challenge in the browser.
- The client polls
connect POST /status-pollevery few seconds with{ exposureKey, hiddenKey }. Once the user finishes, the poll returns{ status: "REALIZED", confirmationKey }. - The client then redeems the three keys at
connect POST /redeemfor{ accessToken, refreshToken }.
This works on any platform with a default browser — Windows, macOS, Linux desktop apps, Electron, etc. The application’s Layer 3 rules must allow STATUS_POLL.
The /establish call is the standard client-auth-signed Connect request — see Web applications for the full shape — except the return method is STATUS_POLL:
curl -X POST https://connect-api.sudomimus.com/establish \ -H "Content-Type: application/json" \ -H "Authorization: SudomimusClientJWT $SUDOMIMUS_CLIENT_AUTH_JWT" \ -d '{ "applicationAnchor": "your-application", "returnMethods": [ { "type": "STATUS_POLL", "payload": {} } ] }'# → { "exposureKey": "exp_...", "hiddenKey": "hid_..." }Then poll /status-poll with those two keys every few seconds. The poll carries no client-auth JWT — possession of the hiddenKey is what authorizes it:
curl -X POST https://connect-api.sudomimus.com/status-poll \ -H "Content-Type: application/json" \ -d '{ "exposureKey": "exp_...", "hiddenKey": "hid_..." }'
# While the user is still authenticating in the browser:# { "status": "PENDING" }# Once they finish:# { "status": "REALIZED", "confirmationKey": "cnf_..." }When the poll returns REALIZED, redeem the three keys at connect POST /redeem for the access and refresh tokens (same /redeem call as the web flow).
Steam direct-issue
Section titled “Steam direct-issue”For games shipped through Steam, Sudomimus supports a silent login that does not open a browser at all. The user does not see a login prompt; their Steam identity is exchanged directly for a Sudomimus session.
curl -X POST https://native-api.sudomimus.com/direct-issue/steam-ticket \ -H "Content-Type: application/json" \ -d '{ "applicationAnchor": "my-game", "steamTicketHex": "<hex-encoded ticket from Steamworks GetAuthTicketForWebApi>", "steamAppId": 480 }'The flow is:
- The game calls Steamworks
ISteamUser::GetAuthTicketForWebApi("sudomimus")— notGetAuthSessionTicket. The two are different ticket types and are not interchangeable. The identity string must be exactly"sudomimus"(case-sensitive); other values are rejected. - The game waits for the
GetTicketForWebApiResponse_tcallback before using the ticket. - The ticket bytes are hex-encoded and sent as
steamTicketHextoPOST /direct-issue/steam-ticket, together withapplicationAnchorandsteamAppId. - Sudomimus verifies the ticket with Steam, looks up or creates the account, and — on the happy path — returns
{ accessToken, refreshToken }in one round trip. If the application requires consent or profile data the Steam account has not provided, this returns a403with an Errand handoff instead — see When direct-issue needs consent or profile data. - The game calls
Steamworks.CancelAuthTicket(handle)after receiving the tokens.
The user never leaves the game. The Steam account is the source of identity.
Application configuration for Steam
Section titled “Application configuration for Steam”The application must have:
- Layer 1: a
STEAM_TICKETAuthenticationRule withallowedSteamAppIds: number[]containing this game’s Steam App ID. - Layer 2: a rule that will match — typically
STEAM_IDwithallowedSteamIds: ["*"]for any verified Steam account, or a list of specific SteamID64 strings. - Layer 3: a
DIRECT_ISSUEReturnRule.
A Steam-first account that has never linked an email needs a STEAM_ID (or ACCOUNT_ALIAS / SECTOR_SUBJECT) Layer 2 rule; an EMAIL-only Layer 2 will reject it.
This endpoint does not require a client-auth JWT — the Steam ticket itself attests both the user and the binary’s right to talk to the application.
Web counterpart. The browser-side “Sign in with Steam” button uses the
STEAM_OPENIDLayer 1 method instead ofSTEAM_TICKET. Both paths land on the same per-user Steam identity, so a user who first signed in through a game can subsequently sign in through the web button (and vice versa) without any account-linking step. See Authentication rules for theSTEAM_OPENIDrule shape.
AccessKey direct-issue
Section titled “AccessKey direct-issue”For environments without a Steam ticket but where the target Sudomimus account is already known — CLI tools, custom launchers, headless services, automated test rigs. The “proof” is a Sudomimus-issued credential pair (accessKeyIdentifier + accessKeySecret) generated from the developer portal and handed out-of-band to the operator.
curl -X POST https://native-api.sudomimus.com/direct-issue/access-key \ -H "Content-Type: application/json" \ -d '{ "applicationAnchor": "my-cli-tool", "accessKeyIdentifier": "acs_k_<uuidv4>", "accessKeySecret": "acs_t_<64-char lowercase hex>" }'Both credential strings carry mandatory prefixes:
acs_k_— the public identifier, followed by a UUIDv4.acs_t_— the secret half, followed by 64 lowercase hex characters (shown exactly once at creation; unrecoverable afterwards).
The prefixes are part of the canonical form. They make the two halves visually distinguishable and let secret scanners match accidentally-committed credentials by literal substring.
Application configuration for AccessKey
Section titled “Application configuration for AccessKey”The application must have:
- Layer 1: an
ACCESS_KEY_DIRECTAuthenticationRule (empty payload). Default-deny unless explicitly added. - Layer 2: a rule matching the target account —
EMAIL,STEAM_ID,ACCOUNT_ALIAS, orSECTOR_SUBJECT. - Layer 3: a
DIRECT_ISSUEReturnRule.
AccessKey credentials cannot create new accounts. The credential is issued against an existing Sudomimus account; if that account is deleted, every credential bound to it is rejected at login time.
Credentials are managed from with.sudomimus.com on the application detail page → Access keys tab. Revocation is a soft delete (revokedAt timestamp); rotation = revoke + reissue. Expired credentials are not auto-evicted but are rejected at the handler.
This endpoint does not require a client-auth JWT either — the access-key secret is itself the credential. Embedding the client-auth private key in a distributable CLI would be reversible by any operator anyway.
When direct-issue needs consent or profile data
Section titled “When direct-issue needs consent or profile data”Both direct-issue endpoints are one-shot readers — they cannot pop a consent screen or ask the user to type in an email. So when an application requires a claim the user has not granted, or requires data the account does not have yet (a Steam account with no email, for instance), the call cannot just succeed. Instead it returns a 403 carrying an Errand — a short-lived browser side-trip where the user completes that work:
{ "reason": "ClaimConsentRequired", "claims": { "email": { "requirement": "REQUIRED", "state": "UNKNOWN" }, "firstName": { "requirement": "OPTIONAL", "state": "UNKNOWN" }, "lastName": { "requirement": "OFF", "state": "UNKNOWN" } }, "errand": { "errandKey": "ernd_...", "url": "https://via.sudomimus.com/errand?key=ernd_...", "expiresAt": "2026-06-10T12:30:00Z" }}The reason is one of ClaimConsentRequired (the user must agree to share a required claim) or RequiredClaimDataMissing (consent is there, but the account data is not). Both Steam and AccessKey direct-issue behave identically here. To recover:
- Open
errand.urlin the user’s system browser. The page walks the user through any sign-in, data entry, and consent that is owed. - Poll
GET /errand/{errandKey}/status(native-api) every ~2 seconds until it reportsCOMPLETED— or just let the user tell your UI they’re done. - Retry the same direct-issue call once. It now succeeds with
{ accessToken, refreshToken }.
curl https://native-api.sudomimus.com/errand/ernd_.../status# → { "status": "PENDING" } user still working in the browser# → { "status": "COMPLETED" } done — retry the direct-issue# → { "status": "EXPIRED" } expired/consumed/unknown — re-run direct-issue for a fresh errandA 200 from either endpoint also carries a claims block (the same shape as in the 403), so even on success you can see which optional claims were shared and which were withheld. The full contract — the 30-minute lifetime, why retries reuse the same errandKey, and the two security tiers (consent-only vs. sign-in-required) — is in The Errand.
When to use which
Section titled “When to use which”| Your client is… | Use |
|---|---|
| A web application | Connect + via.sudomimus.com (regular flow) |
| A desktop app / Electron / mobile with a browser available | Connect + via.sudomimus.com via system browser + connect /status-poll |
| A public CLI / launcher without a client secret | Device authorization |
| A Steam-distributed game | native-api POST /direct-issue/steam-ticket (silent, one round trip) |
| A CLI / headless service tied to a known account by a pre-issued credential | native-api POST /direct-issue/access-key (one round trip with an AccessKey) |
In all cases the token format returned at the end is the same — your application only ever deals with the same access-token shape, regardless of how the user authenticated.