ARIA1.0
English only · Spanish [PLANNED]

Verify a credential offline with the SDK

Task: take an AID an agent presented (or fetched from the API) and decide whether it is valid, without calling the registry at decision time.

1 · Install

npm · @aria-registry/verify 1.1.0
npm install @aria-registry/verify@1.1.0

The package pins the issuer's public keys, so verification is local. It pulls @noble/post-quantum, @noble/curves and @noble/hashes; expect about 5 MB installed.

2 · Get a credential

Any resolvable DID works. Replace <did> with one you were given — your own from the portal, for instance.

curl · runs against https://api.aria.bar
curl -s https://api.aria.bar/v1/aids/<did> > credential.json

3 · Verify

TypeScript · runs against @aria-registry/verify 1.1.0
import { verifyAgent } from '@aria-registry/verify';

// `credential` is the AID JSON the agent presented, or the document at
// https://api.aria.bar/v1/aids/<did>. Verification runs offline.
const result = await verifyAgent(credential);

if (result.valid) {
  result.did;                  // "did:aria:example.com:my-agent"
  result.trustLevel;           // "L0" today; L1–L3 once issued
  result.scopes;               // ["commerce:order:create", …]
  result.credentialId;         // credential-instance URL
  result.principal
    .verificationStatus;       // "self-declared" | "registry-confirmed"
                               // | "legal-verified" | null
  result.revocationStatus;     // 'unknown' — verifyAgent never checks revocation.
                               // Use checkRevocation(did), or a policy that requires it.
}

Both signature halves must pass: a flipped byte in the ML-DSA-65 half fails with signatures.pqValid === false, one in the Ed25519 half with classicalValid === false. This was checked against a live credential on 2026-09-07 (ATP/1 vector 11).

4 · Decide what revocation means to you

verifyAgent() does not check revocation. It returns revocationStatus: 'unknown' and the credential passes. This is deliberate — offline verification is the point — but an integrator who reads valid and stops will accept a revoked credential.

Two things turn revocation into part of the verdict. requireRevocationCheck makes it required at all; maxOfflineAge bounds how old the evidence may be. Never pass null, which means "any age"; one hour is a reasonable default and the protocol floor is 60 seconds (§8, fresh=).

TypeScript · runs against @aria-registry/verify 1.1.0
const status = await checkRevocation(did);           // networked, per DID
const result = await verifyAgent(credential, {
policy: {
  requireRevocationCheck: true,
  lastRevocationCheck: Date.parse(status.checkedAt),
  maxOfflineAge: 60 * 60 * 1000,
},
});

checkRevocation asks /v1/verify/{did}, which tells the registry which agent you are asking about. Reading the aggregate Bitstring Status List yourself avoids that, and moving the SDK to it is planned.

You should see

For a valid L0 credential:

expected shape
result.valid                            // true
result.trustLevel                       // "L0"
result.signatures                       // { pqValid: true, classicalValid: true, suite: "mldsa65-ed25519-2026" }
result.principal.verificationStatus     // "self-declared"
result.revocationStatus                 // "unknown" — until a policy requires the check

What you have now is identification: the credential is genuine and current. You have not proven that the caller holds its key — that is the holder proof under ATP, in deployment (/planned#challenge-endpoint). Read What an AID proves before gating anything on this result.