Exchange an access-key credential for application tokens.
const url = 'https://native-api.sudomimus.com/direct-issue/access-key';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"applicationAnchor":"example","accessKeyIdentifier":"example","accessKeySecret":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://native-api.sudomimus.com/direct-issue/access-key \ --header 'Content-Type: application/json' \ --data '{ "applicationAnchor": "example", "accessKeyIdentifier": "example", "accessKeySecret": "example" }'Trade a long-lived access-key credential (identifier + secret) for application access and refresh tokens. Access keys are application-scoped credentials bound to a specific account; the credential carries optional TTL bounds and an opt-in expiry timestamp.
The server:
- Validates the application’s authentication-rule layer admits
ACCESS_KEY_DIRECT. - Looks up the credential by
accessKeyIdentifier. - Cross-checks that the credential belongs to the application
named in
applicationAnchor, is not revoked, and has not expired. - Timing-safe-verifies
accessKeySecret. - Validates the realize-rule layer (
EMAIL/STEAM_ID/ACCOUNT_ALIAS/SECTOR_SUBJECT) and ensures aDIRECT_ISSUEreturn rule exists. - Issues access + refresh JWTs and best-effort touches the
credential’s
lastUsedAttimestamp.
All credential-level failures (unknown identifier, app mismatch,
revoked, expired, wrong secret) collapse into a single opaque
AccessKeyDirectDenied 401 reason to avoid leaking identifier
existence.
Request Body required
Section titled “Request Body required ”object
Public anchor identifying the integrating application.
UUID v4 string identifying the access-key credential.
Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
64-char lowercase hex string (32 random bytes) returned exactly once when the access key was issued. Never logged or persisted in plaintext server-side after creation.
Example generated
{ "applicationAnchor": "example", "accessKeyIdentifier": "example", "accessKeySecret": "example"}Responses
Section titled “ Responses ”Tokens issued.
object
Per-claim view across the three shareable claims, carried on both the 200 (why is a claim absent from the minted token) and the claim-gate 403 (what is still owed).
object
One shareable claim: what the application requests (requirement)
joined with the user’s standing decision (state). UNKNOWN means
the user was never asked; DENIED means the user explicitly
declined.
object
The developer’s policy for the claim. SYNTHETIC guarantees the
claim is present but permits a generated placeholder (a stand-in
name, a proxy email) when the user has not shared real data —
unlike REQUIRED it never blocks issuance or raises an errand.
One shareable claim: what the application requests (requirement)
joined with the user’s standing decision (state). UNKNOWN means
the user was never asked; DENIED means the user explicitly
declined.
object
The developer’s policy for the claim. SYNTHETIC guarantees the
claim is present but permits a generated placeholder (a stand-in
name, a proxy email) when the user has not shared real data —
unlike REQUIRED it never blocks issuance or raises an errand.
One shareable claim: what the application requests (requirement)
joined with the user’s standing decision (state). UNKNOWN means
the user was never asked; DENIED means the user explicitly
declined.
object
The developer’s policy for the claim. SYNTHETIC guarantees the
claim is present but permits a generated placeholder (a stand-in
name, a proxy email) when the user has not shared real data —
unlike REQUIRED it never blocks issuance or raises an errand.
Short-lived access token (JWT). Body shape matches Connect’s
AccessTokenBody: the application-visible user key is the
subject (sector subject) claim, not a raw account identifier.
The firstName / lastName / emailAddress claims are
consent-gated and may be absent (see Connect AccessTokenBody).
Long-lived refresh token (JWT). Use Connect’s /refresh for renewal without re-presenting the access key.
Example
{ "claims": { "email": { "requirement": "OFF", "state": "UNKNOWN" }, "firstName": { "requirement": "OFF", "state": "UNKNOWN" }, "lastName": { "requirement": "OFF", "state": "UNKNOWN" } }}Malformed request body. The reason distinguishes
Invalid accessKeyIdentifier (must be a UUID v4) and
Invalid accessKeySecret (must be 64 lowercase hex chars).
Error response body. The Native service emits { "reason": "<SymbolDescription>" }
for known failure modes. When the reason symbol’s description begins with
PRIVATE, the body is empty (zero bytes) and only the HTTP status carries
signal — both reason and the body itself are absent in that case.
object
Stable machine-readable reason code.
Example generated
{ "reason": "example"}The access-key credential was rejected. The reason is
uniformly AccessKeyDirectDenied regardless of root cause
(not found, app mismatch, revoked, expired, wrong secret).
Error response body. The Native service emits { "reason": "<SymbolDescription>" }
for known failure modes. When the reason symbol’s description begins with
PRIVATE, the body is empty (zero bytes) and only the HTTP status carries
signal — both reason and the body itself are absent in that case.
object
Stable machine-readable reason code.
Example generated
{ "reason": "example"}The attempt was refused. The response reason distinguishes:
Layer1Denied,Layer2Denied,Layer3Denied— the application’s three-layer rules rejected this attempt (which layer is indicated by the reason code).ApplicationDisabled— the application is disabled.AccountDisabled— the resolved account is disabled.AccountDeleted— the resolved account has been erased.ClaimConsentRequired— the application requires a claim the user has not yet granted; direct-issue cannot prompt.RequiredClaimDataMissing— every required claim is granted, but the account lacks the underlying data (e.g. no email on a Steam-created account).
For the two claim-gate reasons the body additionally carries a
per-claim claims view (UNKNOWN distinguishes “never asked”
from DENIED “declined”) and an errand handoff — a short-lived
browser URL where the user can authenticate, complete the
missing data, and grant consent. Open errand.url in the
system browser, poll GET /errand/{errandKey}/status (or wait
for the user), then retry this request once.
Repeated blocked calls reuse the live ticket: while it has at
least 15 minutes left and the owed task scope is unchanged, the
same errandKey (with its original expiresAt) is returned
again, so retries cannot split the handoff state.
403 body. For the claim-gate reasons (ClaimConsentRequired,
RequiredClaimDataMissing) the claims view and the errand
handoff are present; for every other reason only reason is set.
object
Stable machine-readable reason code.
Per-claim view across the three shareable claims, carried on both the 200 (why is a claim absent from the minted token) and the claim-gate 403 (what is still owed).
object
One shareable claim: what the application requests (requirement)
joined with the user’s standing decision (state). UNKNOWN means
the user was never asked; DENIED means the user explicitly
declined.
object
The developer’s policy for the claim. SYNTHETIC guarantees the
claim is present but permits a generated placeholder (a stand-in
name, a proxy email) when the user has not shared real data —
unlike REQUIRED it never blocks issuance or raises an errand.
One shareable claim: what the application requests (requirement)
joined with the user’s standing decision (state). UNKNOWN means
the user was never asked; DENIED means the user explicitly
declined.
object
The developer’s policy for the claim. SYNTHETIC guarantees the
claim is present but permits a generated placeholder (a stand-in
name, a proxy email) when the user has not shared real data —
unlike REQUIRED it never blocks issuance or raises an errand.
One shareable claim: what the application requests (requirement)
joined with the user’s standing decision (state). UNKNOWN means
the user was never asked; DENIED means the user explicitly
declined.
object
The developer’s policy for the claim. SYNTHETIC guarantees the
claim is present but permits a generated placeholder (a stand-in
name, a proxy email) when the user has not shared real data —
unlike REQUIRED it never blocks issuance or raises an errand.
The browser side-trip that unblocks a claim-gated direct-issue:
a short-lived (30 minutes), single-use bearer URL where the user
authenticates (when account data is being written), completes any
missing data, and grants consent. The stored task list is server-
side only — open the URL and let the page drive. Repeated blocked
calls re-hand the same live handoff (same errandKey, original
expiresAt) while it has at least 15 minutes left and the owed
task scope is unchanged.
object
Bearer key (ernd_…); also the status-poll path key.
Open this in the user’s system browser.
Example
{ "claims": { "email": { "requirement": "OFF", "state": "UNKNOWN" }, "firstName": { "requirement": "OFF", "state": "UNKNOWN" }, "lastName": { "requirement": "OFF", "state": "UNKNOWN" } }}Application anchor not found.
Error response body. The Native service emits { "reason": "<SymbolDescription>" }
for known failure modes. When the reason symbol’s description begins with
PRIVATE, the body is empty (zero bytes) and only the HTTP status carries
signal — both reason and the body itself are absent in that case.
object
Stable machine-readable reason code.
Example generated
{ "reason": "example"}Server error while resolving the credential’s account. Reason
AccessKeyCredentialAccountMissing.
Error response body. The Native service emits { "reason": "<SymbolDescription>" }
for known failure modes. When the reason symbol’s description begins with
PRIVATE, the body is empty (zero bytes) and only the HTTP status carries
signal — both reason and the body itself are absent in that case.
object
Stable machine-readable reason code.
Example generated
{ "reason": "example"}default
Section titled “default ”Error response.
Error response body. The Native service emits { "reason": "<SymbolDescription>" }
for known failure modes. When the reason symbol’s description begins with
PRIVATE, the body is empty (zero bytes) and only the HTTP status carries
signal — both reason and the body itself are absent in that case.
object
Stable machine-readable reason code.
Example generated
{ "reason": "example"}