Skip to main content

Availability

EditionDeployment Type
EnterpriseSelf-Managed, Hybrid
Available from Tyk AI Studio v2.1.0. Compliance Events are structured audit records that filter scripts can emit to flag governance-relevant activity, such as PII redactions, content rewrites, policy violations, or silent failures, without affecting whether the request is blocked or allowed. They flow through the same analytics pipeline as proxy logs and chat records, are stored centrally on the control plane, and surface in the Compliance dashboard for review, drill-down, and CSV export. This page is the end-to-end reference. For filter script syntax, see Filters.

Why Compliance Events

Before v2.1, the only thing the compliance dashboard could count was 4xx proxy logs, that is, requests the gateway blocked. That misses the most common governance activity in practice: a filter quietly redacts an email address, rewrites a passage of text, or flags something suspicious while still allowing the request through. Those interventions are exactly what compliance teams need visibility into, and they previously left no trace beyond a debug log line. Compliance events fix that. Any filter script can attach a list of structured events to its output. The platform persists them, propagates them from edge gateways back to the control plane, exposes them via API and dashboard, and counts them in Prometheus, all without changing the filter’s block/allow decision.

Event Schema

A compliance event has the following shape (ComplianceEventOutput in the script API):
FieldTypeRequiredDescription
event_typestringYesFree-form type. Suggested conventions: pii_redacted, content_rewritten, policy_violation, harmful_content_detected, silent_failure.
severitystringNoinfo, warning, or critical. Defaults to info if omitted or invalid.
descriptionstringNoHuman-readable description of what happened, shown in the dashboard event row.
metadatamapNoArbitrary key-value data. Stored as JSON. Conventions: matched_pattern (string of the trigger), redacted_types (array of category names).
Validation:
  • Events without an event_type are silently skipped. This avoids polluting the table with empty records when a script branch builds the event partially.
  • severity values outside info|warning|critical are coerced to info.
  • event_type on the API query side is limited to 100 characters; any longer is a 400.
  • severity on the API query side is validated against the same whitelist; an invalid value is a 400.
  • All API queries use GORM parameterized queries, so SQL injection attempts in event_type or severity are treated as literal string matches and return empty results.

Server-Side Enrichment

Scripts only declare the four fields above. The platform enriches each event at recording time with context from the filter execution site:
Enriched fieldSource
app_idApp that owns the request (proxy paths). 0 for chat-session paths, where user_id is the principal.
user_idAuthenticated user (chat-session paths).
llm_idLLM in scope for the request.
vendorLLM vendor string (for example openai, anthropic, bedrock).
model_nameModel being called.
filter_nameName of the filter that emitted the event.
filter_scopeWhere the script ran (see below).
timestampWall-clock time at recording.

Filter Scopes

filter_scope identifies which of the six filter execution sites recorded the event:
ScopeSite
proxy_requestLLM proxy request filters (before reaching the LLM).
proxy_responseLLM proxy response filters (REST and streaming).
chat_requestChat-session message filters (before RAG search and LLM).
chat_responseChat-session response filters (regular and streaming).
file_referenceFile content filters (before RAG indexing).
tool_responseTool response filters (after tool execution).
You can filter on filter_scope indirectly via the dashboard drill-down, or by joining compliance_events.filter_name against your filter catalog.

Emitting Events from a Filter Script

In a Tengo filter script, set compliance_events on the output object:
tyk := import("tyk")

modified_payload := tyk.redact_pattern(
    input,
    "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
    "[EMAIL_REDACTED]"
)

output := {
    block: false,
    payload: modified_payload,
    message: "",
    compliance_events: [
        {
            event_type: "pii_redacted",
            severity: "info",
            description: "Email addresses redacted from request",
            metadata: { "redacted_types": ["email"] }
        }
    ]
}
See Filters for conditional emission, response-filter examples, and metadata conventions.

Behavior

  • Recording is asynchronous. Events are queued and written by the analytics pipeline, so filter latency is unaffected.
  • Compliance events never affect the block/allow decision. Setting block: false with critical events is legitimate (the filter chose to redact rather than block); setting block: true with no events is also legitimate.
  • The list can be empty or omitted. Most filters won’t emit events on every invocation; typically only when a condition fires.
  • Events are recorded per filter, per invocation. A single request can produce multiple events from multiple filters.

Edge Gateways

In a hub-spoke deployment, filter scripts run on the Edge Gateway, but compliance events live on the control plane. The propagation path is:
  1. Filter script on the edge sets compliance_events in its output.
  2. The Edge Gateway’s analytics handler (MicrogatewaAnalyticsHandler.RecordComplianceEvents) queues the events to the pulse plugin’s local buffer, which survives a gateway restart.
  3. On the next analytics pulse (every 30 seconds by default), events are batched into the gRPC AnalyticsPulse message as ComplianceEventProto entries.
  4. The control server’s SendAnalyticsPulse handler reconstructs the compliance event from each entry, preserving LLMID, AppID, UserID, severity, type, description, metadata, vendor, and model name, and persists it.
This means there is no functional difference between an event recorded by the embedded gateway and one recorded by an Edge Gateway: both end up in the same table with the same enrichment. If you need to verify that edge events are arriving, check the aistudio_compliance_events_total counter on the control-plane /metrics endpoint and group by source.

Querying Compliance Events

Admin API

GET /api/v1/compliance/events
Admin-only (requires the v1 route group’s auth middleware and an admin role). Query parameters:
ParamTypeDescription
start_dateISO8601 dateLower bound (inclusive) on timestamp.
end_dateISO8601 dateUpper bound (inclusive) on timestamp.
app_iduintFilter by app.
event_typestring (max 100 chars)Exact match on event_type.
severityinfo / warning / criticalWhitelist; other values return 400.
limitintPage size (default and max are deployment-dependent).
offsetintPage offset.
Example:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  "https://studio.example.com/api/v1/compliance/events?start_date=2026-05-01&end_date=2026-05-14&severity=critical&limit=50"
Response shape:
{
  "events": [
    {
      "id": 1234,
      "app_id": 42,
      "user_id": 0,
      "llm_id": 7,
      "filter_name": "PII Detector",
      "filter_scope": "proxy_request",
      "event_type": "pii_redacted",
      "severity": "info",
      "description": "Email addresses redacted from request",
      "metadata": "{\"redacted_types\":[\"email\"]}",
      "vendor": "openai",
      "model_name": "gpt-4",
      "timestamp": "2026-05-14T09:23:17Z"
    }
  ],
  "total": 1
}
metadata is returned as a JSON-encoded string (the storage shape). Parse it client-side.

SQL

The underlying table is compliance_events. Useful indexes:
  • (app_id, timestamp): composite, for per-app time-bounded queries.
  • user_id, llm_id, filter_name, event_type, severity: single-column, for filtering.
  • timestamp: for retention sweeps.
Example:
SELECT filter_name, event_type, COUNT(*) AS n
FROM compliance_events
WHERE timestamp >= NOW() - INTERVAL '7 days'
  AND severity IN ('warning', 'critical')
GROUP BY filter_name, event_type
ORDER BY n DESC;

Dashboard Surfaces

The Compliance dashboard (/admin/compliance) was extended in v2.1 to surface compliance events alongside the existing blocked-request data.

Summary Cards

Two new cards next to the blocked-request total:
  • Critical Events: count over the selected window, with trend arrow. Escalates the card style above COMPLIANCE_EVENTS_CRITICAL_ESCALATE_AT (default 1, since a single critical event already warrants attention).
  • Warning Events: count over the selected window, with trend arrow. Escalates above COMPLIANCE_EVENTS_WARNING_ESCALATE_AT (default 20, since warnings are advisory and need to accumulate before they matter).

Policy Violations Tab

The summary is split into three counters:
  • Blocked: 4xx proxy logs (existing).
  • Flagged: warning and critical compliance events that passed through (new).
  • Affected Apps: distinct apps appearing in either source.
The timeline chart merges both streams. The per-app violation list unions events into the aggregation, so apps that only ever flag (and never block) appear in the drill-down.

Filter Events Tab

A dedicated tab for compliance events with:
  • Severity totals (info / warning / critical) with a stacked timeline chart.
  • Severity and event-type filters (the event-type dropdown is populated from observed values in the window).
  • Pagination over the event list.
  • Expandable rows showing the raw metadata JSON.
  • CSV export wired to GET /compliance/events.

App Risk Modal

Per-app risk score now includes event counts, with warningEventWeight = 1 and criticalEventWeight = 3, so a single critical event contributes the same to the score as three warning events. Recent Violations interleaves blocked requests and compliance events sorted by timestamp, with severity-aware row rendering.

Metrics

A Prometheus / OpenTelemetry counter is exposed on the /metrics endpoint:
aistudio_compliance_events_total{filter_scope, severity, event_type}
Use this to alert on critical-event bursts, or to watch for a sudden drop in expected redaction activity (which can indicate a misconfigured filter). Combine with aistudio_llm_requests_total to get a per-request rate. See Analytics & Monitoring for the full metric catalog.

Suggested Event-Type Conventions

event_type is free-form, but consistency across filters makes dashboards and queries much more useful. Conventions used by the bundled filter scripts:
Event typeWhen to emit
pii_redactedFilter replaced PII patterns with placeholders. Set metadata.redacted_types to an array of categories (["email", "ssn", "phone"]).
content_rewrittenFilter transformed user or LLM content in a way the user can’t see (for example system-prompt augmentation, anonymization).
sensitive_content_detectedFilter found content matching a sensitive pattern. Pair with block: true for hard blocks; pair with block: false to record the detection without blocking.
harmful_content_detectedResponse filter detected harmful output. Set metadata.matched_pattern to the trigger.
policy_violationFilter detected a policy breach that may or may not be blocked.
silent_failureFilter encountered an error path it chose to swallow (for example an unreachable classifier service, falling back to allow). Use severity: warning so it shows up in the dashboard without paging anyone.
For metadata keys, prefer:
  • matched_pattern (string): the substring or regex match that fired the event.
  • redacted_types (array of strings): categories of PII or content removed.
These keys are what the bundled filter scripts and the dashboard expect.

Migration Notes

  • Schema: v2.1 GORM auto-migration creates the compliance_events table on first startup. No manual step required.
  • Filter scripts written for v2.0 are forward-compatible. The compliance_events field is optional and ignored if absent.
  • Bundled filter library: the ship-with scripts (PII redaction, content blocking, response guardrails) were updated in v2.1 to emit compliance events with the conventions above. Re-import them from the marketplace, or copy from the updated templates in the admin UI, if you want them out of the box.
  • Custom analytics handlers: the AnalyticsHandler interface now takes context.Context on its recording methods, including the new RecordComplianceEvents. Custom implementations need to update their signatures; RecordComplianceEvents can be a no-op if you do not consume compliance events.

See Also