For agents

Proofs and verification

A Proof of Agency is a short-lived ES256-signed JWS for one allowed action request and relying-party audience. It carries no Brainprint. It lets the execution service check the authorization's request binding, then consult current server state before using it.

Verification has two steps: validate the signature and request binding locally, then check current proof state and consume it online. Your application executes the approved operation through its own transaction controls.

Values the verifier must already know

Expected value Trusted source
Issuer Reviewed service configuration; hosted issuer is https://app.graypass.org
Tenant and environment ID Your credential identity or independently configured values
Environment kind The intended sandbox or live integration
Audience The relying-party service configured in the active policy
Action key The operation implemented by this route
Action hash Locally computed from the frozen pending business request
Time A synchronized server clock

Take expected values from trusted configuration and the saved request. For a resumed challenge, compare the child authorization with the original server-held request binding.

Compute the binding

The Node SDK’s actionHash implements canonical request hashing. This illustrative TypeScript excerpt assumes the variables come from your configured environment and durable pending request:

TypeScript
import { actionHash } from '@graypass/node/verify';

const expectedActionHash = actionHash({
  environment: environmentId,
  action: 'payout.destination.change',
  resource: { type: 'merchant_account', id: merchantId },
  context: { destination_fingerprint: destinationFingerprint },
  subject: subjectId,
  actor: subjectId,
  actorType: 'human',
  audience: 'payments-app',
  nonce: pendingNonce,
});

All values in this example come from trusted configuration or your persisted pending request. authorize() also returns verification.actionHash, calculated locally by the SDK from the submitted request, and verification.nonce. Persist them before any asynchronous challenge or review.

Verify the signature and claims

TypeScript
const expected = {
  audience: 'payments-app',
  expectedAction: 'payout.destination.change',
  expectedActionHash,
};
const result = await graypass.verify(proof, expected);
if (!result.valid) {
  throw new Error(`Execution refused: ${result.reason}`);
}

The client checks signature, algorithm, key ID, issuer, time bounds and maximum lifetime, audience, tenant, environment, enforceability, actor constraints, action, and request hash. It resolves tenant and environment from credential() when they were not explicitly configured. jwks() can fetch verification keys as part of this convenience method.

For a cryptographic check that makes no network request, call verifyProof() with an already obtained JWKS and explicit expectations. MemoryReplayStore tracks reuse within one process; use a shared implementation if local replay checking must span several processes. In either case, execute only after the online consumption step below.

Check current state and consume

TypeScript
const state = await graypass.verifyOnline(proof, {
  ...expected,
  consume: true,
});
if (!state.valid) {
  throw new Error(`Execution refused: ${state.reason}`);
}

consume: true accepts the proof once through a scoped conditional state change. consume: false returns a state snapshot. For concurrent workers, execute only in the worker whose consumption succeeds and serialize the business mutation through your application ledger.

Refuse execution if the online request fails or has an ambiguous result. A timeout may occur after consumption; inspect your own operation ledger and reconcile rather than assuming it never happened. See Integration for crash recovery.

Keys and rotation

The hosted public key set is available at /.well-known/graypass-proof-keys.json. The Node client caches JWKS and refreshes once on an unknown key ID. Fetch only from the configured service origin. Do not follow an untrusted proof header to an arbitrary key URL.

Cache public keys according to your integration’s key-refresh policy, keep clocks synchronized, and exercise key rotation in testing. If a key cannot be resolved or a signature cannot be verified, the action remains unexecuted. Complete both signature verification and the current-state consumption check before execution.

Reading failures

Common verifier reasons include malformed, unsupported_alg, unknown_kid, invalid_signature, issuer_mismatch, audience_mismatch, not_yet_valid, expired, ttl_too_long, not_enforceable, environment_mismatch, tenant_mismatch, actor_mismatch, action_mismatch, action_hash_mismatch, and replayed. Log a reason and trace ID rather than the raw proof.

Preserve verifier expectations and correct the request, configuration, or pending-request association that caused the mismatch. The proof API reference describes state queries and online verification.