Shopping and checkout agents in Next.js typically combine the App Router, the Vercel AI SDK, and server-side secrets. Paybond belongs on the server — bind middleware per request, never expose capability tokens to the browser.
TypeScript only. This guide uses
@paybond/kit/vercel-ai(TypeScript-only). For Python shopping agents, use Let agents buy groceries with the agent-agnostic path.
Adapter reference: /docs/kit/vercel-ai.
Server-only Paybond
Open Kit and instrument in Route Handlers — never expose capability tokens to the browser.
Request-scoped bind
Lazy context + AsyncLocalStorage resolve intentId per request for streamText.
Vercel AI toolApproval
Harbor authorize before checkout tools; client receives streamed text only.
TypeScript only
App Router + Vercel AI samples are TypeScript; Python shopping uses the agent-agnostic grocery guide.
Why Paybond (not just Next.js and the AI SDK)?
App Router and Vercel AI SDK tool approvals do not enforce a spend limit, a per-operation permission check (capability token), or a signed completion receipt tied to a spend agreement (intent).
Model / input guardrails
- Next.js alone
- Yes — SDK or host checks and approvals
- With Paybond
- Yes — plus Harbor authorize at the tool boundary
Spend boundary
- Next.js alone
- No per-tool Harbor budget or capability token
- With Paybond
- Per-call and intent budgets enforced before invoke
Signed evidence
- Next.js alone
- SDK traces / logs only
- With Paybond
- Signed completion digests bound to the intent
Intent binding
- Next.js alone
- No Harbor intent or settlement receipt
- With Paybond
- Capability token + intentId from authenticated bind
Paid tool deny / HITL
- Next.js alone
- Host or SDK approvals only
- With Paybond
- spend verify, deny, or HITL hold before side effects
| Capability | Next.js alone | With Paybond |
|---|---|---|
| Model / input guardrails | Yes — SDK or host checks and approvals | Yes — plus Harbor authorize at the tool boundary |
| Spend boundary | No per-tool Harbor budget or capability token | Per-call and intent budgets enforced before invoke |
| Signed evidence | SDK traces / logs only | Signed completion digests bound to the intent |
| Intent binding | No Harbor intent or settlement receipt | Capability token + intentId from authenticated bind |
| Paid tool deny / HITL | Host or SDK approvals only | spend verify, deny, or HITL hold before side effects |
How it works
App Router flow
POST /api/agent
Client sends prompt + session bind credentials
instrumented.bind
Harbor session scoped to this request
- intentId + capabilityToken
- Lazy context store
- Never from raw client tenant ids
streamText + toolApproval
Vercel AI runs guarded checkout tools
Evidence
Wrapped execute finalizes spend + auto-evidence
Next.js Route Handlers bind Paybond per request, pass guarded tools to Vercel AI, and keep capability tokens server-side.
3-minute quickstart
Smoke the vercel-ai checkout sandbox contract — no Next.js server required for this check:
terminal
paybond login
paybond agent demo vercel-ai smoke \
--operation commerce.checkout \
--requested-spend-cents 1000 \
--evidence-preset cost_and_completion \
--format tableScaffold the shopping preset:
terminal
paybond init --solution shopping --max-spend-usd 100 --framework vercel-ai --non-interactive
paybond policy init --preset shopping --out paybond.policy.yamlWhen the smoke succeeds you should see:
- ✓ Spend approved
- ✓ Checkout completed
- ✓ Evidence verified (
cost_and_completion)
What success looks like
What success looks like
Authorized checkout · illustrative
- Operation
- commerce.checkout
- Status
- Approved
- Requested
- $10.00
- Evidence
- Verified
- Preset
- cost_and_completion
Wire middleware
Lazy context pattern (recommended)
Load policy once at module scope; resolve the active bind per request with AsyncLocalStorage.
paybond-session.ts
// lib/paybond.ts
import { Paybond } from "@paybond/kit";
import { AsyncLocalStorage } from "node:async_hooks";
const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });
const requestStore = new AsyncLocalStorage<{ runtime: Awaited<ReturnType<typeof instrumented.bind>> }>();
const instrumented = await paybond.instrument({
policy: "./paybond.policy.yaml",
framework: "vercel-ai",
tools: {
checkout: checkoutToolDef,
searchProducts: searchProductsToolDef,
},
context: () => requestStore.getStore()!.runtime,
});
export { paybond, instrumented, requestStore };Route handler
paybond-session.ts
// app/api/agent/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { instrumented, requestStore } from "@/lib/paybond";
export async function POST(req: Request) {
const { prompt, intentId, capabilityToken } = await req.json();
const runtime = await instrumented.bind({ intentId, capabilityToken });
const { agentTools: tools, toolApproval } = runtime;
return requestStore.run({ runtime }, async () => {
const result = streamText({
model: openai("gpt-4.1"),
tools,
toolApproval,
prompt,
});
return result.toDataStreamResponse();
});
}Sandbox rehearsal: use paybond.agent({ policy: "shopping", framework: "vercel-ai", tools }) to skip manual bind during local dev.
Shopping preset defaults
| Tool | Side effecting | Cap |
|---|---|---|
commerce.checkout | Yes | $100 per call / $100 intent budget |
search.products | No | — |
See Let agents buy groceries for the full multi-tool shopping walkthrough.
Production checklist
Production checklist
- Scaffold shopping + vercel-ai and validate tools
- Keep Paybond.open() / instrument() on the server only
- Fund intents server-side — never from unauthenticated client ids
- Bind per request with lazy context / AsyncLocalStorage
- Smoke with paybond agent demo vercel-ai smoke before ship
Works with
Works with
- Vercel
- OpenAI
- Stripe
- MCP
Ready to test?
Related guides
- Vercel AI SDK spend controls — toolApproval primitives
- Let agents buy groceries — shopping preset
- Express and Fastify agent routes — non-Next.js HTTP servers
Developer reference: /docs/kit/vercel-ai.