Skip to content

API: Auth and sessions

Capability state: every operation in this file is staging-durable (fixture-only). Login ceremonies, session issuance/refresh, recovery policy/execution, rate-limit evaluation, and WebAuthn passkey registration/assertion are recorded in durable M7 state but refuse production admission. Sessions keep an auth_session:fixture_* prefix so a fixture session cannot be confused with a production session at any downstream gate. See 022 gap report for the broader end-user surface.

The auth and sessions cluster covers Gestalt’s login ceremony, session lifecycle, recovery, and rate-limit operations — the membrane surface that an end user (or a hosted operator acting on their behalf) interacts with after humanAuth.* presence.

Three coupled lanes:

  • Login ceremony laneauth.loginStart issues a WebAuthn assertion challenge plus a hash-only rate-limit decision; auth.loginFinish consumes a HumanAuth presence receipt and a durable rate-limit allow record before issuing a signed staging session.
  • Session lifecycle laneauth.sessionIssue, auth.sessionRefresh, and authority.sessionRevoke record session issue/refresh/revoke lifecycle atoms with token commitments only. No raw bearer token is stored durably.
  • Recovery laneauth.recoveryPolicy records hash-only recovery contact commitments after HumanAuth presence; auth.recoveryExecute records replacement-passkey recovery with the consumed policy. Neither lane creates standing.

A login or session lifecycle never creates standing or delegation on its own — see authority.md for those.

WebAuthn passkey registration and assertion options, and the register and import finishers, live alongside humanAuth.challenge / humanAuth.verifyPasskey (human-auth.md) but are documented here because they share the login ceremony’s WebAuthn boundary.

See concepts: glossary — HumanAuth for the human-presence half of this surface.

Start staging login ceremony by recording hash-only rate-limit state and issuing a WebAuthn assertion challenge.

POST /v1/auth/login/start
state: staging-durable
sdk_role: start staging login ceremony by recording hash-only rate-limit state and issuing a WebAuthn assertion challenge without raw identifiers or biometric material
request_record: CloudAuthLoginStartRequest
responses: rate_limit_decision | human_auth_challenge | public_key_credential_request_options | refusal | receipt
interface AuthLoginStartRequest {
tenant: GestaltRef;
subject: GestaltRef;
passkey_binding?: GestaltRef;
relying_party_id: string;
origin: string;
scopes: string[];
policy_ref: GestaltRef;
source_ip_hash: string; // sha256: prefix required
device_binding: GestaltRef;
window_seconds: number;
attempt_count: number;
soft_limit: number;
lockout_threshold: number;
lockout_seconds: number;
fixture: boolean; // must be true; production_login must be false
production_login: boolean;
contains_customer_data: boolean;
contains_biometric_material: boolean;
raw_ip_address?: string; // refused if present
raw_user_agent?: string; // refused if present
raw_credential_id?: string; // refused if present
}
{
"operation": "auth.loginStart",
"outcome": "admitted",
"body": {
"login_attempt": "auth_login_attempt:...",
"auth_rate_limit_evaluation": "auth_rate_limit_evaluation:...",
"rate_limit_decision": "allow",
"challenge": { "id": "human_auth_challenge:...", "...": "..." },
"public_key_credential_request_options": { "challenge": "...", "rp_id": "..." },
"passkey_binding": { "id": "...", "credential_id_hash": "...", "...": "..." },
"durable_records": {
"rate_limit": {...},
"challenge": {...},
"login_attempt": {...}
},
"privacy_boundary": {
"raw_assertion_bytes_stored": false,
"raw_credential_id_stored": false,
"biometric_material_seen_by_gestalt": false,
"production_admission": false
},
"webauthn_origin_policy": {...},
"authority_boundaries": {...},
"source_ip_raw_stored": false,
"raw_user_agent_stored": false,
"raw_credential_id_stored": false,
"raw_biometric_material_stored": false,
"raw_session_token_stored": false,
"database_locator_exposed": false,
"production_login": false,
"production_admission": false
},
"receipt": {...}
}

When the rate-limit decision is throttle or lockout, the operation responds outcome: "refused" with failed_gate set to auth_rate_limit_throttled or auth_rate_limit_locked and HTTP 429.

const start = await client.authLoginStart({
tenant: "tenant_node:rheinwerk_calibration",
subject: "human_person:anna",
passkey_binding: "passkey_binding:anna_platform_passkey_fixture",
relying_party_id: "gestalt.local",
origin: "https://gestalt.local",
scopes: ["auth.login.finish"],
policy_ref: "auth_rate_limit_policy:login_fixture",
source_ip_hash: "sha256:fixture_login_source_ip_hash",
device_binding: "device_binding:staging_login_fixture",
window_seconds: 300,
attempt_count: 1,
soft_limit: 5,
lockout_threshold: 10,
lockout_seconds: 900,
fixture: true,
production_login: false,
contains_customer_data: false,
contains_biometric_material: false,
});

Finish staging login ceremony by consuming a durable HumanAuth presence receipt plus a rate-limit allow record before issuing a signed session commitment.

POST /v1/auth/login/finish
state: staging-durable
sdk_role: finish staging login ceremony by consuming durable HumanAuth presence and rate-limit allow records before issuing a signed session commitment
request_record: CloudAuthLoginFinishRequest
responses: session_lifecycle | human_presence_receipt | rate_limit_decision | refusal | receipt
interface AuthLoginFinishRequest {
tenant: GestaltRef;
session: GestaltRef; // must start with "auth_session:fixture_"
actor: GestaltRef;
vessel: GestaltRef;
standing: GestaltRef;
scopes: string[];
human_presence_receipt: GestaltRef;
login_challenge: GestaltRef;
auth_rate_limit_evaluation: GestaltRef;
passkey_binding?: GestaltRef;
holder_key: GestaltRef;
device_binding: GestaltRef;
expires_in_seconds: number; // 1..=86400
fixture: boolean;
production_login: boolean;
production_session: boolean;
}
{
"operation": "auth.loginFinish",
"outcome": "admitted",
"body": {
"session": "auth_session:fixture_login_staging",
"session_lifecycle": "session_lifecycle:...",
"bearer_token": "...",
"token_type": "Bearer",
"token_commitment": "sha256:...",
"human_presence_receipt": "human_presence_receipt:...",
"login_challenge": "human_auth_challenge:...",
"auth_rate_limit_evaluation": "auth_rate_limit_evaluation:...",
"satisfies_sensitive_approval_requirement": true,
"identity_binding_created": false,
"standing_created": false,
"company_authority_created": false,
"source_ip_raw_stored": false,
"raw_user_agent_stored": false,
"raw_credential_id_stored": false,
"raw_session_token_stored": false,
"raw_session_token_exposed_in_proof": false,
"raw_biometric_material_stored": false,
"fixture_session": true,
"production_login": false,
"production_session": false,
"signer": {...},
"durable_commit": {...},
"durable_state": {...},
"production_admission": false
},
"receipt": {...}
}

identity_binding_created, standing_created, and company_authority_created are asserted explicitly as false: login finishing proves presence and issues a session, but never elevates the session into standing or company authority.

const finished = await client.authLoginFinish({
tenant: "tenant_node:rheinwerk_calibration",
session: "auth_session:fixture_login_staging",
actor: "human_person:anna",
vessel: "vessel:anna_browser_fixture",
standing: "standing:anna_geschaeftsfuehrer_fixture",
scopes: ["invoice.issue", "auth.session.inspect", "auth.session.refresh"],
human_presence_receipt: "human_presence_receipt:fixture_private_presence",
login_challenge: start.body.challenge.id,
auth_rate_limit_evaluation: start.body.auth_rate_limit_evaluation,
passkey_binding: "passkey_binding:anna_platform_passkey_fixture",
holder_key: "key_registry:fixture_session_signing",
device_binding: "device_binding:staging_login_finish_fixture",
expires_in_seconds: 3600,
fixture: true,
production_login: false,
production_session: false,
});

Trade the fixture bearer for a signed session context. This is a fixture-rehearsed convenience for tests and worked examples — it does not run a real authentication ceremony, does not require a HumanAuth presence receipt, and binds no production session.

POST /v1/auth/session/exchange
state: fixture-rehearsed
sdk_role: not yet wrapped — call directly with the fixture bearer
request_record: (empty body)
responses: signed_session_context | receipt

The endpoint takes no body. Authenticate with the fixture bearer token (Authorization: Bearer fixture-session-token).

{
"operation": "auth.session.exchange",
"outcome": "admitted",
"fixture": true,
"body": {
"token": "fixture-session-token",
"token_type": "Bearer",
"session": "auth_session:fixture_valid",
"tenant": "tenant_node:rheinwerk_calibration",
"production_admission": false
},
"receipt": {
"ref": "receipt:fixture_session_exchange",
"outcome": "admitted",
"reasons": ["fixture bearer token exchanged for signed session context"],
"fixture": true
}
}

The TS SDK does not yet have a thin wrapper for this route. Use fetch directly when you need it:

const exchanged = await fetch("http://127.0.0.1:3011/v1/auth/session/exchange", {
method: "POST",
headers: {
authorization: "Bearer fixture-session-token",
},
}).then(r => r.json());

For real session issuance (with HumanAuth presence, scope, and signer provenance) use auth.sessionIssue below.

Issue a staging signed session token after HumanAuth presence while storing only the token commitment.

POST /v1/auth/sessions/issue
state: staging-durable
sdk_role: issue staging signed session token after HumanAuth presence while storing only token commitment
request_record: CloudAuthSessionIssueRequest
responses: session_lifecycle | refusal | receipt
interface AuthSessionIssueRequest {
tenant: GestaltRef;
session: GestaltRef; // must start with "auth_session:fixture_"
actor: GestaltRef;
vessel: GestaltRef;
standing: GestaltRef;
scopes: string[];
human_presence_receipt: GestaltRef;
holder_key: GestaltRef;
device_binding: GestaltRef;
expires_in_seconds: number; // 1..=86400
fixture: boolean; // must be true
production_session: boolean; // must be false
}
{
"operation": "auth.sessionIssue",
"outcome": "admitted",
"body": {
"session": "auth_session:fixture_signed_staging",
"session_lifecycle": "session_lifecycle:...",
"bearer_token": "...",
"token_type": "Bearer",
"token_commitment": "sha256:...",
"raw_session_token_stored": false,
"raw_session_token_exposed_in_proof": false,
"fixture_session": true,
"production_session": false,
"signer": {...},
"durable_commit": {...},
"durable_state": {...},
"production_admission": false
},
"receipt": {...}
}

The bearer token is returned in the response body but never stored durably; only token_commitment (a sha256 of the token) is persisted.

const issued = await client.authSessionIssue({
tenant: "tenant_node:rheinwerk_calibration",
session: "auth_session:fixture_signed_staging",
actor: "human_person:anna",
vessel: "vessel:anna_browser_fixture",
standing: "standing:anna_geschaeftsfuehrer_fixture",
scopes: ["invoice.issue"],
human_presence_receipt: "human_presence_receipt:fixture_private_presence",
holder_key: "key_registry:fixture_session_signing",
device_binding: "device_binding:staging_signed_fixture",
expires_in_seconds: 3600,
fixture: true,
production_session: false,
});

Inspect membrane-safe authenticated session metadata without exposing bearer token material or raw database access.

POST /v1/auth/sessions/inspect
state: staging-durable
sdk_role: inspect membrane-safe authenticated session metadata without exposing bearer token material or raw database access
request_record: CloudAuthSessionInspectRequest
responses: session_metadata | refusal | receipt
interface AuthSessionInspectRequest {
tenant?: GestaltRef;
session?: GestaltRef; // if set, must equal the authenticated session
}

The session ref is optional. If omitted, inspection targets the authenticated session. If supplied, it must match the authenticated session — otherwise the membrane refuses with auth_session_inspect_session_mismatch.

{
"operation": "auth.sessionInspect",
"outcome": "admitted",
"body": {
"session": "auth_session:...",
"session_lifecycle": "session_lifecycle:...",
"tenant": "tenant_node:...",
"actor": "human_person:...",
"vessel": "vessel:...",
"standing": "standing:...",
"scopes": ["..."],
"holder_key": "key_registry:...",
"issued_at": "...",
"expires_at": "...",
"token_commitment": "sha256:...",
"fixture_session": true,
"production_session": false,
"revoked": false,
"stale_key": false,
"raw_session_token_exposed": false,
"raw_session_token_stored_durably": false,
"database_locator_exposed": false,
"production_admission": false
},
"receipt": {...}
}

The bearer token itself is never returned — only the token_commitment (sha256 of the issued token) is exposed. database_locator_exposed: false and raw_session_token_stored_durably: false are asserted explicitly as membrane invariants.

Refusal codes: auth_session_inspect_session_mismatch, auth_session_unknown.

const inspected = await client.authSessionInspect({
tenant: "tenant_node:rheinwerk_calibration",
});

The SDK method accepts an empty object — client.authSessionInspect() inspects the authenticated session.

Refresh a staging signed session after HumanAuth presence while storing only token commitments and lifecycle provenance.

POST /v1/auth/sessions/refresh
state: staging-durable
sdk_role: refresh staging signed session after HumanAuth presence while storing only token commitments and lifecycle provenance
request_record: CloudAuthSessionRefreshRequest
responses: session_lifecycle | refusal | receipt
interface AuthSessionRefreshRequest {
tenant: GestaltRef;
session: GestaltRef; // must start with "auth_session:fixture_"
scopes: string[]; // cannot expand beyond authenticated scopes
human_presence_receipt: GestaltRef;
holder_key: GestaltRef;
device_binding: GestaltRef;
expires_in_seconds: number; // 1..=86400
fixture: boolean;
production_session: boolean;
}
{
"operation": "auth.sessionRefresh",
"outcome": "admitted",
"body": {
"session": "auth_session:fixture_refreshed_staging",
"previous_session": "auth_session:fixture_signed_staging",
"session_lifecycle": "session_lifecycle:...",
"bearer_token": "...",
"token_type": "Bearer",
"token_commitment": "sha256:...",
"previous_token_commitment": "sha256:...",
"scope_expansion_allowed": false,
"previous_session_remains_valid": true,
"raw_session_token_stored": false,
"raw_session_token_exposed_in_proof": false,
"fixture_session": true,
"production_session": false,
"signer": {...},
"durable_commit": {...},
"durable_state": {...},
"production_admission": false
},
"receipt": {...}
}

Refresh refuses with auth_session_refresh_scope_expansion_refused if requested scopes exceed the authenticated session’s scopes.

const refreshed = await client.authSessionRefresh({
tenant: "tenant_node:rheinwerk_calibration",
session: "auth_session:fixture_refreshed_staging",
scopes: [],
human_presence_receipt: "human_presence_receipt:fixture_private_presence",
holder_key: "key_registry:fixture_session_signing",
device_binding: "device_binding:staging_refresh_fixture",
expires_in_seconds: 3600,
fixture: true,
production_session: false,
});

Record session revocation lifecycle without exposing raw session token. (The contract operation name is authority.sessionRevoke; the underlying request record is M13SessionRevokeRequest and the SDK method is m13SessionRevoke.)

POST /v1/authority/sessions/revoke
state: staging-durable
sdk_role: record session revocation lifecycle without exposing raw session token
request_record: CloudM13SessionRevokeRequest
responses: session_lifecycle | refusal | receipt
interface M13SessionRevokeRequest {
tenant: GestaltRef;
session: GestaltRef;
enforce_runtime_overlay: boolean; // when true, future bearer use of `session` is rejected
}
{
"operation": "authority.sessionRevoke",
"outcome": "admitted",
"body": {
"session_lifecycle": "session_lifecycle:...",
"session": "auth_session:fixture_valid",
"revoked": true,
"enforced_in_runtime_overlay": true,
"raw_session_token_exposed": false,
"signer": {...},
"durable_commit": {...},
"durable_state": {...},
"production_admission": false
},
"receipt": {...}
}

A repeat call for an already-revoked session returns outcome: "verified" with body.stable_code: "auth_session_already_revoked", which is the idempotent shape callers should rely on.

const revoked = await client.m13SessionRevoke({
tenant: "tenant_node:rheinwerk_calibration",
session: "auth_session:fixture_valid",
enforce_runtime_overlay: true,
});

Evaluate staging auth rate-limit and lockout policy with hash-only identifiers and durable policy provenance. (The user-facing operation is auth.rateLimit; the contract names it auth.rateLimitEvaluate.)

POST /v1/auth/rate-limit/evaluate
state: staging-durable
sdk_role: evaluate staging auth rate-limit and lockout policy with hash-only identifiers and durable policy provenance
request_record: CloudAuthRateLimitEvaluateRequest
responses: rate_limit_decision | refusal | receipt
interface AuthRateLimitEvaluateRequest {
tenant: GestaltRef;
policy_ref: GestaltRef;
subject: GestaltRef;
route: string;
action: string;
source_ip_hash: string; // sha256: prefix required
device_binding: GestaltRef;
window_seconds: number;
attempt_count: number;
soft_limit: number;
lockout_threshold: number;
lockout_seconds: number;
fixture: boolean;
production_login: boolean;
enforce_runtime_overlay: boolean;
contains_customer_data: boolean;
raw_ip_address?: string; // refused if present
raw_user_agent?: string; // refused if present
raw_credential_id?: string; // refused if present
}
{
"operation": "auth.rateLimitEvaluate",
"outcome": "admitted",
"body": {
"auth_rate_limit_evaluation": "auth_rate_limit_evaluation:...",
"policy": "auth_rate_limit_policy:m13_fixture",
"subject": "human_person:anna",
"route": "/v1/auth/sessions/issue",
"action": "auth.session.issue",
"decision": "allow",
"allowed": true,
"failed_gate": null,
"retry_after_seconds": 0,
"source_ip_hash": "sha256:...",
"device_binding": "device_binding:...",
"source_ip_raw_stored": false,
"raw_user_agent_stored": false,
"raw_credential_id_stored": false,
"raw_session_token_stored": false,
"database_locator_exposed": false,
"enforced_in_runtime_overlay": false,
"production_login": false,
"production_admission": false,
"durable_state": {...}
},
"receipt": {...}
}

decision is allow, throttle, or lockout. Non-allow decisions return outcome: "refused" with HTTP 429 and failed_gate set to auth_rate_limit_throttled or auth_rate_limit_locked.

const decision = await client.authRateLimitEvaluate({
tenant: "tenant_node:rheinwerk_calibration",
policy_ref: "auth_rate_limit_policy:m13_fixture",
subject: "human_person:anna",
route: "/v1/auth/sessions/issue",
action: "auth.session.issue",
source_ip_hash: "sha256:fixture_auth_source_ip_hash",
device_binding: "device_binding:staging_rate_limit_fixture",
window_seconds: 300,
attempt_count: 1,
soft_limit: 5,
lockout_threshold: 10,
lockout_seconds: 900,
fixture: true,
production_login: false,
enforce_runtime_overlay: false,
contains_customer_data: false,
});

Record staging auth recovery policy with hash-only recovery contact commitments after HumanAuth presence without creating standing.

POST /v1/auth/recovery/policy
state: staging-durable
sdk_role: record staging auth recovery policy with hash-only recovery contact commitments after HumanAuth presence without creating standing
request_record: CloudAuthRecoveryPolicyRequest
responses: recovery_policy | human_presence_receipt | refusal | receipt
interface AuthRecoveryPolicyRequest {
tenant: GestaltRef;
actor: GestaltRef;
vessel: GestaltRef;
standing: GestaltRef;
passkey_binding: GestaltRef;
human_presence_receipt: GestaltRef;
recovery_policy_ref: GestaltRef;
recovery_policy_hash: string; // sha256: prefix required
recovery_contact_hashes: string[]; // each sha256: prefix required
recovery_methods: string[];
fixture: boolean;
production_login: boolean;
contains_customer_data: boolean;
contains_biometric_material: boolean;
raw_recovery_secret?: string; // refused if present
raw_recovery_contact?: string; // refused if present
create_standing_from_recovery: boolean; // refused if true
}
{
"operation": "auth.recoveryPolicy",
"outcome": "admitted",
"body": {
"auth_recovery_policy": "auth_recovery_policy:fixture",
"passkey_binding": "passkey_binding:...",
"human_presence_receipt": "human_presence_receipt:...",
"recovery_policy_hash": "sha256:...",
"recovery_contact_hashes": ["sha256:..."],
"recovery_methods": ["passkey_reenrollment_with_human_presence"],
"raw_recovery_secret_stored": false,
"raw_recovery_contact_stored": false,
"raw_biometric_material_stored": false,
"identity_binding_created": false,
"standing_created": false,
"company_authority_created": false,
"signer": {...},
"durable_commit": {...},
"durable_state": {...},
"production_login": false,
"production_admission": false
},
"receipt": {...}
}
const policy = await client.authRecoveryPolicy({
tenant: "tenant_node:rheinwerk_calibration",
actor: "human_person:anna",
vessel: "vessel:anna_browser_fixture",
standing: "standing:anna_geschaeftsfuehrer_fixture",
passkey_binding: "passkey_binding:anna_platform_passkey_fixture",
human_presence_receipt: "human_presence_receipt:fixture_private_presence",
recovery_policy_ref: "auth_recovery_policy:fixture",
recovery_policy_hash: "sha256:fixture_recovery_policy_hash",
recovery_contact_hashes: ["sha256:fixture_recovery_contact_hash"],
recovery_methods: ["passkey_reenrollment_with_human_presence"],
fixture: true,
production_login: false,
contains_customer_data: false,
contains_biometric_material: false,
create_standing_from_recovery: false,
});

Execute staging auth recovery with a durable recovery policy plus replacement-passkey HumanAuth presence without exposing raw recovery material or creating standing.

POST /v1/auth/recovery/execute
state: staging-durable
sdk_role: execute staging auth recovery with durable recovery policy plus replacement-passkey HumanAuth presence without exposing raw recovery material or creating standing
request_record: CloudAuthRecoveryExecuteRequest
responses: recovery_execution | recovery_policy | replacement_passkey_binding | human_presence_receipt | refusal | receipt
interface AuthRecoveryExecuteRequest {
tenant: GestaltRef;
actor: GestaltRef;
vessel: GestaltRef;
standing: GestaltRef;
recovery_policy: GestaltRef;
recovery_execution_ref: GestaltRef;
replacement_passkey_binding: GestaltRef;
human_presence_receipt: GestaltRef;
recovery_contact_hash: string;
device_binding: GestaltRef;
fixture: boolean;
production_login: boolean;
contains_customer_data: boolean;
contains_biometric_material: boolean;
raw_recovery_secret?: string; // refused if present
raw_recovery_contact?: string; // refused if present
create_identity_binding_from_recovery: boolean;
create_standing_from_recovery: boolean;
create_company_authority_from_recovery: boolean;
}
{
"operation": "auth.recoveryExecute",
"outcome": "admitted",
"body": {
"auth_recovery_execution": "auth_recovery_execution:fixture",
"auth_recovery_policy": "auth_recovery_policy:fixture",
"replacement_passkey_binding": "passkey_binding:fixture_recovery_replacement",
"human_presence_receipt": "human_presence_receipt:fixture_recovery_presence",
"recovery_contact_hash": "sha256:...",
"recovery_policy_consumed": true,
"raw_recovery_secret_stored": false,
"raw_recovery_contact_stored": false,
"raw_biometric_material_stored": false,
"identity_binding_created": false,
"standing_created": false,
"company_authority_created": false,
"signer": {...},
"durable_commit": {...},
"durable_state": {...},
"production_login": false,
"production_admission": false
},
"receipt": {...}
}

The handler enforces that recovery cannot create identity_binding, standing, or company authority — those flags are explicit boundary assertions in both the request (refused if true) and the response (always false).

const recovery = await client.authRecoveryExecute({
tenant: "tenant_node:rheinwerk_calibration",
actor: "human_person:anna",
vessel: "vessel:anna_browser_fixture",
standing: "standing:anna_geschaeftsfuehrer_fixture",
recovery_policy: "auth_recovery_policy:fixture",
recovery_execution_ref: "auth_recovery_execution:fixture",
replacement_passkey_binding: "passkey_binding:fixture_recovery_replacement",
human_presence_receipt: "human_presence_receipt:fixture_recovery_presence",
recovery_contact_hash: "sha256:fixture_recovery_contact_hash",
device_binding: "device_binding:fixture_recovery_device",
fixture: true,
production_login: false,
contains_customer_data: false,
contains_biometric_material: false,
create_identity_binding_from_recovery: false,
create_standing_from_recovery: false,
create_company_authority_from_recovery: false,
});

Issue browser WebAuthn registration options under secure RP/origin policy while storing only a durable challenge and hashed user/credential boundary material.

POST /v1/human-auth/passkey/registration/options
state: staging-durable
sdk_role: issue browser WebAuthn registration options under secure RP/origin policy while storing only a durable challenge and hashed user/credential boundary material
request_record: CloudHumanAuthPasskeyRegistrationOptionsRequest
responses: public_key_credential_creation_options | human_auth_challenge | refusal | receipt
interface HumanAuthPasskeyRegistrationOptionsRequest {
tenant: GestaltRef;
subject: GestaltRef;
relying_party_id: string;
origin: string;
user_handle_hash: string; // sha256: hash of the WebAuthn user handle
user_name_hash: string;
user_display_name_hash: string;
attestation: string; // expected: "none"
authenticator_attachment: string; // e.g. "platform"
resident_key: string; // e.g. "preferred"
user_verification: string; // e.g. "required"
exclude_credential_id_hashes: string[];
contains_biometric_material: boolean; // refused if true
contains_customer_data: boolean; // refused if true
}
{
"operation": "humanAuth.passkeyRegistrationOptions",
"outcome": "verified",
"body": {
"challenge": { "id": "human_auth_challenge:...", "...": "..." },
"public_key_credential_creation_options": {
"challenge": "...b64url...",
"rp": { "id": "gestalt.local", "name": "Gestalt" },
"user": {
"id_b64url": "...",
"handle_hash": "sha256:...",
"name_hash": "sha256:...",
"display_name_hash": "sha256:...",
"raw_name_exposed": false,
"raw_display_name_exposed": false
},
"pub_key_cred_params": [{ "type": "public-key", "alg": -7 }],
"authenticator_selection": {...},
"attestation": "none",
"exclude_credential_id_hashes": [],
"raw_credential_ids_exposed": false
},
"durable_record": {...},
"privacy_boundary": {
"raw_credential_id_stored": false,
"raw_attestation_object_stored": false,
"biometric_material_seen_by_gestalt": false,
"production_admission": false
},
"webauthn_origin_policy": {...},
"authority_boundaries": {...},
"production_admission": false
},
"receipt": {...}
}
const options = await client.humanAuthPasskeyRegistrationOptions({
tenant: "tenant_node:rheinwerk_calibration",
subject: "human_person:anna",
relying_party_id: "gestalt.local",
origin: "https://gestalt.local",
user_handle_hash: "sha256:fixture_user_handle_hash",
user_name_hash: "sha256:fixture_user_name_hash",
user_display_name_hash: "sha256:fixture_user_display_name_hash",
attestation: "none",
authenticator_attachment: "platform",
resident_key: "preferred",
user_verification: "required",
exclude_credential_id_hashes: [],
contains_biometric_material: false,
contains_customer_data: false,
});

Issue browser WebAuthn assertion options for discoverable passkeys under secure RP/origin policy without exposing raw credential ids.

POST /v1/human-auth/passkey/assertion/options
state: staging-durable
sdk_role: issue browser WebAuthn assertion options for discoverable passkeys under secure RP/origin policy without exposing raw credential ids
request_record: CloudHumanAuthPasskeyAssertionOptionsRequest
responses: public_key_credential_request_options | human_auth_challenge | refusal | receipt
interface HumanAuthPasskeyAssertionOptionsRequest {
tenant: GestaltRef;
subject: GestaltRef;
relying_party_id: string;
origin: string;
passkey_binding?: GestaltRef;
scopes: string[];
user_verification: string; // e.g. "required"
contains_biometric_material: boolean;
contains_customer_data: boolean;
}
{
"operation": "humanAuth.passkeyAssertionOptions",
"outcome": "verified",
"body": {
"challenge": { "id": "human_auth_challenge:...", "...": "..." },
"public_key_credential_request_options": {
"challenge": "...b64url...",
"rp_id": "gestalt.local",
"user_verification": "required",
"timeout_ms": 300000,
"allow_credentials": "omitted_discoverable_credentials_only",
"allow_credential_id_hashes": ["sha256:..."],
"raw_credential_ids_exposed": false
},
"passkey_binding": { "id": "...", "credential_id_hash": "sha256:...", "...": "..." },
"durable_record": {...},
"privacy_boundary": {...},
"webauthn_origin_policy": {...},
"authority_boundaries": {...},
"production_admission": false
},
"receipt": {...}
}
const options = await client.humanAuthPasskeyAssertionOptions({
tenant: "tenant_node:rheinwerk_calibration",
subject: "human_person:anna",
relying_party_id: "gestalt.local",
origin: "https://gestalt.local",
passkey_binding: "passkey_binding:anna_platform_passkey_fixture",
scopes: ["sensitive_approval"],
user_verification: "required",
contains_biometric_material: false,
contains_customer_data: false,
});

Finish browser WebAuthn registration under secure RP/origin and attestation:none policy while storing only credential hash, public verification material, clientDataJSON hash, and durable challenge state. (SDK method: humanAuthRegisterPasskey. Earlier drafts referred to this as humanAuth.passkeyRegister.)

POST /v1/human-auth/passkey/register
state: staging-durable
sdk_role: finish browser WebAuthn registration under secure RP/origin and attestation:none policy while storing only credential hash, public verification material, clientDataJSON hash, and durable challenge state
request_record: CloudHumanAuthPasskeyRegisterRequest
responses: passkey_binding | attestation_policy | refusal | receipt
interface HumanAuthPasskeyRegisterRequest {
tenant: GestaltRef;
subject: GestaltRef;
challenge: GestaltRef;
passkey_binding: GestaltRef;
relying_party_id: string;
origin: string;
credential_id_hash: string; // sha256: prefix required
public_key_algorithm: string; // expected: "ES256"
public_key_uncompressed_hex: string;
authenticator_attachment: string;
synced_passkey_allowed: boolean;
sign_count: number;
attestation_conveyance: string; // must be "" or "none"
attestation_object_hash: string; // sha256: prefix required
client_data_json_hash: string;
client_data_json_b64url?: string; // required at boundary; verified, not stored
contains_biometric_material: boolean; // refused if true
contains_customer_data: boolean; // refused if true
raw_credential_id_supplied: boolean; // refused if true
private_key_material_supplied: boolean; // refused if true
}
{
"operation": "humanAuth.registerPasskey",
"outcome": "admitted",
"body": {
"passkey_binding": {
"id": "passkey_binding:m24_registered_passkey",
"subject": "human_person:anna",
"relying_party_id": "gestalt.local",
"origin": "https://gestalt.local",
"credential_id_hash": "sha256:...",
"public_key_algorithm": "ES256",
"public_key_hash": "sha256:...",
"authenticator_attachment": "platform",
"synced_passkey_allowed": true,
"sign_count": 0,
"biometric_data_seen_by_gestalt": false,
"commitment": "sha256:..."
},
"durable_records": {
"passkey_binding": {...},
"challenge": {...}
},
"registration_finish": {...},
"attestation_policy": {
"mode": "none_only_staging",
"verified_attestation_statement": false,
"direct_attestation_allowed": false,
"enterprise_attestation_allowed": false
},
"raw_credential_id_stored": false,
"raw_attestation_object_stored": false,
"raw_client_data_json_stored": false,
"raw_private_key_material_stored": false,
"raw_biometric_material_stored": false,
"webauthn_origin_policy": {...},
"authority_boundaries": {...},
"production_admission": false
},
"receipt": {...}
}

The challenge consumed must have been issued by humanAuth.passkeyRegistrationOptions (it must carry the passkey_registration scope and not be expired or already consumed). Only attestation:none is admitted — direct or enterprise attestation will be refused with human_auth_attestation_policy_refused.

const registered = await client.humanAuthRegisterPasskey({
tenant: "tenant_node:rheinwerk_calibration",
subject: "human_person:anna",
challenge: options.body.challenge.id,
passkey_binding: "passkey_binding:m24_registered_passkey",
relying_party_id: "gestalt.local",
origin: "https://gestalt.local",
credential_id_hash: "sha256:m24_registered_credential_id_hash",
public_key_algorithm: "ES256",
public_key_uncompressed_hex: "04...",
authenticator_attachment: "platform",
synced_passkey_allowed: true,
sign_count: 0,
attestation_conveyance: "none",
attestation_object_hash: "sha256:fixture_registration_attestation_object",
client_data_json_hash: "",
client_data_json_b64url: "...",
contains_biometric_material: false,
contains_customer_data: false,
raw_credential_id_supplied: false,
private_key_material_supplied: false,
});

Import passkey public verification material without raw credential id, private key, or biometric material.

POST /v1/human-auth/passkey/import
state: staging-durable
sdk_role: import passkey public verification material without raw credential id, private key, or biometric material
request_record: CloudHumanAuthPasskeyImportRequest
responses: passkey_binding | refusal | receipt
interface HumanAuthPasskeyImportRequest {
tenant: GestaltRef;
subject: GestaltRef;
passkey_binding: GestaltRef;
relying_party_id: string;
origin: string;
credential_id_hash: string;
public_key_algorithm: string; // expected: "ES256"
public_key_uncompressed_hex: string;
authenticator_attachment: string;
synced_passkey_allowed: boolean;
sign_count: number;
attestation_object_hash?: string;
fixture: boolean; // must be true
contains_biometric_material: boolean;
contains_customer_data: boolean;
}
{
"operation": "humanAuth.passkeyImport",
"outcome": "admitted",
"body": {
"passkey_binding": {
"id": "passkey_binding:m24_imported_passkey",
"subject": "human_person:anna",
"relying_party_id": "gestalt.local",
"origin": "https://gestalt.local",
"credential_id_hash": "sha256:...",
"public_key_algorithm": "ES256",
"public_key_hash": "sha256:...",
"sign_count": 0,
"biometric_data_seen_by_gestalt": false,
"commitment": "sha256:..."
},
"durable_record": {...},
"raw_credential_id_stored": false,
"raw_private_key_material_stored": false,
"raw_biometric_material_stored": false,
"webauthn_origin_policy": {...},
"authority_boundaries": {...},
"production_admission": false
},
"receipt": {...}
}
const imported = await client.humanAuthPasskeyImport({
tenant: "tenant_node:rheinwerk_calibration",
subject: "human_person:anna",
passkey_binding: "passkey_binding:m24_imported_passkey",
relying_party_id: "gestalt.local",
origin: "https://gestalt.local",
credential_id_hash: "sha256:m24_imported_credential_id_hash",
public_key_algorithm: "ES256",
public_key_uncompressed_hex: "04...",
authenticator_attachment: "platform",
synced_passkey_allowed: true,
sign_count: 0,
fixture: true,
contains_biometric_material: false,
contains_customer_data: false,
});

Selected refusal codes returned by this cluster (see refusal.codes for the full list):

auth_login_fixture_only
auth_login_raw_identifier_refused
auth_login_source_hash_required
auth_login_rate_limit_policy_invalid
auth_login_passkey_binding_unknown
auth_login_passkey_subject_mismatch
auth_login_passkey_origin_mismatch
auth_login_rate_limit_missing
auth_login_rate_limit_not_allowed
auth_login_standing_mismatch
auth_login_holder_key_mismatch
auth_login_expiry_invalid
auth_login_challenge_mismatch
auth_login_human_presence_invalid
auth_login_session_already_issued
auth_session_issue_fixture_only
auth_session_issue_standing_mismatch
auth_session_issue_holder_key_mismatch
auth_session_issue_expiry_invalid
auth_session_refresh_fixture_only
auth_session_refresh_holder_key_mismatch
auth_session_refresh_expiry_invalid
auth_session_refresh_scope_expansion_refused
auth_rate_limit_fixture_only
auth_rate_limit_raw_identifier_refused
auth_rate_limit_hash_required
auth_rate_limit_policy_invalid
auth_rate_limit_throttled
auth_rate_limit_locked
auth_recovery_policy_fixture_only
auth_recovery_policy_raw_material_refused
auth_recovery_policy_cannot_create_standing
auth_recovery_policy_standing_mismatch
auth_recovery_policy_hash_required
auth_recovery_policy_passkey_unknown
auth_recovery_policy_passkey_subject_mismatch
auth_recovery_policy_presence_invalid
human_auth_biometric_material_refused
human_auth_raw_credential_material_refused
human_auth_attestation_policy_refused
human_auth_passkey_public_key_missing
human_auth_passkey_binding_unknown
human_auth_passkey_credential_mismatch
human_auth_passkey_subject_mismatch
human_auth_passkey_import_fixture_only
human_auth_registration_challenge_required
human_auth_challenge_unknown
human_auth_challenge_expired
human_auth_challenge_replayed
human_auth_user_verification_missing
human_auth_webauthn_assertion_missing
human_auth_webauthn_challenge_mismatch
human_auth_webauthn_client_data_invalid
human_auth_wrong_rp_id
human_auth_wrong_origin
m13_human_presence_missing
m13_human_presence_subject_mismatch
m13_session_unknown
m13_wrong_actor
m13_wrong_vessel