# Arcjet > Arcjet is the runtime security platform that ships in your AI code. Detect prompt injection, authorize agent tool calls, redact sensitive data, and block bots and abuse. Real-time security building blocks you call inside your app, before an action happens. Arcjet is a lightweight SDK that enforces controls inline, with real identity and session context – configured by your agent with the CLI or MCP server. Arcjet's primary use case is securing the actions AI agents take in production. Agents have gone from answering questions to moving money, changing records, and shipping code, and the security or engineering leader now owns that risk. Identity and RBAC authenticate the agent but don't govern the action it's about to take, and network proxies can't see inside the workflow. Arcjet gives security and engineering teams visibility into what each agent is doing, real-time enforcement before a consequential action (prompt injection, PII, tool authorization), and an audit trail. Because it runs inside the same application code, Arcjet protects traditional entry points the same way – enterprise API protection across HTTP routes and APIs: enforce budgets, detect bots, run the Shield WAF, validate email, rate limit, and block common attacks. PII detection and redaction run locally, in-process, so inspected content stays in your environment. Arcjet protects two types of entry points: - **Request-based** – HTTP route handlers, API endpoints, middleware. Use `protect()` with any supported framework. - **Guards** – tool calls, queue consumers, agentic pipelines, and anywhere else you process untrusted input without an HTTP request. Use `guard()` to pass inputs directly and get a decision back. Use `capture()` to record that an allowed action happened (visibility only; never changes a decision). Arcjet runs server-side. Bot protection advanced client signals are an optional extra layer of defense. Pricing is based on usage, see https://arcjet.com/pricing ## Get started Set up Arcjet in two steps: (1) install a skill that gives your agent the documentation to integrate the Arcjet SDK, and (2) connect to Arcjet with the CLI to create sites, retrieve credentials, and verify decisions. Full guide: https://docs.arcjet.com/agent-get-started ### Step 1: Install the skill The Arcjet skill gives your agent the documentation to detect your framework, install the SDK, and wire up protection rules – for HTTP routes, tool calls, MCP servers, queues, and more: ```bash npx skills add arcjet/skills ``` Then describe what you want to protect. The skill handles the rest. Source: https://github.com/arcjet/skills You can also use the Arcjet plugin for Claude Code and Cursor, which bundles skills, MCP, and coding rules: https://docs.arcjet.com/arcjet-plugin ### Step 2: Connect with the CLI ```bash npm i -g @arcjet/cli arcjet auth login arcjet teams list arcjet sites list --team-id team_01abc123 arcjet sites get-key --site-id site_01abc123 ``` Full documentation: https://docs.arcjet.com/cli You can also use the MCP server to manage sites and keys. See the MCP section below for setup instructions. ## MCP Server Endpoint: `https://api.arcjet.com/mcp` Auth: OAuth (browser-based, automatic on first connection) Claude Code: ```bash claude mcp add arcjet --transport http https://api.arcjet.com/mcp ``` VS Code (Copilot) `.vscode/mcp.json`: ```json { "servers": { "arcjet": { "type": "http", "url": "https://api.arcjet.com/mcp" } } } ``` Cursor `.cursor/mcp.json`: ```json { "mcpServers": { "arcjet": { "type": "streamable-http", "url": "https://api.arcjet.com/mcp" } } } ``` Windsurf `mcp_config.json`: ```json { "mcpServers": { "arcjet": { "serverUrl": "https://api.arcjet.com/mcp" } } } ``` Full documentation: https://docs.arcjet.com/mcp-server ### Tools - **List teams** you belong to. - **List sites** within a team. - **Create new sites** within a team. - **Get site keys** (`ARCJET_KEY`) for use in your projects. - **List requests** received by a site with optional filtering. - **Get request details** including headers, rules executed, and decision info. - **Explain decisions** to understand why requests were allowed or denied. - **Get site quota** usage and limits for the current billing window. - **Analyze traffic** patterns, denial rates, top paths, top IPs, and trend vs previous period. - **Detect anomalies** by comparing current traffic to the previous period – traffic spikes, geographic shifts, new threats, suspicious IPs. - **Investigate IPs** with geo location, ASN, threat intelligence, and per-site request activity. - **Get dry-run impact** – see what would happen if dry-run rules were promoted to live (blocked requests, affected IPs, false-positive estimate). - **Get a security briefing** – comprehensive daily overview combining traffic, threats, anomalies, dry-run readiness, quota, and recommendations. - **List remote rules** configured for a site. - **Create remote rules** with DRY_RUN or LIVE mode – no code changes needed. - **Update remote rules** by replacing the full rule configuration. - **Delete remote rules** to immediately stop evaluation. - **Promote remote rules** from DRY_RUN to LIVE after verification. ### Typical workflow **Setup:** list-teams → list-sites (or create-site) → get-site-key → set `ARCJET_KEY` in your environment. **Investigate:** list-requests → get-request-details or explain-decision for a specific request. **Analyze and monitor:** analyze-traffic for a Console-level overview → get-anomalies to detect unusual patterns → investigate-ip for deep-dive on suspicious IPs. **Daily security briefing:** get-security-briefing for a comprehensive overview (traffic, threats, anomalies, dry-run readiness, quota, and recommendations) in a single call. **Manage remote rules:** list-rules → create-rule (DRY_RUN) → get-dry-run-impact to check impact → promote-rule to LIVE. **Update/delete rules:** list-rules → update-rule (full replacement) or delete-rule. ### Remote rules Remote rules are managed via the MCP server or Console – no code changes or redeployment needed. They apply globally to all requests for a site. Supported types: rate_limit, bot, shield, filter. Rules needing request body content (email, sensitive_info, prompt_injection) require the SDK. **Responding to an active attack:** The most common use case is blocking suspicious traffic immediately. For example, to block a specific country, VPN, or IP range during an attack: 1. `list-requests` – investigate traffic and identify patterns. 2. `create-rule` – add a filter rule in DRY_RUN. Examples: `ip.src.country == "XX"` (ISO 3166-1 alpha-2 code, such as `US`, `CN`, or `RU`), `ip.src.vpn`, `ip.src in { 1.2.3.0/24 }`. 3. `list-requests` – confirm the rule matches attack traffic, not legitimate users. 4. `promote-rule` – switch to LIVE to start blocking. 5. `delete-rule` – remove the block once the attack subsides. ## Use cases ### Request-based (HTTP route handlers) - Protecting AI endpoints from cost abuse? → tokenBucket + detectBot (AI Endpoint Abuse Protection) - Preventing data leaks from AI features? → sensitiveInfo (AI Data Loss Prevention) - Blocking prompt injection? → detectPromptInjection - Responding to an active attack? → remote rules via MCP or CLI ### Guards (tool calls, agentic pipelines, queues) - Securing MCP server tool handlers? → guard() with rate limiting + prompt injection detection - Rate limiting per-user tool calls? → guard() with tokenBucket - Scanning tool inputs/outputs for PII? → guard() with `localDetectSensitiveInfo()` (JS) or `LocalDetectSensitiveInfo()` (Python). The HTTP `sensitiveInfo` / `detect_sensitive_info` rule is not exported by the Guard SDK. Pass `allow` or `deny`, and a `backend` for any entity type beyond the default four. - Detecting prompt injection in agent tool results? → guard() with detectPromptInjection - Recording that an allowed action happened? → capture() / Capture (batched, best-effort) - Moderating untrusted text at a tool boundary? → moderateContent() (JS), ModerateContent() (Python), or GuardModerateContent (Go). - Using an agent framework that owns the tool loop? → the framework adapter, not a raw guard() call. See "Agent framework adapters". Add guard protection with the skill: ```bash npx skills add arcjet/skills ``` Source: https://github.com/arcjet/skills JS/TS SDK: https://github.com/arcjet/arcjet-js Python SDK: https://github.com/arcjet/arcjet-py Go SDK: https://github.com/arcjet/arcjet-go ## Quick start – choose your framework Each link below directs to the SDK-scoped quick start guide for that framework. Go is the exception: `/sdk/go/get-started/` is not published yet, so it still uses `?f=go`. - [Astro quick start](https://docs.arcjet.com/sdk/astro/get-started/) - [Bun quick start](https://docs.arcjet.com/sdk/bun/get-started/) - [Deno quick start](https://docs.arcjet.com/sdk/deno/get-started/) - [Fastify quick start](https://docs.arcjet.com/sdk/fastify/get-started/) - [Go quick start](https://docs.arcjet.com/get-started?f=go) (see also the [Go SDK reference](https://docs.arcjet.com/reference/go)) - [NestJS quick start](https://docs.arcjet.com/sdk/nest/get-started/) - [Next.js quick start](https://docs.arcjet.com/sdk/next/get-started/) - [Node.js quick start](https://docs.arcjet.com/sdk/node/get-started/) - [Node.js + Express quick start](https://docs.arcjet.com/sdk/node/plus/express/get-started/) - [Node.js + Hono quick start](https://docs.arcjet.com/sdk/node/plus/hono/get-started/) - [Nuxt quick start](https://docs.arcjet.com/sdk/nuxt/get-started/) - [Python + FastAPI quick start](https://docs.arcjet.com/sdk/python/plus/fastapi/get-started/) - [Python + Flask quick start](https://docs.arcjet.com/sdk/python/plus/flask/get-started/) - [React Router quick start](https://docs.arcjet.com/sdk/react-router/get-started/) - [Remix quick start](https://docs.arcjet.com/sdk/remix/get-started/) - [SvelteKit quick start](https://docs.arcjet.com/sdk/sveltekit/get-started/) - [Bun + Hono quick start](https://docs.arcjet.com/sdk/bun/plus/hono/get-started/) Agent guard quick starts: - [Claude Agent SDK](https://docs.arcjet.com/sdk/claude-agent-sdk/get-started/) - [Claude Managed Agents](https://docs.arcjet.com/sdk/claude-managed-agents/get-started/) - [CrewAI](https://docs.arcjet.com/sdk/crewai/get-started/) - [Genkit](https://docs.arcjet.com/sdk/genkit/get-started/) - [Google ADK](https://docs.arcjet.com/sdk/google-adk/get-started/) - [LangChain](https://docs.arcjet.com/sdk/langchain/get-started/) - [LangGraph](https://docs.arcjet.com/sdk/langgraph/get-started/) - [Mastra](https://docs.arcjet.com/sdk/mastra/get-started/) - [OpenAI Agents](https://docs.arcjet.com/sdk/openai-agents/get-started/) - [Strands Agents](https://docs.arcjet.com/sdk/strands-agents/get-started/) - [TanStack AI](https://docs.arcjet.com/sdk/tanstack-ai/get-started/) - [Vercel AI SDK](https://docs.arcjet.com/sdk/vercel-ai/get-started/) - [Vercel Eve](https://docs.arcjet.com/sdk/vercel-eve/get-started/) Full docs: https://docs.arcjet.com ## SDK packages | Framework | Package | Install | | -------------- | ---------------------- | -------------------------------------- | | Next.js | `@arcjet/next` | `npm i @arcjet/next` | | Node.js | `@arcjet/node` | `npm i @arcjet/node` | | Express | `@arcjet/node` | `npm i @arcjet/node` | | Hono (Node.js) | `@arcjet/node` | `npm i @arcjet/node @hono/node-server` | | Bun | `@arcjet/bun` | `bun add @arcjet/bun` | | Bun + Hono | `@arcjet/bun` | `bun add @arcjet/bun hono` | | Deno | `@arcjet/deno` | `deno add npm:@arcjet/deno` | | Fastify | `@arcjet/fastify` | `npm i @arcjet/fastify` | | NestJS | `@arcjet/nest` | `npm i @arcjet/nest` | | Nuxt | `@arcjet/nuxt` | `npx nuxt module add @arcjet/nuxt` | | Remix | `@arcjet/remix` | `npm i @arcjet/remix` | | React Router | `@arcjet/react-router` | `npm i @arcjet/react-router` | | SvelteKit | `@arcjet/sveltekit` | `npm i @arcjet/sveltekit` | | Astro | `@arcjet/astro` | `npx astro add @arcjet/astro` | | Python FastAPI | `arcjet` | `pip install arcjet` | | Python Flask | `arcjet` | `pip install arcjet flask` | | Go | `github.com/arcjet/arcjet-go` | `go get github.com/arcjet/arcjet-go@latest` | ## Go SDK The Go SDK is pre-release. Version 0.1.0 requires Go 1.25 or later and supports `net/http` request protection plus Guard protection for non-HTTP operations. Create clients once at package scope and reuse them. Full reference: https://docs.arcjet.com/reference/go ### Go HTTP request protection ```go var aj = must(arcjet.NewClient(arcjet.Config{ Key: os.Getenv("ARCJET_KEY"), Rules: []arcjet.Rule{ arcjet.Shield(arcjet.ShieldOptions{Mode: arcjet.ModeLive}), arcjet.DetectBot(arcjet.BotOptions{ Mode: arcjet.ModeLive, Allow: []string{}, }), arcjet.TokenBucket(arcjet.TokenBucketOptions{ Mode: arcjet.ModeLive, Characteristics: []string{"userId"}, RefillRate: 10, Interval: time.Minute, Capacity: 10, }), }, })) func handler(w http.ResponseWriter, r *http.Request) { decision, err := aj.Protect( r.Context(), r, arcjet.WithCharacteristics(map[string]string{"userId": "user_123"}), arcjet.WithRequested(1), ) if err != nil { // Fail-open: ERROR decision plus err. Log it and continue. log.Printf("arcjet: %v", err) } else if decision.IsDenied() { status := http.StatusForbidden if decision.Reason.IsRateLimit() { status = http.StatusTooManyRequests } http.Error(w, "denied", status) return } } func must[T any](value T, err error) T { if err != nil { panic(err) } return value } ``` Call `Protect(r.Context(), r, ...)` once inside each handler. Use `WithCharacteristics`, `WithRequested`, `WithDetectPromptInjectionMessage`, `WithSensitiveInfoValue`, and `WithCorrelationId` for dynamic inputs. On a transport failure, `Protect` returns an `ERROR` conclusion `Decision` together with `err`. `IsAllowed()` and `IsErrored()` are both true; `IsDenied()` is false. If the client or request is nil, `Protect` returns the zero `Decision`. Log `err` and deny only when `IsDenied()` is true. Use `IsErrored()` to distinguish a real allow from a fail-open error. ### Go Guard protection ```go var guard = must(arcjet.NewGuardClient(arcjet.GuardConfig{ Key: os.Getenv("ARCJET_KEY"), })) var promptScan = must(arcjet.GuardPromptInjection( arcjet.GuardPromptInjectionOptions{Mode: arcjet.ModeLive}, // required )) decision, err := guard.Guard(ctx, arcjet.GuardRequest{ Label: "tools.summarize", CorrelationId: "trace_123", Metadata: arcjet.Metadata{ "user": map[string]any{"id": userID}, }, Rules: []arcjet.GuardRuleInput{ promptScan.Text(prompt), }, }) if err != nil { return err } if decision.IsDenied() { return fmt.Errorf("blocked: %s", decision.Reason) } if decision.HasFailedOpen() { log.Printf("guard failed open: %+v", decision.ErrorResults()) } ``` Guard also supports rate limiting, sensitive information detection, custom local rules, and content moderation (`GuardModerateContent`). Use `Capture` to record what happened after a Guard call. Labels and buckets must be lowercase slugs containing letters, digits, dashes, or dots. Standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` variables configure outbound calls. Every Guard rule constructor requires `Mode` (`ModeLive` or `ModeDryRun`). An empty `Mode` returns `ErrInvalidMode`. HTTP `Protect` rules default an empty `Mode` to `ModeDryRun`. JavaScript and Python Guard rules default to `LIVE`; Go returns a constructor error instead of defaulting to `LIVE`. ## Common setup for all frameworks ### 1. Set your key [Create an Arcjet account](https://console.arcjet.com) then follow the instructions to add a site and get a key. Store the key securely using environment variables provided by your hosting platform to avoid exposing it in source control. Add these to your `.env.local` (Next.js), `.env` file, or environment: ```ini ARCJET_KEY=ajkey_yourkey ARCJET_ENV=development ``` `ARCJET_ENV=development` is required during local development so Arcjet can correctly identify the environment. In production, set `ARCJET_ENV=production` or omit it (defaults to production when not set). ## Next.js example ### Install ```shell npm i @arcjet/next ``` ### Configure Create a new API route at `/app/api/arcjet/route.ts`: ```ts import { openai } from "@ai-sdk/openai"; import arcjet, { detectBot, detectPromptInjection, sensitiveInfo, shield, tokenBucket, } from "@arcjet/next"; import type { UIMessage } from "ai"; import { convertToModelMessages, isTextUIPart, streamText } from "ai"; const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site key from https://console.arcjet.com // Track budgets per user – replace "userId" with any stable identifier characteristics: ["userId"], 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 }), // Enforce budgets to control AI costs. Adjust rates and limits as needed. tokenBucket({ mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only refillRate: 2_000, // Refill 2,000 tokens per hour interval: "1h", capacity: 5_000, // Maximum 5,000 tokens in the bucket }), // Block messages containing sensitive information to prevent data leaks sensitiveInfo({ mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only // Block PII types that should never appear in AI prompts. // Remove types your app legitimately handles (for example, EMAIL for a support bot). deny: ["CREDIT_CARD_NUMBER", "EMAIL"], }), // Detect prompt injection attacks before they reach your AI model detectPromptInjection({ mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only }), ], }); export async function POST(req: Request) { // Replace with your session/auth lookup to get a stable user ID const userId = "user-123"; const { messages }: { messages: UIMessage[] } = await req.json(); const modelMessages = await convertToModelMessages(messages); // Estimate token cost: ~1 token per 4 characters of text (rough heuristic). // For accurate counts use https://www.npmjs.com/package/tiktoken const totalChars = modelMessages.reduce((sum, m) => { const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content); return sum + content.length; }, 0); const estimate = Math.ceil(totalChars / 4); // Check the most recent user message for sensitive information and prompt injection. // Pass the full conversation if you want to scan all messages. const lastMessage: string = (messages.at(-1)?.parts ?? []) .filter(isTextUIPart) .map((p) => p.text) .join(" "); // Check with Arcjet before calling the AI provider const decision = await aj.protect(req, { userId, requested: estimate, sensitiveInfoValue: lastMessage, detectPromptInjectionMessage: lastMessage, }); if (decision.isDenied()) { if (decision.reason.isBot()) { return new Response("Automated clients are not permitted", { status: 403, }); } else if (decision.reason.isRateLimit()) { return new Response("AI usage limit exceeded", { status: 429 }); } else if (decision.reason.isSensitiveInfo()) { return new Response("Sensitive information detected", { status: 400 }); } else if (decision.reason.isPromptInjection()) { return new Response( "Prompt injection detected – rephrase your message", { status: 400 }, ); } else { return new Response("Forbidden", { status: 403 }); } } const result = await streamText({ model: openai("gpt-4o"), messages: modelMessages, }); return result.toUIMessageStreamResponse(); } ``` The `requested` option specifies how many tokens this request consumes from the rate limit bucket. The example estimates cost at ~1 token per 4 characters of text. Adjust this value based on your AI provider's billing model or use a tokenizer like `tiktoken` for accurate counts. ## Node.js + Express example ### Install ```shell npm i @arcjet/node @arcjet/inspect express ``` ### Configure ```js // index.js import arcjet, { shield, detectBot, tokenBucket } from "@arcjet/node"; import { isSpoofedBot } from "@arcjet/inspect"; import express from "express"; const app = express(); const port = 3000; const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); app.get("/", async (req, res) => { const decision = await aj.protect(req, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { res.writeHead(429, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Too Many Requests" })); } else if (decision.reason.isBot()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "No bots allowed" })); } else { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); } } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello World" })); } }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); }); ``` ### Start ```shell node --env-file .env index.js ``` ## SvelteKit example ### Install ```shell npm i @arcjet/sveltekit @arcjet/inspect ``` ### Configure Create a new route at `/src/routes/api/arcjet/+server.ts`: ```ts import { env } from "$env/dynamic/private"; import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/sveltekit"; import { isSpoofedBot } from "@arcjet/inspect"; import { error, json, type RequestEvent } from "@sveltejs/kit"; const aj = arcjet({ key: env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); export async function GET(event: RequestEvent) { const decision = await aj.protect(event, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return error(429, "Too Many Requests"); } else if (decision.reason.isBot()) { return error(403, "No Bots Allowed"); } else { return error(403, "Forbidden"); } } return json({ message: "Hello World" }); } ``` Note: SvelteKit passes the `event` object (not `req`) to `protect()`. ### Start ```shell npm run dev ``` ## Bun example ### Install ```shell bun add @arcjet/bun @arcjet/inspect ``` ### Configure ```ts // index.ts import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/bun"; import { isSpoofedBot } from "@arcjet/inspect"; import { env } from "bun"; const aj = arcjet({ key: env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); export default { port: 3000, fetch: aj.handler(async (req) => { const decision = await aj.protect(req, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return new Response("Too many requests", { status: 429 }); } else if (decision.reason.isBot()) { return new Response("No bots allowed", { status: 403 }); } else { return new Response("Forbidden", { status: 403 }); } } return new Response("Hello world"); }), }; ``` Note: Bun uses `aj.handler()` to wrap the fetch handler, and `env` from `"bun"` for environment variables. ### Start ```shell bun run index.ts ``` ## Deno example ### Install ```shell deno add npm:@arcjet/deno npm:@arcjet/inspect ``` ### Configure ```ts // index.ts import "jsr:@std/dotenv/load"; import arcjet, { detectBot, shield, tokenBucket } from "npm:@arcjet/deno"; import { isSpoofedBot } from "@arcjet/inspect"; const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); Deno.serve( { port: 3000 }, aj.handler(async (req) => { const decision = await aj.protect(req, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return new Response("Too many requests", { status: 429 }); } else if (decision.reason.isBot()) { return new Response("No bots allowed", { status: 403 }); } else { return new Response("Forbidden", { status: 403 }); } } return new Response("Hello world"); }), ); ``` Note: Deno uses `aj.handler()` to wrap the fetch handler, `Deno.env.get()` for environment variables, and `npm:` prefix for imports. ### Start ```shell deno run --allow-net --allow-env index.ts ``` ## Fastify example ### Install ```shell npm i @arcjet/fastify ``` ### Configure ```ts // server.ts import Fastify from "fastify"; import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/fastify"; const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); const fastify = Fastify({ logger: true }); fastify.get("/", async (request, reply) => { const decision = await aj.protect(request, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return reply.status(429).send({ message: "Too many requests" }); } if (decision.reason.isBot()) { return reply.status(403).send({ message: "No bots allowed" }); } return reply.status(403).send({ message: "Forbidden" }); } return reply.status(200).send({ message: "Hello world" }); }); await fastify.listen({ port: 3000 }); ``` Note: Fastify passes the `request` object (Fastify's request, not Node.js IncomingMessage) to `protect()`. ### Start ```shell npx tsx server.ts ``` ## NestJS example ### Install ```shell npm i @arcjet/nest ``` ### Configure Update `src/main.ts`: ```ts import { ArcjetGuard, ArcjetModule, detectBot, fixedWindow, shield, } from "@arcjet/nest"; import { Module } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; import { APP_GUARD, NestFactory } from "@nestjs/core"; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, }), ArcjetModule.forRoot({ isGlobal: true, key: process.env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), fixedWindow({ mode: "LIVE", window: "60s", max: 100, }), ], }), ], controllers: [], providers: [ { provide: APP_GUARD, useClass: ArcjetGuard, }, ], }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap(); ``` Note: NestJS uses `ArcjetModule.forRoot()` for configuration and `ArcjetGuard` as a global guard. For per-route protection, implement custom guards instead. ### Start ```shell npm run start:dev ``` ## Nuxt example ### Install ```shell npx nuxt module add @arcjet/nuxt ``` This automatically installs and configures the Arcjet Nuxt integration. ### Configure Create a server route at `server/api/protected.get.ts`: ```ts import arcjetNuxt, { detectBot, shield, tokenBucket } from "#arcjet"; const arcjet = arcjetNuxt({ rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); export default defineEventHandler(async (event) => { const decision = await arcjet.protect(event, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { throw createError({ statusCode: 429, statusMessage: "Too Many Requests", }); } if (decision.reason.isBot()) { throw createError({ statusCode: 403, statusMessage: "No bots allowed", }); } throw createError({ statusCode: 403, statusMessage: "Forbidden", }); } return { message: "Hello world" }; }); ``` Note: Nuxt imports from the `#arcjet` virtual module (not a package name). The ARCJET_KEY is set in your `nuxt.config.ts` via the module options. Nuxt passes the `event` object to `protect()`. ### Start ```shell npm run dev ``` ## Remix example ### Install ```shell npm i @arcjet/remix @arcjet/inspect ``` ### Configure Create a route at `app/routes/arcjet.tsx`: ```tsx import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/remix"; import type { LoaderFunctionArgs } from "@remix-run/node"; const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); export async function loader(args: LoaderFunctionArgs) { const decision = await aj.protect(args, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { throw new Response("Too many requests", { status: 429 }); } else if (decision.reason.isBot()) { throw new Response("Bots forbidden", { status: 403 }); } else { throw new Response("Forbidden", { status: 403 }); } } return null; } export default function Index() { return

Hello world

; } ``` Note: Remix passes the `args` (LoaderFunctionArgs or ActionFunctionArgs) to `protect()`. ### Start ```shell npm run dev ``` ## React Router example ### Install ```shell npm i @arcjet/react-router @arcjet/inspect ``` ### Configure Create a route at `app/routes/home.tsx`: ```tsx import arcjetReactRouter, { detectBot, shield, tokenBucket, } from "@arcjet/react-router"; import type { Route } from "../routes/+types/home"; const arcjet = arcjetReactRouter({ key: process.env.ARCJET_KEY!, rules: [ detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), shield({ mode: "LIVE" }), tokenBucket({ mode: "LIVE", capacity: 10, interval: 10, refillRate: 5, }), ], }); export async function loader(args: Route.LoaderArgs) { const decision = await arcjet.protect(args, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { throw new Response("Too many requests", { status: 429 }); } else if (decision.reason.isBot()) { throw new Response("Bots forbidden", { status: 403 }); } else { throw new Response("Forbidden", { status: 403 }); } } return undefined; } export default function Home() { return

Hello world

; } ``` Note: React Router passes loader/action `args` to `protect()`. ### Start ```shell npm run dev ``` ## Astro example ### Install ```shell npx astro add @arcjet/astro ``` ### Configure Update `astro.config.mjs`: ```js import { defineConfig } from "astro/config"; import node from "@astrojs/node"; import arcjet, { shield, detectBot, tokenBucket } from "@arcjet/astro"; export default defineConfig({ adapter: node({ mode: "standalone" }), integrations: [ arcjet({ rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: [ "CATEGORY:SEARCH_ENGINE", ], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }), ], }); ``` Create an API route at `src/pages/api.json.ts`: ```ts export const prerender = false; import type { APIRoute } from "astro"; import aj from "arcjet:client"; export const GET: APIRoute = async ({ request }) => { const decision = await aj.protect(request, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return Response.json({ error: "Too Many Requests" }, { status: 429 }); } else if (decision.reason.isBot()) { return Response.json({ error: "No bots allowed" }, { status: 403 }); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } } return Response.json({ message: "Hello world" }); }; ``` Note: Astro imports the Arcjet client from the `arcjet:client` virtual module. The ARCJET_KEY is set in your environment variables. The Astro adapter must be configured for server-side rendering. ### Start ```shell npm run dev ``` ## Node.js + Hono example ### Install ```shell npm i @arcjet/node @arcjet/inspect @hono/node-server hono ``` ### Configure ```ts // index.ts import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/node"; import { serve, type HttpBindings } from "@hono/node-server"; import { Hono } from "hono"; const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: ["CATEGORY:SEARCH_ENGINE"], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); const app = new Hono<{ Bindings: HttpBindings }>(); app.get("/", async (c) => { const decision = await aj.protect(c.env.incoming, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return c.json({ error: "Too Many Requests" }, 429); } else if (decision.reason.isBot()) { return c.json({ error: "No Bots Allowed" }, 403); } else { return c.json({ error: "Forbidden" }, 403); } } return c.json({ message: "Hello Hono!" }); }); serve({ fetch: app.fetch, port: 3000 }); ``` Note: With Hono on Node.js, pass `c.env.incoming` (the Node.js IncomingMessage) to `protect()`, not `c.req`. ## Bun + Hono example ### Install ```shell bun add @arcjet/bun @arcjet/inspect hono ``` ### Configure ```ts // index.ts import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/bun"; import { Hono } from "hono"; import { env } from "bun"; const aj = arcjet({ key: env.ARCJET_KEY!, rules: [ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", allow: ["CATEGORY:SEARCH_ENGINE"], }), tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10, }), ], }); const app = new Hono(); app.get("/", async (c) => { const decision = await aj.protect(c.req.raw, { requested: 5 }); if (decision.isDenied()) { if (decision.reason.isRateLimit()) { return c.json({ error: "Too many requests" }, 429); } else if (decision.reason.isBot()) { return c.json({ error: "No bots allowed" }, 403); } else { return c.json({ error: "Forbidden" }, 403); } } return c.json({ message: "Hello world" }); }); export default { fetch: aj.handler(app.fetch), port: 3000, }; ``` Note: With Hono on Bun, pass `c.req.raw` (the raw Request) to `protect()`, and wrap the fetch handler with `aj.handler()`. ## Python FastAPI example ### Install ```shell pip install arcjet fastapi uvicorn # or with uv: uv add arcjet fastapi uvicorn ``` ### Configure ```python # main.py import os from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from pydantic import BaseModel from arcjet import ( Mode, SensitiveInfoEntityType, arcjet, detect_bot, detect_prompt_injection, detect_sensitive_info, shield, token_bucket, ) app = FastAPI() aj = arcjet( key=os.getenv("ARCJET_KEY"), # Get your key from https://console.arcjet.com rules=[ # Detect prompt injection attacks before they reach your LLM detect_prompt_injection(mode=Mode.LIVE), # Block sensitive data (PII, credit cards) from entering your AI pipeline detect_sensitive_info( mode=Mode.LIVE, deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, SensitiveInfoEntityType.PHONE_NUMBER, ], ), # Rate limit by token budget per user token_bucket( characteristics=["userId"], mode=Mode.LIVE, refill_rate=100, interval=60, capacity=1000, ), # Block automated clients and scrapers detect_bot( mode=Mode.LIVE, allow=[], # empty = block all bots ), # Protect against common web attacks (SQLi, XSS, etc.) shield(mode=Mode.LIVE), ], ) class ChatRequest(BaseModel): message: str @app.post("/chat") async def chat(request: Request, body: ChatRequest): userId = "user_123" # Replace with real user ID from session decision = await aj.protect( request, requested=5, characteristics={"userId": userId}, detect_prompt_injection_message=body.message, sensitive_info_value=body.message, ) if decision.is_denied(): status = 429 if decision.reason_v2.type == "RATE_LIMIT" else 403 return JSONResponse({"error": "Denied"}, status_code=status) # Safe to pass body.message to your LLM return {"reply": "Hello!"} ``` ### Start ```shell uvicorn main:app --reload ``` ## Python Flask example ### Install ```shell pip install arcjet flask # or with uv: uv add arcjet flask ``` ### Configure ```python # main.py import os from flask import Flask, jsonify, request from arcjet import ( Mode, SensitiveInfoEntityType, arcjet_sync, detect_bot, detect_prompt_injection, detect_sensitive_info, shield, token_bucket, ) app = Flask(__name__) aj = arcjet_sync( key=os.getenv("ARCJET_KEY"), # Get your key from https://console.arcjet.com rules=[ # Detect prompt injection attacks before they reach your LLM detect_prompt_injection(mode=Mode.LIVE), # Block sensitive data (PII, credit cards) from entering your AI pipeline detect_sensitive_info( mode=Mode.LIVE, deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, SensitiveInfoEntityType.PHONE_NUMBER, ], ), # Rate limit by token budget per user token_bucket( characteristics=["userId"], mode=Mode.LIVE, refill_rate=100, interval=60, capacity=1000, ), # Block automated clients and scrapers detect_bot( mode=Mode.LIVE, allow=[], # empty = block all bots ), # Protect against common web attacks (SQLi, XSS, etc.) shield(mode=Mode.LIVE), ], ) @app.post("/chat") def chat(): userId = "user_123" # Replace with real user ID from session body = request.get_json() message = body.get("message", "") if body else "" decision = aj.protect( request, requested=5, characteristics={"userId": userId}, detect_prompt_injection_message=message, sensitive_info_value=message, ) if decision.is_denied(): status = 429 if decision.reason_v2.type == "RATE_LIMIT" else 403 return jsonify(error="Denied"), status # Safe to pass message to your LLM return jsonify(reply="Hello!") if __name__ == "__main__": app.run(debug=True) ``` Note: Flask uses `arcjet_sync` (synchronous) instead of `arcjet` (async). ### Start ```shell flask run # or: uv run flask run ``` ## Rule parameter reference Every rule accepts `mode: "LIVE" | "DRY_RUN"`. In `DRY_RUN` mode the rule evaluates and returns a decision but never blocks. Use `DRY_RUN` for testing. In Python, HTTP rule factories require `mode`. Omitting it raises `TypeError`. Guard constructors default to `Mode.LIVE`. JavaScript HTTP rules default to `"DRY_RUN"`. ### shield(options) Protects against common web attacks, including SQL injection and XSS. ```ts shield({ mode: "LIVE", // or "DRY_RUN" }) ``` Parameters: - `mode` (optional): `"LIVE"` (default) or `"DRY_RUN"` Python: `shield(mode=Mode.LIVE)` ### detectBot(options) Detects and blocks automated clients. ```ts detectBot({ mode: "LIVE", // Use allow OR deny (mutually exclusive) allow: [ "CATEGORY:SEARCH_ENGINE", // Google, Bing, etc "CATEGORY:MONITOR", // Uptime monitoring "CATEGORY:PREVIEW", // Link previews (Slack, Discord) // Or specific bots: "GOOGLEBOT", "BINGBOT", etc. ], // OR: // deny: ["CATEGORY:DEFINITELY_AUTOMATED"], }) ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `allow` (array): Bots/categories to allow – everything else is denied - `deny` (array): Bots/categories to deny – everything else is allowed - `allow` and `deny` are mutually exclusive; use one or the other Bot categories use the `CATEGORY:` prefix. Full list: https://arcjet.com/bot-list Python: `detect_bot(mode=Mode.LIVE, allow=[BotCategory.SEARCH_ENGINE])` or `detect_bot(mode=Mode.LIVE, allow=["CURL"])`. Use `BotCategory.` for categories or pass specific bot name strings directly. Pass exactly one of `allow` or `deny`. `allow=[]` blocks every detected bot. Passing neither list or both lists raises `ValueError`. ### tokenBucket(options) Token bucket rate limiting. Tokens refill at a steady rate. Best for AI cost control where each request consumes a variable number of tokens. ```ts tokenBucket({ mode: "LIVE", characteristics: ["userId"], // Optional. Defaults to IP-based tracking refillRate: 2_000, // Tokens added per interval interval: "1h", // Refill interval (number in seconds, or string: "1s", "1m", "1h", "1d") capacity: 5_000, // Maximum tokens the bucket can hold }) ``` At protect() time, pass `requested` to deduct tokens: ```ts const decision = await aj.protect(req, { requested: 50 }); ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `characteristics` (optional): Array of strings for tracking (default: IP) - `refillRate` (required): Number of tokens to add per interval - `interval` (required): Seconds (number) or duration string (`"1h"`, `"10m"`) - `capacity` (required): Maximum tokens in the bucket Python: `token_bucket(mode=Mode.LIVE, refill_rate=100, interval=60, capacity=1000, characteristics=["userId"])` The `interval` parameter accepts seconds as a number in Python. ### fixedWindow(options) Fixed window rate limiting. Counts requests in non-overlapping time windows. ```ts fixedWindow({ mode: "LIVE", characteristics: ["userId"], // Optional window: "60s", // Window duration (string: "1s", "10s", "1m", "1h", "1d") max: 100, // Maximum requests per window }) ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `characteristics` (optional): Array of strings for tracking (default: IP) - `window` (required): Duration string, such as `"60s"` or `"1h"` - `max` (required): Maximum requests allowed per window Python: `fixed_window(mode=Mode.LIVE, window=60, max=100)` – `window` takes seconds as a number in Python. ### slidingWindow(options) Sliding window rate limiting. Smooths out the edges of fixed windows. ```ts slidingWindow({ mode: "LIVE", characteristics: ["userId"], // Optional interval: 60, // Window size in seconds max: 100, // Maximum requests per window }) ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `characteristics` (optional): Array of strings for tracking (default: IP) - `interval` (required): Window size in seconds (number) - `max` (required): Maximum requests allowed per window Python: `sliding_window(mode=Mode.LIVE, interval=60, max=100)` – `interval` takes seconds as a number. Rate-limit characteristics are combined into one fingerprint. For example, `["ip.src", "userId"]` creates one bucket for each unique IP/user pair, not separate IP and user counters. Configure separate rate-limit rules when you need independent limits, such as one rule keyed by `userId` for an account quota and another keyed by `ip.src` for per-IP throttling. IP-based limits are useful for anonymous traffic, but IP addresses can be shared or rotated; use a stable user, account, tenant, or API key for authenticated traffic. ### sensitiveInfo(options) Detects and blocks requests containing sensitive information (PII). ```ts sensitiveInfo({ mode: "LIVE", // Use allow OR deny (mutually exclusive) deny: ["CREDIT_CARD_NUMBER", "EMAIL", "PHONE_NUMBER", "IP_ADDRESS"], // OR: allow: ["EMAIL"], // Only allow email, block everything else }) ``` At protect() time, pass the text to scan: ```ts const decision = await aj.protect(req, { sensitiveInfoValue: "text to scan for PII", }); ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `deny` (array): Entity types to block - `allow` (array): Entity types to allow (blocks everything else) - `deny` and `allow` are mutually exclusive - `contextWindowSize` (optional): Number of tokens for detection context (default: 1) - `detect` (optional): Custom detection function `(tokens: string[]) => Array` - `backend` (optional): Alternative detection backend. Defaults to the built-in WebAssembly engine; pass the on-device Rampart NER model to also detect names, addresses, and government/financial identifiers. All detection stays local. Entity types detected by the built-in engine: `CREDIT_CARD_NUMBER`, `EMAIL`, `PHONE_NUMBER`, `IP_ADDRESS`. The optional Rampart backend adds `GIVEN_NAME`, `SURNAME`, `SSN`, `URL`, `TAX_ID`, `BANK_ACCOUNT`, `ROUTING_NUMBER`, `GOVERNMENT_ID`, `PASSPORT`, `DRIVERS_LICENSE`, and address parts (`BUILDING_NUMBER`, `STREET_NAME`, `SECONDARY_ADDRESS`, `CITY`, `STATE`, `ZIP_CODE`). Rampart is an optional package: `@arcjet/sensitive-info-rampart` for the JavaScript SDKs, or the `arcjet[sensitive-info-rampart]` extra for Python. Import `rampart` and pass it as `backend: rampart()` (JS) / `backend=rampart()` (Python). It loads a bundled ONNX model on first use and needs a server runtime (Node.js, Bun, or Deno in JS). Full reference: https://docs.arcjet.com/sensitive-info/reference#on-device-detection-with-rampart Python: `detect_sensitive_info(mode=Mode.LIVE, deny=[SensitiveInfoEntityType.EMAIL, SensitiveInfoEntityType.CREDIT_CARD_NUMBER])` At protect() time: `sensitive_info_value="text to scan"` ### detectPromptInjection(options) Detects prompt injection attacks in user messages before they reach your AI model. ```ts detectPromptInjection({ mode: "LIVE", }) ``` At protect() time, pass the message to scan: ```ts const decision = await aj.protect(req, { detectPromptInjectionMessage: userMessage, }); ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` JavaScript accepts only `mode`. `threshold` and `score` are removed on JS SDK `main` – drop them on upgrade. The core SDK ignores leftover `threshold`. `@arcjet/astro` Zod `.strict()` throws at startup if `threshold` is still in the integration config. The verdict is binary: `decision.reason.isPromptInjection()` or `decision.reason.injectionDetected`. Python: `detect_prompt_injection(mode=Mode.LIVE)` with `detect_prompt_injection_message=message` at protect() time. `mode` is required: omitting it or passing `threshold=` raises `TypeError`. Drop `threshold`. `PromptInjectionReason.score` remains deprecated. Guard `DetectPromptInjection` defaults to `LIVE`. ### validateEmail(options) Validates email addresses for signup forms. ```ts validateEmail({ mode: "LIVE", deny: ["DISPOSABLE", "NO_MX_RECORDS", "INVALID"], // OR: allow: ["FREE"], }) ``` At protect() time, pass the email: ```ts const decision = await aj.protect(req, { email: "user@example.com" }); ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `deny` (array): Email types to reject - `allow` (array): Email types to allow (rejects everything else) - `requireTopLevelDomain` (optional): Require a TLD (default: `true`) - `allowDomainLiteral` (optional): Allow domain literals like `[127.0.0.1]` (default: `false`) Valid email types: `DISPOSABLE`, `FREE`, `NO_MX_RECORDS`, `NO_GRAVATAR`, `INVALID` Python: `validate_email(mode=Mode.LIVE, deny=[EmailType.DISPOSABLE, EmailType.INVALID, EmailType.NO_MX_RECORDS])` At protect() time: `email="user@example.com"`. Pass exactly one of `allow` or `deny`. `allow=[]` allows no email types. Passing neither list or both lists raises `ValueError`. ### protectSignup(options) Combined rule for signup form protection (bot detection + email validation + rate limiting). ```ts protectSignup({ email: { mode: "LIVE", deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], }, bots: { mode: "LIVE", deny: ["CATEGORY:DEFINITELY_AUTOMATED"], }, rateLimit: { mode: "LIVE", characteristics: ["ip.src"], interval: 600, max: 5, }, }) ``` At protect() time, pass the email: ```ts const decision = await aj.protect(req, { email: "user@example.com" }); ``` Python: `protect_signup` is not one composite rule. It returns a `(SlidingWindow, BotDetection, EmailValidation)` tuple that you unpack into `rules`. All three mappings are required (JS `ProtectSignupOptions` fields are optional). Nested `bots` and `email` must include exactly one of `allow` or `deny` (`allow=[]` is valid). Nested mappings are forwarded to `sliding_window()`, `detect_bot()`, and `validate_email()`, so each mapping must include `mode`. ```py import os from arcjet import EmailType, Mode, arcjet, protect_signup aj = arcjet( key=os.environ["ARCJET_KEY"], rules=[ *protect_signup( rate_limit={"mode": Mode.LIVE, "max": 5, "interval": 600}, bots={"mode": Mode.LIVE, "allow": []}, email={ "mode": Mode.LIVE, "deny": [ EmailType.DISPOSABLE, EmailType.INVALID, EmailType.NO_MX_RECORDS, ], }, ) ], ) ``` At protect() time: `email="user@example.com"`. ### filter(options) Filter requests based on expressions using request and IP metadata. ```ts filter({ mode: "LIVE", // Use allow OR deny (mutually exclusive) deny: ["ip.src.vpn", "ip.src.tor"], // OR: allow: ['ip.src.country eq "US"'], }) ``` Parameters: - `mode` (optional): `"LIVE"` or `"DRY_RUN"` - `deny` (array): Expressions that cause a DENY when matched - `allow` (array): Expressions that cause an ALLOW when matched (denies everything else) - Maximum 10 expressions per rule, each max 1024 bytes Available fields include: `http.host`, `http.request.method`, `http.request.uri.path`, `ip.src`, `ip.src.country`, `ip.src.vpn`, `ip.src.tor`, `ip.src.hosting`, `ip.src.proxy`, and many more. Python: `filter_request(mode=Mode.LIVE, deny=["ip.src.vpn", "ip.src.tor"])` Custom local fields: pass `filter_local={"key": "value"}` at protect() time, then reference as `local.key` in expressions. ## Decision API reference `protect()` returns a decision object with these methods: ### Conclusion methods ```ts const decision = await aj.protect(req); decision.isDenied() // true if any LIVE rule triggered a DENY decision.isAllowed() // true if all rules passed decision.isErrored() // true if there was an error evaluating rules decision.isChallenged() // true if a challenge is required ``` ### Reason methods (check WHY a request was denied) ```ts if (decision.isDenied()) { decision.reason.isRateLimit() // Rate limit exceeded decision.reason.isBot() // Bot detected decision.reason.isShield() // Shield WAF triggered decision.reason.isSensitiveInfo() // PII detected decision.reason.isEmail() // Email validation failed decision.reason.isPromptInjection() // Prompt injection detected decision.reason.injectionDetected // Binary prompt-injection verdict decision.reason.isFilterRule() // Filter rule matched } ``` ### Error handling ```ts if (decision.isErrored()) { // Arcjet fails open – log the error and allow the request console.error("Arcjet error", decision.reason.message); } ``` ### IP analysis (available on every decision) ```ts decision.ip.isHosting() // true if from a hosting/cloud provider decision.ip.isVpn() // true if from a VPN decision.ip.isTor() // true if from Tor decision.ip.isProxy() // true if from a proxy decision.ip.isRelay() // true if from a relay ``` ### Rate limit metadata ```ts // Available when a rate limit rule is configured for (const result of decision.results) { if (result.reason.isRateLimit()) { result.reason.max // Configured maximum result.reason.remaining // Requests/tokens remaining result.reason.window // Total window in seconds result.reason.reset // Seconds until window resets } } ``` ### Python decision API ```python # Top-level checks decision.is_denied() # True if any rule denied the request decision.is_allowed() # True if all rules allowed the request decision.is_error() # True if Arcjet encountered an error (fails open) # reason_v2.type values: "BOT", "RATE_LIMIT", "SHIELD", "EMAIL", "ERROR", "FILTER" if decision.reason_v2.type == "RATE_LIMIT": print(decision.reason_v2.remaining) # tokens/requests remaining elif decision.reason_v2.type == "BOT": print(decision.reason_v2.denied) # list of denied bot names # Per-rule results (for granular handling) for result in decision.results: print(result.reason_v2.type, result.is_denied()) # Inspect helpers (from arcjet; no extra package). DRY_RUN is ignored by # is_verified_bot, is_spoofed_bot, and is_missing_user_agent. from arcjet import ( is_missing_user_agent, is_spoofed_bot, is_verified_bot, set_rate_limit_headers, ) if any(is_verified_bot(r) for r in decision.results): print("verified bot") if any(is_spoofed_bot(r) for r in decision.results): print("spoofed bot") if any(is_missing_user_agent(r) for r in decision.results): print("missing User-Agent") # IETF RateLimit / RateLimit-Policy headers (same as JS @arcjet/decorate) set_rate_limit_headers(response, decision) # IP helpers (same as JS) decision.ip.is_hosting() decision.ip.is_vpn() decision.ip.is_tor() decision.ip.is_proxy() ``` ### Python protect() parameters All parameters are optional keyword arguments passed alongside `request`: | Parameter | Type | Used by | | --------------------------------- | ---------------- | -------------------------- | | `requested` | `int` | Token bucket rate limit | | `characteristics` | `dict[str, Any]` | Rate limiting | | `detect_prompt_injection_message` | `str` | Prompt injection detection | | `sensitive_info_value` | `str` | Sensitive info detection | | `email` | `str` | Email validation | | `filter_local` | `dict[str, str]` | Request filters | | `ip_src` | `str` | Manual IP override | ## withRule() pattern – reusing a single client Create one Arcjet instance and add route-specific rules with `withRule()` (JS) or `with_rule()` (Python). The Python clone shares `DecisionCache`, key, characteristics, and transport. The original client is unchanged. `with_rule()` accepts a single rule or a sequence of rules. HTTP Python rule factories require `mode`. ```ts // lib/arcjet.ts – create and export a base instance import arcjet, { detectBot, fixedWindow, sensitiveInfo, shield, } from "@arcjet/next"; export { detectBot, fixedWindow, sensitiveInfo, shield }; export default arcjet({ key: process.env.ARCJET_KEY!, rules: [ // Base rules that apply to every route (optional) ], }); ``` ```ts // app/api/chat/route.ts – add route-specific rules import arcjet, { detectBot, fixedWindow } from "@/lib/arcjet"; const aj = arcjet .withRule( detectBot({ mode: "LIVE", allow: [], }), ) .withRule( fixedWindow({ mode: "LIVE", max: 100, window: "60s", }), ); export async function GET(req: Request) { const decision = await aj.protect(req); // ... } ``` Python: ```py import os from arcjet import Mode, arcjet, detect_bot, fixed_window, shield aj = arcjet(key=os.environ["ARCJET_KEY"], rules=[shield(mode=Mode.LIVE)]) protected = aj.with_rule( [ detect_bot(mode=Mode.LIVE, allow=[]), fixed_window(mode=Mode.LIVE, window=60, max=100), ] ) ``` ## Best practices and anti-patterns ### Do - Create the Arcjet client ONCE, outside the request handler, and reuse it. - Call `protect()` inside the route handler where you have the full request context. - Start new rules in `DRY_RUN` mode, verify in the Console, then switch to `LIVE`. - Handle every denial reason explicitly, including rate limit, bot, and shield. - Use `withRule()` (JS) or `with_rule()` (Python) to attach route-specific rules to a shared base instance. ### Don't - Don't create a new Arcjet instance per request – this defeats caching. - Don't call `protect()` multiple times for the same request – it may double- count rate limits. - Don't use Arcjet in middleware – middleware lacks route context. Call `protect()` in each route handler instead. If you must use middleware, scope it carefully and don't also call `protect()` in the route handler. - Don't pass personal information (email addresses, names) as rate limit `characteristics` – use opaque identifiers such as user IDs. - Next.js renamed `middleware.js` to `proxy.js` in Next.js 16. Call the Arcjet `protect()` function only once per request. ### Proxies and load balancers If your app is behind a proxy or load balancer, configure Arcjet to see the real client IP: ```ts const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [], proxies: [ "203.0.113.100", // A single IP "203.0.113.0/24", // A CIDR range ], }); ``` This is not needed on Firebase, Netlify, Fly.io, or Vercel – Arcjet auto-detects proxy IPs on these platforms. ## Product philosophy 1. Enforcement runs inline in your application – with access to identity, route, session, and spend context no proxy can see. 2. Your agent configures protections via MCP. You review and approve. 3. Start in DRY_RUN, verify against real traffic, promote to LIVE. 4. Remote rules let you and your agents respond to attacks immediately – no code deployment needed. Find out more at https://docs.arcjet.com/architecture ## Important notes - Arcjet runs server-side and does not require any client-side integration. - Arcjet is a paid service. See https://arcjet.com/pricing for details. - Review https://docs.arcjet.com/best-practices for best practices. - Calls to `protect()` never throw. Arcjet fails open so that a service issue or misconfiguration does not block all requests. ## Guards – non-request protection Guards apply Arcjet security rules inside AI agent tool calls, MCP tool handlers, queue workers, and anywhere else you process untrusted input without an HTTP request. Pass inputs directly, get a decision back. Supported languages: **JavaScript / TypeScript** (`@arcjet/guard`), **Python** (the `arcjet` package), and **Go** (`arcjet-go`, pre-release). ### JavaScript / TypeScript example ```ts import { launchArcjet, tokenBucket, detectPromptInjection } from "@arcjet/guard"; // Create once at module scope const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! }); // Configure rules at module scope (stable IDs for server-side aggregation) const userLimit = tokenBucket({ label: "user.tool_call_bucket", bucket: "tool-calls", refillRate: 100, intervalSeconds: 60, maxTokens: 500, }); const piRule = detectPromptInjection(); // Call guard() inline in each tool handler async function searchWeb(query: string, userId: string) { const decision = await arcjet.guard({ label: "tools.search_web", metadata: { userId }, rules: [ userLimit({ key: userId, requested: 1 }), piRule(query), ], }); if (decision.conclusion === "DENY") { const rateDenied = userLimit.deniedResult(decision); if (rateDenied) { throw new Error(`Rate limited -- try again in ${rateDenied.resetInSeconds}s`); } throw new Error(`Blocked: ${decision.reason}`); } // Safe to proceed } ``` ### Python example ```python import os from arcjet.guard import launch_arcjet, TokenBucket, DetectPromptInjection # Create once at module scope arcjet = launch_arcjet(key=os.environ["ARCJET_KEY"]) # Configure rules at module scope (stable IDs for server-side aggregation) user_limit = TokenBucket( label="user.tool_call_bucket", bucket="tool-calls", refill_rate=100, interval_seconds=60, max_tokens=500, ) pi_rule = DetectPromptInjection() # Call guard() inline in each tool handler async def search_web(query: str, user_id: str): decision = await arcjet.guard( label="tools.search_web", metadata={"user_id": user_id}, rules=[ user_limit(key=user_id, requested=1), pi_rule(query), ], ) if decision.conclusion == "DENY": rate_denied = user_limit.denied_result(decision) if rate_denied: raise RuntimeError(f"Rate limited -- try again in {rate_denied.reset_in_seconds}s") raise RuntimeError(f"Blocked: {decision.reason}") # Safe to proceed ``` The guard skill is the source of truth for code patterns: ```bash npx skills add arcjet/skills ``` For the full API reference, read the installed library source: - JS/TS: `node_modules/@arcjet/guard` - Python: `arcjet.guard` module ### Agent framework adapters The examples above call `guard()` directly. When the agent framework owns the tool loop, use its adapter instead: the wrapper sits between the model's generated arguments and the tool's own handler, so a denial stops the side effect and returns an envelope the model can read. Framework wrappers take `action`; direct `guard()` calls take `label` for the same slug. Every adapter page below documents one integration and selects the language with a tab where both a JavaScript and a Python adapter exist. | Framework | JavaScript import | Python import | Deny point | | --- | --- | --- | --- | | Vercel AI SDK | `@arcjet/guard/vercel-ai/v7` | – | `guardTool`, `guardAction` | | LangChain | `@arcjet/guard/langchain/v1` | `arcjet.guard.langchain` | `guardTool` / `guard_tool`, `guardMiddleware` / `ArcjetMiddleware` | | LangGraph | `@arcjet/guard/langgraph/v1` | – | `guardTool`, `guardToolNode` | | CrewAI | – | `arcjet.guard.crewai` | `register_arcjet_hooks` on `PRE_TOOL_CALL`, `guard_tool` | | Genkit | `@arcjet/guard/genkit/v1` | – | `guardTool`, `guardMiddleware` | | Google ADK | `@arcjet/guard/google-adk/v2` | – | `guardPlugin` (`beforeToolCallback`). No `guardTool` | | OpenAI Agents | `@arcjet/guard/openai-agents/v0` | `arcjet.guard.openai_agents` | `guardTool` on `invoke` / `guard_tool` on `tool_input_guardrails` | | Strands Agents | `@arcjet/guard/strands-agents/v1` | `arcjet.guard.strands_agents` | `guardTool` / `guard_tool`, `guardHooks` / `guard_hooks` | | TanStack AI | `@arcjet/guard/tanstack-ai/v0` | – | `guardMiddleware` (`onBeforeToolCall`). No `guardTool` | | Mastra | `@arcjet/guard/mastra/v1` | – | `guardProcessor`, `guardTool`, `guardHooks` | | Vercel Eve | `@arcjet/guard/vercel-eve/v0` | – | `guardInbound`, `guardTool`, `guardApproval` (connections) | | Claude Agent SDK | `@arcjet/guard/claude-agent-sdk/v0` | `arcjet.guard.claude_agent_sdk` | `guardTool` / `guard_tool`, `guardHooks` / `guard_hooks` (`UserPromptSubmit`, `PreToolUse`) | | Claude Managed Agents | `@arcjet/guard/claude-managed-agents/v0` | `arcjet.guard.claude_managed_agents` | `guardEvents` / `guard_events`, `guardCustomTool` / `guard_custom_tool` | Every JavaScript path is versioned. Unversioned aliases such as `@arcjet/guard/vercel-ai` do not resolve. Don't wrap the same tool with two adapters, and don't mix the JavaScript and Python adapter for one framework. A framework's human-in-the-loop confirmation is not a policy gate. `needsApproval`, `humanInTheLoopMiddleware`, `interrupt()`, `requireApproval`, `human_input`, `can_use_tool`, and `always_ask` all pause a run for a person. The runtime can skip some of them, and none of them evaluates a policy. Don't put Arcjet policy on any of them. The one exception is `guardApproval` on `@arcjet/guard/vercel-eve/v0`, which evaluates a policy on an Eve connection's `approval` field. A connection's tools have no local handler to wrap, so this is the only enforcement point that reaches them. `onAllow: "user-approval"` still requires a person after the policy passes. No other adapter has an approval helper. The wrappers fail closed: if Guard cannot be evaluated the tool does not run. Direct `guard()` fails open and reports `hasFailedOpen()` / `has_failed_open()`, so an `ALLOW` from a direct call is not proof the rules ran. ### Common mistakes when writing Guard code These are the traps that produce code which looks correct, runs without an error, and enforces nothing. **Configure the local sensitive-information rule.** In Python, `LocalDetectSensitiveInfo()` with neither `allow` nor `deny` fails during local evaluation. The rule result is `RuleResultError(conclusion='ALLOW', reason='ERROR', code='AJ1203')` and the decision conclusion is `ALLOW`, so the check looks configured and blocks nothing. Only `has_failed_open()` reveals it. JavaScript `localDetectSensitiveInfo()` does work with no arguments. Always pass an explicit list in both languages. **The rule needs its own `backend`.** The default WASM backend detects `EMAIL`, `PHONE_NUMBER`, `IP_ADDRESS`, and `CREDIT_CARD_NUMBER`. Every other entity type needs a backend that supports it, such as Rampart. The rule does not inherit the client's `sensitiveInfoBackend` / `sensitive_info_backend`, so listing `BANK_ACCOUNT` or `ROUTING_NUMBER` without passing `backend` to the rule itself throws at construction. Share one instance: ```ts const sensitiveInfoBackend = rampart(); const arcjet = launchArcjet({ key: process.env.ARCJET_KEY!, sensitiveInfoBackend }); const detectPii = localDetectSensitiveInfo({ deny: ["BANK_ACCOUNT", "ROUTING_NUMBER"], backend: sensitiveInfoBackend, }); ``` **Only some adapters map typed `inputs`.** A remote policy evaluates the typed inputs a guard call submits. In JavaScript only `@arcjet/guard/vercel-ai/v7` accepts `inputs` and `actor`; every other JavaScript adapter takes `action` and SDK `rules` only, so a remote policy has nothing to evaluate and none of its rules fire. Every Python adapter accepts `inputs`. Use SDK `rules` where `inputs` is unavailable, and don't assume a published policy is enforcing. The builders differ by language. JavaScript uses one `policyInput` namespace (`policyInput.server.string`, `policyInput.server.stringList`, `policyInput.local.string`). Python uses two module-level objects, `server_input` and `local_input` (`server_input.string`, `server_input.string_list`, `local_input.string`). `SERVER` inputs go to Arcjet; `LOCAL` inputs are evaluated on your machine, so the value never leaves your application. **The `inputs` and `actor` resolver arity varies by surface.** Python `guard_tool` calls the resolver with the arguments mapping alone. CrewAI's `register_arcjet_hooks` calls it with `(arguments, ctx)`, and LangChain's `guard_tool` with `(arguments, config)`. **Annotate the `rules` callback where `TInput` defaults to `unknown`.** On the Genkit, OpenAI Agents, and Strands Agents JavaScript adapters, `guardTool` cannot infer the tool input, so destructuring it is a type error. Write `rules: (input: { body: string }) => [detectPii(input.body)]`. **A missing decision is not a denial.** If the model asks a clarifying question instead of calling the guarded tool, nothing is sent, no guard call happens, and no decision is returned. That looks identical to a working guard. When verifying an integration, read the decision in the Console or your logs rather than concluding from the absence of a side effect. Give a test agent a system prompt that tells it to complete the request without follow-up questions, and to quote retrieved values verbatim: a model that masks sensitive values itself leaves the rule nothing to detect, and the guard then correctly allows. **Guarding one tool only helps if it is the only path.** If the same session also exposes an unguarded route to the capability, the model can take it. On the Claude Agent SDK, `settingSources: []` / `setting_sources=[]` drops CLAUDE.md and filesystem settings, and `strictMcpConfig: true` / `strict_mcp_config=True` drops inherited MCP servers. Both are needed. **Claude Agent SDK uses two distinct ids.** `ClaudeAgentOptions.sessionId` / `session_id` names a new SDK session and must be unique per run; reusing one fails with `Session ID ... is already in use`. The guard `sessionId` / `session_id` identifies the actor and can be long-lived. An authored `tool()` handler carries no session id of its own, so pass one to `guardTool`. **Claude Managed Agents correlates on your own id.** `claudeManagedAgentsContext` / `claude_managed_agents_context` drops Anthropic session and event ids (`sesn_…`, `sevt_…`), because they are not ids you created. Pass a conversation id your application owns. Anthropic's session id still addresses the session you send events to. In Python, `guard_events` has no `inbound` option: `action` and `rules` sit at the top level, it takes `send=`, and the callable it returns replaces `send` and raises `ArcjetDeniedError` on a denial. Pass an async client, because a blocking one makes the wrapper synchronous. ## Learning Center The [Arcjet Learning Center](https://arcjet.com/learn) contains technical guides for AI agent runtime security and application protection: - [A sandbox is not a tool policy](https://arcjet.com/learn/agent-sandbox-vs-tool-policy): an agent sandbox isolates untrusted code; it does not authorize tools or connections. - [Mastra guardrails vs an action gate](https://arcjet.com/learn/mastra-guardrails-vs-action-gate): Mastra processors classify messages; an action gate decides whether this tool runs. - [The lethal trifecta for AI agents](https://arcjet.com/learn/lethal-trifecta): private data, untrusted content, and external communication in one agent is an exfiltration path. - [Guardian Agents](https://arcjet.com/learn/guardian-agents): visibility, assurance, and runtime enforcement for the agents you run. - [Application-native vs remote security policies](https://arcjet.com/learn/application-native-vs-remote-security-policies): compare application-native rules with remotely managed policies. - [AI agent security architecture](https://arcjet.com/learn/ai-agent-security-architecture): compare in-code controls, proxies, AI gateways, and security agents by visibility, enforcement, context, latency, failure modes, and threat coverage. - [AI agent identity and on-behalf-of authorization](https://arcjet.com/learn/ai-agent-on-behalf-of-identity): preserve user and agent identities through delegation, token exchange, downscoping, runtime authorization, and audit trails. - [What is AI security?](https://arcjet.com/learn/what-is-ai-security): a plain-language definition of the category, how model safety, application security, and runtime enforcement divide the work, which risks each layer can address, and the boundaries where controls run. - [AI security checklist for production LLM apps](https://arcjet.com/learn/ai-security-checklist): an engineer-first checklist of 20 items you can verify in code or a decision log, grouped by the NIST AI RMF functions GOVERN, MAP, MEASURE, and MANAGE, with each OWASP LLM Top 10 entry mapped to the control that denies before the side effect. - [AI agent runtime security](https://arcjet.com/learn/ai-agent-runtime-security): stop unauthorized tool calls, prompt injection, sensitive-data exposure, automated abuse, and runaway costs inside production workflows. - [API security best practices](https://arcjet.com/learn/api-security-best-practices): apply authentication, object-level authorization, input validation, rate limiting, bot detection, monitoring, and testing throughout the API lifecycle. - [What is API abuse?](https://arcjet.com/learn/what-is-api-abuse): understand automated abuse patterns, detection signals, and application-layer defenses for valid-looking malicious traffic. - [Rate limiting guide](https://arcjet.com/learn/rate-limiting-guide): compare token bucket, sliding log, sliding counter, fixed window, and leaky bucket algorithms, including state cost, failure modes, and distributed-system trade-offs. - [AI agent bot management](https://arcjet.com/learn/ai-agent-bot-management): detect and apply policy to AI agents that hit logins, checkouts, forms, and APIs as clients. - [What is runtime application security?](https://arcjet.com/learn/what-is-runtime-application-security): enforces controls while the application runs, at the point untrusted input meets your code, with context that pre- and post-runtime tools do not have. - [Runtime security for LLM applications](https://arcjet.com/learn/runtime-security-llm-applications): the three runtime failure modes for LLM applications — prompt injection, data exfiltration, and unsafe actions — and the control that addresses each. - [Pre-runtime vs post-runtime AI security](https://arcjet.com/learn/pre-runtime-vs-post-runtime-ai-security): pre-runtime testing, runtime enforcement, and post-runtime observability solve different problems; enforcement and observability are not substitutes. - [Keeping security inspection local](https://arcjet.com/learn/keeping-security-inspection-local): local inspection so data stays in your environment, why it matters for data residency, and what Arcjet keeps in-process. - [Runtime controls for agents on enterprise systems](https://arcjet.com/learn/runtime-controls-agents-enterprise-systems): when agents touch CRMs, warehouses, and internal APIs, enforce budgets, injection checks, and data controls at each action. - [Token and spend budgets for AI agents](https://arcjet.com/learn/enforce-token-spend-budgets-ai-agents): cap token and action spend per identity at the tool call, with the bucket shared by key. - [Prevent data exfiltration through AI agents](https://arcjet.com/learn/prevent-data-exfiltration-ai-agents): detect and block agent data leaks locally, without sending that content to a third-party scanner. - [How do I secure a RAG application?](https://arcjet.com/learn/secure-rag-application): treat retrieved chunks as untrusted; screen for prompt injection and PII before the provider, and treat the vector store as a store boundary. - [Stop AI agents accessing data they should not](https://arcjet.com/learn/stop-agents-accessing-data): scoped credentials limit reach; a runtime check at the point of access limits what the agent does with the data. - [How to detect and redact PII in LLM inputs and outputs](https://arcjet.com/learn/detect-redact-pii-llm-inputs-outputs): detect PII in the prompt and in the model reply before either reaches a provider or a user. - [PII detection at runtime: gateway, sidecar, or in-process](https://arcjet.com/learn/pii-detection-runtime-gateway-vs-application): which platforms detect PII at runtime, where each one classifies the text, and which paths each deployment model can see. - [How to stop users sending PII to an LLM](https://arcjet.com/learn/stop-users-sending-pii-to-llm): block, redact, or warn by data class, and log the block without logging the data. - [How to prevent PII leakage from AI agents](https://arcjet.com/learn/prevent-pii-leakage-ai-agents): the five agent leak points, with a coverage matrix; tool call arguments are the one most tools miss. - [How to redact sensitive data before sending it to OpenAI or Anthropic](https://arcjet.com/learn/redact-before-openai-anthropic): middleware around the provider clients, reversible versus irreversible redaction, and streaming chunk boundaries. - [How to prevent LLMs surfacing confidential employee or customer data](https://arcjet.com/learn/prevent-llm-confidential-data-disclosure): permission-aware retrieval, tenant boundaries in memory and vector stores, mapped to OWASP LLM02. - [GDPR and CCPA compliance for LLM applications](https://arcjet.com/learn/gdpr-ccpa-compliance-llm-applications): requirement-to-control mapping for engineers, including erasure across derived stores and audit trails without a second PII store. - [Best AI security tools for developers](https://arcjet.com/learn/best-ai-security-tools-for-developers): tools you call in the handler before the provider or the tool runs, not a dashboard after the refund. - [Human approval gates for agent actions](https://arcjet.com/learn/human-approval-gates-agent-actions): hold high-risk agent actions for human approval, using a runtime allow or deny decision as the trigger. - [Compliance evidence for AI agent activity](https://arcjet.com/learn/compliance-evidence-ai-agents): record what controls allowed and blocked on agent actions, which is the evidence auditors ask for. - [Secure MCP server and agent tool calls](https://arcjet.com/learn/secure-mcp-server-agent-tool-calls): MCP tools have no HTTP front door; add budgets, injection detection, and data controls where the tool actually runs. - [Enforce security rules at runtime in code](https://arcjet.com/learn/enforce-security-rules-runtime-in-code): the rule ships with the feature and runs in the request path; how in-code enforcement works and how to roll it out. - [Anatomy of an agent incident](https://arcjet.com/learn/anatomy-agent-incident-sequence): an agent with correctly scoped tools can still commit fraud, because the question is whether the sequence of actions is permitted. - [How do I secure AI agents in production?](https://arcjet.com/learn/secure-ai-agents-in-production): screen inbound HTTP and channel text, then gate tools and MCP before the side effect. Observe-only hooks are a diary, not a gate. - [Prompt injection protection for LangChain, LlamaIndex, and Vercel AI SDK](https://arcjet.com/learn/prompt-injection-langchain-llamaindex-vercel-ai-sdk): screen the user message before the model, and screen tool or retriever output before it re-enters context. - [How to prevent prompt injection in LLM applications](https://arcjet.com/learn/prevent-prompt-injection-llm-applications): the five-layer defense stack, with what each layer catches and what it misses. - [How to sanitize user input before passing it to an LLM](https://arcjet.com/learn/sanitize-user-input-before-llm): why escaping and regex fail, structural separation, encoding normalization, and the multi-turn payload. - [How to prevent a malicious tool call from hijacking your AI agent](https://arcjet.com/learn/prevent-malicious-tool-call-agent-hijack): a hijack is a well-formed call the model was persuaded to make; validate against application state, not just a schema. - [How to defend against indirect prompt injection in agentic workflows](https://arcjet.com/learn/indirect-prompt-injection-agentic-workflows): payloads arriving through retrieved content, with provenance tracking and blast-radius limits. - [How AI security platforms detect prompt injection at runtime](https://arcjet.com/learn/how-platforms-detect-prompt-injection-runtime): five detection mechanisms compared on what each catches and what evades it. - [The best tools to detect and block prompt injection in production](https://arcjet.com/learn/best-prompt-injection-detection-production): tool comparison with maintenance status checked August 2026, and where Arcjet is the wrong choice. - [SDK-based security vs WAF vs API gateway](https://arcjet.com/learn/sdk-based-security-vs-waf-vs-api-gateway): what each layer sees, and why a library in the handler knows the authenticated user when a proxy does not. - [How enterprises secure APIs against abuse and bots](https://arcjet.com/learn/enterprises-secure-apis-against-abuse-and-bots): identity-aware rate limits and bot detection on the operation, with an honest split between edge platforms and in-app SDKs. - [What's the best AI security provider for enterprises?](https://arcjet.com/learn/best-ai-security-provider-for-enterprises): the answer depends on the layer — identity, gateway, guardrails, observability, or in-code enforcement. Ranked lists mix those jobs. - [Best AI security for healthcare and regulated industries](https://arcjet.com/learn/ai-security-healthcare-regulated-industries): inspect PHI and PII in-process so the raw body never leaves to be classified. Ask per control, and do not accept a HIPAA certification logo. - [Best AI security solution for fintech](https://arcjet.com/learn/ai-security-for-fintech): inspect financial data in-process, gate money-moving actions before they run, and log an attributable decision per action. Keeps a cloud scanner out of your PCI scope. - [How do I add guardrails to an AI agent that calls external APIs?](https://arcjet.com/learn/runtime-controls-ai-agents-external-apis): enforce on outbound HTTP the agent starts (Stripe, GitHub, search, send-email), inside execute or a shared client. Not the CRM or warehouse path. - [Runtime security for LLM applications: prompt injection, data leakage, and output validation](https://arcjet.com/learn/runtime-security-llm-full-stack): full-stack wiring of input injection, leakage in retrieved context, and output validation before render or write. - [How do I detect and block attacks at runtime?](https://arcjet.com/learn/detect-and-block-attacks-at-runtime): taxonomy of injection, abuse, exfiltration, and agent manipulation, plus in-process instrumentation in Node.js, Python, and Go. - [Which runtime security tools integrate with LangChain or AutoGPT?](https://arcjet.com/learn/runtime-security-langchain-autogpt-crewai): hooks that can still deny. LangChain Python, LangChain JS, LangGraph JS, CrewAI, and the Vercel AI SDK have published wrappers. AutoGPT does not. - [How do I prevent an AI agent from taking irreversible actions?](https://arcjet.com/learn/prevent-irreversible-ai-agent-actions): classify transfer, delete, send, and prod config; deny by default; a human click is a hold, not a policy. - [How do I implement the OWASP Top 10 for LLM Applications?](https://arcjet.com/learn/owasp-top-10-llm-implementation): a runtime control and short snippet for each 2026 OWASP GenAI item, not another list summary. - [What changes for application security when your app calls an LLM?](https://arcjet.com/learn/ai-security-for-developers): the new attack surface for developers: user prompt injection, retrieved-document injection, downstream output, and model-API supply chain. - [Eve agent security is three jobs](https://arcjet.com/learn/eve-framework-agent-security): screen inbound channel text, gate tools and connections, and treat hooks as observe-only. - [How to secure a Mastra agent](https://arcjet.com/learn/secure-mastra-agent): screen inbound Mastra messages with guardProcessor, wrap authored createTool execute with guardTool, and deny unwrapped MCP, workspace, and toolset tools with guardHooks. - [Claude Agent SDK security](https://arcjet.com/learn/claude-agent-sdk-security): screen inbound prompts on UserPromptSubmit and deny tools at PreToolUse. guardTool wraps authored tool(); guardHooks covers inbound text and unwrapped built-ins. - [Human approval is not a security policy](https://arcjet.com/learn/human-approval-is-not-a-security-policy): a click is a hold, not a remote allow or deny. Eve user-approval, Mastra requireApproval, and Claude canUseTool park a call; they do not decide it. - [canUseTool is not a policy gate](https://arcjet.com/learn/canusetool-is-not-a-policy-gate): Claude's canUseTool looks like a gate. allowedTools, allow rules, and bypassPermissions / acceptEdits skip it. A Bash or Write in allowedTools never hits the callback. - [How to detect prompt injection in an Eve agent](https://arcjet.com/learn/eve-agent-prompt-injection): screen the Slack or GitHub body with guardInbound and detectPromptInjection after the signature check, before the Eve agent starts. - [How to secure Eve MCP connections](https://arcjet.com/learn/eve-mcp-connection-security): secure Eve MCP and OpenAPI connections with guardApproval() on the connection's approval field. There is no local execute to wrap. - [How to secure Mastra MCP tools](https://arcjet.com/learn/mastra-mcp-tool-security): gate Mastra MCP, workspace, and toolset tools with guardHooks so beforeToolCall can deny. Do not also wrap the same authored tool with guardTool. - [How to screen inbound prompts in Claude Agent SDK](https://arcjet.com/learn/claude-agent-sdk-inbound-prompts): screen inbound prompts on guardHooks({ inbound }) via UserPromptSubmit. A DENY returns { decision: "block" } and Claude Code erases the prompt. - [How to block Bash in Claude Agent SDK](https://arcjet.com/learn/block-bash-claude-agent-sdk): deny Bash on PreToolUse. That is the only labeled deny for unwrapped built-in tools. canUseTool and a bare allowedTools name are not a policy gate. - [How do I secure an OpenAI Agents SDK agent?](https://arcjet.com/learn/openai-agents-sdk-security): screen user text with a direct guard() before run(). Wrap authored tool() with guardTool from @arcjet/guard/openai-agents/v0. Hosted tools, MCP, handoffs, Realtime, and Sandbox are not deny points. - [How do I screen inbound prompts in OpenAI Agents SDK?](https://arcjet.com/learn/openai-agents-sdk-inbound-prompts): there is no guardInbound. Direct guard() before run(). On DENY do not call run(). Direct guard() fails open; gate hasFailedOpen() to fail closed. - [How do I secure a LangGraph JS agent?](https://arcjet.com/learn/langgraph-js-agent-security): Graph API (StateGraph + ToolNode) only. Screen with guard() before graph.invoke. guardTool for authored tools; guardToolNode in place for unwrapped and MCP tools. interrupt() is HITL. - [How do I secure MCP tools in LangGraph?](https://arcjet.com/learn/langgraph-mcp-tool-security): MCP and unwrapped tools execute inside ToolNode. Graph hooks and HITL cannot stop tool.invoke. guardToolNode is the gate. Wrap in place; a copy leaves the original unguarded. - [How do I secure a LangChain Python agent?](https://arcjet.com/learn/langchain-python-agent-security): guard_action on a callable; guard_tool on a BaseTool (arcjet[langchain]); ArcjetMiddleware + ToolPolicy on create_agent (arcjet[langchain-agents]). ArcjetCaptureHandler cannot deny. - [How do I secure a Vercel AI SDK agent?](https://arcjet.com/learn/vercel-ai-sdk-agent-security): guardTool + createAgentContext + aiToolsContext on generateText. The wrapped tool must have execute and cannot already declare contextSchema. On deny the model gets ArcjetDenialResult. - [Claude Agent SDK security guide](https://arcjet.com/learn/claude-agent-sdk-security-guide): Use this eight-item guide on Claude Agent SDK 0.3.x. Pin the bundled binary, screen prompts on UserPromptSubmit, wrap authored tools, deny Bash on PreToolUse, and run the same scanners in CI that you run in the editor. - [Claude Managed Agents security guide](https://arcjet.com/learn/claude-managed-agents-security-guide): Use this eight-item guide on Anthropic's hosted agent harness. Screen inbound user.message with guardEvents, gate custom tools on agent.custom_tool_use, deny with is_error rather than a throw, and guard any MCP server you host. - [CrewAI security guide](https://arcjet.com/learn/crewai-security-guide): Use this eight-item guide on official CrewAI 1.15.x. Screen text before kickoff(), register PRE_TOOL_CALL, wrap standalone BaseTool.run(), and run the same scanners in CI that you run in the editor. - [Genkit security guide](https://arcjet.com/learn/genkit-security-guide): Use this eight-item guide on Genkit JS 1.41.x. Screen text before generate(), wrap the returned ToolAction, pass guardMiddleware, and run the same scanners in CI that you run in the editor. - [Google ADK security guide](https://arcjet.com/learn/google-adk-security-guide): Use this eight-item guide on @google/adk 2.x TypeScript. Screen text before runner.runAsync, put guardPlugin first in the Runner plugins list, deny by returning the deny dict, and correlate on an ID you own. - [LangChain security guide](https://arcjet.com/learn/langchain-security-guide): Use this eight-item guide on LangChain Python 1.3 and JS 1.2.34+. Screen text before invoke, wrap authored tools or put policy on middleware, and run the same scanners in CI that you run in the editor. - [LangGraph security guide](https://arcjet.com/learn/langgraph-security-guide): Use this eight-item guide on LangGraph JS 1.4.x StateGraph plus ToolNode. Wrap authored tools, wrap ToolNode in place for MCP, read arcjetDenied on the payload, and run the same scanners in CI that you run in the editor. - [Mastra security guide](https://arcjet.com/learn/mastra-security-guide): Use this eight-item guide on Mastra 1.63.x. Screen with guardProcessor, wrap createTool, deny MCP with guardHooks, and run the same scanners in CI that you run in the editor. - [OpenAI Agents security guide](https://arcjet.com/learn/openai-agents-security-guide): Use this eight-item guide on OpenAI Agents JS 0.17+ and Python 0.19+. Screen text before run(), wrap authored tools, return a denial instead of throwing, and run the same scanners in CI that you run in the editor. - [Strands Agents security guide](https://arcjet.com/learn/strands-agents-security-guide): Use this eight-item guide on @strands-agents/sdk 1.x. Wrap authored tools, pass guardHooks as a Plugin, deny with BeforeToolCallEvent.cancel, and run the same scanners in CI that you run in the editor. - [TanStack AI security guide](https://arcjet.com/learn/tanstack-ai-security-guide): Use this eight-item guide on @tanstack/ai 0.8+. Screen text before chat(), put guardMiddleware first in the middleware array, deny by skipping rather than throwing from execute, and correlate on an ID you own. - [Vercel AI SDK security guide](https://arcjet.com/learn/vercel-ai-sdk-security-guide): Use this eight-item guide on Vercel AI SDK 7. Screen text before generateText, wrap tool({ execute }), use guardAction for app-invoked work, and run the same scanners in CI that you run in the editor. - [Vercel Eve security guide](https://arcjet.com/learn/vercel-eve-security-guide): Use this eight-item guide on Eve 0.47.x. Screen inbound text after the signature check, wrap authored execute, put guardApproval on every MCP or OpenAPI connection, and run the same scanners in CI that you run in the editor. - [needsApproval and LangGraph interrupt() are not a security policy](https://arcjet.com/learn/needsapproval-and-interrupt-are-not-a-policy): OpenAI Agents needsApproval / hosted requireApproval and LangGraph interrupt() park a call. They do not decide it. Extends the live Eve/Mastra/Claude HITL page. - [Content moderation for AI applications](https://arcjet.com/learn/content-moderation-for-ai-applications): screen user prompts before AI-generated content, including image prompts. Samples for the Vercel Chat SDK, LangChain, the Claude SDK, and the OpenAI SDK. - [How to identify AI agents and bots](https://arcjet.com/learn/identify-ai-agents-and-bots): AI agent identification is determining whether an HTTP client is a named automated agent, then verifying that claim, so you can allow useful crawlers and assistants while denying impersonators. - [Bot detection techniques for developers](https://arcjet.com/learn/bot-detection-techniques): Bot detection is classifying automated HTTP clients and enforcing a policy: allow a verified crawler, constrain a script, or deny abuse. - [Is CAPTCHA still effective, and what should you use instead?](https://arcjet.com/learn/captcha-alternatives): CAPTCHA is no longer an effective primary control against modern, economically motivated abuse. - [What is bot spoofing and how do you detect it?](https://arcjet.com/learn/detect-bot-spoofing): Bot spoofing is a client claiming to be a known, usually trusted, automated agent while actually being someone else. - [How do you detect bots at the application layer?](https://arcjet.com/learn/application-layer-bot-detection): Application-layer bot detection classifies and constrains automated clients inside the request handler, where you can see the route, user, body, and business operation. - [How to protect a React Hook Form from spam](https://arcjet.com/learn/protect-react-hook-form-spam): Protect a React Hook Form by combining client and server validation, rate limiting, bot detection, and email verification. - [How do you secure a Node.js/Express API?](https://arcjet.com/learn/secure-express-api): Secure a Node.js/Express API by calling Arcjet `protect()` in middleware before the route runs. - [How do you secure a GraphQL API?](https://arcjet.com/learn/graphql-security): GraphQL security is more than an HTTP rate limit: one request can batch, alias, or nest enough work to exhaust the process. - [How do you add rate limits and bot detection to a GraphQL API?](https://arcjet.com/learn/graphql-rate-limiting-bot-detection): On Yoga and Next.js, call Arcjet `protect()` on the GraphQL route with a token bucket (`interval` in seconds, plus `requested` tokens) and a `detectBot` allow list. - [How do you secure serverless and edge apps?](https://arcjet.com/learn/serverless-edge-security): Serverless and edge apps expose many independently invocable functions, so a perimeter WAF is not enough. - [Are Next.js server actions a security risk?](https://arcjet.com/learn/nextjs-server-action-security): Yes: a server action is a public HTTP API with a generated ID. - [Were you affected by the Next.js middleware bypasses?](https://arcjet.com/learn/nextjs-middleware-bypass-cve-2025-29927): Yes if you ran unpatched Next.js and used middleware as your only authorization check. - [How do you prevent SQL injection and XSS in Node.js?](https://arcjet.com/learn/prevent-sql-injection-xss-nodejs): SQL injection is untrusted input that becomes SQL syntax. - [Does Next.js need a WAF?](https://arcjet.com/learn/does-nextjs-need-a-waf): Yes. - [What is permissions-based security in Next.js?](https://arcjet.com/learn/nextjs-authorization-permitio): Authentication names the user, Permit.io decides whether that identity may act on an object, and Arcjet stops attacks and abuse on the request. - [How to differentiate DoS attacks from legitimate traffic](https://arcjet.com/learn/dos-vs-legitimate-traffic): A DoS attack exhausts a resource so intended clients cannot use it. - [How to add rate limiting to SvelteKit form actions](https://arcjet.com/learn/rate-limit-sveltekit-form-actions): Rate limiting controls how many actions an identity can perform in a period. - [Dynamic rate limiting with feature flags](https://arcjet.com/learn/dynamic-rate-limiting-feature-flags): You change rate limits at runtime without redeploying, driven by a feature flag. - [Next.js security checklist](https://arcjet.com/learn/nextjs-security-checklist): Use this seven-item checklist on Next.js 14 and 15 App Router apps. - [Remix security checklist](https://arcjet.com/learn/remix-security-checklist): React Router 7 superseded Remix. - [Web app security checklist](https://arcjet.com/learn/web-app-security-checklist): After you follow the framework guides, check whether the live app can tell bots from humans, rate-limit the costly operation, enforce product rules on automatable flows, reject bad requests before they cost money, and log decisions you can alert on. - [How to secure login pages](https://arcjet.com/learn/secure-login-pages): Plan for four threats: brute force, credential stuffing, SQL injection, and session theft. - [Security advice for self-hosting Next.js in Docker](https://arcjet.com/learn/self-host-nextjs-docker-security): Pin `node:22-bookworm` for builds and `node:22-bookworm-slim` or `gcr.io/distroless/nodejs22-debian13:nonroot` for the runner. - [How to secure your NestJS application with Arcjet](https://arcjet.com/learn/secure-nestjs-application): `@arcjet/nest` v1.0 is ESM-only. Register ArcjetModule, apply ArcjetGuard, and overlay per-route rules with WithArcjetRules. - [Structured logging in JSON for Next.js](https://arcjet.com/learn/nextjs-structured-logging): Use Pino from `instrumentation.ts` on the Node runtime. - [Protecting self-hosted Coolify apps](https://arcjet.com/learn/secure-coolify-apps): Install Tailscale first, deny inbound except `tailscale0`, then install Coolify on the tailnet. - [How do I deploy an Arcjet-protected app to Fly.io?](https://arcjet.com/learn/secure-fly-io-apps): Install the Arcjet SDK, set ARCJET_KEY as a Fly secret, and deploy. - [How do I secure a container deployment?](https://arcjet.com/learn/secure-container-deployments): Run Node 22 on a current minimal image (Distroless Debian 13, Wolfi, or Alpine), set USER to non-root, mount the root read-only, keep secrets out of layers, and scan the image in CI. - [Why do TLS certificates fail in slim Node.js containers on OrbStack?](https://arcjet.com/learn/secure-local-dev-servers-orbstack): Node.js trusts a frozen Mozilla CA snapshot, not OrbStack's local CA. - [How do I detect the client IP on Firebase?](https://arcjet.com/learn/detect-client-ip-firebase): Firebase sits behind Google proxies, so X-Forwarded-For is spoofable and their published IPs drift. - [Why do NEXT_PUBLIC_ variables leak into the Next.js client bundle?](https://arcjet.com/learn/secret-scanning-nextjs): Any NEXT_PUBLIC_ value is public. - [How do I test Next.js App Router API routes?](https://arcjet.com/learn/test-nextjs-api-routes): Use next-test-api-route-handler as the first import, run Vitest in the node environment, and mock auth() from Auth.js v5. - [How do I functionally test security rules?](https://arcjet.com/learn/functional-testing-security-rules): Send the traffic that should trip each LIVE rule and assert the status. - [What is a race condition attack?](https://arcjet.com/learn/race-condition-attacks): A race condition attack exploits a race window: two tasks operate on the same data at once, and the intended order is not enforced. - [What is a dependency confusion attack?](https://arcjet.com/learn/dependency-confusion-attacks): A dependency confusion attack (substitution attack) tricks the installer into taking a public package that shares an internal name when the public version is higher. - [What is a trivial package?](https://arcjet.com/learn/trivial-packages-security): Concordia University (Abdalkareem et al., FSE 2017) defined trivial packages as low-complexity dependencies under 35 lines and found they were 16.8% of studied npm packages. - [What is package hijacking?](https://arcjet.com/learn/package-hijacking): Hijacking is an ownership failure, not a name collision. - [What is secrets exfiltration?](https://arcjet.com/learn/secrets-exfiltration): Secrets exfiltration is how access material leaves a place you thought was private. - [Should you store secrets in environment variables?](https://arcjet.com/learn/storing-secrets-env-vars): Short answer: avoid env vars for production secrets. - [How do you redact sensitive data from Go logs?](https://arcjet.com/learn/redact-sensitive-data-logs): The direct answer is slog.LogValuer: list the fields that may appear. - [PII detection for production AI applications](https://arcjet.com/learn/pii-detection-for-ai-applications): per-framework placement, latency budgets, fail-open versus fail-closed per route, staged rollout, and the security review answers. ## Comparisons Product-versus-product and category pages on arcjet.com/compare. FAQPage JSON-LD is generated from each page's FAQ data. - [How Arcjet compares](https://arcjet.com/compare): index of product and category comparisons. - [Arcjet build vs buy](https://arcjet.com/compare/arcjet-build-vs-buy): every Arcjet control with its API surface and local-versus-cloud split, then the build-versus-buy case argued from primary sources, including when building in-house is the right call. - [AI agent security platforms](https://arcjet.com/compare/ai-agent-security-platforms): maps identity, gateways, guardrails, observability, and in-code enforcement by what each layer does, what it cannot do, and who is in it. - [Rein vs Arcjet](https://arcjet.com/compare/rein-vs-arcjet): two in-app approaches — Rein's agent sidecar versus Arcjet in request handlers and on tool calls. - [Datadog AI Guard vs Arcjet](https://arcjet.com/compare/datadog-ai-guard-vs-arcjet): Datadog's remote evaluator versus in-app rules and local content inspection. - [Cloudflare vs Arcjet](https://arcjet.com/compare/cloudflare-vs-arcjet): edge WAF, bots, rate limiting, and AI Gateway versus security inside the app and on agent tool calls. - [Vercel WAF vs Arcjet](https://arcjet.com/compare/vercel-waf-vs-arcjet): Vercel edge WAF versus security inside the application on any host. - [Vercel BotID vs Arcjet](https://arcjet.com/compare/vercel-botid-vs-arcjet): inbound browser bot check versus rules-as-code bot detection in request handlers. - [Aikido Zen Firewall vs Arcjet](https://arcjet.com/compare/aikido-vs-arcjet): monkey-patching runtime firewall versus a library you call. - [CAPTCHAs vs Arcjet](https://arcjet.com/compare/captchas-vs-arcjet): Turnstile, reCAPTCHA, and hCaptcha versus Arcjet advanced bot signals. - [Lakera alternatives](https://arcjet.com/compare/lakera-alternatives): content inspection versus in-code enforcement, and which job each vendor in the space actually does. - [Agent framework security](https://arcjet.com/compare/agent-framework-security): hub for Eve, Mastra, Claude Agent SDK, OpenAI Agents SDK, LangGraph JS, and LangChain Python. One row per stack: inbound screen, authored-tool deny, MCP deny, HITL trap. - [OpenAI Agents guardrails vs Arcjet](https://arcjet.com/compare/openai-agents-guardrails-vs-arcjet): SDK tripwires (tripwireTriggered, rejectContent) versus an in-process deny you call (guard() / guardTool). They are not the same control. - [OpenAI Agents SDK vs Claude Agent SDK](https://arcjet.com/compare/openai-agents-sdk-vs-claude-agent-sdk): two first-party agent SDKs. Claude: UserPromptSubmit + PreToolUse. OpenAI Agents: guard() before run(), guardTool on authored invoke only; no MCP/hosted deny. ## Product - [AI abuse protection](https://arcjet.com/ai-abuse-protection): bots, prompt injection, and budgets for AI endpoints. ## Reference guides ### Features - [Shield](https://docs.arcjet.com/shield) - [Rate limiting](https://docs.arcjet.com/rate-limiting) - [Bot protection](https://docs.arcjet.com/bot-protection) - [Email validation](https://docs.arcjet.com/email-validation) - [Sensitive information](https://docs.arcjet.com/sensitive-info) - [Prompt injection](https://docs.arcjet.com/prompt-injection) - [Content moderation](https://docs.arcjet.com/content-moderation) - [Signup form protection](https://docs.arcjet.com/signup-protection) - [Filters](https://docs.arcjet.com/filters) - [AI protection](https://docs.arcjet.com/ai-protection) - [Guards](https://docs.arcjet.com/guards) - [Agent guard quick start](https://docs.arcjet.com/guards/quick-start) - [Agent guard integrations](https://docs.arcjet.com/guards/framework-integrations) - [Agent guard remote policies](https://docs.arcjet.com/guards/remote-policies) - [Agent guard testing and reference](https://docs.arcjet.com/guards/reference) - [Capture events](https://docs.arcjet.com/guards/capture) - [Vercel AI SDK agent guard](https://docs.arcjet.com/guards/vercel-ai) - [LangChain agent guard](https://docs.arcjet.com/guards/langchain) - [CrewAI agent guard](https://docs.arcjet.com/guards/crewai) - [LangGraph agent guard](https://docs.arcjet.com/guards/langgraph) - [Genkit agent guard](https://docs.arcjet.com/guards/genkit) - [Google ADK agent guard](https://docs.arcjet.com/guards/google-adk) - [OpenAI Agents agent guard](https://docs.arcjet.com/guards/openai-agents) - [Strands Agents agent guard](https://docs.arcjet.com/guards/strands-agents) - [TanStack AI agent guard](https://docs.arcjet.com/guards/tanstack-ai) - [Vercel Eve agent guard](https://docs.arcjet.com/guards/vercel-eve) - [Mastra agent guard](https://docs.arcjet.com/guards/mastra) - [Claude Agent SDK agent guard](https://docs.arcjet.com/guards/claude-agent-sdk) - [Claude Managed Agents agent guard](https://docs.arcjet.com/guards/claude-managed-agents) - [Nosecone security headers](https://docs.arcjet.com/nosecone/quick-start) - [`@arcjet/redact`](https://docs.arcjet.com/redact/quick-start) ### SDKs - [Astro](https://docs.arcjet.com/reference/astro) - [Bun](https://docs.arcjet.com/reference/bun) - [Deno](https://docs.arcjet.com/reference/deno) - [Fastify](https://docs.arcjet.com/reference/fastify) - [Go](https://docs.arcjet.com/reference/go) - [NestJS](https://docs.arcjet.com/reference/nestjs) - [Next.js](https://docs.arcjet.com/reference/nextjs) - [Node.js](https://docs.arcjet.com/reference/nodejs) - [Nuxt](https://docs.arcjet.com/reference/nuxt) - [Python](https://docs.arcjet.com/reference/python) - [React Router](https://docs.arcjet.com/reference/react-router) - [Remix](https://docs.arcjet.com/reference/remix) - [SvelteKit](https://docs.arcjet.com/reference/sveltekit) ## Support See the troubleshooting guide at https://docs.arcjet.com/troubleshooting. For help, email or [join the Discord server](https://arcjet.com/discord). ## Blog Original engineering posts at https://blog.arcjet.com: - [How we achieve our 25ms p95 response time SLA](https://blog.arcjet.com/how-we-achieve-our-25ms-p95-response-time-sla/): local Wasm analysis, a regional gRPC API, persistent HTTP/2, and deny caching keep Cloud API p95 around 25ms (goal 20-30ms). - [Making Arcjet's Wasm bot detector smaller and faster](https://blog.arcjet.com/making-arcjets-wasm-bot-detector-smaller-and-faster/): the Rust Wasm bot detector shrank 27% with Aho-Corasick, keeping per-request memory isolation and Wizer snapshots. - [Introducing Arcjet Guards](https://blog.arcjet.com/introducing-arcjet-guards-security-inside-the-agent-loop/): Guards run security rules inside agent tool handlers, queue consumers, and workflow steps that have no HTTP request. - [Introducing the Arcjet Python SDK](https://blog.arcjet.com/introducing-the-arcjet-python-sdk-beta/): Python SDK for FastAPI and Flask with rate limiting, bot detection, email validation, and signup protection as application-native rules. - [Designing a CLI for AI agents](https://blog.arcjet.com/designing-a-cli-for-ai-agents/): Arcjet CLI as a stable contract for humans and agents — machine-readable output, strict validation, and confirmation before production changes. - [How we defend MCP tool outputs from prompt injection](https://blog.arcjet.com/how-we-defend-mcp-tool-outputs-from-prompt-injection/): trusted MCP tool guidance must never contain untrusted text; put raw tool output in labeled untrustedData. - [Announcing advanced bot signals](https://blog.arcjet.com/announcing-advanced-bot-signals-to-detect-automation-without-captchas/): browser telemetry without a CAPTCHA, evaluated server-side when the request hits a critical flow.