Gateway Rules & Tenant Routing
AgentWatch — Feature Reference
Section titled “AgentWatch — Feature Reference”1. Gateway Routes
Section titled “1. Gateway Routes”What Are Gateway Routes?
Section titled “What Are Gateway Routes?”Gateway Routes is a platform-wide traffic routing layer that intercepts HTTP requests by URL path pattern and forwards them to configured upstream services. It acts as a general-purpose reverse proxy — sitting outside the LLM pipeline and routing traffic to any backend, not just LLM providers.
Every AI API call made to AgentWatch is matched against the gateway route table. Routes are evaluated in priority order — the first matching route wins.
Developer API Call │ ▼┌─────────────────────────────────────────┐│ Route Matching (path pattern + method) ││ First matching route wins │└──────────────┬──────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Security Checks ││ • IP allowlist / blocklist (CIDR) ││ • Rate limit (token bucket per route) ││ • Circuit breaker — open? Fail fast │└──────────────┬──────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Request Transforms ││ • Rewrite path ││ • Inject / remove / modify headers ││ • Replace query parameters ││ • Modify request body (templates) │└──────────────┬──────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Plugin Hooks (pre-request) ││ Custom JavaScript logic runs here │└──────────────┬──────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Forward to Upstream Target ││ (HTTP / HTTPS; streaming supported) │└──────────────┬──────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Plugin Hooks (pre-response) ││ Transform response before client │└──────────────┬──────────────────────────┘ │ ▼ Return to ClientKey Features
Section titled “Key Features”| Feature | Description |
|---|---|
| Path Matching | Glob patterns (/api/*, /v1/**) evaluated in priority order |
| Header-Based Routing | Route based on presence/value of request headers (matchHeaders, matchHeaderMode: all or any) |
| Path Rewriting | Strip or replace path prefixes before forwarding ({ "match": "^/v1/", "replace": "/openai/v1/" }) |
| Header Injection | Inject static or template-variable headers into upstream requests |
| Request/Response Transforms | Add, remove, or modify headers, query params, and body fields |
| Rate Limiting | Token-bucket per route (maxRequests / windowMs). Returns 429 Too Many Requests with Retry-After when exhausted |
| Circuit Breaker | Auto-opens after N consecutive failures; recovers via half-open probe |
| IP Allow/Blocklist | CIDR-range filtering per route. Blocklist evaluated before allowlist |
| mTLS / TLS Config | Client cert, CA cert, and minimum TLS version per route |
| Schema Validation | Request and response body validation with reject or warn action |
| Plugin Hooks | pre-request and post-response plugin execution per route |
| Health Monitoring | Periodic HEAD checks to upstream; tracks healthy, degraded, unhealthy states |
| Load Balancing | Distribute traffic across multiple upstreams with weighted, round-robin, least-loaded, or failover strategy |
| Analytics | Per-route metrics: request count, latency, error rate, timeseries |
| Import / Export | Export routes as JSON or CSV; import in bulk |
| Clone Routes | Duplicate an existing route as starting point |
| Drag-to-Reorder | Reorder routes by priority via drag-and-drop in the UI |
| Bulk Actions | Enable, disable, or delete multiple routes at once |
| Connectivity Test | Test upstream reachability directly from the route form |
Proxy Modes
Section titled “Proxy Modes”Gateway Routes support three proxy modes:
| Mode | Behaviour |
|---|---|
| Active (default) | Standard reverse proxy. Request goes directly to upstream, bypassing the LLM pipeline entirely |
| Enriched | Request processes through the full AgentWatch pipeline (DLP, provider selection, LLM call) first, then the LLM response is forwarded to the upstream URL |
| Post-Process | Request goes to the upstream first; its response replaces or augments the original request body, which is then sent through the AgentWatch LLM pipeline |
How to Create a Gateway Route
Section titled “How to Create a Gateway Route”Step 1: Navigate to Gateway Routes
- Log in to the AgentWatch Admin Panel
- In the left sidebar go to Settings → Gateway Routes
- Click Add Route
Step 2: Fill in Basic Fields
| Field | Description | Example |
|---|---|---|
| Name | Human-readable identifier | Production OpenAI |
| Path Pattern | URL path glob to match | /v1/chat/completions |
| Method | HTTP method (GET, POST, * for any) |
POST |
| Target URL | Upstream endpoint | https://api.openai.com |
| Priority | Evaluation order — lower number = evaluated first | 1 |
| Timeout (ms) | Max time to wait for upstream response | 60000 |
| Enabled | Activate the route immediately | true |
| Mode | proxied, enriched, or post-process |
proxied |
Step 3: (Optional) Configure Request Transforms
Under the Transforms tab, add header operations using template variables:
| Variable | Resolves to |
|---|---|
{{route_name}} |
The matched route’s name |
{{tenant_id}} |
The API key’s tenant ID |
{{organization_id}} |
The tenant’s organization ID |
{{timestamp}} |
Current UTC timestamp |
// Header injection{ "name": "X-Tenant-Id", "value": "{{tenant_id}}", "action": "set" }
// Header removal{ "name": "Authorization", "action": "remove" }
// Path rewrite{ "match": "^/v1/", "replace": "/openai/v1/" }Step 4: (Optional) Configure Rate Limiting
Under the Rate Limit tab:
- Max Requests — tokens in the bucket (e.g.,
1000) - Window (ms) — refill window (e.g.,
60000= 1 minute)
When exhausted, returns 429 Too Many Requests with a Retry-After header.
Step 5: (Optional) Configure IP Filtering
Under the Security tab:
- IP Allowlist — CIDR ranges allowed (empty = allow all)
- IP Blocklist — CIDR ranges blocked with
403 Forbidden
Blocklist is evaluated before allowlist. Supports CIDR notation (e.g., 192.168.0.0/16).
Step 6: (Optional) Configure Circuit Breaker
Under the Circuit Breaker tab:
- Set failure threshold (consecutive failures before opening)
- Set recovery threshold (successes in half-open state to close)
Step 7: (Optional) Configure TLS
Under the TLS tab:
- Upload client certificate and key for mTLS
- Set CA certificate for upstream verification
- Set minimum TLS version
Step 8: Save
Click Save. The route becomes active immediately for all matching requests.
Step 9: Test Connectivity
Click the Test button on the route row to verify upstream reachability.
How to Create a Load-Balanced Route
Section titled “How to Create a Load-Balanced Route”- Create the route following Steps 1–8 above
- Toggle Load Balancing on
- Add multiple provider entries, each with:
- URL — upstream endpoint
- Weight — traffic share (e.g., OpenAI: 70, Anthropic: 30)
- Select Strategy:
weighted— distribute by weight ratioround-robin— rotate sequentiallyleast-loaded— send to upstream with fewest active connectionsfailover— use primary until unhealthy, then fall back to next
- Click Save
AgentWatch distributes traffic according to weights and real-time health state.
Advanced Configuration Reference
Section titled “Advanced Configuration Reference”Health Monitoring States
| State | Meaning |
|---|---|
healthy |
Responding within expected latency |
degraded |
Elevated latency or occasional errors |
unhealthy |
Auto-disabled after 6 consecutive failures; re-enabled after 3 successes |
View real-time health at Settings → Gateway Analytics.
Header-Based Routing
Match routes based on request headers:
matchHeaders— JSON object of expected header name/value pairsmatchHeaderMode—all(all headers must match) orany(any one header matches)
Gateway Routes API Reference
Section titled “Gateway Routes API Reference”| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/gateway/routes |
List all routes |
| GET | /admin/gateway/routes/:id |
Get a single route |
| POST | /admin/gateway/routes |
Create a route |
| PUT | /admin/gateway/routes/:id |
Update a route |
| DELETE | /admin/gateway/routes/:id |
Delete a route |
| POST | /admin/gateway/routes/:id/test |
Test upstream connectivity |
| GET | /admin/gateway/health |
Health status for all routes |
| GET | /admin/gateway/routes/:id/health |
Health status for a single route |
| GET | /admin/gateway/analytics |
Metrics for all routes |
| GET | /admin/gateway/routes/:id/analytics |
Metrics for a single route |
| GET | /admin/gateway/routes-export |
Export routes as JSON or CSV |
| POST | /admin/gateway/routes-import |
Import routes from JSON |
| POST | /admin/gateway/routes/:id/clone |
Duplicate a route |
| PUT | /admin/gateway/routes-reorder |
Reorder routes by priority |
| POST | /admin/gateway/routes-bulk |
Bulk enable/disable/delete |
| GET | /admin/gateway/circuit-breakers |
Circuit breaker states |
2. Tenant Routing Rules
Section titled “2. Tenant Routing Rules”What Are Tenant Routing Rules?
Section titled “What Are Tenant Routing Rules?”Tenant Routing Rules give organization admins fine-grained control over how API requests are processed before and after the LLM call. Rules are evaluated for every inbound request and can inject context, run external guardrails, post-process responses, or bypass the LLM pipeline entirely — all without changing application code.
Rules are org-scoped: company admins manage rules for their own organization. Super admins can view and manage rules across all organizations.
Inbound Request │ ▼┌─────────────────────────────────────────────────────────────────┐│ Rules sorted by priority (highest first) ││ ││ 1. Check each rule's match conditions (all must pass — AND) ││ 2. Collect all matching rules grouped by mode ││ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ PASSTHROUGH rule matched? │ ││ │ YES → Forward directly to upstream. Stop. (Terminal) │ ││ │ NO → Continue ↓ │ ││ └──────────────────────────────────────────────────────────┘ ││ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ ENRICHED rules (stacked, in priority order) │ ││ │ Inject system prompts, fetch context, run guardrails │ ││ └──────────────────────────────────────────────────────────┘ ││ │ ││ ▼ (LLM called exactly once) ││ ┌──────────────────────────────────────────────────────────┐ ││ │ LLM CALL (Proxied pipeline: DLP → Guardrails → LLM) │ ││ └──────────────────────────────────────────────────────────┘ ││ │ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ POST-PROCESS rules (stacked, in priority order) │ ││ │ Modify, redact, or validate the LLM response │ ││ └──────────────────────────────────────────────────────────┘ ││ ││ Rule cache TTL: 30 seconds │└─────────────────────────────────────────────────────────────────┘Tenant Routing Key Features
Section titled “Tenant Routing Key Features”| Feature | Description |
|---|---|
| 4 Processing Modes | proxied, passthrough, enriched, post_process |
| Rule Stacking | Multiple enriched and post-process rules apply sequentially per request |
| Passthrough Terminal | Passthrough rule short-circuits all other rules |
| Skip DLP/Guardrails | Passthrough rules bypass DLP, guardrails, and usage tracking |
| External Guardrails | Integrates Prisma AIRS, Bedrock Guardrails, Lakera, Azure Content Safety, and any REST API |
| System Prompt Injection | Prepend dept-specific or org-wide system prompts before the LLM call |
| Context Enrichment | Fetch context from an internal API and inject it into the LLM request |
| Response Post-Processing | Send LLM output to an external service for redaction, summarization, or validation |
| Rich Match Conditions | Match on model glob, input regex, keywords, providers, employee header/values, or custom headers |
| Priority Ordering | Higher priority number = evaluated first; drag-to-reorder in the UI |
| Rule Cache | 30-second TTL cache for enabled rules; cleared immediately on create/update/delete |
| Custom API Body Template | Full control over the JSON sent to external guardrail/context APIs via {{placeholder}} templates |
| Audit Logging | Every create, update, and delete is logged to the audit trail |
| Access Control | company_admin: own org only; super_admin: all orgs |
Processing Modes
Section titled “Processing Modes”Proxied (Default)
Section titled “Proxied (Default)”Full internal pipeline: DLP scanning → guardrails → LLM call → usage tracking. No additional configuration.
Use when: Standard requests where all security controls should apply.
Passthrough
Section titled “Passthrough”Forwards directly to an upstream URL, bypassing the entire AgentWatch pipeline. Terminal — if matched, all other rules are skipped.
Use when: Routing specific tenants or models to an external service without DLP or tracking overhead.
| Config Field | Description | Example |
|---|---|---|
upstreamUrl |
URL to forward to | https://internal-llm.company.com/v1 |
upstreamAuthHeaders (optional) |
Auth headers for upstream | {"Authorization": "Bearer sk-..."} |
forwardHeaders (optional) |
Forward original client headers | true |
skipDlp |
Skip DLP scanning | true |
skipGuardrails |
Skip guardrail checks | true |
skipUsageTracking |
Skip token/cost tracking | false |
Enriched (Pre-LLM Enhancement)
Section titled “Enriched (Pre-LLM Enhancement)”Runs before the LLM call to inject context, prepend system prompts, or validate via an external guardrail. Multiple enriched rules stack in priority order.
Use when: Dept-specific system prompts, fetching user context from an internal API, or running third-party guardrails (Prisma AIRS, Lakera, AWS Bedrock, Azure Content Safety).
| Config Field | Description | Example |
|---|---|---|
systemPrompt |
Injected as system message before LLM call | "You are a helpful assistant for Engineering." |
contextApiUrl |
External API URL for context or guardrail verdict | http://guardrail.internal.com/check |
apiMode |
context — inject API response as context; guardrail — treat as block/allow verdict |
guardrail |
onGuardrailFailure |
allow — fail-open; block — fail-closed |
allow |
contextApiReplaceInput |
Replace last user message with API response (e.g., PII redaction) | false |
contextApiResponsePath |
JSON path to extract from API response | "result.cleanedText" |
contextApiHeaders (optional) |
Auth headers for external API | {"Authorization": "Bearer token"} |
contextApiBodyTemplate (optional) |
JSON template for body sent to external API | See Custom API Body Templates |
Post-Process (Response Modification)
Section titled “Post-Process (Response Modification)”Runs after the LLM responds to transform or validate output. Multiple post-process rules stack in priority order. The LLM is called exactly once.
Use when: Redacting sensitive data from responses, summarizing long outputs, or validating responses against a compliance policy.
| Config Field | Description | Example |
|---|---|---|
postProcessUrl |
External service URL | https://redaction.internal.com/api/scrub |
postProcessHeaders (optional) |
Auth headers | {"x-api-key": "..."} |
detectEmptyContent |
Skip post-processing if LLM response is empty | true |
detectMinTokens (optional) |
Minimum token count to trigger post-processing | 50 |
Match Conditions
Section titled “Match Conditions”All specified conditions must be satisfied (AND logic). Leave a field blank to skip that condition.
| Condition | Format | Behaviour |
|---|---|---|
| Model Pattern | Glob or regex (e.g., gemini*, gpt-4.*) |
Matches the model name in the request |
| Input Pattern | Regex (e.g., \b(translate|summarize)\b) |
Applied to the last user message content |
| Keywords | Comma-separated (e.g., urgent, priority) |
Any keyword present in user message triggers match (OR within keywords) |
| Providers | Comma-separated (e.g., openai, anthropic) |
Matches the resolved LLM provider name |
| Employee Header | Header name (e.g., X-Employee-Id) |
Checks for presence of this request header |
| Employee Values | Comma-separated (e.g., emp001, emp002) |
Header value must match one of these |
| Headers (JSON) | JSON object (e.g., {"X-Department": "engineering"}) |
All specified headers must be present and match (values are regex tested) |
If no match conditions are specified, the rule matches all requests — useful for org-wide system prompts or global guardrails.
How to Create a Tenant Routing Rule
Section titled “How to Create a Tenant Routing Rule”Step 1: Navigate to Tenant Routing Rules
- Log in to the AgentWatch Admin Panel
- In the left sidebar go to Gateway → Tenant Routing Rules
- Click + Add Rule
Step 2: Set Basic Properties
| Field | Description |
|---|---|
| Name | Descriptive identifier (e.g., Engineering Team Guardrail) |
| Mode | Select: Proxied, Passthrough, Enriched, or Post-Process |
| Description | Optional notes |
| Priority | Higher number = evaluated first. Use 100 for high-priority org-wide rules, 0 for defaults |
| Enabled | Set to Yes to activate immediately |
Step 3: Configure Match Conditions
Add only the conditions relevant to your use case:
- Apply to all Gemini requests → Model Pattern:
gemini* - Apply to the Engineering team → Headers (JSON):
{"X-Department": "engineering"} - Apply when user asks to translate → Keywords:
translate - Apply to specific employees → Employee Header:
X-Employee-Id+ Employee Values:emp001, emp002 - Match all requests (org-wide) → leave all conditions blank
Step 4: Configure Mode Settings
For Enriched — System Prompt only:
- Enter text in the System Prompt field
- Leave External API URL blank
- Save
For Enriched — External Guardrail:
- Enter the guardrail service URL in External API URL
- Set API Mode to
Guardrail - Choose On Guardrail Failure:
Fail Open(allow if unreachable) orFail Closed(block) - Add required auth headers in API Headers (JSON)
- (Optional) Enter a JSON template in Custom API Body to control exactly what is sent
- Save
For Passthrough:
- Enter the Upstream URL
- Optionally add Upstream Auth Headers in JSON format
- Check Forward Original Headers if the upstream needs them
- Save
For Post-Process:
- Enter the Post-Process URL
- Add auth headers if required
- Configure skip conditions (empty content, min tokens)
- Save
Step 5: Reorder Rules by Priority
Drag rows in the rules table to reorder. Changes take effect within 30 seconds (cache TTL).
Common Patterns
Section titled “Common Patterns”Pattern 1: Organization-Wide System Prompt
Name: Company AI PolicyMode: EnrichedMatch Conditions: (none — matches all)System Prompt: "You are an assistant for Acme Corp. Do not discuss competitor products. Always respond in a professional tone. If asked about pricing, direct users to sales@acme.com."Priority: 10Pattern 2: Department-Specific Guardrail
Name: Legal Team GuardrailMode: EnrichedMatch Conditions: Headers (JSON): {"X-Department": "legal"}External API URL: https://guardrail.internal.company.com/legalAPI Mode: GuardrailOn Guardrail Failure: Fail OpenPriority: 50Pattern 3: Restrict Model Access
Name: Restrict GPT-4 to Approved TeamsMode: Passthrough (to internal proxy that enforces team check)Match Conditions: Model Pattern: gpt-4*Upstream URL: https://model-access-proxy.internal.company.com/v1Priority: 100Pattern 4: Response Redaction for Customer Data
Name: PII Response RedactorMode: Post-ProcessMatch Conditions: Providers: openai, anthropicPost-Process URL: https://redaction.internal.company.com/api/scrubPriority: 20Pattern 5: Prisma AIRS with User Identity Headers
Name: prisma-guardrailMode: EnrichedMatch Conditions: Model Pattern: gpt*External API URL: https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/requestAPI Mode: GuardrailOn Guardrail Failure: Fail OpenAPI Headers (JSON): {"x-pan-token": "<YOUR_PRISMA_TOKEN>", "Content-Type": "application/json"}Custom API Body: { "tr": { "id": "{{request_id}}", "user_id": "{{header.X-Employee-Id}}", "dept": "{{header.X-Department}}", "model": "{{model}}" }, "ai_profile": { "name": "prisma-guardrail" }, "metadata": { "ai_model": "{{model}}", "app_name": "AgentWatch" }, "contents": [{ "prompt": "{{query}}" }] }Priority: 100Custom API Body Templates
Section titled “Custom API Body Templates”By default AgentWatch sends the following body to the External API URL:
Context mode default:
{ "query": "<last user message>", "messages": [...]}Guardrail mode default:
{ "query": "<last user message>", "messages": [...], "metadata": { "request_id": "...", "tenant_id": "...", "organization_id": "...", "model": "...", "provider": "..." }}Use the Custom API Body field to override with a JSON template for your guardrail service’s expected format. Leave empty to keep the default.
Available template variables:
| Variable | Value |
|---|---|
{{query}} |
The last user message content |
{{model}} |
The LLM model name (e.g., gpt-4o) |
{{tenant_id}} |
Tenant API key identifier |
{{org_id}} |
Organization ID |
{{request_id}} |
Unique request ID for tracing |
{{session_id}} |
Session ID (when available) |
{{provider}} |
Resolved LLM provider name |
{{header.X-Header-Name}} |
Value of any incoming request header (case-insensitive) |
Template rules:
- Template must be valid JSON after substituting all
{{placeholder}}values - String values containing
{{query}}are auto-escaped (quotes, newlines) for valid JSON - Variables with no value substitute with empty string
"" - Invalid template falls back to default body and logs a warning
Lakera Guard example:
{ "input": "{{query}}", "metadata": { "user": "{{header.X-Employee-Id}}", "model": "{{model}}" }}Custom internal context API example:
{ "query": "{{query}}", "tenant": "{{tenant_id}}", "org": "{{org_id}}", "requestId": "{{request_id}}"}Tenant Routing API Reference
Section titled “Tenant Routing API Reference”| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/tenant-routing-rules |
List all rules (org-scoped) |
| GET | /admin/tenant-routing-rules/:id |
Get a single rule |
| POST | /admin/tenant-routing-rules |
Create a new rule |
| PUT | /admin/tenant-routing-rules/:id |
Update a rule |
| PUT | /admin/tenant-routing-rules/reorder |
Reorder rules (send ordered array of IDs) |
| DELETE | /admin/tenant-routing-rules/:id |
Delete a rule |
Access control:
| Role | Capability |
|---|---|
super_admin |
Create, view, edit, delete rules across all organizations |
company_admin |
Create, view, edit, delete rules within their own organization |
| Other roles | No access |
3. Plugins
Section titled “3. Plugins”What Are Plugins?
Section titled “What Are Plugins?”The Plugin System lets administrators extend gateway behavior with custom JavaScript logic — no core code changes, no server restarts. Plugins run in a sandboxed Node.js vm context at specific lifecycle hooks and can modify requests, transform responses, block traffic, or fire-and-forget metrics.
Fail-open design: If a plugin throws an unhandled exception, the error is logged and the request proceeds normally. Plugins can never crash the gateway.
Inbound Request │ ▼┌──────────────────────────────────────────────────────────────┐│ pre-request plugins (sorted by priority, lowest runs first) ││ Can: modify request body/headers, short-circuit with error │└──────────────────────────────────────────────────────────────┘ │ ▼ (forwarded to upstream) │┌──────────────────────────────────────────────────────────────┐│ post-request plugins ││ Can: inspect request metadata after forwarding │└──────────────────────────────────────────────────────────────┘ │ ▼┌──────────────────────────────────────────────────────────────┐│ pre-response plugins ││ Can: modify response body/headers before sending to client │└──────────────────────────────────────────────────────────────┘ │ ▼ (response sent to client) │┌──────────────────────────────────────────────────────────────┐│ post-response plugins (async, non-blocking) ││ Can: fire-and-forget logging, metrics │└──────────────────────────────────────────────────────────────┘
on-error plugins fire only when a gateway error occurs.Plugin Key Features
Section titled “Plugin Key Features”| Feature | Description |
|---|---|
| 5 Lifecycle Hooks | pre-request, post-request, pre-response, post-response, on-error |
| Sandboxed Execution | Runs in Node.js vm module — no access to filesystem, network, process, or timers |
| Route Filter | Glob pattern to limit which routes trigger the plugin (default * = all routes) |
| Priority Ordering | Within the same hook, lower priority number runs first |
| Configurable Timeout | Per-plugin execution time limit (default 5000ms, max 30000ms) |
| Request Modification | Plugins can add/remove/modify headers and body fields |
| Response Modification | Plugins can transform response body and headers before the client receives them |
| Short-Circuit | Return { proceed: false } to block the request with a custom status code and body |
| Plugin Config | Pass configuration JSON per plugin, accessed as ctx.config |
| Execution Logging | Last 100 log entries per plugin, accessible from the UI |
| Execution Stats | Per-plugin execution count, error count, last error, average latency |
| Built-in Templates | 4 starter templates: Log Request, Add Header, Rate Limit by Header, Transform Body |
| Test Runner | Test a plugin against a sample context directly from the UI |
| Enable/Disable Toggle | Enable or disable without deleting |
| Auto-reload | Plugin changes are hot-reloaded — no restart required |
Lifecycle Hooks
Section titled “Lifecycle Hooks”| Hook | When It Fires | Context Available | Typical Use |
|---|---|---|---|
pre-request |
Before forwarding to upstream | ctx.request, ctx.route |
Add/remove headers, validate body, block requests |
post-request |
After upstream receives request, before response processing | ctx.request, ctx.route |
Log request metadata |
pre-response |
Before sending upstream response back to client | ctx.request, ctx.response, ctx.route |
Transform or redact response body |
post-response |
After response sent to client (async) | ctx.request, ctx.response, ctx.route |
Fire-and-forget logging, metrics |
on-error |
When any gateway error occurs | ctx.request, ctx.route |
Custom error responses, alerting |
Execution order within same hook: priority ascending — lower number runs first.
Plugin Context API
Section titled “Plugin Context API”Every plugin receives a ctx object (also aliased as context):
ctx.request = { method, // "POST" path, // "/v1/chat/completions" headers, // { "authorization": "Bearer ...", ... } body, // parsed JSON body object query // query string params as object}
ctx.response = { // only in post-request, pre-response, post-response statusCode, // 200, 400, etc. headers, // response headers body // parsed JSON body}
ctx.route = { name, // route display name id, // route UUID upstream // upstream URL}
ctx.config // values from the plugin's config field (JSON object)
ctx.logger.info("message", { optional: "data" })ctx.logger.warn("message")ctx.logger.error("message")
ctx.metrics.increment("my-counter")ctx.metrics.record("latency-ms", 42)Return values:
// Continue normallyreturn { proceed: true };
// Modify the requestreturn { proceed: true, modifiedRequest: { method: ctx.request.method, path: ctx.request.path, headers: { ...ctx.request.headers, "X-Request-Id": "abc123" }, body: ctx.request.body, query: ctx.request.query }};
// Modify the responsereturn { proceed: true, modifiedResponse: { statusCode: ctx.response.statusCode, headers: ctx.response.headers, body: { ...ctx.response.body, _source: "agentwatch" } }};
// Short-circuit (block the request)return { proceed: false, statusCode: 403, body: { error: "Blocked by plugin", code: "PLUGIN_BLOCKED" }};Plugin Fields Reference
Section titled “Plugin Fields Reference”| Field | Required | Description |
|---|---|---|
| Name | Yes | Short kebab-case identifier (e.g., add-request-id) |
| Description | No | What the plugin does |
| Hook | Yes | Which lifecycle stage to run at |
| Route Filter | No | Glob pattern to limit which routes trigger this plugin. Default * = all routes |
| Priority | No | Execution order within same hook. Lower = runs first. Default 0 |
| Timeout (ms) | No | Max execution time before plugin is killed. Default 5000, max 30000 |
| Enabled | Yes | Toggle on/off without deleting |
| Plugin Code | Yes | JavaScript source. Must return { proceed: true } or { proceed: false, ... } |
| Config (JSON) | No | Key-value config accessible as ctx.config inside the plugin |
Route filter patterns:
| Pattern | Matches |
|---|---|
* |
All routes |
chat-* |
Routes whose name starts with chat- |
*/production |
Routes ending in /production |
** |
All routes including nested names |
openai-gpt4 |
Exactly the route named openai-gpt4 |
How to Create a Plugin
Section titled “How to Create a Plugin”Step 1: Navigate to Plugins
- Log in to the AgentWatch Admin Panel
- In the left sidebar go to Gateway → Plugins
- Click + New Plugin
Step 2: Set Name and Description
- Name: Short kebab-case identifier, e.g.,
add-correlation-id - Description: What the plugin does, e.g.,
Injects X-Correlation-Id header on every request
Step 3: Choose a Hook
Pick the hook that matches when your logic should run:
- Modifying the request before it reaches upstream →
pre-request - Transforming the LLM response before sending to client →
pre-response - Logging after response is sent →
post-response - Custom error handling →
on-error
Step 4: Set the Route Filter
- Leave as
*to apply to all gateway routes - Enter a specific pattern like
openai-*to target only matching routes
Step 5: Set Priority and Timeout
- Priority: If multiple plugins share the same hook, lower number runs first. Use
0for most plugins - Timeout: Default
5000msis appropriate for most logic. Decrease for lightweight header work; do not use for external HTTP calls (network is blocked in sandbox)
Step 6: Write the Plugin Code
The code must always return { proceed: true } or { proceed: false, ... }. Use a built-in template (see below) or write custom logic. Add any configuration values to the Config (JSON) field and access them via ctx.config.
Step 7: Save and Test
- Click Save Plugin
- On the plugin row, click Test to run the plugin against a sample context
- Click Logs to view execution logs (last 100 entries per plugin)
Built-in Templates
Section titled “Built-in Templates”Template 1: Log Request Logs every request’s method, path, and headers.
ctx.logger.info("Incoming request", { method: ctx.request.method, path: ctx.request.path, headers: ctx.request.headers, query: ctx.request.query});return { proceed: true };Template 2: Add Header
Adds custom headers to the request (reads from ctx.config.headers).
var headers = ctx.request.headers || {};var configHeaders = ctx.config.headers || {};var keys = Object.keys(configHeaders);for (var i = 0; i < keys.length; i++) { headers[keys[i]] = configHeaders[keys[i]];}return { proceed: true, modifiedRequest: { method: ctx.request.method, path: ctx.request.path, headers: headers, body: ctx.request.body, query: ctx.request.query }};Template 3: Rate Limit by Header Blocks requests missing a required header.
var headerName = ctx.config.headerName || "x-api-key";var key = ctx.request.headers[headerName];if (!key) { return { proceed: false, statusCode: 401, body: { error: "Missing required header: " + headerName } };}ctx.logger.info("Header present", { key: key });return { proceed: true };Template 4: Transform Body Adds or removes fields from the request body.
var body = ctx.request.body || {};var addFields = ctx.config.addFields || {};var removeFields = ctx.config.removeFields || [];var keys = Object.keys(addFields);for (var i = 0; i < keys.length; i++) { body[keys[i]] = addFields[keys[i]];}for (var j = 0; j < removeFields.length; j++) { delete body[removeFields[j]];}return { proceed: true, modifiedRequest: { method: ctx.request.method, path: ctx.request.path, headers: ctx.request.headers, body: body, query: ctx.request.query }};Sandbox Restrictions
Section titled “Sandbox Restrictions”The following are not available inside plugin code:
| Blocked | Reason |
|---|---|
require / import |
No module loading |
fetch / XMLHttpRequest |
No outbound network calls |
process / global |
No server environment access |
fs / child_process |
No filesystem or shell access |
setTimeout / setInterval |
No async timers |
Buffer |
No raw binary access |
Use ctx.config to pass configuration values into the plugin rather than hardcoding secrets.
Plugins API Reference
Section titled “Plugins API Reference”| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/gateway/plugins |
List all plugins |
| GET | /api/admin/gateway/plugins/templates |
Get built-in plugin templates |
| GET | /api/admin/gateway/plugins/:id |
Get a single plugin |
| POST | /api/admin/gateway/plugins |
Create a plugin |
| PUT | /api/admin/gateway/plugins/:id |
Update a plugin |
| DELETE | /api/admin/gateway/plugins/:id |
Delete a plugin |
| POST | /api/admin/gateway/plugins/:id/test |
Test-execute in sandbox |
| GET | /api/admin/gateway/plugins/:id/logs |
Get last 100 execution logs |
| GET | /api/admin/gateway/plugin-stats |
Global stats: executions, errors, avg latency |
All endpoints require company_admin role or higher.
4. Feature Comparison
Section titled “4. Feature Comparison”| Gateway Routes | Tenant Routing Rules | Plugins | |
|---|---|---|---|
| Scope | Platform-wide, path-based | Per-organization, request-content-based | Per-route, lifecycle-based |
| Triggers on | URL path pattern | Model name, input content, keywords, headers, providers | Lifecycle hook within a route |
| Purpose | Proxy/forward traffic to upstream URLs | Modify how the LLM pipeline processes a request | Custom logic at request/response stages |
| Modes | active, enriched, post_process |
passthrough, proxied, enriched, post_process |
N/A — hook-based |
| Passthrough | Not supported | Supported — bypasses DLP, guardrails, LLM pipeline | N/A |
| Stacking | One route matches per request | Multiple enriched and post-process rules stack | Multiple plugins stack per hook |
| Skip DLP/Guardrails | Not configurable | Yes — per rule | Not directly (block with proceed: false) |
| External guardrail | Not supported | Yes — Prisma AIRS, Bedrock Guardrails, Lakera, custom REST | Possible via shared state only |
| Configured by | Super admins (platform level) | Organization admins (per-org) | Organization admins (per-org) |
| Execution order | Priority (lower = first) | Priority (higher = first) | Priority (lower = first) within hook |
5. When to Use Which
Section titled “5. When to Use Which”Use Gateway Routes when you need to:
- Route specific API paths to a different backend service
- Apply IP filtering or rate limiting at the network level
- Run schema validation on requests regardless of tenant
- Load-balance traffic across multiple LLM providers
- Apply mTLS or TLS client cert auth to upstream calls
Use Tenant Routing Rules when you need to:
- Inject a system prompt for a specific team or all requests org-wide
- Run an external guardrail (Prisma AIRS, Lakera, Bedrock) before the LLM
- Bypass the LLM proxy entirely for specific tenants or models
- Post-process LLM responses with an external redaction or compliance service
- Match on input content, model names, keywords, or custom headers
Use Plugins when you need to:
- Add or remove headers on every request through a specific route
- Log request/response metadata to a custom metric store
- Block requests based on custom business logic not covered by built-in features
- Transform response bodies before they reach clients
- Inject request IDs, correlation headers, or metadata at the HTTP level
6. DLP Settings Reference
Section titled “6. DLP Settings Reference”DLP settings in AgentWatch are configured at two independent levels — they do not cascade:
Level 1: Platform Default (Super Admin) Applies only to the platform’s own internal employees. Does not affect customer organizations.
Level 2: Organization Settings (Company Admin) Each organization has its own isolated DLP record, created automatically on onboarding. Platform defaults do not carry over — must be configured explicitly.
Navigate to: Company Settings → Compliance & Data Governance → DLP
Confirm all three are set:
- DLP Scanning: Enabled
- Action on Detection: Block
- Sensitivity: chosen deliberately (see below)
Sensitivity (2.0.363 and later). Sensitivity is a set of risk levels, not a score threshold. With Block selected, a finding in the chosen levels refuses the request and any other finding is redacted; every finding is recorded.
| Sensitivity | Blocks | Stored dlpRiskThreshold |
|---|---|---|
| Low | High-risk data only — SSN, passport, driver’s license, cards, bank / routing / IBAN, medical record and member IDs, credentials | 7 or more |
| Medium | High-risk data plus email, phone, date of birth, street address, tax ID, medication, diagnosis, lab results. Recommended balance | 4 to under 7 |
| High | Everything above, plus IP addresses and ZIP codes. Names, organizations, places and dates are redacted, never blocked | under 4, including the default 0 |