Skip to content

AI app abuse protection for Next.js

Automated clients – scrapers, data harvesters, and script-based attackers - treat AI features as free compute. Without bot protection, every request from a bot reaches your AI provider and inflates your costs.

Arcjet bot detection runs inside your application, before the AI call, so denied requests never reach your provider. It classifies known bots, verifies good bots, and detects emerging threats in real time so you can control access per route with full application context (identity, subscription level, session state).

In this example we use the Vercel AI SDK to create a simple AI chat endpoint with Next.js, and Arcjet to protect it from abuse. The same principles can be applied to any AI application, including those built with other frameworks.

We assume you already have a Next.js app set up.

Install the dependencies:

Terminal window
# Export your Arcjet API key from https://console.arcjet.com
export ARCJET_KEY="ajkey_..."
npm install @arcjet/next ai @ai-sdk/openai

Create an AI chat endpoint:

/app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import arcjet, { detectBot, shield } from "@arcjet/next";
import type { UIMessage } from "ai";
import { convertToModelMessages, streamText } from "ai";
const aj = arcjet({
key: process.env.ARCJET_KEY!, // Get your site key from https://console.arcjet.com
rules: [
// Shield protects against common web attacks such as SQL injection
shield({ mode: "LIVE" }),
// Block all automated clients – bots inflate AI costs
detectBot({
mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
allow: [], // Block all bots. See https://arcjet.com/bot-list
}),
],
});
export async function POST(req: Request) {
const decision = await aj.protect(req);
if (decision.isDenied()) {
if (decision.reason.isBot()) {
return new Response("Automated clients are not permitted", {
status: 403,
});
}
return new Response("Forbidden", { status: 403 });
}
// Arcjet approved - now read the body and call your AI provider
const { messages }: { messages: UIMessage[] } = await req.json();
const result = await streamText({
model: openai("gpt-4o"),
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}

And hook it up to a chat UI:

/app/page.tsx
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export default function Chat() {
const [input, setInput] = useState("");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const { messages, sendMessage } = useChat({
onError: async (e) => setErrorMessage(e.message),
});
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map((message) => (
<div key={message.id} className="whitespace-pre-wrap">
{message.role === "user" ? "User: " : "AI: "}
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
))}
{errorMessage && (
<div className="text-red-500 text-sm mb-4">{errorMessage}</div>
)}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput("");
setErrorMessage(null);
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={(e) => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}

Then run the server:

Terminal window
npm run dev

Requests appear in your Arcjet dashboard in real time.

allow: [] blocks all automated clients. This is the recommended default for AI routes where no bot traffic is legitimate.

To allow specific categories or named bots from our list of known bots, add them to the allow list:

detectBot({
mode: "LIVE",
allow: [
"CURL", // Allow curl-based scripts
"CATEGORY:MONITOR", // Uptime monitoring services
"CATEGORY:PREVIEW", // Link previewers (Slack, Discord, etc.)
],
})

Bot protection controls who can call your AI features. To also control how much each user can consume, combine it with AI budget control:

rules: [
detectBot({ mode: "LIVE", allow: [] }),
tokenBucket({ // Token bucket rate limiting is best for AI budget control
mode: "LIVE",
characteristics: ["userId"], // Link limits to users
refillRate: 2_000, // Refill 2000 tokens per interval
interval: "1h", // Refill interval
capacity: 5_000, // Max tokens
}),
]

The get started guide shows the combined pattern.

Bot protection controls who can call your AI features, but legitimate users can still submit malicious prompts. Combine bot detection with prompt injection detection to also block jailbreaks, role-play escapes, and instruction overrides before they reach your AI model.