Native claims and the Errand
Steam ticket, AccessKey, and PublicKey direct-issue authenticate without an application login page. That keeps the happy path short, but it also means the client cannot display a consent form or ask the user to complete missing profile data.
This page covers both sides of that constraint:
- Choose claim policies that fit a non-interactive client.
- Handle the Errand browser handoff when a required real claim cannot yet be issued.
The shared policy and consent model is documented in Identity claims and sharing.
Choose a native claim policy
Section titled “Choose a native claim policy”| Policy | Native direct-issue behavior |
|---|---|
| Off | Never requested. |
| Optional | Shared only if the user already granted it. Never blocks, so a native-only user may never be prompted. |
| Required | Guaranteed present with real data. Missing consent or data returns 403 with an Errand. |
Placeholder only (SYNTHETIC_ONLY) |
Guaranteed present as a stable placeholder. Never asks for real data, never blocks, and never creates an Errand. |
Fallback placeholder (SYNTHETIC_FALLBACK) |
Guaranteed present, using real data when granted and a stable placeholder otherwise. Never blocks and never creates an Errand. |
For most native integrations, prefer SYNTHETIC_ONLY or SYNTHETIC_FALLBACK over REQUIRED unless you specifically need verified real data.
- Need a stable name or email-shaped value, and a placeholder is acceptable without consent: use
SYNTHETIC_ONLY. - Want to let the user share real data when they choose, but keep direct-issue non-blocking with a placeholder fallback: use
SYNTHETIC_FALLBACK. - Need a real verified email for delivery or reconciliation: use
REQUIREDand implement the Errand flow. - Want real data when already granted but can continue without it: use
OPTIONAL.
If every requested claim is OFF, SYNTHETIC_ONLY, or SYNTHETIC_FALLBACK, claim policy can never force direct-issue into a browser handoff.
Synthetic names are generated placeholders. Synthetic emails use a stable …@proxy.sudomimus.email address, and synthetic avatars use generated sector avatar images. Proxy delivery is best-effort, not guaranteed, and OIDC exposes synthetic email with email_verified: false.
For avatar URL scope, rotation, and caching behavior, see Avatar claims and delivery.
The Errand
Section titled “The Errand”An Errand is short-lived account remediation, not token issuance. When direct-issue cannot satisfy a required claim, its 403 response gives the client a browser URL. The user completes consent or missing profile work there, then the client makes a new direct-issue attempt with the credential-specific proof described below.
Only two claim-gate reasons carry an Errand:
403 reason |
Meaning | Browser work |
|---|---|---|
ClaimConsentRequired |
A required claim has not been granted. | Grant consent and, when necessary, first add the missing data. |
RequiredClaimDataMissing |
Consent exists, but the account lacks the real value. | Register an email or complete the missing name. |
Other 403 responses, such as rule denial or a disabled account, are terminal and do not include an Errand.
Handoff response
Section titled “Handoff response”{ "reason": "ClaimConsentRequired", "claims": { "email": { "requirement": "REQUIRED", "state": "UNKNOWN" }, "firstName": { "requirement": "OPTIONAL", "state": "UNKNOWN" }, "lastName": { "requirement": "OFF", "state": "UNKNOWN" }, "staticAvatar": { "requirement": "SYNTHETIC_ONLY", "state": "UNKNOWN" }, "animatedAvatar": { "requirement": "OFF", "state": "UNKNOWN" } }, "errand": { "errandKey": "ernd_...", "url": "https://via.sudomimus.com/errand?key=ernd_...", "expiresAt": "2026-06-10T12:30:00Z" }}- Open
errand.urlin the user’s system browser. - Treat
errandKeyas a bearer secret. It is also used to poll status. - The Errand is single-use and expires after 30 minutes.
Client loop
Section titled “Client loop”sequenceDiagram
autonumber
participant Client as Native client
participant Native as Native API
participant Browser as System browser
participant Via as via
Client->>Native: POST /direct-issue/...
Native-->>Client: 403 { reason, claims, errand }
Client->>Browser: Open errand.url
Browser->>Via: Complete the required tasks
Via-->>Browser: Done
loop Optional status polling
Client->>Native: GET /errand/{errandKey}/status
Native-->>Client: PENDING or COMPLETED
end
Note over Client: Prepare credential-specific proof (fresh Steam ticket or PublicKey assertion)
Client->>Native: POST /direct-issue/... once with that proof
Native-->>Client: Tokens if current issuance checks pass; otherwise an error
Prepare the retry proof
Section titled “Prepare the retry proof”| Credential | Proof for each new direct-issue attempt |
|---|---|
| Steam | Obtain a fresh ticket with GetAuthTicketForWebApi. The original ticket is replay-protected even when issuance returned an Errand. |
| AccessKey | Reuse the identifier and secret while the credential remains valid. |
| PublicKey | Sign a fresh assertion with a new jti and the hash of the current request body. Reuse the registered key, not the previous assertion. |
Apply these rules after COMPLETED, after EXPIRED, and when recovering from a refresh denial. Completion repairs account data or consent; token issuance still depends on current credential, application, and policy checks. Use the status endpoint to wait instead of repeatedly submitting credentials.
Polling is optional. A client may instead ask the user to confirm that they finished in the browser before retrying.
curl https://native-api.sudomimus.com/errand/ernd_.../status# → { "status": "PENDING" }# → { "status": "COMPLETED" }# → { "status": "EXPIRED" }Poll about every two seconds with a sensible overall timeout. EXPIRED deliberately covers unknown, malformed, consumed, and genuinely expired keys; prepare the credential-specific proof above and rerun direct-issue to obtain a fresh handoff. The status endpoint never issues tokens.
Direct-issue attempts that pass credential verification normally return the same live Errand when it has at least 15 minutes remaining and the required work has not changed. User progress therefore remains associated with one URL.
Security behavior
Section titled “Security behavior”- Consent only: no additional sign-in is required because the credential holder already proved control of a token-minting credential.
- Writing identity data: the browser requires sign-in, and the signed-in account must match the account resolved from the Steam ticket, AccessKey, or PublicKey.
- Optional and synthetic claims never create an Errand.
- Session API
/refreshand OIDC/tokendo not embed Errand handoffs. A native session blocked during refresh recovers by running direct-issue again.
Related
Section titled “Related”- Native integration — browser polling, Steam ticket, and AccessKey flows.
- Identity claims and sharing — the shared policy, grant, and inclusion model.
- Avatar claims and delivery — real and placeholder avatar URL behavior.
- Tokens and verification — the tokens issued after the claim gate is satisfied.