Tenant Routing Rules
Tenant Routing Rules give administrators fine-grained control over how API requests are processed before and after the LLM call. Rules are evaluated for every inbound request, enabling per-team prompt injection, external guardrails, response post-processing, and direct passthrough — all without touching application code.
How Rules Are Evaluated
Section titled “How Rules Are Evaluated”Rules are sorted by priority (higher value = evaluated first). Every rule whose match conditions are satisfied is collected and applied:
Inbound Request │ ▼┌─────────────────────────────────────────────────────────────────┐│ Rules sorted by priority (highest first) ││ ││ 1. Check each rule's match conditions (all must pass — AND) ││ 2. Collect all matching rules 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 │ ││ └──────────────────────────────────────────────────────────┘ ││ │ ││ ▼ (exactly once) ││ ┌──────────────────────────────────────────────────────────┐ ││ │ LLM CALL (Proxied pipeline: DLP → Guardrails → LLM) │ ││ └──────────────────────────────────────────────────────────┘ ││ │ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ POST-PROCESS rules (stacked, in priority order) │ ││ │ Modify, redact, or validate the LLM response │ ││ └──────────────────────────────────────────────────────────┘ ││ ││ Note: The LLM is called exactly once regardless of how many ││ rules match. Rule cache TTL is 30 seconds. │└─────────────────────────────────────────────────────────────────┘Processing Modes
Section titled “Processing Modes”Proxied (Default)
Section titled “Proxied (Default)”The default pipeline — all requests that do not match any other mode run through the full internal pipeline: DLP scanning → guardrails → LLM call → usage tracking. No additional configuration required.
When to use: Standard requests where you want all security controls applied.
Passthrough
Section titled “Passthrough”Forwards the request directly to an upstream URL, bypassing the entire AgentWatch pipeline. Terminal: if a passthrough rule matches, all other rules are skipped.
When to use: Routing specific tenants or models directly to an external service without DLP or tracking overhead.
Required configuration:
| Field | Description | Example |
|---|---|---|
upstreamUrl |
URL to forward the request to | https://internal-llm.company.com/v1 |
upstreamAuthHeaders (optional) |
Auth headers for the upstream service | {"Authorization": "Bearer sk-..."} |
forwardHeaders (optional) |
Forward original client request headers | true |
Enriched (Pre-LLM Enhancement)
Section titled “Enriched (Pre-LLM Enhancement)”Runs before the LLM call to inject context, prepend system prompts, or validate the request against an external guardrail service. Multiple enriched rules can match and apply sequentially in priority order.
When to use: Injecting department-specific system prompts, fetching user context from an internal API, or running a third-party guardrail (Prisma AIRS, Lakera, AWS Bedrock Guardrails, Azure Content Safety).
Configuration fields:
| Field | Description | Example |
|---|---|---|
systemPrompt |
Text injected as a system message before the LLM call | "You are a helpful assistant for the Engineering team." |
contextApiUrl |
External API to call for context or guardrail verdict | https://guardrail.example.com/verdict |
apiMode |
context — inject API response as additional context; guardrail — treat API response as block/allow verdict |
guardrail |
onGuardrailFailure |
"allow" — fail-open (proceed if guardrail unreachable); "block" — fail-closed |
"allow" |
contextApiReplaceInput |
Replace the last user message with the API response instead of appending context (useful for PII redaction) | false |
contextApiResponsePath |
JSON path to extract from the API response | "result.cleanedText" |
contextApiHeaders (optional) |
Auth/custom headers for the external API | {"Authorization": "Bearer token"} |
contextApiBodyTemplate (optional) |
JSON template for the body sent to the external API. Supports {{placeholder}} substitution. When omitted, the default body is used. |
See Custom API Body Template |
Guardrail API mode: When apiMode is set to guardrail, AgentWatch sends the request payload to the external URL and expects a response indicating allow or block. Supported with Prisma AIRS, Lakera Guard, AWS Bedrock Guardrails, and Azure Content Safety.
Post-Process (Response Modification)
Section titled “Post-Process (Response Modification)”Runs after the LLM responds to transform or validate the output. Multiple post-process rules stack in priority order. The LLM is called exactly once.
When to use: Redacting sensitive data from responses, summarizing long outputs, or validating responses against a compliance policy.
Configuration fields:
| Field | Description | Example |
|---|---|---|
postProcessUrl |
External service URL to send the LLM response to | https://redaction.internal.company.com/process |
postProcessHeaders (optional) |
Auth headers for the post-process service | {"x-api-key": "..."} |
detectEmptyContent |
Skip post-processing if the LLM response is empty | true (default) |
detectMinTokens (optional) |
Minimum token count before post-processing triggers | 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 | Behavior |
|---|---|---|
| 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 the 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 global system prompts or org-wide guardrails.
Step-by-Step: Setting Up Tenant Routing Rules
Section titled “Step-by-Step: Setting Up Tenant Routing Rules”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 | A descriptive name (e.g., Engineering Team Guardrail) |
| Mode | Select the processing mode (see modes above) |
| Description | Optional notes about what the rule does |
| 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. Examples:
- Apply to all Gemini requests: Set Model Pattern to
gemini* - Apply to the Engineering team: Set Headers (JSON) to
{"X-Department": "engineering"} - Apply when user asks to translate: Set Keywords to
translate - Apply to specific employees: Set Employee Header to
X-Employee-Idand Employee Values toemp001, emp002
Step 4: Configure Mode Settings
Depending on the mode selected:
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 any required auth headers in API Headers (JSON)
- (Optional) Enter a JSON template in Custom API Body to control exactly what is sent to the guardrail — see Custom API Body Template
- Save
For Passthrough:
- Enter the Upstream URL (the service to forward to)
- Optionally add Upstream Auth Headers in JSON format
- Check Forward Original Headers if the upstream needs client headers
- 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. The order is reflected as priority values and takes effect within 30 seconds (cache TTL).
Common Setup Patterns
Section titled “Common Setup 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: Block Sensitive 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 Guardrail with User Identity
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 Template
Section titled “Custom API Body Template”By default, AgentWatch sends the following JSON body to the External API URL:
Context mode (default body):
{ "query": "<last user message>", "messages": [...]}Guardrail mode (default body):
{ "query": "<last user message>", "messages": [...], "metadata": { "request_id": "...", "tenant_id": "...", "organization_id": "...", "model": "...", "provider": "..." }}Use the Custom API Body field to override this with a JSON template tailored to your guardrail or context API’s expected format. Leave it empty to keep the default.
Available Template Variables
Section titled “Available Template Variables”| Variable | Value |
|---|---|
{{query}} |
The last user message content |
{{model}} |
The LLM model name (e.g., gpt-4o) |
{{tenant_id}} |
The tenant API key identifier |
{{org_id}} |
The organization ID |
{{request_id}} |
Unique request ID for tracing |
{{session_id}} |
Session ID (when available) |
{{provider}} |
The resolved LLM provider name |
{{header.X-Header-Name}} |
Value of any incoming request header (case-insensitive) |
Template Rules
Section titled “Template Rules”- The template must be valid JSON after substituting all
{{placeholder}}values with"test". Validation runs on save. - String values containing
{{query}}are automatically escaped (quotes, newlines) so the resulting JSON is always valid. - If the substituted template cannot be parsed as JSON (e.g., a malformed template), AgentWatch falls back to the default body and logs a warning.
- Variables that have no value (e.g., a header that wasn’t sent) are substituted with an empty string
"".
Example: Prisma AIRS body
Section titled “Example: Prisma AIRS body”{ "tr": { "id": "{{request_id}}", "user_id": "{{header.X-Employee-Id}}", "dept": "{{header.X-Department}}" }, "ai_profile": { "name": "prisma-guardrail" }, "metadata": { "ai_model": "{{model}}", "app_name": "AgentWatch" }, "contents": [{ "prompt": "{{query}}" }]}Example: Lakera Guard body
Section titled “Example: Lakera Guard body”{ "input": "{{query}}", "metadata": { "user": "{{header.X-Employee-Id}}", "model": "{{model}}" }}Example: Custom internal context API
Section titled “Example: Custom internal context API”{ "query": "{{query}}", "tenant": "{{tenant_id}}", "org": "{{org_id}}", "requestId": "{{request_id}}"}Access Control
Section titled “Access Control”| Role | Capability |
|---|---|
| super_admin | Create, view, edit, and delete rules across all organizations |
| company_admin | Create, view, edit, and delete rules within their own organization |
| Other roles | No access |
API Reference
Section titled “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 |