---
description: "Builds the /api/chat handlers for a Vercel Function: @webmcp-agents/chat's handler behind origin and per-IP guards, with a global daily budget on model calls."
---

# `createVercelChatRoute`

Builds the `/api/chat` handlers for a Vercel Function: `@webmcp-agents/chat`'s handler with chat sessions and a tool allowlist, behind origin and per-IP guards, with per-session limits and a global daily budget on model calls. Reach for it in the example apps' `api/chat.ts`; every limit is an environment variable. The package is private and not published to npm.

## Import

```ts
import { createVercelChatRoute } from "@webmcp-agents/vercel-api";
import type { VercelChatRouteOptions } from "@webmcp-agents/vercel-api";
```

## Example

The route file of an example app, with the agents the page passes to `<AgentChat>`:

```ts title="api/chat.ts"
import { defineWebAgent } from "@webmcp-agents/chat/agent";
import { createVercelChatRoute } from "@webmcp-agents/vercel-api";

const shopper = defineWebAgent({
  id: "shopper",
  instructions: ["You help people shop at Field Store."],
});

// api/chat.ts: the chat handler with sessions and a tool allowlist,
// behind origin, rate-limit and budget guards. Export all three;
// they are the same handler.
export const { GET, POST, OPTIONS } = createVercelChatRoute({
  app: "ecommerce",
  agents: [shopper],
  // Only these reach the model; the inline tools of components by id.
  tools: ["search_products", "add_to_cart", "checkout"],
});
```

## Options

`createVercelChatRoute(options)` takes a `VercelChatRouteOptions`.

| Name | Type | Default | Description |
|---|---|---|---|
| `app` | `string` | required | Namespaces the rate-limit keys per app (`<app>:minute`, `<app>:day`, `<app>:session`, `<app>:global`), so apps sharing one Redis do not share counts. |
| `env` | `Env` | `process.env` | Where every variable below is read. Also passed to the chat handler, which reads the provider's API key from it. |
| `model` | `LanguageModel \| string` | `env.WEB_AGENTS_MODEL`, else `"anthropic/claude-sonnet-5-5"` | A model instance, or a `"<provider>/<model>"` id (see [`resolveModel`](/docs/reference/chat/resolve-model)). |
| `agents` | `readonly WebAgent[]` | none | The page's agents ([`defineWebAgent`](/docs/reference/chat/define-web-agent)): the same list the page passes to `<AgentChat>`, so the server knows each agent's instructions. With agents, a request must name one (`requireAgent`). |
| `tools` | `readonly (string \| WebTool)[]` | every tool the page sends | The tools the page may offer the model: the app's `defineWebTool` tools (pinned) and the ids of tools defined inline in components. See [`createChatHandler`](/docs/reference/chat/create-chat-handler)'s `tools`. |
| `session` | `false \| ChatSessionOptions` | `{}` (on) | Chat sessions; Turnstile gates them when `TURNSTILE_SECRET_KEY` is set, and `verify` adds any other check. The secret is `WEB_AGENTS_SESSION_SECRET`. See [Sessions](/docs/reference/chat/create-chat-handler#sessions). |

### Environment variables

Numeric variables that are missing, not a number, or not greater than 0 fall back to the default.

| Name | Type | Default | Description |
|---|---|---|---|
| `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, … | `string` | none | The key of the provider in `WEB_AGENTS_MODEL` (see [`resolveModel`](/docs/reference/chat/resolve-model)). Without it `GET` reports `enabled: false` and `POST` answers 503 `chat_disabled`, naming the variable. |
| `AI_GATEWAY_API_KEY` | `string` | none | Serves any `a/b` model id through the AI Gateway when the provider's own key is missing. |
| `WEB_AGENTS_MODEL` | `string` | `"anthropic/claude-sonnet-5-5"` | The model, as `<provider>/<model>`, when `model` is not passed. |
| `WEB_AGENTS_MAX_OUTPUT_TOKENS` | `number` | `4096` | Output token cap per model step. Thinking counts against it. |
| `WEB_AGENTS_MAX_STEPS` | `number` | `6` | Tool steps per user turn. |
| `RATE_LIMIT_PER_MINUTE` | `number` | `30` | Requests per IP per minute (fails open). |
| `RATE_LIMIT_PER_DAY` | `number` | `300` | Requests per IP per day (fails open). |
| `RATE_LIMIT_PER_SESSION` | `number` | `150` | Requests per chat session per day (fails open). Past it, the page needs a new session. |
| `RATE_LIMIT_GLOBAL_DAILY` | `number` | `3000` | Model calls per day across all visitors (fails closed). |
| `TURNSTILE_SECRET_KEY` | `string` | none | Turns on Cloudflare Turnstile for new sessions (`session.turnstile`). The page sends the token with [`turnstileHeaders`](/docs/reference/chat/create-cloud-adapter#turnstile) and the widget's public site key (`VITE_TURNSTILE_SITE_KEY`, `NEXT_PUBLIC_TURNSTILE_SITE_KEY` in the examples). |
| `WEB_AGENTS_SESSION_SECRET` | `string` | random per instance | Signs the session cookies, 32+ characters, the same on every instance. Required when `VERCEL_ENV` is `production` or `preview`: without it chat stays off (`GET` says `enabled: false`, `POST` gets 503 `chat_disabled`) and the function log says so. |
| `KV_REST_API_URL`, `KV_REST_API_TOKEN` | `string` | none | Upstash Redis for the limiters; `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` also work. Without them limits are per function instance. |

## Returns

`{ GET, POST, OPTIONS }`, all the same handler: `(req: Request) => Promise<Response>`. Export them from the function file.

## Notes

- Env is read when the route is created (the function's cold start), not at import time.
- Each tool step is one request: a 6-step turn is 7. The per-minute limit must leave room for a few turns.
- The per-session limit and the budget are checked in the handler's `beforeModel` hook, after validation, so malformed requests never use them up.
- The per-IP limiters fail open (a Redis outage must not take the chat down). The global budget fails closed (an outage must not turn into an unbounded bill); the chat then answers 503 with reason `busy`.
- The step cap is counted from the conversation the browser sends, so a scripted caller can sidestep it. The budget is what bounds the bill.
- Never prefix these variables with `VITE_`: Vite ships `VITE_*` variables to the browser.
- The example apps (`examples/playground`, `examples/ecommerce`, `examples/flights`) use the package as a workspace dependency.

## See also

- [Deploy](/docs/more/deploy), where the environment variables are set
- [Guards](/docs/reference/vercel-api/guards) and [`createLimiter`](/docs/reference/vercel-api/limiter), the parts it is made of
- [`createChatHandler`](/docs/reference/chat/create-chat-handler), the handler it wraps
