APIs & SDKs
Integrate red-team scanning into your AI applications using the REST API, Python SDK, or framework-specific libraries.
REST API
The dashboard server exposes a full REST API on port 4200.
Start a scan
curl -X POST http://localhost:4200/api/run \
-H "Content-Type: application/json" \
-d '{
"target": {
"baseUrl": "http://localhost:3000",
"agentEndpoint": "/api/chat"
},
"categories": ["prompt_injection", "tool_misuse", "data_exfiltration"],
"attackConfig": {
"adaptiveRounds": 2,
"maxAttacksPerCategory": 5
}
}'
Response:
{ "id": "a1b2c3d4", "status": "queued" }
Poll for status
curl http://localhost:4200/api/run/a1b2c3d4
Get results
# JSON report
curl http://localhost:4200/api/report/report-2026-05-17-a1b2c3d4.json
# CSV export
curl http://localhost:4200/api/report-csv/report-2026-05-17-a1b2c3d4.json
List all reports
curl http://localhost:4200/api/reports-meta?limit=10&search=prompt
Run compliance analysis
curl -X POST http://localhost:4200/api/owasp-analyze \
-H "Content-Type: application/json" \
-d '{"reportFilename": "report-2026-05-17-a1b2c3d4.json"}'
LangChain Integration
Using LangChain Tools (Python)
Wrap the red-team REST API as LangChain tools so any LangChain agent can trigger scans, poll results, and analyze reports conversationally.
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
import requests
RED_TEAM_URL = "http://localhost:4200"
@tool
def start_red_team_scan(
target_url: str,
endpoint: str = "/api/chat",
categories: list[str] | None = None,
rounds: int = 2,
) -> dict:
"""Start a red-team security scan against an AI agent endpoint."""
config = {
"target": {
"baseUrl": target_url,
"agentEndpoint": endpoint,
},
"attackConfig": {
"adaptiveRounds": rounds,
"maxAttacksPerCategory": 5,
},
}
if categories:
config["categories"] = categories
resp = requests.post(f"{RED_TEAM_URL}/api/run", json=config)
return resp.json()
@tool
def check_scan_status(run_id: str) -> dict:
"""Check the status and progress of a red-team scan."""
resp = requests.get(f"{RED_TEAM_URL}/api/run/{run_id}")
data = resp.json()
return {
"status": data.get("status"),
"progress": len(data.get("progress", [])),
"total": data.get("estimatedTotal"),
"reportFile": data.get("reportFile"),
}
@tool
def get_scan_results(run_id: str) -> dict:
"""Get the full results of a completed red-team scan."""
# First get the report filename from the run
run = requests.get(f"{RED_TEAM_URL}/api/run/{run_id}").json()
report_file = run.get("reportFile")
if not report_file:
return {"error": "Scan not complete yet", "status": run.get("status")}
resp = requests.get(f"{RED_TEAM_URL}/api/report/{report_file}")
report = resp.json()
return {
"score": report["summary"]["score"],
"total_attacks": report["summary"]["totalAttacks"],
"passed": report["summary"]["passed"],
"failed": report["summary"]["failed"],
"by_category": {
cat: {"total": v["total"], "passed": v["passed"]}
for cat, v in report["summary"]["byCategory"].items()
},
}
@tool
def list_reports(search: str = "", limit: int = 5) -> list[dict]:
"""List recent red-team scan reports."""
resp = requests.get(
f"{RED_TEAM_URL}/api/reports-meta",
params={"limit": limit, "search": search},
)
return resp.json().get("reports", [])
# Create the agent
llm = ChatOpenAI(model="gpt-4.1")
tools = [start_red_team_scan, check_scan_status, get_scan_results, list_reports]
agent = create_react_agent(llm, tools)
# Run it
result = agent.invoke({
"messages": [
{"role": "user", "content": "Scan my chatbot at http://localhost:3000 for prompt injection and tool misuse vulnerabilities"}
]
})
Red-teaming a LangChain Agent
Test your own LangChain agent by pointing the scanner at its serving endpoint:
# 1. Serve your LangChain agent
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langserve import add_routes
from fastapi import FastAPI
llm = ChatOpenAI(model="gpt-4.1")
agent = create_react_agent(llm, tools=[...])
app = FastAPI()
add_routes(app, agent, path="/chat")
# Run: uvicorn app:app --port 3000
// 2. Red-team config targeting the LangChain agent
{
"target": {
"baseUrl": "http://localhost:3000",
"agentEndpoint": "/chat/invoke",
"customApiTemplate": {
"bodyTemplate": "{\"input\": {\"messages\": [{\"role\": \"user\", \"content\": \"\"}]}}",
"responsePath": "output.content"
}
},
"categories": [
"prompt_injection",
"tool_misuse",
"tool_result_injection",
"tool_chain_hijack",
"data_exfiltration"
]
}
LangChain + MCP Server
Connect the red-team MCP server directly to a LangChain agent using langchain-mcp-adapters:
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-sonnet-4-20250514")
async with MultiServerMCPClient(
{
"red-team": {
"command": "npx",
"args": ["tsx", "/path/to/hermes-redteam/mcp-server.ts"],
"transport_type": "stdio",
}
}
) as client:
tools = client.get_tools()
# Tools: read_repo, probe_target, write_config, run_scan,
# check_run_status, get_run_results, list_categories_and_strategies, etc.
agent = create_react_agent(llm, tools)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": "Scan my app at http://localhost:3000 for agentic security issues"}]
})
CrewAI Integration
Red-team agent crew
from crewai import Agent, Task, Crew
from crewai_tools import tool
import requests
RED_TEAM_URL = "http://localhost:4200"
@tool("Start Security Scan")
def start_scan(target_url: str, categories: str = "") -> str:
"""Start a red-team scan. Categories: comma-separated list like 'prompt_injection,tool_misuse'."""
config = {
"target": {"baseUrl": target_url, "agentEndpoint": "/api/chat"},
"attackConfig": {"adaptiveRounds": 2, "maxAttacksPerCategory": 5},
}
if categories:
config["categories"] = [c.strip() for c in categories.split(",")]
resp = requests.post(f"{RED_TEAM_URL}/api/run", json=config)
return str(resp.json())
@tool("Check Scan Status")
def check_status(run_id: str) -> str:
"""Check if a red-team scan is complete."""
resp = requests.get(f"{RED_TEAM_URL}/api/run/{run_id}")
return str(resp.json())
@tool("Get Scan Report")
def get_report(report_filename: str) -> str:
"""Get the summary of a completed scan report."""
resp = requests.get(f"{RED_TEAM_URL}/api/report/{report_filename}")
data = resp.json()
summary = data.get("summary", {})
return f"Score: {summary.get('score')}/100, Attacks: {summary.get('totalAttacks')}, Failed: {summary.get('failed')}"
scanner = Agent(
role="AI Security Scanner",
goal="Run comprehensive red-team scans against AI applications",
backstory="You are an AI security expert who identifies vulnerabilities in LLM-powered applications.",
tools=[start_scan, check_status, get_report],
)
analyst = Agent(
role="Security Analyst",
goal="Analyze red-team results and produce actionable remediation plans",
backstory="You review security scan results and create prioritized fix recommendations.",
tools=[get_report],
)
scan_task = Task(
description="Scan the AI chatbot at {target_url} for prompt injection, tool misuse, and data exfiltration vulnerabilities.",
expected_output="Scan run ID and status",
agent=scanner,
)
analysis_task = Task(
description="Analyze the scan results and create a remediation plan prioritized by severity.",
expected_output="Prioritized list of vulnerabilities with specific fix recommendations",
agent=analyst,
)
crew = Crew(agents=[scanner, analyst], tasks=[scan_task, analysis_task], verbose=True)
result = crew.kickoff(inputs={"target_url": "http://localhost:3000"})
OpenAI Assistants / Responses API
Function calling with the Responses API
from openai import OpenAI
import requests, json
client = OpenAI()
RED_TEAM_URL = "http://localhost:4200"
tools = [
{
"type": "function",
"name": "start_red_team_scan",
"description": "Start a red-team security scan against an AI agent",
"parameters": {
"type": "object",
"properties": {
"target_url": {"type": "string", "description": "Base URL of the target"},
"endpoint": {"type": "string", "description": "Chat endpoint path"},
"categories": {
"type": "array",
"items": {"type": "string"},
"description": "Attack categories to test",
},
},
"required": ["target_url"],
},
},
{
"type": "function",
"name": "get_scan_results",
"description": "Get results of a completed scan by run ID",
"parameters": {
"type": "object",
"properties": {
"run_id": {"type": "string"},
},
"required": ["run_id"],
},
},
]
def handle_tool_call(name: str, args: dict) -> str:
if name == "start_red_team_scan":
config = {
"target": {
"baseUrl": args["target_url"],
"agentEndpoint": args.get("endpoint", "/api/chat"),
},
}
if args.get("categories"):
config["categories"] = args["categories"]
resp = requests.post(f"{RED_TEAM_URL}/api/run", json=config)
return json.dumps(resp.json())
elif name == "get_scan_results":
resp = requests.get(f"{RED_TEAM_URL}/api/run/{args['run_id']}")
return json.dumps(resp.json())
return json.dumps({"error": "Unknown tool"})
response = client.responses.create(
model="gpt-4.1",
tools=tools,
input="Scan my chatbot at http://localhost:3000 for security issues",
)
# Handle tool calls in a loop
while response.output:
tool_calls = [o for o in response.output if o.type == "function_call"]
if not tool_calls:
break
tool_results = []
for tc in tool_calls:
result = handle_tool_call(tc.name, json.loads(tc.arguments))
tool_results.append({"type": "function_call_output", "call_id": tc.call_id, "output": result})
response = client.responses.create(
model="gpt-4.1",
tools=tools,
input=tool_results,
previous_response_id=response.id,
)
# Final text response
print([o.text for o in response.output if hasattr(o, "text")])
Anthropic Claude API
Tool use with Claude
import anthropic
import requests, json
client = anthropic.Anthropic()
RED_TEAM_URL = "http://localhost:4200"
tools = [
{
"name": "start_red_team_scan",
"description": "Start a red-team security scan against an AI agent endpoint",
"input_schema": {
"type": "object",
"properties": {
"target_url": {"type": "string", "description": "Base URL of the target"},
"endpoint": {"type": "string", "description": "Chat endpoint path"},
"categories": {
"type": "array",
"items": {"type": "string"},
"description": "Attack categories to test",
},
},
"required": ["target_url"],
},
},
{
"name": "get_scan_results",
"description": "Get results of a completed scan by run ID",
"input_schema": {
"type": "object",
"properties": {"run_id": {"type": "string"}},
"required": ["run_id"],
},
},
]
def handle_tool(name, input_data):
if name == "start_red_team_scan":
config = {
"target": {
"baseUrl": input_data["target_url"],
"agentEndpoint": input_data.get("endpoint", "/api/chat"),
},
}
if input_data.get("categories"):
config["categories"] = input_data["categories"]
return requests.post(f"{RED_TEAM_URL}/api/run", json=config).json()
elif name == "get_scan_results":
return requests.get(f"{RED_TEAM_URL}/api/run/{input_data['run_id']}").json()
messages = [{"role": "user", "content": "Scan http://localhost:3000 for prompt injection and tool misuse"}]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=tools,
messages=messages,
)
# Agentic loop
while response.stop_reason == "tool_use":
tool_block = next(b for b in response.content if b.type == "tool_use")
result = handle_tool(tool_block.name, tool_block.input)
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{"type": "tool_result", "tool_use_id": tool_block.id, "content": json.dumps(result)}],
})
response = client.messages.create(
model="claude-sonnet-4-20250514", max_tokens=4096, tools=tools, messages=messages,
)
# Print final response
print(next(b.text for b in response.content if hasattr(b, "text")))
Vercel AI SDK (TypeScript)
import { openai } from "@ai-sdk/openai";
import { generateText, tool } from "ai";
import { z } from "zod";
const RED_TEAM_URL = "http://localhost:4200";
const result = await generateText({
model: openai("gpt-4.1"),
tools: {
startScan: tool({
description: "Start a red-team security scan",
parameters: z.object({
targetUrl: z.string().describe("Base URL of the AI agent"),
endpoint: z.string().default("/api/chat"),
categories: z.array(z.string()).optional(),
}),
execute: async ({ targetUrl, endpoint, categories }) => {
const config: Record<string, unknown> = {
target: { baseUrl: targetUrl, agentEndpoint: endpoint },
attackConfig: { adaptiveRounds: 2, maxAttacksPerCategory: 5 },
};
if (categories) config.categories = categories;
const resp = await fetch(`${RED_TEAM_URL}/api/run`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(config),
});
return resp.json();
},
}),
getScanResults: tool({
description: "Get results of a completed scan",
parameters: z.object({ runId: z.string() }),
execute: async ({ runId }) => {
const resp = await fetch(`${RED_TEAM_URL}/api/run/${runId}`);
return resp.json();
},
}),
},
maxSteps: 5,
prompt: "Scan my chatbot at http://localhost:3000 for security issues",
});
console.log(result.text);
CI/CD Integration
GitHub Actions
name: AI Security Scan
on:
pull_request:
branches: [main]
jobs:
red-team:
runs-on: ubuntu-latest
services:
red-team:
image: ghcr.io/sundi133/wb-red-team:latest
ports:
- 4200:4200
env:
ANTHROPIC_API_KEY: $
steps:
- name: Wait for server
run: |
for i in $(seq 1 30); do
curl -sf http://localhost:4200/api/auth-config && break
sleep 2
done
- name: Run scan
run: |
RUN_ID=$(curl -s -X POST http://localhost:4200/api/run \
-H "Content-Type: application/json" \
-d '{
"target": {
"baseUrl": "$",
"agentEndpoint": "/api/chat"
},
"categories": ["prompt_injection", "tool_misuse", "data_exfiltration"],
"attackConfig": {"adaptiveRounds": 1, "maxAttacksPerCategory": 3}
}' | jq -r '.id')
echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV
- name: Wait for completion
run: |
for i in $(seq 1 60); do
STATUS=$(curl -s http://localhost:4200/api/run/$RUN_ID | jq -r '.status')
[ "$STATUS" = "done" ] && break
[ "$STATUS" = "error" ] && exit 1
sleep 10
done
- name: Check score
run: |
SCORE=$(curl -s http://localhost:4200/api/run/$RUN_ID | jq -r '.report.summary.score')
echo "Security score: $SCORE/100"
if [ "$SCORE" -lt 70 ]; then
echo "::error::Security score $SCORE is below threshold (70)"
exit 1
fi
Python script for CI pipelines
"""Minimal CI script -- exits non-zero if score < threshold."""
import requests, time, sys
RED_TEAM_URL = "http://localhost:4200"
TARGET_URL = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000"
THRESHOLD = int(sys.argv[2]) if len(sys.argv) > 2 else 70
# Start scan
run = requests.post(f"{RED_TEAM_URL}/api/run", json={
"target": {"baseUrl": TARGET_URL, "agentEndpoint": "/api/chat"},
"categories": ["prompt_injection", "tool_misuse", "data_exfiltration"],
"attackConfig": {"adaptiveRounds": 1, "maxAttacksPerCategory": 3},
}).json()
run_id = run["id"]
print(f"Scan started: {run_id}")
# Poll until done
while True:
status = requests.get(f"{RED_TEAM_URL}/api/run/{run_id}").json()
if status["status"] in ("done", "error"):
break
print(f" Progress: {len(status.get('progress', []))} attacks completed...")
time.sleep(10)
if status["status"] == "error":
print("Scan failed")
sys.exit(1)
# Check score
report_file = status["reportFile"]
report = requests.get(f"{RED_TEAM_URL}/api/report/{report_file}").json()
score = report["summary"]["score"]
failed = report["summary"]["failed"]
print(f"Score: {score}/100 | Passed: {report['summary']['passed']} | Failed: {failed}")
if score < THRESHOLD:
print(f"FAIL: Score {score} below threshold {THRESHOLD}")
sys.exit(1)
print("PASS")
API Reference
| Endpoint | Method | Description |
|---|---|---|
/api/run |
POST | Start a new scan |
/api/run/:id |
GET | Get run status + progress |
/api/run/:id |
DELETE | Cancel a running scan |
/api/runs |
GET | List all runs |
/api/reports |
GET | List report filenames |
/api/reports-meta |
GET | Paginated reports with metadata |
/api/report/:filename |
GET | Full report JSON |
/api/report-csv/:filename |
GET | Export as CSV |
/api/owasp-analyze |
POST | OWASP compliance analysis (NDJSON stream) |
/api/risk-analyze |
POST | Business risk analysis (NDJSON stream) |
/api/compliance-frameworks |
GET | List compliance frameworks |
/api/audit-log |
GET | Query audit log (enterprise) |