Checkpoint Docs
Govern (KYA-OS)

Proof Verification

Verify KYA-OS cryptographic proofs from AI agents

What Are KYA-OS Proofs?

A KYA-OS proof is a cryptographic assertion that an AI agent attaches to every request. It proves:

  1. Identity — The agent is who it claims to be (verified via DID)
  2. Authorization — The agent has a valid delegation for the requested action
  3. Freshness — The proof was recently created and hasn't been replayed

Proofs are signed with the agent's private key and verified by Checkpoint's infrastructure.

Prerequisites

  • A Checkpoint project and API key. See Credentials for how to find your Project ID and API key in Installations.
  • An MCP server or API you control, where you can install @kya-os/bouncer-middleware (or verify proofs directly against the API) in front of the endpoints you want to protect.

Proof Structure

A KYA-OS proof is a compact JWS (RFC 7515) that travels in the request body at _meta.proof.jws:

<base64url(header)>.<base64url(payload)>.<base64url(signature)>

The protected header always uses alg: "EdDSA" (Ed25519), with kid naming the verification method that signed the proof:

{ "alg": "EdDSA", "kid": "did:key:z6Mk...#z6Mk..." }

The payload carries the eight required claims from KYA-OS spec § 7.4, canonicalized with RFC 8785 (JCS) before signing:

{
  "aud": "https://api.example.com",
  "iss": "did:key:z6Mk...",
  "nonce": "c2Vzc2lvbi1ub25jZQ",
  "requestHash": "sha256:6a1f09...",
  "responseHash": "sha256:9b2e4c...",
  "sessionId": "sess-01HTZX...",
  "sub": "did:key:z6Mk...",
  "ts": 1706745600
}
ClaimDescription
audAudience — the intended recipient DID or URL
issIssuer — the signing principal's DID
subSubject — the principal the proof is about (equal to iss when self-asserted)
nonceSession nonce — binds the proof to the handshake that established the session
sessionIdSession identifier from the handshake response
tsUnix epoch seconds when the proof was generated
requestHashSHA-256 of the canonicalized request, formatted sha256:<64-hex>
responseHashSHA-256 of the canonicalized response, formatted sha256:<64-hex>

The legacy envelope — {(protected, payload, signature)} JSON with JWT-style iat/exp claims, sent in a KYA-Delegation HTTP header — is gone. @kya-os/bouncer-middleware reads only the body envelope and has no header fallback. The Checkpoint SDKs (@kya-os/checkpoint-express, @kya-os/checkpoint-nextjs) can accept the legacy header form from pre-cutover agents behind their opt-in legacyEnvelopeFallback flag (default false).

Server-Side Verification

Using the Middleware

The recommended approach is @kya-os/bouncer-middleware, which handles proof extraction and verification automatically:

import express from 'express';
import { createBouncerMiddleware } from '@kya-os/bouncer-middleware';

const app = express();
app.use(express.json());

// Protect an endpoint — proofs are verified automatically
app.post(
  '/api/files',
  createBouncerMiddleware({
    apiKey: process.env.AGENTSHIELD_API_KEY!,
    projectId: process.env.AGENTSHIELD_PROJECT_ID!,
    requiredScopes: ['files:write'],
    reputationThreshold: 60,
  }),
  (req, res) => {
    // Verified agent data is available on req.bouncer
    const { agentDid, scopes, reputation, delegation } = req.bouncer;

    console.log(`Agent ${agentDid} with reputation ${reputation}`);
    console.log(`Granted scopes: ${scopes.join(', ')}`);

    res.json({ message: 'File created', agent: agentDid });
  }
);

AGENTSHIELD_API_KEY is the KYA-OS/bouncer naming convention for the same dashboard API key the Checkpoint SDKs read as CHECKPOINT_API_KEY — two env names, one key. The middleware itself reads no environment variables; values are passed explicitly in its config, so use whichever name your deployment already defines.

The middleware performs these steps:

  1. Extracts the compact JWS from the request body's _meta.proof.jws (which is why express.json() must run first)
  2. Parses the envelope and reads the signed claims
  3. Forwards the proof to Checkpoint's POST /api/v1/bouncer/proofs endpoint, which performs the cryptographic signature verification — the middleware itself is a parser and forwarder, not a verifier
  4. Enforces freshness on ts: a 5-minute replay window, plus rejection of timestamps more than 60 seconds in the future
  5. When the proof references a delegation, fetches it and checks its status (revoked / expired) and constraints (time window, origin, IP)
  6. Enforces required scopes — exact string match, with no wildcard or hierarchy semantics
  7. Checks agent reputation against reputationThreshold (when configured)
  8. Attaches verified data to req.bouncer

Middleware Configuration Reference

The full createBouncerMiddleware option set (BouncerConfig), verified against packages/bouncer-middleware/src/types.ts:

interface BouncerConfig {
  /** Checkpoint API key (required) */
  apiKey: string;

  /** Checkpoint project ID (required) */
  projectId: string;

  /** API base URL (default: 'https://kya.vouched.id') */
  apiUrl?: string;

  /** Minimum reputation score 0-100 (optional) */
  reputationThreshold?: number;

  /** Scopes the agent must have in its delegation (optional) */
  requiredScopes?: string[];

  /** Custom error handler (optional) */
  onError?: (error: BouncerError, req: Request) => void;

  /** Enable debug logging (optional) */
  debug?: boolean;
}

Other pages in this section show createBouncerMiddleware calls that only set the options relevant to what they're teaching — this is the complete reference.

Verified Request Data

After successful verification, req.bouncer contains:

interface BouncerRequest extends Request {
  bouncer?: {
    /** Verified KYA-OS proof payload */
    proof: MCPIProofPayload;

    /** Agent DID (Decentralized Identifier) */
    agentDid: string;

    /** Delegation information */
    delegation?: Delegation;

    /** Agent reputation score (0-100) */
    reputation?: number;

    /** Granted scopes from delegation */
    scopes: string[];
  };
}

Registry and Bouncer surfaces score reputation on a 0–100 scale — that's what req.bouncer.reputation and reputationThreshold use. The Cedar policy engine's principal.reputation fact is the same signal normalized to 0.0–1.0 (see Policies) — don't conflate the two scales.

How Agents Send Proofs

AI agents embed the proof in the JSON request body under _meta.proof.jws (KYA-OS spec § 7.4) — not in an HTTP header:

curl -X POST https://api.example.com/api/files \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "report.txt",
    "content": "...",
    "_meta": {
      "proof": {
        "jws": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNay4uLiJ9.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsIC4uLn0.c2lnbmF0dXJl..."
      }
    }
  }'

Error Handling

When proof verification fails, the middleware returns a structured error:

{
  "error": {
    "code": "INSUFFICIENT_SCOPES",
    "message": "Required scopes: files:write. Granted: files:read",
    "details": {
      "required": ["files:write"],
      "granted": ["files:read"]
    }
  }
}

Error Codes

CodeHTTP StatusDescription
MISSING_PROOF401No proof at body._meta.proof.jws, or the envelope failed to parse
INVALID_PROOF401Proof timestamp is more than 60 seconds in the future (clock skew)
EXPIRED_PROOF401Proof timestamp is outside the 5-minute replay window
INVALID_SIGNATURE401Ed25519 signature verification failed
DELEGATION_NOT_FOUND404Referenced delegation does not exist
DELEGATION_EXPIRED403Delegation has passed its expiration
DELEGATION_REVOKED403Delegation was explicitly revoked
INSUFFICIENT_SCOPES403Agent lacks required scopes
REPUTATION_TOO_LOW403Agent reputation below threshold
CONSTRAINT_VIOLATION403Delegation constraint not satisfied
INVALID_CONFIG500Middleware misconfiguration (e.g. an invalid apiUrl)
API_ERROR500Unexpected failure calling the Checkpoint API

A malformed envelope is indistinguishable from a missing one — structural parse failures surface as MISSING_PROOF, not INVALID_PROOF. Proof verifications, including failures, are recorded for audit and inspectable from each delegation's activity in the dashboard under Project → Delegations.

Proof Lifecycle

1. Agent requests delegation (via OAuth or API)
2. Agent creates a proof for a specific request
3. Proof travels in the request body (_meta.proof.jws)
4. Middleware parses the envelope and forwards it to Checkpoint
5. Checkpoint verifies the signature; the middleware enforces
   freshness, delegation status, scopes, and constraints
6. Request proceeds or is rejected
7. Verification event recorded for audit

Proof Expiration

Freshness is enforced through the single ts claim (Unix epoch seconds) rather than JWT-style iat/exp claims. Proofs older than the 5-minute (300-second) replay window are rejected with EXPIRED_PROOF; timestamps more than 60 seconds in the future are rejected with INVALID_PROOF. The nonce binds the proof to the session's handshake, and requestHash/responseHash bind it to the exact payload it covers.

Monitoring Proofs

Proof activity is inspectable from the Checkpoint dashboard:

  1. Navigate to Project → Delegations
  2. Open a delegation to see its activity, including tool-call proofs
  3. Open any proof to inspect its claims and signature material
  4. Failed verifications are recorded with a failed outcome, so rejected proofs are auditable too

Next Steps