Forensics
API endpoints for forensic investigation — capabilities, enrichment, queries, and row tracing
The forensics API provides endpoints for investigating who made database changes, what connections were active, and what SQL was executed. Requires the forensics plan feature (Pro, Premium, or Enterprise).
Endpoints
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /forensics/capabilities/{server_id} | forensics plan feature | Check available forensic data sources |
POST | /forensics/enrich | forensics plan feature | Look up connection metadata for thread IDs |
POST | /forensics/query | forensics plan feature | Query user activity, connections, or DDL history |
POST | /forensics/who-changed | forensics plan feature | Trace who modified a specific row |
Check capabilities
GET /forensics/capabilities/{server_id}Detect available forensic data sources on a MySQL server. Call this first to understand what data is available before running other forensic queries.
Response
{
"success": true,
"performance_schema": {
"enabled": true,
"consumers": {
"events_statements_history": true,
"events_statements_history_long": false
},
"threads_accessible": true
},
"audit_log": {
"installed": true,
"plugin_name": "audit_log",
"plugin_status": "ACTIVE",
"variant": "percona",
"config": {
"audit_log_format": "JSON",
"audit_log_file": "/var/log/mysql/audit.log"
}
},
"server_info": {
"version": "8.0.35",
"version_comment": "Percona Server",
"variant": "percona"
},
"setup_guide": {
"summary": "Enable events_statements_history_long for full statement history",
"recommendations": [
{
"category": "performance_schema",
"title": "Enable long statement history",
"description": "The events_statements_history_long consumer is disabled",
"impact": "Enables forensic queries across all threads, not just active ones",
"performance_note": "Minimal overhead on most workloads",
"runtime_sql": [
"UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_statements_history_long'"
],
"mycnf_snippet": "",
"priority": "high"
}
]
}
}When all forensic capabilities are already optimal, setup_guide may be null.
Enrich thread IDs
POST /forensics/enrichLook up connection metadata for specific MySQL thread/connection IDs. Queries performance_schema.threads and session_connect_attrs to resolve thread IDs to user@host, client program, and connection attributes.
Request body
{
"server_id": "uuid",
"thread_ids": [8834, 8835]
}| Field | Required | Description |
|---|---|---|
server_id | yes | Server UUID |
thread_ids | yes | Array of MySQL thread/connection IDs to look up (1–500 items) |
Response
{
"success": true,
"threads": {
"8834": {
"user": "app_user",
"host": "10.0.1.50",
"connection_id": 8834,
"db": "mydb",
"command": "Query",
"state": "executing",
"connection_attributes": {
"_client_name": "libmysql",
"program_name": "myapp-backend",
"_os": "Linux",
"_pid": "12345"
}
}
},
"source": "performance_schema",
"not_found": [8835],
"fallback_queries": [
{
"description": "Look up thread 8835 in processlist",
"sql": "SELECT * FROM information_schema.PROCESSLIST WHERE ID = 8835"
}
]
}Thread IDs no longer present in performance_schema appear in not_found with corresponding fallback_queries.
Forensic queries
POST /forensics/queryExecute a forensic query against the MySQL server. The query_type field determines what data is returned.
Request body
{
"server_id": "uuid",
"query_type": "user_activity",
"user": "app_user",
"limit": 50
}| Field | Required | Description |
|---|---|---|
server_id | yes | Server UUID |
query_type | yes | One of user_activity, connection_history, ddl_history |
user | depends | MySQL username (required for user_activity; at least user or host required for connection_history) |
host | no | Filter by client host/IP |
schema | no | Filter DDL history by schema |
since / until | no | Time range (ISO 8601) |
limit | no | Max results (1–1000, default: 50) |
Query types
| Type | Required fields | Description |
|---|---|---|
user_activity | user | Recent SQL statements by a MySQL user |
connection_history | user or host | Active connections matching filter |
ddl_history | (optional: schema) | Recent DDL statements (CREATE/ALTER/DROP) |
Response
Results for user_activity and ddl_history are returned in the events array. Results for connection_history are returned in the connections array.
{
"success": true,
"events": [
{
"connection_id": 8834,
"user": "app_user",
"host": "10.0.1.50",
"sql_text": "UPDATE orders SET status = 'shipped' WHERE id = 42",
"rows_affected": 1,
"duration_ms": 2.4,
"current_db": "mydb"
}
],
"connections": [],
"source": "performance_schema",
"count": 1
}When performance_schema is unavailable, see Fallback behavior below.
Who changed a row
POST /forensics/who-changedTrace who modified a specific row. Queries binlog events for the given table and primary key, then correlates with performance_schema data to identify the MySQL user, host, and client program responsible for each change.
Request body
{
"server_id": "uuid",
"schema": "mydb",
"table": "orders",
"pk": "42",
"since": "2026-03-01T00:00:00Z"
}| Field | Required | Description |
|---|---|---|
server_id | yes | Server UUID |
schema | yes | Database schema name |
table | yes | Table name |
pk | yes | Primary key value |
since | no | Only show changes after this time (ISO 8601) |
until | no | Only show changes before this time (ISO 8601) |
limit | no | Max results (1–1000, default: 50) |
Response
{
"success": true,
"events": [
{
"event_id": "12345",
"timestamp": "2026-03-15 09:22:31",
"event_type": "UPDATE",
"schema": "mydb",
"table": "orders",
"primary_key": {"id": 42},
"forensics": {
"user": "app_user@10.0.1.50",
"host": "10.0.1.50",
"client_program": "myapp-backend",
"connection_id": 8834
}
}
],
"total_count": 1,
"forensic_source": "performance_schema"
}When forensic enrichment isn't possible for historical connections no longer in performance_schema, events include forensic_queries with fallback SQL instead of the forensics object.
Fallback behavior
When performance_schema is unavailable or the relevant consumers are disabled, forensic endpoints return "source": "fallback" with ready-to-run SQL queries that can be executed directly against MySQL:
{
"success": true,
"events": [],
"source": "fallback",
"fallback_queries": [
{
"description": "Check current connections for this user",
"sql": "SELECT * FROM information_schema.PROCESSLIST WHERE USER = 'app_user'"
},
{
"description": "Recent statement history (requires events_statements_history consumer)",
"sql": "SELECT t.PROCESSLIST_ID, t.PROCESSLIST_HOST, esh.SQL_TEXT ..."
}
],
"note": "performance_schema.events_statements_history_long is not available. Use the provided SQL queries to investigate."
}