Skip to content
Guides

Challenges and verdicts

A challenge names what the device must prove and the policy that judges it. The device answers; verify returns the verdict on the same call.

server (Node)ts
const { challengeId, challenge } = await rh.issueChallenge({
  ask: ["identity", "posture"],     // "identity" | "posture" | "key"; omitted = identity + posture
  policy: "rootherald:builtin:strict-hardware",
  keyPurpose: "sign",               // only with a key ask
});
// relay `challenge` verbatim → device → `evidence`

const result = await rh.verify(evidence, {
  challengeId,
  policy: undefined,                // omit, or name one at least as strict (else 422 policy_downgrade)
  requestedDisclosureClass: "pseudonymous",
});
if (result.enrollmentRequired) { /* enrol, retry */ }
if (result.device.verdict === "pass" && result.assuranceClaimsMet.includes("rootherald:assurance:secure-boot")) { /* … */ }

The ask

askDevice provesCollectedVerdict adds
identitySame enrolled chip as before.Quote over PCR 7. No event log.ueid, quoteVerified, real-device.
postureHow it booted.PCRs 0–7, event log, Secure Boot variables.Boot booleans, trustworthinessVector, cohort block, secure-boot / oem-keyed.
keyHolds a fresh key that cannot leave the chip.Everything posture collects, plus TPM2_Certify of a new P-256 key.key block on pass.

Asks nest. Not every platform serves every ask: macOS answers identity only, iOS identity and key. The device refuses what it cannot serve (AskUnsupportedError / RH_ERR_ASK_UNSUPPORTED) before touching the network; the fix is a smaller ask. Matrix on the app keys page.

The challenge string

rhc1.<base64url nonce>.<base64url ask-json>     single-use, five-minute TTL

Relay it verbatim and never parse it. The client reads the ask from it; the server binds the ask and the policy to the challenge row, so a client lied to about the ask produces evidence the server refuses. A string without the rhc1. prefix is refused by every client (TypeError in the browser, RH_ERR_INVALID_ARG in C).

Respond

browser · native · iOSts
const { evidence, key } = await respond(challenge);            // @rootherald/browser; key only for a key ask
RootHeraldRespond(h, challenge, NULL, ev, cap, &len, blob, 512, &blen);   // C; blob written only for a key ask
let evidence = try await client.respond(to: challenge)          // Swift; evidence.jsonData() is the blob

Unprivileged everywhere. On a device with no attestation key it fails with a not-enrolled error; see Enrolment.

The verdict

Fields are present or omitted, never null. A smaller ask produces a smaller verdict. A policy that requires a field the ask did not collect fails the verdict.

FieldTypeMeaning
assuranceClaimsMetstring[]rootherald:assurance:* URNs the device satisfied. Gate with required ⊆ met.
enrollmentRequiredbooleanThe device is not enrolled. Enrol and retry; never re-enrol on a policy failure.
keyobjectCertified public key. Present only for a key ask on a passing verdict.
verdict.acrURNurn:rootherald:device:any or urn:rootherald:device:high. The tier satisfied, never a weaker substitute.
verdict.amrstring[]Method, RFC 8176. TPM path: ["hwk_tpm"].
verdict.userIduuidThe device ueid; the device is the principal on this path.
verdict.authTime / expiresAtISO-8601When appraised, and the advisory freshness deadline (about five minutes).
device.ueiduuidStable per-tenant device id. Survives OS reinstall and key rotation. No PII.
device.verdictpass | warn | failThe coarse verdict.
device.earStatusaffirming | warning | contraindicatedThe IETF EAR tier. Allow, step up, deny.
device.attestationTypetpm20 | apple-se | android-ka | ios-appattestWhich root of trust produced the evidence.
device.quoteVerifiedbooleanThe quote signature verified against the enrolled AK.
device.secureBootVerifiedbooleanA quote-bound event log established Secure Boot. Posture ask only.
device.eventLogVerifiedbooleanThe event log replayed to the signed PCRs. Posture ask only.
device.platformwindows | linux | macos | android | iosOmitted when unresolved.
device.hardwareModelstringFiner model when resolved; usually omitted today.
device.trustworthinessVectorRecord<string, int>AR4SI tiers per dimension: instance-identity, configuration, executables, hardware.
device.cohort*blockcohortPrevalence, cohortPrevalencePerPcr, novelProfile, cohortScope, cohortKey, cohortSampleSize. Advisory; see Policies.
verify response — posture ask, Windows, passjson
{
  "verdict": {
    "acr": "urn:rootherald:device:high",
    "amr": ["hwk_tpm"],
    "authTime": "2026-07-03T17:08:13Z",
    "expiresAt": "2026-07-03T17:13:13Z",
    "userId": "1ade0832-6a16-cf50-a2bd-2a5c4cb498a5",
    "device": {
      "ueid": "1ade0832-6a16-cf50-a2bd-2a5c4cb498a5",
      "verdict": "pass",
      "earStatus": "affirming",
      "attestationType": "tpm20",
      "platform": "windows",
      "quoteVerified": true,
      "secureBootVerified": true,
      "eventLogVerified": true,
      "trustworthinessVector": { "instance-identity": 2, "configuration": 2, "executables": 2, "hardware": 2 }
    }
  },
  "assuranceClaimsMet": ["rootherald:assurance:real-device", "rootherald:assurance:secure-boot", "rootherald:assurance:oem-keyed"],
  "enrollmentRequired": false
}

Assurance claims

  • rootherald:assurance:real-device — a hardware, firmware or mobile-hardware class, chain-validated.
  • rootherald:assurance:secure-boot — Secure Boot on, proven by a quote-bound event log.
  • rootherald:assurance:oem-keyed — the Secure Boot platform key is a known OEM's.
  • rootherald:assurance:hardware-bound-key — the certified app key is inside the chip.
  • rootherald:assurance:cloud-vtpm — a cloud virtual TPM, admitted by a permissive policy.

Write gates as claims, not platforms: required ⊆ met ⇒ allow.

Disclosure classes

requestedDisclosureClass on verify caps what the verdict reveals: verdict (booleans and enums, no identifier), pseudonymous (adds ueid; the default), derived (lifecycle facts), full. The policy's default applies when omitted. Webhook endpoints carry their own class.

A failing device is a 200

Policy failures, rejected classes and banned devices return a normal response with device.verdict of fail or warn. Only auth, challenge, evidence, policy and quota problems are 4xx; codes are on the API reference.