Not every agent runs inside a framework SDK. Express and Fastify apps often expose /agent/run endpoints that orchestrate tool calls server-side. Paybond loads policy once at startup and binds per request so each session gets its own intent scope.
TypeScript only. The route samples below use Express/Fastify Node APIs. For Python HTTP hosts, use the same
paybond.instrument()/bind()contract from agent-agnostic spend controls.
Adapter reference: /docs/kit/agent-middleware.
Load once, bind per request
instrument() at startup; bind intentId + capabilityToken on each /agent route.
spend verify before execute
Guarded tools authorize operation and amount before HTTP handlers run side effects.
Auto-evidence
Wrapped execute finalizes spend and submits signed evidence after success.
TypeScript HTTP samples
Express/Fastify route examples are TypeScript; Python hosts use the same instrument()+bind() contract via agent-agnostic.
Why Paybond (not just HTTP auth)?
Route-level auth does 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
- HTTP routes alone
- Yes — SDK or host checks and approvals
- With Paybond
- Yes — plus Harbor authorize at the tool boundary
Spend boundary
- HTTP routes alone
- No per-tool Harbor budget or capability token
- With Paybond
- Per-call and intent budgets enforced before invoke
Signed evidence
- HTTP routes alone
- SDK traces / logs only
- With Paybond
- Signed completion digests bound to the intent
Intent binding
- HTTP routes alone
- No Harbor intent or settlement receipt
- With Paybond
- Capability token + intentId from authenticated bind
Paid tool deny / HITL
- HTTP routes alone
- Host or SDK approvals only
- With Paybond
- spend verify, deny, or HITL hold before side effects
| Capability | HTTP routes 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
HTTP route flow
POST /agent/tools/execute
Client sends tool call + session bind credentials
instrumented.bind
Harbor session scoped to this request
- intentId + capabilityToken
- From auth/session layer
- Never raw client tenant ids
tool.execute
Guarded handler performs the paid work
Evidence
Wrapped execute finalizes spend + auto-evidence
Express and Fastify load policy once, bind per request, then execute Paybond-wrapped tools over HTTP.
3-minute quickstart
Smoke the sandbox contract your HTTP routes will enforce:
terminal
paybond login
paybond agent sandbox smoke \
--operation paid-tool \
--requested-spend-cents 100 \
--evidence-preset cost_and_completion \
--result-body '{"status":"ok","cost_cents":100}' \
--format jsonWhen the smoke succeeds you should see:
- ✓ Spend approved
- ✓ Tool completed
- ✓ Evidence verified (
cost_and_completion)
What success looks like
What success looks like
Authorized tool call · illustrative
- Operation
- paid-tool
- Status
- Approved
- Requested
- $1.00
- Evidence
- Verified
- Preset
- cost_and_completion
Wire middleware
Shared module
Load policy once at process startup; bind per request in Express or Fastify handlers.
paybond-session.ts
// paybond-runtime.ts
import { Paybond } from "@paybond/kit";
export const paybond = await Paybond.open({
apiKey: process.env.PAYBOND_API_KEY!,
});
export const instrumented = await paybond.instrument({
policy: process.env.PAYBOND_POLICY_FILE ?? "./paybond.policy.yaml",
tools: {
"travel.book_hotel": bookHotel,
searchWeb: searchWeb,
},
});Express
paybond-session.ts
import express from "express";
import { instrumented } from "./paybond-runtime.js";
const app = express();
app.use(express.json());
app.post("/agent/tools/execute", async (req, res) => {
const { toolName, toolCallId, arguments: args, intentId, capabilityToken } = req.body;
// Resolve credentials from your session — not raw client tenant ids
const runtime = await instrumented.bind({ intentId, capabilityToken });
const tool = runtime.tools.find((t) => t.name === toolName);
if (!tool) {
res.status(404).json({ error: "tool_not_registered" });
return;
}
const result = await tool.execute({ toolName, toolCallId, arguments: args });
res.json({ result });
});
app.listen(3000);Fastify
paybond-session.ts
import Fastify from "fastify";
import { instrumented } from "./paybond-runtime.js";
const fastify = Fastify();
fastify.post("/agent/tools/execute", async (request, reply) => {
const { toolName, toolCallId, arguments: args, intentId, capabilityToken } = request.body as {
toolName: string;
toolCallId: string;
arguments: Record<string, unknown>;
intentId: string;
capabilityToken: string;
};
const runtime = await instrumented.bind({ intentId, capabilityToken });
const tool = runtime.tools.find((t) => t.name === toolName);
if (!tool) {
return reply.code(404).send({ error: "tool_not_registered" });
}
const result = await tool.execute({ toolName, toolCallId, arguments: args });
return { result };
});
await fastify.listen({ port: 3000 });Lazy context alternative
When bind credentials live on request.session:
paybond-session.ts
const instrumented = await paybond.instrument({
policy: "./paybond.policy.yaml",
tools: { /* ... */ },
context: () => requestStore.getStore()!.runtime,
});Set requestStore in middleware before tool routes run — same pattern as Next.js agent checkout.
Sandbox shortcut
For local dev without manual bind:
paybond-session.ts
const { tools, run } = await paybond.agent({
policy: "travel",
tools: { "travel.book_hotel": bookHotel },
});
// tools are pre-bound to sandboxCI validation
terminal
paybond policy validate-tools --file paybond.policy.yaml --local-only
npm run smoke # paybond agent sandbox smoke --policy-file paybond.policy.yaml ...Production checklist
Production checklist
- Load paybond.instrument() once at process startup
- Bind per request from your session/auth layer
- Validate policy in CI with paybond policy validate-tools
- Never accept tenant ids from unauthenticated client input
- Smoke with paybond agent sandbox smoke before ship
Works with
Works with
- Agent-agnostic
- Vercel
- OpenAI
- MCP
Ready to test?
Related guides
- Agent-agnostic spend controls —
{ name, execute }tools - Agent middleware — interceptor and evidence
- Protect Stripe payments from agents — payment handler example
Developer reference: /docs/kit/agent-middleware.