Users paste sensitive data into AI prompts - credit card numbers, phone numbers, passwords, and other PII - often without realizing the risk. Once that data reaches your AI provider it may appear in logs, training pipelines, or model outputs.
Arcjet sensitive info detection scans prompt content inside your application before it reaches the AI provider. Detected PII is blocked before it leaves your app environment.
Get started
Section titled “Get started”In this example we use the Vercel AI SDK to create a simple AI chat endpoint with Next.js, and Arcjet to prevent sensitive information from being sent to the AI model. The same principles can be applied to any AI application, including those built with other frameworks.
We assume you already have a Next.js app set up.
Install the dependencies:
# Export your Arcjet API key from https://app.arcjet.comexport ARCJET_KEY="ajkey_..."
npm install @arcjet/next ai @ai-sdk/openaiCreate an AI chat endpoint:
import { openai } from "@ai-sdk/openai";import arcjet, { sensitiveInfo } 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://app.arcjet.com rules: [ 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 (e.g. EMAIL for a support bot). deny: ["CREDIT_CARD_NUMBER", "EMAIL"], }), ],});
export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json();
// Check the most recent user message for sensitive information. // 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(" ");
const decision = await aj.protect(req, { sensitiveInfoValue: lastMessage });
if (decision.isDenied() && decision.reason.isSensitiveInfo()) { console.warn("Request blocked due to sensitive information"); return new Response( "Sensitive information detected — please remove it from your prompt", { status: 400 }, ); }
// Arcjet approved — call your AI provider const result = await streamText({ model: openai("gpt-4o"), messages: await convertToModelMessages(messages), });
return result.toUIMessageStreamResponse();}And hook it up to a chat UI:
"use client";
import { useChat } from "@ai-sdk/react";import { useState } from "react";
export default function Chat() { const [input, setInput] = useState(""); const [errorMessage, setErrorMessage] = useState<string | null>(null); const { messages, sendMessage } = useChat({ onError: async (e) => setErrorMessage(e.message), }); return ( <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch"> {messages.map((message) => ( <div key={message.id} className="whitespace-pre-wrap"> {message.role === "user" ? "User: " : "AI: "} {message.parts.map((part, i) => { switch (part.type) { case "text": return <div key={`${message.id}-${i}`}>{part.text}</div>; } })} </div> ))}
{errorMessage && ( <div className="text-red-500 text-sm mb-4">{errorMessage}</div> )}
<form onSubmit={(e) => { e.preventDefault(); sendMessage({ text: input }); setInput(""); setErrorMessage(null); }} > <input className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl" value={input} placeholder="Say something..." onChange={(e) => setInput(e.currentTarget.value)} /> </form> </div> );}Then run the server:
npm run devYou will see requests being processed in your Arcjet dashboard in real time.
In this example we use LangChain to create a simple AI chat server with FastAPI, and Arcjet to prevent sensitive information from being sent to the AI model. The same principles can be applied to any AI application, including those built with other frameworks.
Set up the environment and install dependencies (uses uv, but you can also use pip to install the Arcjet Python SDK):
# Export your Arcjet API key from https://app.arcjet.comexport ARCJET_KEY="ajkey_..."export ARCJET_ENV=development
# Export your OpenAI API key (used by LangChain)export OPENAI_API_KEY="sk-..."
# Install dependenciesuv add arcjet fastapi uvicorn langchain langchain-openaiCreate the chat server:
import loggingimport os
from arcjet import Mode, SensitiveInfoEntityType, arcjet, detect_sensitive_infofrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponsefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_openai import ChatOpenAIfrom pydantic import BaseModel
app = FastAPI()
logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)
arcjet_key = os.getenv("ARCJET_KEY")if not arcjet_key: raise RuntimeError("ARCJET_KEY is required. Get one at https://app.arcjet.com")
openai_api_key = os.getenv("OPENAI_API_KEY")if not openai_api_key: raise RuntimeError( "OPENAI_API_KEY is required. Get one at https://platform.openai.com" )
llm = ChatOpenAI(model="gpt-4o-mini", api_key=openai_api_key)
prompt = ChatPromptTemplate.from_messages( [ ("system", "You are a helpful assistant."), ("human", "{message}"), ])
chain = prompt | llm | StrOutputParser()
class ChatRequest(BaseModel): message: str
# Create a single Arcjet client at startup and reuse it across requestsaj = arcjet( key=arcjet_key, # Get your key from https://app.arcjet.com rules=[ detect_sensitive_info( mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only # Block PII types that should never appear in AI prompts. # Remove types your app legitimately handles (e.g. EMAIL for a # support bot). deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, ], ), ],)
@app.post("/chat")async def chat(request: Request, body: ChatRequest): # Scan the user message for sensitive information before it reaches the # AI model. Pass the full conversation if you want to scan all messages. decision = await aj.protect(request, sensitive_info_value=body.message)
if decision.is_denied() and decision.reason_v2.type == "SENSITIVE_INFO": logger.warning("Request blocked due to sensitive information") return JSONResponse( {"error": "Sensitive information detected — please remove it from your prompt"}, status_code=400, )
# Arcjet approved — call the AI model reply = await chain.ainvoke({"message": body.message})
return {"reply": reply}Then run the server:
uv run uvicorn main:app --reloadAnd send a message to the API endpoint:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "My email is test@example.com"}'You will see requests being processed in your Arcjet dashboard in real time.
In this example we use LangChain to create a simple AI chat server with Flask, and Arcjet to prevent sensitive information from being sent to the AI model. The same principles can be applied to any AI application, including those built with other frameworks.
Set up the environment and install dependencies (uses uv, but you can also use pip to install the Arcjet Python SDK):
# Export your Arcjet API key from https://app.arcjet.comexport ARCJET_KEY="ajkey_..."export ARCJET_ENV=development
# Export your OpenAI API key (used by LangChain)export OPENAI_API_KEY="sk-..."
# Install dependenciesuv add arcjet flask langchain langchain-openaiCreate the chat server:
import loggingimport os
from arcjet import ( Mode, SensitiveInfoEntityType, arcjet_sync, detect_sensitive_info,)from flask import Flask, jsonify, requestfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_openai import ChatOpenAI
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)
arcjet_key = os.getenv("ARCJET_KEY")if not arcjet_key: raise RuntimeError("ARCJET_KEY is required. Get one at https://app.arcjet.com")
openai_api_key = os.getenv("OPENAI_API_KEY")if not openai_api_key: raise RuntimeError( "OPENAI_API_KEY is required. Get one at https://platform.openai.com" )
llm = ChatOpenAI(model="gpt-4o-mini", api_key=openai_api_key)
prompt = ChatPromptTemplate.from_messages( [ ("system", "You are a helpful assistant."), ("human", "{message}"), ])
chain = prompt | llm | StrOutputParser()
# Create a single Arcjet client at startup and reuse it across requestsaj = arcjet_sync( key=arcjet_key, # Get your key from https://app.arcjet.com rules=[ detect_sensitive_info( mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only # Block PII types that should never appear in AI prompts. # Remove types your app legitimately handles (e.g. EMAIL for a # support bot). deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, ], ), ],)
@app.post("/chat")def chat(): body = request.get_json() message = body.get("message", "") if body else ""
# Scan the user message for sensitive information before it reaches the # AI model. Pass the full conversation if you want to scan all messages. decision = aj.protect(request, sensitive_info_value=message)
if decision.is_denied() and decision.reason_v2.type == "SENSITIVE_INFO": logger.warning("Request blocked due to sensitive information") return jsonify( error="Sensitive information detected — please remove it from your prompt" ), 400
# Arcjet approved — call the AI model reply = chain.invoke({"message": message})
return jsonify(reply=reply)
if __name__ == "__main__": app.run(debug=True)Then run the server:
uv run python app.pyAnd send a message to the API endpoint:
curl -X POST http://localhost:5000/chat \ -H "Content-Type: application/json" \ -d '{"message": "My email is test@example.com"}'You will see requests being processed in your Arcjet dashboard in real time.
Configuring sensitive info detection
Section titled “Configuring sensitive info detection”deny: ["CREDIT_CARD_NUMBER", "EMAIL"] (Python: deny=[SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL]) -
the list of PII entity types to block. Remove any types your application
legitimately handles - for example, a support bot that collects phone numbers
should remove "PHONE_NUMBER" from the deny list.
Built-in entity types: CREDIT_CARD_NUMBER, PHONE_NUMBER, EMAIL,
IP_ADDRESS. The JS SDK additionally supports URL. See the full
reference for the complete list and for defining
your own custom detectors.
sensitiveInfoValue (JS) / sensitive_info_value (Python) - The text to scan.
Pass the user’s prompt or the most recent message. You can also pass the full
conversation history if you want to scan all messages, not just the latest one.
mode: "DRY_RUN" (JS) / mode=Mode.DRY_RUN (Python) - Logs detections without
blocking. Use this to audit what PII appears in prompts before switching to
"LIVE".
Combine with abuse protection
Section titled “Combine with abuse protection”Sensitive info detection controls what reaches your AI provider. To also block hostile instructions and jailbreak attempts, combine it with prompt injection detection. To block automated clients and enforce per-user budgets, combine it with AI abuse protection and AI budget control. For agentic pipelines that don’t route through HTTP, Arcjet Guards provides the same PII detection directly inside tool handlers.