The four things
you came here to do.
Working recipes with the JavaScript SDK’s real exports: authorize an agent action, gate it behind a named human, leave evidence of what ran, and protect the resource itself. The same loop reads identically in the other five languages.
- 4 recipes
- 1 security model
- Real SDK surface
- 01
Authorize an AI agent action
Evaluate a proposed action against a signed intent, and only execute inside the helper that verifies the grant, redeems it once, and records what ran.
executeWithAuthorization verifies the ES256 signature and exact envelope, redeems the single-use grant at the resource, runs your function, and submits the execution receipt - on failure as well as success.
import { VeridProcurementClient, VeridGrantVerifier, executeWithAuthorization } from "@verid/agent-sdk"; const client = new VeridProcurementClient({ baseUrl, tenantId, accessToken }); const proposal = { intentId: signedIntentId, agentId, tool: "datadog-billing", action: "renew", amount: "13800.00", currency: "USD", counterparty: "datadog" }; const envelope = client.envelopeFor(proposal); const decision = await client.evaluate(proposal); if (decision.decision !== "ALLOW") { throw new Error(`Refused: ${decision.decision}`); } const grant = await client.authorizationById(decision.authorizationId); const verifier = new VeridGrantVerifier({baseUrl}); const result = await executeWithAuthorization({ client, verifier, grant, envelope, agentId, execute: () => renewSubscription(proposal), externalReference: "invoice-2026-0031" }); - 02
Require human approval
When the amount crosses the intent's approval threshold, the decision comes back REQUIRE_APPROVAL instead of ALLOW. Wait for a named human, then proceed only if they approved.
The approval is a named, expiring decision bound to the exact envelope that was evaluated - who approved, what, when, and under which decision id are all recorded in the audit chain. Changing any material parameter afterwards invalidates it; there is no approved-in-general state.
const decision = await client.evaluate({ ...proposal, amount: "7500.00" // above the 5,000 approval threshold }); if (decision.decision === "REQUIRE_APPROVAL") { // Polls /v1/runtime/approvals/{id} until a named human decides, // or the timeout elapses. A timeout throws - it never approves. const approval = await client.waitForApproval(decision.approvalId, { timeoutMs: 300000, intervalMs: 1000 }); if (approval.status !== "Approved") { throw new Error(`Not approved: ${approval.status}`); } // The approval is an evidence artifact, not a flag. It records who // decided, what exact envelope they decided on, when, and under which // decision - and it joins the same audit chain as the receipt. console.log({ approvedBy: approval.decidedBy, // the named human evaluationId: approval.evaluationId, // the decision it answers actionHash: approval.actionHash, // exactly what was approved decidedAt: approval.updatedAt, expiresAt: approval.expiresAt }); } // The approval is bound to this exact action. Re-evaluate or fetch the // grant it unlocked; a different amount would need its own decision. const grant = await client.authorizationById(decision.authorizationId); - 03
Issue an evidence receipt
Record what actually executed so the approved action and the executed action can be compared. The helper does this automatically; do it manually when you execute outside it.
The receipt joins the hash-linked audit chain next to the decision, the grant, and the redemption. An authorization without a receipt is a visible gap, not a silent one.
// Automatic: executeWithAuthorization submits the receipt for you, // with status recorded on success and on failure. // Manual, when your execution happens elsewhere: await client.submitReceipt({ authorizationId: grant.id, status: "Succeeded", externalReference: "invoice-2026-0031", evidence: { invoiceTotal: "13800.00", confirmedBy: "billing-api" }, executed: { tool: "datadog-billing", action: "renew", amount: "13800.00", currency: "USD" } }); - 04
Protect a production deployment
Flip the perspective: you own the protected resource. Caller possession of a decision or grant is insufficient - the protected resource independently verifies and redeems it. Refuse to deploy unless that verification and redemption succeed for this exact action.
This is the most important idea on the page: the caller does not get to declare itself authorized. Verification and redemption happen where the action executes, so a compromised caller cannot skip them. Production verification refuses ephemeral signing keys by default.
import {VeridGrantVerifier} from "@verid/agent-sdk"; // Inside the deploy service - the protected resource. const verifier = new VeridGrantVerifier({baseUrl}); export async function deploy({grant, envelope, agentId, release}) { // 1. Independent verification: ES256 signature against the // published keys, and the exact envelope this grant covers. await verifier.verify(grant, {tenantId, agentId, envelope}); // 2. Single-use redemption, here, immediately before acting. // A replayed or already-consumed grant fails this call. await client.consume({ authorizationId: grant.id, agentId, envelope }); // 3. Only now does the consequential action run. const outcome = await rollOut(release); await client.submitReceipt({ authorizationId: grant.id, status: "Succeeded", externalReference: release.id, executed: {tool: "deploy", action: "release", environment: "production"} }); return outcome; }
Follow each recipe above from identity to evidence.
Each consequential action moves through the same six control points. See where 2AYE evaluates authority, requests human review, and preserves the outcome.
- 01IdentityWho or what is acting
- 02IntentSigned purpose and limits
- 03PolicyDeterministic evaluation
- 04ApprovalA named human when required
- 05ExecutionSingle-use grant, redeemed at the resource
- 06EvidenceReceipt joined to the audit chain