Add Forge
Create Forge once and mount its middleware beforepaymentMiddleware and before your /openapi.json route:
import { createForge } from "@forgeintel/sdk";
const forge = createForge({
apiKey: process.env.FORGE_API_KEY,
backendUrl: process.env.FORGE_BACKEND_URL,
publicUrl: process.env.PUBLIC_URL,
});
app.use(forge.middleware());
app.use(paymentMiddleware(routes, resourceServer));
What changes on the wire
| Response | Change |
|---|---|
| 402 | PAYMENT-REQUIRED: sentence appended to resource.description, forge-feedback added to extensions |
| Paid 2xx JSON object | feedback_id, feedback_url, rate_this_call in the body; Forge-Feedback-Id header |
| Paid 2xx, anything else | Forge-Feedback-Id header only |
GET /openapi.json | Served enriched (OpenAPI) |
res.json, res.send, express.static or res.sendFile. Forge works with all of them.
Complete example
A text-statistics API on Base, paid through the CDP facilitator. Forge lines are marked(forge). From examples/express-x402 in the Forge SDK repository.
server.ts
// An existing @x402/express service on Base with the CDP facilitator, wrapped by the Forge feedback SDK.
// The only Forge-specific lines are marked with (forge).
import express from "express";
import { facilitator } from "@coinbase/x402";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { createForge } from "@forgeintel/sdk"; // (forge)
const required = (name: string) => {
const value = process.env[name];
if (!value) throw new Error(`Set ${name}`);
return value;
};
const NETWORK = (process.env.NETWORK ?? "eip155:8453") as `${string}:${string}`; // Base mainnet; eip155:84532 for Sepolia
const PAY_TO = required("PAY_TO");
// (forge) PUBLIC_URL is this service's public origin; it's used in the rating URLs agents see.
const forge = createForge({
apiKey: required("FORGE_API_KEY"),
backendUrl: required("FORGE_BACKEND_URL"),
publicUrl: required("PUBLIC_URL"),
});
// CDP facilitator; needs CDP_API_KEY_ID and CDP_API_KEY_SECRET in the environment for verify/settle.
const resourceServer = new x402ResourceServer(new HTTPFacilitatorClient(facilitator)).register(
NETWORK,
new ExactEvmScheme(),
);
const app = express();
app.use(forge.middleware()); // (forge) before payment middleware
app.use(
paymentMiddleware(
{
"POST /api/text-stats": {
accepts: { scheme: "exact", price: "$0.001", network: NETWORK, payTo: PAY_TO },
description: "Count characters, words and sentences in a text and estimate reading time.",
mimeType: "application/json",
},
},
resourceServer,
),
);
app.get("/health", (_req, res) => void res.json({ status: "ok" }));
app.post("/api/text-stats", express.json({ limit: "100kb" }), (req, res) => {
const text = typeof req.body?.text === "string" ? req.body.text : "";
if (!text) return void res.status(400).json({ error: "text is required" });
const words = text.split(/\s+/).filter(Boolean).length;
res.json({
characters: text.length,
words,
sentences: text.split(/[.!?]+/).filter((s: string) => s.trim()).length,
reading_time_seconds: Math.ceil((words / 238) * 60),
});
});
const port = Number(process.env.PORT ?? 4021);
const server = app.listen(port, () => console.log(`example merchant on http://localhost:${port}`));
process.on("SIGTERM", () => server.close(() => void forge.shutdown())); // (forge) flush pending events
export PAY_TO=0xYourAddress PUBLIC_URL=http://localhost:4021
export FORGE_API_KEY=forge_… FORGE_BACKEND_URL=https://your-forge-api/api/sdk/v2
export CDP_API_KEY_ID=… CDP_API_KEY_SECRET=…
npm start
The weather API in
examples/weather is a larger Express example: two paid routes, Bazaar discovery, a static OpenAPI document and input validation before payment. It’s the service we use to test agents.