Skip to content
Docs

Gateway Rules & Tenant Routing


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 Client

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

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

Step 1: Navigate to Gateway Routes

  1. Log in to the AgentWatch Admin Panel
  2. In the left sidebar go to Settings → Gateway Routes
  3. 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.


  1. Create the route following Steps 1–8 above
  2. Toggle Load Balancing on
  3. Add multiple provider entries, each with:
    • URL — upstream endpoint
    • Weight — traffic share (e.g., OpenAI: 70, Anthropic: 30)
  4. Select Strategy:
    • weighted — distribute by weight ratio
    • round-robin — rotate sequentially
    • least-loaded — send to upstream with fewest active connections
    • failover — use primary until unhealthy, then fall back to next
  5. Click Save

AgentWatch distributes traffic according to weights and real-time health state.


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 pairs
  • matchHeaderMode — all (all headers must match) or any (any one header matches)

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

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 │
└─────────────────────────────────────────────────────────────────┘

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

Full internal pipeline: DLP scanning → guardrails → LLM call → usage tracking. No additional configuration.

Use when: Standard requests where all security controls should apply.


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

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

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

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.


Step 1: Navigate to Tenant Routing Rules

  1. Log in to the AgentWatch Admin Panel
  2. In the left sidebar go to Gateway → Tenant Routing Rules
  3. 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:

  1. Enter text in the System Prompt field
  2. Leave External API URL blank
  3. Save

For Enriched — External Guardrail:

  1. Enter the guardrail service URL in External API URL
  2. Set API Mode to Guardrail
  3. Choose On Guardrail Failure: Fail Open (allow if unreachable) or Fail Closed (block)
  4. Add required auth headers in API Headers (JSON)
  5. (Optional) Enter a JSON template in Custom API Body to control exactly what is sent
  6. Save

For Passthrough:

  1. Enter the Upstream URL
  2. Optionally add Upstream Auth Headers in JSON format
  3. Check Forward Original Headers if the upstream needs them
  4. Save

For Post-Process:

  1. Enter the Post-Process URL
  2. Add auth headers if required
  3. Configure skip conditions (empty content, min tokens)
  4. Save

Step 5: Reorder Rules by Priority

Drag rows in the rules table to reorder. Changes take effect within 30 seconds (cache TTL).


Pattern 1: Organization-Wide System Prompt

Name: Company AI Policy
Mode: Enriched
Match 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: 10

Pattern 2: Department-Specific Guardrail

Name: Legal Team Guardrail
Mode: Enriched
Match Conditions:
Headers (JSON): {"X-Department": "legal"}
External API URL: https://guardrail.internal.company.com/legal
API Mode: Guardrail
On Guardrail Failure: Fail Open
Priority: 50

Pattern 3: Restrict Model Access

Name: Restrict GPT-4 to Approved Teams
Mode: Passthrough (to internal proxy that enforces team check)
Match Conditions:
Model Pattern: gpt-4*
Upstream URL: https://model-access-proxy.internal.company.com/v1
Priority: 100

Pattern 4: Response Redaction for Customer Data

Name: PII Response Redactor
Mode: Post-Process
Match Conditions:
Providers: openai, anthropic
Post-Process URL: https://redaction.internal.company.com/api/scrub
Priority: 20

Pattern 5: Prisma AIRS with User Identity Headers

Name: prisma-guardrail
Mode: Enriched
Match Conditions:
Model Pattern: gpt*
External API URL: https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/request
API Mode: Guardrail
On Guardrail Failure: Fail Open
API 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: 100

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}}"
}

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

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.

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

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.


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 normally
return { proceed: true };
// Modify the request
return {
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 response
return {
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" }
};

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

Step 1: Navigate to Plugins

  1. Log in to the AgentWatch Admin Panel
  2. In the left sidebar go to Gateway → Plugins
  3. 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 0 for most plugins
  • Timeout: Default 5000ms is 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

  1. Click Save Plugin
  2. On the plugin row, click Test to run the plugin against a sample context
  3. Click Logs to view execution logs (last 100 entries per plugin)

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
}
};

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.


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.


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

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

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