Arcjet
Contents
Python Guard adapters for LangChain, CrewAI, OpenAI Agents, Claude Agent SDK, Claude Managed Agents, and Strands Agents are dedicated skills (see Step 3). JS Guard adapters are per-file references under references/guards_js_*.md. Shared fundamentals stay in references/guards_javascript.md and references/guards_python.md.
Add Arcjet protection to your app
Checklist
Step 1: Check language support
If the project's server-side code is not JavaScript, TypeScript, Python, or Go → tell the user in chat that Arcjet doesn't support their language yet. Don't modify the project, don't write a NOTES.md, don't invent a package. Just say it and stop.
Step 2: Get an ARCJET_KEY into the project's env file
Before writing any code, the project needs a real ARCJET_KEY in its env file. Don't write Arcjet code first and "leave the key as a TODO" – that just produces dead code. Get the key first, then wire it up.
In order of preference:
- Arcjet CLI (preferred). Check whether you're already signed in, then retrieve a key.
- Arcjet MCP server (endpoint:
https://api.arcjet.com/mcp) – for clients with built-in MCP. See references/mcp.md.
- Manual (last resort): tell the user to grab a key from https://console.arcjet.com.
CLI bootstrap (the normal path)
npx -y @arcjet/cli@latest auth status # is the user already signed in?
# if not signed in:
npx -y @arcjet/cli@latest auth login # browser device flow, see references/cli.md
npx -y @arcjet/cli@latest teams list --output json --fields id,name
npx -y @arcjet/cli@latest sites list --team-id <team_id> --output json --fields id,name
# if no suitable site exists:
npx -y @arcjet/cli@latest sites create --team-id <team_id> --name "<project>"
npx -y @arcjet/cli@latest sites get-key --site-id <site_id> --output json --fields key
Write the key value to the project's env file as ARCJET_KEY=ajkey_.... Match whatever the project already does – filename, .env.example companion, .gitignore entry. If the project doesn't have a convention yet, default to whatever the framework expects and add the env file to .gitignore. Never hardcode the key in source.
See references/cli.md for install options beyond npx, agent-mode flags, and the full command reference.
Install the SDK with the project's package manager
Once you know which SDK you need (see Step 3), install it with the package manager the project already uses: npm install, pnpm add, yarn add, bun add, pip install, uv add, poetry add, or go get. Don't hand-edit package.json / requirements.txt / go.mod and guess a version: typed versions go stale (@arcjet/next is currently 1.11.0; Python arcjet is 1.0.0; Go must use the module tag, not a copied pseudo-version), and the lockfile/module metadata won't get updated. Let the package manager pick the real version and pin it.
Step 3: Detect protection type and read the reference
Determine which protection type applies:
|
Request-based |
Guard |
| When to use |
Code has an HTTP request object (Express req, Next.js Request, FastAPI Request) |
No HTTP request (tool calls, MCP handlers, queue workers, background jobs, agent loops) |
| JS/TS SDK |
Framework adapters such as @arcjet/next, @arcjet/node, @arcjet/fastify |
@arcjet/guard |
| Python SDK |
arcjet (with arcjet() / arcjet_sync()) |
arcjet (with launch_arcjet() / launch_arcjet_sync()) |
| Go SDK |
github.com/arcjet/arcjet-go (with NewClient) |
github.com/arcjet/arcjet-go (with NewGuardClient) |
| Entry point |
protect(request) / Protect(ctx, r) |
guard(label, rules) / Guard(ctx, request) |
A single project can use both – for example, request-based on API routes and Guard on agent tool calls. If the project already uses a supported agent framework, prefer the official wrapper over hand-wrapping every tool. In Python, load the dedicated skill (table below) — not a raw guard() around every callable, and not the JS @arcjet/guard/... path. In JavaScript, load fundamentals plus exactly one adapter file from the JS table — not the sibling adapters.
Common misclassifications to watch for:
- MCP servers: the word "server" is misleading. MCP tools don't receive HTTP requests – they're invoked by an MCP client over stdio or SSE. Use Guard, not request-based.
- Background jobs / queue consumers: no HTTP request at the protection site. Use Guard.
- Server actions / RPC over HTTP (Next.js server actions, tRPC): there is an HTTP request underneath. Use request-based.
- Agent tool calls inside a request handler: if you want to limit per-user-per-route, request-based is fine. If you want per-tool budgets independent of any HTTP boundary, use Guard at the tool call site.
Read the appropriate reference:
- Request-based JS/TS: references/requests_javascript.md
- Request-based Python: references/requests_python.md
- Request-based Go: references/requests_go.md
- Guard JS/TS fundamentals: references/guards_javascript.md — then exactly one adapter file from the table in that file (source of truth; do not copy it here)
- Guard Python: references/guards_python.md
- Guard Go: references/guards_go.md
When the project already uses an official JS agent framework, open the fundamentals file and load exactly one adapter from its table. Do not open sibling adapters.
When the project already uses an official Python agent framework, load the dedicated skill instead of the long adapter sections that used to live in the Python Guard reference:
These references explain architectural decisions and patterns that can't be inferred from the source code alone. For exact API signatures, read the installed package's types and doc comments.
Step 4: Implement protection
Follow the patterns in the reference file from Step 3. Key principles:
Request-based (HTTP routes):
- Create shared clients outside handlers and include Shield as a base rule. Use the exact constructor and rule names from the language reference. JS HTTP rules omitted
mode default to DRY_RUN – pass mode: "LIVE" to enforce. Python HTTP factories require mode= (TypeError if omitted). Go HTTP Protect rules also default to dry run – set Mode: arcjet.ModeLive to enforce.
- In JavaScript/TypeScript, create one
arcjet() client and use withRule() for route-specific extras so clones share the decision cache. Check decision.isDenied(). Sibling arcjet() constructors do not share cache. detectBot requires exactly one of allow or deny (neither or both throws).
- In Python, create one
arcjet() / arcjet_sync() client and use with_rule() for route-specific extras so clones share DecisionCache. Check decision.is_denied(). Sibling constructors do not share cache.
- In Go, create one
NewClient at package scope. WithRule() derives route-specific clients that share the parent cache, and returns (*Client, error), so handle initialization errors. Check decision.IsDenied(). Separate NewClient calls do not share cache.
- Call
protect() / Protect() inside each route handler (not in app-level middleware), once per request.
- Map denial reasons to HTTP responses. Only branch on reasons that produce a different response – there is no point in a Shield-specific arm that returns the same status as the default 403.
- Put the language's
userId characteristic selector on the specific rule that needs it, then pass a trusted, authenticated user ID at protection time. Never rate limit by a client-controlled header unless a trusted proxy strips and rewrites it.
- Treat client-IP provenance as security configuration. JavaScript, Python, and Go may fall back to common forwarding headers when no usable public address is available and produce one
client_ip_provenance="unverified-header" warning for the lifetime of each SDK client instance. Configure every trusted proxy and verify the application is reachable only through infrastructure that overwrites or safely appends forwarding headers. Never silence the warning by copying a client-controlled header into a manual override.
- If the application already has an independently trusted client IP, pass it explicitly:
ipSrc (JS), ip_src (Python; also set disable_automatic_ip_detection=True when constructing the client), or WithIPSrc (Go). The SDKs reject malformed values, although JS treats an empty ipSrc as omitted. Syntax validation does not prove provenance. Before shipping, inspect representative requests with JS clientIpDetails() / findIpDetails(), Python client_ip_details(), or Go ClientIPDetails().
protect() accepts nested-JSON metadata (same shape as Guard). It does not affect fingerprinting. Do not put secrets or PII in it. When present, request decisions also expose optional IP threat intelligence (decision.ip.threat / ip_details.threat / IP.Threat).
Guard (non-HTTP code):
- Client at module scope:
launchArcjet() (JS), launch_arcjet() / launch_arcjet_sync() (Python – match async vs sync), NewGuardClient (Go).
- Rules at module scope. One
guard() per operation with a hardcoded slug label (tools.get-weather). Interpolated labels break grep and Console grouping. Slugs: lowercase letters, digits, -, . only; start and end with a letter or digit; max 256 bytes.
metadata is nested JSON for audit only. No secrets or PII. capture() is visibility, never a decision – flush on shutdown. Python helper success is not "the action ran" – see the Python Guard reference.
- Free
guard() (JS/Python registration) fail-opens if nothing is registered. Go has no registration API. Prefer an explicit client.
- Prefer official wrappers over hand-wrapping. Import the versioned JS path and load that adapter file from Step 3. Load the dedicated Python skill from Step 3. Unversioned
@arcjet/guard/<adapter> aliases do not resolve.
- Branch on which rule denied, not just
DENY. Guard decision.reason is a flat string ("PROMPT_INJECTION") and is undefined on ALLOW. A denial by one rule still spends the others' budget in the same rules array – split calls if a PII false positive must not drain a rate limit.
- Every rate-limit rule needs a
key and a bucket. Use a trusted user/session id when you have one; otherwise a stable identifier you control.
JS adapters: wrappers fail closed; core guard() fails open. Load the Step 3 file for the project's framework. Unpublished adapters (Google ADK, TanStack AI, Claude Managed Agents) pin a git SHA in that file — do not npm install @arcjet/guard and import them from 1.11.0.
Python: guard_action / guard_action_sync is core. LangChain, CrewAI, and OpenAI Agents ship in PyPI arcjet 1.0.0. Claude Agent SDK, Claude Managed Agents, and Strands Agents still need the git pin in their dedicated skill. There is no arcjet[crewai] extra.
Traps that run without error and enforce nothing (full write-up in the Guard references and https://docs.arcjet.com/llms.txt):
- Guard has no
sensitiveInfo rule – that is HTTP protect() only. Use localDetectSensitiveInfo / LocalDetectSensitiveInfo.
- Python
LocalDetectSensitiveInfo() with neither allow nor deny fail-opens as ALLOW (AJ1203). Always pass a list. JS works with no args; still pass a list.
- The sensitive-info rule does not inherit the client's backend. Entity types beyond
EMAIL / PHONE_NUMBER / IP_ADDRESS / CREDIT_CARD_NUMBER need backend on the rule. Share one Rampart instance with the client.
- Typed
inputs reach a remote policy from every Python adapter, but in JS only from @arcjet/guard/vercel-ai/v7. Elsewhere use SDK rules or the published policy will not fire. JS builders: policyInput.server / policyInput.local. Python: server_input / local_input.
- A missing decision is not a denial. If the model asks a question or masks values itself, Guard never runs. Verify in Console/CLI.
- Guarding one tool only helps if it is the only path. Claude Agent SDK needs
settingSources: [] / setting_sources=[] and strictMcpConfig: true / strict_mcp_config=True.
- HITL (
needsApproval, human_input, can_use_tool, event.interrupt(), always_ask) is not a policy gate.
Conventions outside the Arcjet flow
For everything that isn't an Arcjet-specific decision – dev scripts, file/module layout, named-vs-default exports, comment style, env-file naming, type hints, error class patterns – match the project's existing conventions. If the project has no convention yet, default to modern best practice for the language. This skill is opinionated about where Arcjet goes and how its API is used; it must not reach further than that.
Step 5: Verify decisions
After wiring up protection, confirm it's actually firing. Three steps:
1. Type-check / build first. Run tsc, next build, python -m py_compile, or whatever check command the project uses. Catches wrong imports, wrong rule names, and stale type signatures before the user does.
2. Trigger a real call so a decision exists to check. Without one, the Console and CLI are empty and you can't tell whether protection is actually wired up.
- Request-based: start the dev server (
npm run dev, uvicorn main:app --reload) and curl the protected route with the method the route actually accepts (curl -I is HEAD – FastAPI GET-only routes answer 405, not the documented 403). Empty bots.allow / allow=[] denies CURL before later rules run – temporarily allow: ["CURL"] when you need to reach email or rate-limit checks, then drop it. To trip a rate limit: for i in {1..50}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/your-route; done.
- Guard: invoke the protected function so a decision exists. A missing decision is not a denial – read Console/CLI, not the absence of a side effect. Don't test Guard by
curling anything.
3. Confirm the decision in the Arcjet platform.
- CLI:
npx -y @arcjet/cli@latest requests list --site-id <id> (request-based) or ... guards list --site-id <id> (Guard)
- MCP:
list-requests / list-guards
- Console: https://console.arcjet.com
For deeper investigation: arcjet requests explain --site-id <id> --request-id <id> or arcjet guards explain --site-id <id> --guard-id <id>.
If you can't run the app in the current environment, tell the user exactly what to do (which command to run, what to look for in the output) instead of silently skipping verification.
Gotchas
- Wrong SDK/client:
@arcjet/guard, arcjet.guard, and Go's NewGuardClient are for non-HTTP code. @arcjet/node / @arcjet/next / Python arcjet() / Go NewClient are for HTTP routes.
- Wrong placement:
protect() must not be called in Express middleware or Next.js middleware. Call it inside each route handler.
- Wrong layer for
guard(): don't put guard() in a generic dispatcher. Use the official wrapper from the table above, or put guard() inside the specific tool. Load the dedicated Python skill instead of hand-wrapping.
- Python adapter denials and HITL: load the dedicated skill. Capture handlers never block. HITL is not a policy gate. Helper
metadata.outcome details live in the Python Guard reference.
- Hand-edited dependency manifests: run the project's package manager so the version is real (
@arcjet/* 1.11.0, Python arcjet 1.0.0).
- Double-counting: calling
protect() or guard() multiple times for the same operation counts against rate limits multiple times.
- Client-IP warning bypass: never "fix" an
unverified-header warning by copying X-Forwarded-For into ipSrc / ip_src / WithIPSrc.
- JS denial envelopes: one
ArcjetDenialResult payload; delivery is per-framework. Read the adapter file from Step 3 before inventing a status or throwing. guardTool and guardAction are different handlers.
- Never hardcode
ARCJET_KEY – always use environment variables.
Choose protections
When you need to pick which rules address the user's concern – bot abuse, rate limits, prompt injection, signup spam, PII, or IP filtering – load references/choosing_protections.md. It maps common problems to Arcjet rules and explains the tradeoffs between strategies (for example token bucket vs sliding window). The mapping doesn't need to be in your context for the rest of the workflow.
Resources
For exact API signatures, parameter names, and the full set of rules and helpers, read the installed SDK's source – types and docstrings are the source of truth:
- Python SDK: https://github.com/arcjet/arcjet-py –
arcjet package (request protection) and arcjet.guard subpackage (non-HTTP guard).
- Python Guard integration skills: integrate-arcjet-guard-langchain-py, integrate-arcjet-guard-crewai, integrate-arcjet-guard-openai-agents-py, integrate-arcjet-guard-claude-agent-sdk-py, integrate-arcjet-guard-claude-managed-agents-py, integrate-arcjet-guard-strands-agents-py.
- JavaScript / TypeScript SDK: https://github.com/arcjet/arcjet-js – monorepo with framework-specific packages (
@arcjet/next, @arcjet/node, @arcjet/fastify, @arcjet/sveltekit, @arcjet/guard). JS Guard adapter files: references/guards_js_vercel_ai.md and siblings listed in Step 3.
- Go SDK: https://github.com/arcjet/arcjet-go –
github.com/arcjet/arcjet-go module with request and guard clients. go get ...@latest still resolves v0.1.0. v1.0.0-rc.1 exists as a pre-release; APIs described in the Go references live on the default branch / that rc.
- Docs: https://docs.arcjet.com – narrative guides, blueprints, and product reference.
- Console: https://console.arcjet.com – sites, keys, and decision history.
1---2name: arcjet-23description: Add Arcjet security protection to HTTP routes, AI agent tool calls, MCP servers, background jobs, and queue workers. Covers rate limiting, bot detection, email validation, prompt injection, sensitive information blocking (including Rampart NER), content moderation, capture/flush, remote Guard policies, typed inputs, and abuse prevention. Works in JavaScript/TypeScript, Python, and Go. HTTP frameworks share this skill. JS Guard adapters (Vercel AI SDK, Eve, Mastra, LangChain, LangGraph, OpenAI Agents, Genkit, Google ADK, Strands, TanStack AI, Claude Agent SDK, Claude Managed Agents) are per-adapter reference files loaded from Step 3 — do not keep the whole JS Guard reference in context. Official Python LangChain, CrewAI, OpenAI Agents, Claude Agent SDK, Claude Managed Agents, and Strands Agents have dedicated integrate-arcjet-guard skills. Use when the user wants security, rate limiting, bot protection, or abuse prevention – "protect my API," "rate limit tool calls," "block bots," "secure my endpoint," or "pre4license: Apache-2.05---67# Arcjet89## Contents1011- [Add Arcjet protection to your app](#add-arcjet-protection-to-your-app)12- [Choose protections](#choose-protections)13- [Resources](#resources)1415Python Guard adapters for LangChain, CrewAI, OpenAI Agents, Claude Agent SDK, Claude Managed Agents, and Strands Agents are dedicated skills (see Step 3). JS Guard adapters are per-file references under `references/guards_js_*.md`. Shared fundamentals stay in [references/guards_javascript.md](references/guards_javascript.md) and [references/guards_python.md](references/guards_python.md).1617## Add Arcjet protection to your app1819### Checklist2021- [ ] **Step 1:** Verify language support (JS/TS, Python, or Go only – stop if unsupported)22- [ ] **Step 2:** Connect to Arcjet platform (CLI → MCP → manual Console setup)23- [ ] **Step 3:** Detect protection type and read the appropriate reference file24- [ ] **Step 4:** Implement protection (separate client file, correct SDK, correct patterns)25- [ ] **Step 5:** Verify decisions are firing correctly (trigger a real call, then check CLI / MCP / Console)2627### Step 1: Check language support2829If the project's server-side code is not JavaScript, TypeScript, Python, or Go → tell the user in chat that Arcjet doesn't support their language yet. Don't modify the project, don't write a `NOTES.md`, don't invent a package. Just say it and stop.3031### Step 2: Get an `ARCJET_KEY` into the project's env file3233Before writing any code, the project needs a real `ARCJET_KEY` in its env file. Don't write Arcjet code first and "leave the key as a TODO" – that just produces dead code. Get the key first, then wire it up.3435**In order of preference:**36371. **Arcjet CLI** (preferred). Check whether you're already signed in, then retrieve a key.382. **Arcjet MCP server** (endpoint: `https://api.arcjet.com/mcp`) – for clients with built-in MCP. See [references/mcp.md](references/mcp.md).393. **Manual** (last resort): tell the user to grab a key from https://console.arcjet.com.4041#### CLI bootstrap (the normal path)4243```bash44npx -y @arcjet/cli@latest auth status # is the user already signed in?45# if not signed in:46npx -y @arcjet/cli@latest auth login # browser device flow, see references/cli.md4748npx -y @arcjet/cli@latest teams list --output json --fields id,name49npx -y @arcjet/cli@latest sites list --team-id <team_id> --output json --fields id,name50# if no suitable site exists:51npx -y @arcjet/cli@latest sites create --team-id <team_id> --name "<project>"5253npx -y @arcjet/cli@latest sites get-key --site-id <site_id> --output json --fields key54```5556Write the `key` value to the project's env file as `ARCJET_KEY=ajkey_...`. Match whatever the project already does – filename, `.env.example` companion, `.gitignore` entry. If the project doesn't have a convention yet, default to whatever the framework expects and add the env file to `.gitignore`. Never hardcode the key in source.5758See [references/cli.md](references/cli.md) for install options beyond `npx`, agent-mode flags, and the full command reference.5960#### Install the SDK with the project's package manager6162Once you know which SDK you need (see Step 3), install it with the package manager the project already uses: `npm install`, `pnpm add`, `yarn add`, `bun add`, `pip install`, `uv add`, `poetry add`, or `go get`. Don't hand-edit `package.json` / `requirements.txt` / `go.mod` and guess a version: typed versions go stale (`@arcjet/next` is currently `1.11.0`; Python `arcjet` is `1.0.0`; Go must use the module tag, not a copied pseudo-version), and the lockfile/module metadata won't get updated. Let the package manager pick the real version and pin it.6364### Step 3: Detect protection type and read the reference6566Determine which protection type applies:6768| | **Request-based** | **Guard** |69| --------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |70| **When to use** | Code has an HTTP request object (Express `req`, Next.js `Request`, FastAPI `Request`) | No HTTP request (tool calls, MCP handlers, queue workers, background jobs, agent loops) |71| **JS/TS SDK** | Framework adapters such as `@arcjet/next`, `@arcjet/node`, `@arcjet/fastify` | `@arcjet/guard` |72| **Python SDK** | `arcjet` (with `arcjet()` / `arcjet_sync()`) | `arcjet` (with `launch_arcjet()` / `launch_arcjet_sync()`) |73| **Go SDK** | `github.com/arcjet/arcjet-go` (with `NewClient`) | `github.com/arcjet/arcjet-go` (with `NewGuardClient`) |74| **Entry point** | `protect(request)` / `Protect(ctx, r)` | `guard(label, rules)` / `Guard(ctx, request)` |7576A single project can use both – for example, request-based on API routes and Guard on agent tool calls. If the project already uses a supported agent framework, prefer the official wrapper over hand-wrapping every tool. In Python, load the dedicated skill (table below) — not a raw `guard()` around every callable, and not the JS `@arcjet/guard/...` path. In JavaScript, load fundamentals plus **exactly one** adapter file from the JS table — not the sibling adapters.7778**Common misclassifications to watch for:**7980- **MCP servers**: the word "server" is misleading. MCP tools don't receive HTTP requests – they're invoked by an MCP client over stdio or SSE. Use **Guard**, not request-based.81- **Background jobs / queue consumers**: no HTTP request at the protection site. Use **Guard**.82- **Server actions / RPC over HTTP** (Next.js server actions, tRPC): there _is_ an HTTP request underneath. Use **request-based**.83- **Agent tool calls inside a request handler**: if you want to limit per-user-per-route, request-based is fine. If you want per-tool budgets independent of any HTTP boundary, use Guard at the tool call site.8485Read the appropriate reference:8687- **Request-based JS/TS**: [references/requests_javascript.md](references/requests_javascript.md)88- **Request-based Python**: [references/requests_python.md](references/requests_python.md)89- **Request-based Go**: [references/requests_go.md](references/requests_go.md)90- **Guard JS/TS fundamentals**: [references/guards_javascript.md](references/guards_javascript.md) — then exactly one adapter file from the table in that file (source of truth; do not copy it here)91- **Guard Python**: [references/guards_python.md](references/guards_python.md)92- **Guard Go**: [references/guards_go.md](references/guards_go.md)9394When the project already uses an official JS agent framework, open the fundamentals file and load **exactly one** adapter from its table. Do not open sibling adapters.9596When the project already uses an official Python agent framework, load the dedicated skill instead of the long adapter sections that used to live in the Python Guard reference:9798| Python framework | Import | Skill |99| --------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |100| LangChain (`BaseTool` / `create_agent`) | `arcjet.guard.langchain` | [integrate-arcjet-guard-langchain-py](../integrate-arcjet-guard-langchain-py/SKILL.md) |101| CrewAI | `arcjet.guard.crewai` | [integrate-arcjet-guard-crewai](../integrate-arcjet-guard-crewai/SKILL.md) |102| OpenAI Agents | `arcjet.guard.openai_agents` | [integrate-arcjet-guard-openai-agents-py](../integrate-arcjet-guard-openai-agents-py/SKILL.md) |103| Claude Agent SDK | `arcjet.guard.claude_agent_sdk` | [integrate-arcjet-guard-claude-agent-sdk-py](../integrate-arcjet-guard-claude-agent-sdk-py/SKILL.md) |104| Claude Managed Agents | `arcjet.guard.claude_managed_agents` | [integrate-arcjet-guard-claude-managed-agents-py](../integrate-arcjet-guard-claude-managed-agents-py/SKILL.md) |105| Strands Agents | `arcjet.guard.strands_agents` | [integrate-arcjet-guard-strands-agents-py](../integrate-arcjet-guard-strands-agents-py/SKILL.md) |106107These references explain architectural decisions and patterns that can't be inferred from the source code alone. For exact API signatures, read the installed package's types and doc comments.108109### Step 4: Implement protection110111Follow the patterns in the reference file from Step 3. Key principles:112113#### Request-based (HTTP routes):114115- Create shared clients outside handlers and include Shield as a base rule. Use the exact constructor and rule names from the language reference. JS HTTP rules omitted `mode` default to `DRY_RUN` – pass `mode: "LIVE"` to enforce. Python HTTP factories require `mode=` (`TypeError` if omitted). Go HTTP Protect rules also default to dry run – set `Mode: arcjet.ModeLive` to enforce.116- In JavaScript/TypeScript, create one `arcjet()` client and use `withRule()` for route-specific extras so clones share the decision cache. Check `decision.isDenied()`. Sibling `arcjet()` constructors do not share cache. `detectBot` requires exactly one of `allow` or `deny` (neither or both throws).117- In Python, create one `arcjet()` / `arcjet_sync()` client and use `with_rule()` for route-specific extras so clones share `DecisionCache`. Check `decision.is_denied()`. Sibling constructors do not share cache.118- In Go, create one `NewClient` at package scope. `WithRule()` derives route-specific clients that share the parent cache, and returns `(*Client, error)`, so handle initialization errors. Check `decision.IsDenied()`. Separate `NewClient` calls do not share cache.119- Call `protect()` / `Protect()` inside each route handler (not in app-level middleware), once per request.120- Map denial reasons to HTTP responses. Only branch on reasons that produce a _different_ response – there is no point in a Shield-specific arm that returns the same status as the default 403.121- Put the language's `userId` characteristic selector on the specific rule that needs it, then pass a **trusted, authenticated** user ID at protection time. Never rate limit by a client-controlled header unless a trusted proxy strips and rewrites it.122- Treat client-IP provenance as security configuration. JavaScript, Python, and Go may fall back to common forwarding headers when no usable public address is available and produce one `client_ip_provenance="unverified-header"` warning for the lifetime of each SDK client instance. Configure every trusted proxy and verify the application is reachable only through infrastructure that overwrites or safely appends forwarding headers. Never silence the warning by copying a client-controlled header into a manual override.123- If the application already has an independently trusted client IP, pass it explicitly: `ipSrc` (JS), `ip_src` (Python; also set `disable_automatic_ip_detection=True` when constructing the client), or `WithIPSrc` (Go). The SDKs reject malformed values, although JS treats an empty `ipSrc` as omitted. Syntax validation does not prove provenance. Before shipping, inspect representative requests with JS `clientIpDetails()` / `findIpDetails()`, Python `client_ip_details()`, or Go `ClientIPDetails()`.124- `protect()` accepts nested-JSON `metadata` (same shape as Guard). It does not affect fingerprinting. Do not put secrets or PII in it. When present, request decisions also expose optional IP threat intelligence (`decision.ip.threat` / `ip_details.threat` / `IP.Threat`).125126#### Guard (non-HTTP code):127128- Client at module scope: `launchArcjet()` (JS), `launch_arcjet()` / `launch_arcjet_sync()` (Python – match async vs sync), `NewGuardClient` (Go).129- Rules at module scope. **One `guard()` per operation with a hardcoded slug label** (`tools.get-weather`). Interpolated labels break grep and Console grouping. Slugs: lowercase letters, digits, `-`, `.` only; start and end with a letter or digit; max 256 bytes.130- `metadata` is nested JSON for audit only. No secrets or PII. `capture()` is visibility, never a decision – flush on shutdown. Python helper `success` is not "the action ran" – see the Python Guard reference.131- Free `guard()` (JS/Python registration) fail-opens if nothing is registered. Go has no registration API. Prefer an explicit client.132- Prefer official wrappers over hand-wrapping. Import the **versioned** JS path and load that adapter file from Step 3. Load the dedicated Python skill from Step 3. Unversioned `@arcjet/guard/<adapter>` aliases do not resolve.133- **Branch on which rule denied**, not just `DENY`. Guard `decision.reason` is a flat string (`"PROMPT_INJECTION"`) and is `undefined` on ALLOW. A denial by one rule still spends the others' budget in the same `rules` array – split calls if a PII false positive must not drain a rate limit.134- Every rate-limit rule needs a `key` and a `bucket`. Use a trusted user/session id when you have one; otherwise a stable identifier you control.135136**JS adapters:** wrappers fail closed; core `guard()` fails open. Load the Step 3 file for the project's framework. Unpublished adapters (Google ADK, TanStack AI, Claude Managed Agents) pin a git SHA in that file — do not `npm install @arcjet/guard` and import them from 1.11.0.137138**Python:** `guard_action` / `guard_action_sync` is core. LangChain, CrewAI, and OpenAI Agents ship in PyPI `arcjet` **1.0.0**. Claude Agent SDK, Claude Managed Agents, and Strands Agents still need the git pin in their dedicated skill. There is no `arcjet[crewai]` extra.139140**Traps that run without error and enforce nothing** (full write-up in the Guard references and https://docs.arcjet.com/llms.txt):141142- Guard has no `sensitiveInfo` rule – that is HTTP `protect()` only. Use `localDetectSensitiveInfo` / `LocalDetectSensitiveInfo`.143- Python `LocalDetectSensitiveInfo()` with neither `allow` nor `deny` fail-opens as ALLOW (`AJ1203`). Always pass a list. JS works with no args; still pass a list.144- The sensitive-info rule does **not** inherit the client's backend. Entity types beyond `EMAIL` / `PHONE_NUMBER` / `IP_ADDRESS` / `CREDIT_CARD_NUMBER` need `backend` on the rule. Share one Rampart instance with the client.145- Typed `inputs` reach a remote policy from every Python adapter, but in JS only from `@arcjet/guard/vercel-ai/v7`. Elsewhere use SDK `rules` or the published policy will not fire. JS builders: `policyInput.server` / `policyInput.local`. Python: `server_input` / `local_input`.146- A missing decision is not a denial. If the model asks a question or masks values itself, Guard never runs. Verify in Console/CLI.147- Guarding one tool only helps if it is the only path. Claude Agent SDK needs `settingSources: []` / `setting_sources=[]` **and** `strictMcpConfig: true` / `strict_mcp_config=True`.148- HITL (`needsApproval`, `human_input`, `can_use_tool`, `event.interrupt()`, `always_ask`) is not a policy gate.149150#### Conventions outside the Arcjet flow151152For everything that _isn't_ an Arcjet-specific decision – dev scripts, file/module layout, named-vs-default exports, comment style, env-file naming, type hints, error class patterns – match the project's existing conventions. If the project has no convention yet, default to modern best practice for the language. This skill is opinionated about _where Arcjet goes_ and _how its API is used_; it must not reach further than that.153154### Step 5: Verify decisions155156After wiring up protection, confirm it's actually firing. Three steps:157158**1. Type-check / build first.** Run `tsc`, `next build`, `python -m py_compile`, or whatever check command the project uses. Catches wrong imports, wrong rule names, and stale type signatures before the user does.159160**2. Trigger a real call so a decision exists to check.** Without one, the Console and CLI are empty and you can't tell whether protection is actually wired up.161162- **Request-based**: start the dev server (`npm run dev`, `uvicorn main:app --reload`) and `curl` the protected route with the method the route actually accepts (`curl -I` is HEAD – FastAPI GET-only routes answer 405, not the documented 403). Empty `bots.allow` / `allow=[]` denies `CURL` before later rules run – temporarily `allow: ["CURL"]` when you need to reach email or rate-limit checks, then drop it. To trip a rate limit: `for i in {1..50}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/your-route; done`.163- **Guard**: invoke the protected function so a decision exists. A missing decision is not a denial – read Console/CLI, not the absence of a side effect. Don't test Guard by `curl`ing anything.164165**3. Confirm the decision in the Arcjet platform.**166167- **CLI**: `npx -y @arcjet/cli@latest requests list --site-id <id>` (request-based) or `... guards list --site-id <id>` (Guard)168- **MCP**: `list-requests` / `list-guards`169- **Console**: https://console.arcjet.com170171For deeper investigation: `arcjet requests explain --site-id <id> --request-id <id>` or `arcjet guards explain --site-id <id> --guard-id <id>`.172173If you can't run the app in the current environment, tell the user exactly what to do (which command to run, what to look for in the output) instead of silently skipping verification.174175### Gotchas176177- **Wrong SDK/client**: `@arcjet/guard`, `arcjet.guard`, and Go's `NewGuardClient` are for non-HTTP code. `@arcjet/node` / `@arcjet/next` / Python `arcjet()` / Go `NewClient` are for HTTP routes.178- **Wrong placement**: `protect()` must not be called in Express middleware or Next.js middleware. Call it inside each route handler.179- **Wrong layer for `guard()`**: don't put `guard()` in a generic dispatcher. Use the official wrapper from the table above, or put `guard()` inside the specific tool. Load the dedicated Python skill instead of hand-wrapping.180- **Python adapter denials and HITL:** load the dedicated skill. Capture handlers never block. HITL is not a policy gate. Helper `metadata.outcome` details live in the Python Guard reference.181- **Hand-edited dependency manifests**: run the project's package manager so the version is real (`@arcjet/*` 1.11.0, Python `arcjet` 1.0.0).182- **Double-counting**: calling `protect()` or `guard()` multiple times for the same operation counts against rate limits multiple times.183- **Client-IP warning bypass**: never "fix" an `unverified-header` warning by copying `X-Forwarded-For` into `ipSrc` / `ip_src` / `WithIPSrc`.184- **JS denial envelopes:** one `ArcjetDenialResult` payload; delivery is per-framework. Read the adapter file from Step 3 before inventing a status or throwing. `guardTool` and `guardAction` are different handlers.185- **Never hardcode `ARCJET_KEY`** – always use environment variables.186187## Choose protections188189When you need to pick which rules address the user's concern – bot abuse, rate limits, prompt injection, signup spam, PII, or IP filtering – load [references/choosing_protections.md](references/choosing_protections.md). It maps common problems to Arcjet rules and explains the tradeoffs between strategies (for example token bucket vs sliding window). The mapping doesn't need to be in your context for the rest of the workflow.190191## Resources192193For exact API signatures, parameter names, and the full set of rules and helpers, read the installed SDK's source – types and docstrings are the source of truth:194195- **Python SDK**: https://github.com/arcjet/arcjet-py – `arcjet` package (request protection) and `arcjet.guard` subpackage (non-HTTP guard).196- **Python Guard integration skills**: [integrate-arcjet-guard-langchain-py](../integrate-arcjet-guard-langchain-py/SKILL.md), [integrate-arcjet-guard-crewai](../integrate-arcjet-guard-crewai/SKILL.md), [integrate-arcjet-guard-openai-agents-py](../integrate-arcjet-guard-openai-agents-py/SKILL.md), [integrate-arcjet-guard-claude-agent-sdk-py](../integrate-arcjet-guard-claude-agent-sdk-py/SKILL.md), [integrate-arcjet-guard-claude-managed-agents-py](../integrate-arcjet-guard-claude-managed-agents-py/SKILL.md), [integrate-arcjet-guard-strands-agents-py](../integrate-arcjet-guard-strands-agents-py/SKILL.md).197- **JavaScript / TypeScript SDK**: https://github.com/arcjet/arcjet-js – monorepo with framework-specific packages (`@arcjet/next`, `@arcjet/node`, `@arcjet/fastify`, `@arcjet/sveltekit`, `@arcjet/guard`). JS Guard adapter files: [references/guards_js_vercel_ai.md](references/guards_js_vercel_ai.md) and siblings listed in Step 3.198- **Go SDK**: https://github.com/arcjet/arcjet-go – `github.com/arcjet/arcjet-go` module with request and guard clients. `go get ...@latest` still resolves **v0.1.0**. `v1.0.0-rc.1` exists as a pre-release; APIs described in the Go references live on the default branch / that rc.199- **Docs**: https://docs.arcjet.com – narrative guides, blueprints, and product reference.200- **Console**: https://console.arcjet.com – sites, keys, and decision history.