Skip to main content
Every guarded request writes one row to the security_events table (input and output are separate rows). Self-hosted deployments own that Postgres database directly, so you can point psql, a BI tool, or your warehouse’s foreign-data wrapper at it and run these recipes as-is. On PromptGuard Cloud, pull the same data through the Interactions API and load it into your own store.
There is no pre-aggregated roll-up table today — these recipes run directly against the raw security_events (and, for configuration/auth activity, audit_events) event tables. The Daily and hourly roll-ups section shows how to build your own materialized view if you want cheaper dashboards.

The tables you’ll query

security_events — the request log

One row per policy evaluation. The columns that matter for analytics:
security_events is retention-bounded. Rows are purged on a per-plan schedule (Free 24h, Pro 7d, Scale 30d, Enterprise 90d / custom). A query over a 90-day window on a Pro plan returns at most 7 days of rows. For durable analytics, raise your retention (Enterprise custom_retention_days) or stream events into your own warehouse. audit_events is never purged.

audit_events — the compliance trail

Configuration changes, authentication, and data access. Hash-chained and never purged. Columns: created_at, organization_id, user_id, event_type, category (security | authentication | data_access | configuration | billing), action, outcome (success | failure | denied | error), resource_type, resource_id, ip_address, details (jsonb).

Latency: p50 / p95 / p99

Engine processing time by percentile over the last 24 hours. Use percentile_cont for interpolated percentiles.
Break the same percentiles down by surface to compare the developer proxy against Shadow AI scans:
processing_time_ms is the time PromptGuard’s engine spent, not end-to-end request time. On the proxy path it excludes the upstream provider call. See Latency Budgets for what each detector contributes.

Token volume by model

Grouped by upstream model. Tokens are NULL on /guard-only scans (they never call a provider), so filter them out.
These are token counts, not dollars. PromptGuard used to publish a cost_usd_estimate here. It was tokens multiplied by a price table we maintained by hand for every model on every provider, and it drifted — one model carried a price 7.5× its real rate for months, and nothing detected it, because a wrong price looks exactly like a right one.Token counts come from the provider’s own usage block, so they cannot go stale. To convert them to spend, join against your provider’s current published rates, or read the figure straight from their billing console — which is authoritative in a way a copy of their price list never is.
Token volume attributed to blocked requests (the calls you avoided sending, plus those redacted and still forwarded):

High-risk activity by user

Ranks callers by blocked-request volume. Uses end_user_id (populated when you send the X-End-User header); fall back to user_id for account-level attribution.
Drill into a single high-risk user’s most recent blocks, including the threat type and detector that fired:

Blocked over time

Hourly blocked-vs-total counts for a time-series chart:
Swap date_trunc('hour', …) for date_trunc('day', …) and widen the interval for a daily trend.

Threats by detector

decision/threat_type tell you what was caught; the detector that caught it lives in event_metadata->>'detector' (values include regex, ml, agentic, and specific detector names). This recipe attributes blocks to the detection layer that fired — useful for tuning which layers earn their latency.
Threat mix by surface (are Shadow AI pastes producing different threats than the developer proxy?):

Daily and hourly roll-ups

There is no roll-up table shipped, so long-window dashboards scan raw events every load. If that gets expensive, materialize a daily summary yourself. This view is safe to REFRESH on a schedule (nightly, or hourly with CONCURRENTLY):
Refresh it:
Because security_events is retention-bounded, a materialized view built from it inherits the same horizon — refreshing after a purge drops the aged-out days. To keep history beyond your plan’s retention, INSERT each day’s roll-up into a plain table you own (self-host) or into your warehouse (Cloud) before the source rows are purged, rather than relying on the view alone.
An INSERT-based accumulator that survives purges:
Schedule that with pg_cron (self-host / Supabase) or any external scheduler:

Audit trail (configuration & auth)

Config changes and authentication live in audit_events, not security_events. Recent denied or failed sensitive actions:

Next steps

Latency Budgets

Per-detector p99 budgets behind processing_time_ms

Audit Logs

Export the same data through the Interactions API

Usage Tracking

Plan quotas, request counts, and spend

Dashboard

Prebuilt analytics in the app