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:
- Identity: The agent is who it claims to be (verified via DID)
- Authorization: The agent has a valid delegation for the requested action
- 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
}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.CHECKPOINT_API_KEY!,
projectId: process.env.CHECKPOINT_PROJECT_ID!,
requiredScopes: ['files:write'],
}),
(req, res) => {
// Verified agent data is available on req.bouncer
const { agentDid, scopes, delegation } = req.bouncer;
console.log(`Agent ${agentDid}`);
console.log(`Granted scopes: ${scopes.join(', ')}`);
res.json({ message: 'File created', agent: agentDid });
}
);The middleware performs these steps:
- Extracts the compact JWS from the request body's
_meta.proof.jws(which is whyexpress.json()must run first) - Parses the envelope and reads the signed claims
- Forwards the proof to Checkpoint's
POST /api/v1/bouncer/proofsendpoint, which performs the cryptographic signature verification. The middleware itself is a parser and forwarder, not a verifier - Enforces freshness on
ts: a 5-minute replay window, plus rejection of timestamps more than 60 seconds in the future - When the proof references a delegation, fetches it and checks its status (revoked / expired) and constraints (time window, origin, IP)
- Enforces required scopes: exact string match, with no wildcard or hierarchy semantics
- Checks agent reputation against
reputationThreshold(when configured) - 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). Leave unset: reputation scores are not served yet */
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;
/** Circuit breaker for the Checkpoint API: opens after `failureThreshold` consecutive failed calls (default 5), probes every `probeEvery`th call while open (default 5) */
circuitBreaker?: { failureThreshold?: number; probeEvery?: number };
/** Structured logger for API failures, e.g. pino (optional) */
logger?: { warn(fields: Record<string, unknown>, message: string): void };
}No Checkpoint API call runs on a fixed deadline. Each call is aborted as soon as the client closes the request, and a circuit breaker denies requests immediately once the API has failed several calls in a row. The middleware fails closed: an aborted request is answered with 499 and API_ERROR, an open circuit with 503, and a network failure with 500. An unreachable reputation service is denied with API_ERROR rather than treated as a reputation score, while a 400 from proof verification is reported as INVALID_SIGNATURE, and any other non-2xx answer from it is API_ERROR (503).
A 499 from proof verification does not mean the server discarded the proof: it may still have recorded it and consumed its nonce. Sign a fresh proof before retrying, because resending the same one is answered with INVALID_SIGNATURE (nonce_reused).
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[];
};
}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
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 auditProof 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:
- Navigate to Project → Delegations
- Open a delegation to see its activity, including tool-call proofs
- Open any proof to inspect its claims and signature material
- Failed verifications are recorded with a
failedoutcome, so rejected proofs are auditable too
Next Steps
- Managing Delegations: Create the authorization grants that proofs reference
- OAuth Integration: Automated delegation creation
- Tool Protection: Map proofs to specific tool permissions
