Skip to content

Native claims and the Errand

View as Markdown

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:

  1. Choose claim policies that fit a non-interactive client.
  2. 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.

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 REQUIRED and 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.

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.

{
"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.url in the user’s system browser.
  • Treat errandKey as a bearer secret. It is also used to poll status.
  • The Errand is single-use and expires after 30 minutes.
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
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.

Terminal window
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.

  • 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 /refresh and OIDC /token do not embed Errand handoffs. A native session blocked during refresh recovers by running direct-issue again.