Skip to main content
The router speaks Web-standard Request/Response and dispatches through an embedded Hono app, so the same route definitions run anywhere. Three hosting modes: One route file serves every registered route:
app/api/[[...route]]/route.ts
import '@/lib/routes'; // side-effect import: registers all routes
import { router } from '@/lib/router';
import { nextHandlers } from '@agentcash/router/next';

export const { GET, POST, PUT, PATCH, DELETE } = nextHandlers(router);
The catch-all serves /{basePath}/* (default /api/*), including /api/openapi.json and /api/llms.txt. Unmatched paths get a JSON 404 with rediscovery links automatically.

Next.js per-file

One route file per endpoint also works:
app/api/search/route.ts
import { router } from '@/lib/router';

export const POST = router
  .route('search')
  .paid('0.01')
  .body(schema)
  .handler(handler);
Three per-file specifics the catch-all makes obsolete:
  • Verb declaration: the exported const name (GET/POST) controls which verb Next.js serves, while .method() controls the verb advertised in discovery. The router defaults to POST (or GET when .query() is used), so any other GET route must declare it explicitly.
  • Discovery needs a barrel: Next.js lazy-loads route files, so your openapi.json route must import a barrel module that imports every route file, or unloaded routes won’t appear in the spec.
  • 404 fallback: mount router.notFound() in a catch-all so stale agent calls get a JSON 404 with rediscovery links instead of an HTML error page.

Hono / Bun / Node / any fetch runtime

import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import './routes';
import { router } from './router';

const app = new Hono();
app.route('/', router.hono()); // or use router.fetch directly
serve({ fetch: app.fetch, port: 3000 });
router.fetch(request) is a standard fetch handler; router.hono() exposes the internal app for mounting into a larger one. See examples/hono for a runnable server.

basePath

RouterConfig.basePath (default 'api') controls the mount and advertised prefix: routes serve at {baseUrl}/{basePath}/{path}. Pass an empty string to mount at the origin root.

Discovery endpoints

The router generates two discovery surfaces from your route definitions:
  • /openapi.json: OpenAPI 3.1 with pricing extensions. Indexed by AgentCash, x402scan, and mppscan.
  • /llms.txt: plain-text guidance for agents.
router.fetch (and the Next.js catch-all) serves both under the basePath automatically, plus the root aliases on fetch runtimes. On Next.js, add the conventional root paths with two one-line route files:
app/openapi.json/route.ts
import '@/lib/routes';
import { router } from '@/lib/router';
export const GET = router.openapi();
app/llms.txt/route.ts
import { router } from '@/lib/router';
export const GET = router.llmsTxt();
router.wellKnown() (the /.well-known/x402 listing) is deprecated. It still works for legacy x402 clients, but new integrations should rely on /openapi.json and /llms.txt. See Become discoverable for the full discovery contract.

Bazaar catalog metadata

x402 facilitators persist service metadata from settled payments into the Bazaar catalog. The router forwards it from discovery config onto each 402 challenge:
createRouterFromEnv({
  title: 'Quote API',                       // serviceName defaults to title when ≤32 ASCII chars
  serviceName: 'Quote API',                 // explicit override
  tags: ['quotes', 'wisdom'],               // ≤5 tags, each ≤32 chars
  iconUrl: 'https://example.com/icon.png',  // HTTPS, ≤2048 chars
  // ...
});
Invalid values fail at startup with structured config issues rather than being silently dropped by the facilitator.