Dynatrace Classic retrieves audit events through the Environment API's v2/auditlogs endpoint. In Latest Dynatrace, audit events are stored in Grail as dt.system.events, queryable with Dynatrace Query Language (DQL) alongside your logs, metrics, and traces. This guide walks you through inventorying the integrations that call v2/auditlogs, translating their filter logic to DQL, and retiring the classic API calls once the DQL replacements are validated.
authentication.token field lets you filter all API calls made by a specific credential, with no classic API equivalent.v2/auditlogs nor dt.system.events captures it.In this upgrade guide, you'll:
v2/auditlogs.To complete this upgrade guide, you need:
ALLOW storage:system:read WHERE storage:event.kind="AUDIT_EVENT" and ALLOW storage:buckets:read WHERE storage:bucket-name="dt_system_events".auditLogs.read scope, to inventory current v2/auditlogs callers.v2/auditlogs from: scripts, CI/CD pipelines, scheduled exports, or SIEM toolsThe following table summarizes breaking changes when upgrading to Latest Dynatrace audit queries.
| Change | Required action |
|---|---|
| Complete the migration to DQL before your environment reaches the latest state. |
Filter syntax changes from URL query parameters to DQL pipeline commands | Translate each integration's |
Response field names and formats differ (for example, | Update any code that parses the API response to use the new field names and formats. |
The Grail data object holding all audit and system events. Every DQL query for audit events must filter to event.kind == "AUDIT_EVENT", since dt.system.events also holds non-audit system events.
The API used to run DQL queries programmatically, either synchronously, with results returned inline, or asynchronously, by polling for results with a request token.
An environment with both classic and latest functionality. v2/auditlogs and Grail-based audit queries are both available and can run in parallel. This is the most common state for environments that are migrating to Latest Dynatrace functionality.
Before making any changes, identify every integration, script, or tool calling v2/auditlogs. Compliance and SIEM integrations often run on monthly or quarterly schedules. Without an inventory, a periodic integration can break silently, weeks after migration completes, by which point the classic API may no longer be available for rollback.
Calls to v2/auditlogs are themselves recorded as audit events in Grail, so query dt.system.events directly to see who calls the endpoint, from where, and how often.
fetch dt.system.events, from: now()-30d| filter event.kind == "AUDIT_EVENT"| filter event.provider == "API_GATEWAY"| filter contains(resource, "/api/v2/auditlogs")| fields timestamp, user.name, user.id, origin.address, resource| sort timestamp desc
Here's an example query to group results by caller instead of listing every call:
fetch dt.system.events, from: now()-30d| filter event.kind == "AUDIT_EVENT"| filter event.provider == "API_GATEWAY"| filter contains(resource, "/api/v2/auditlogs")| summarize calls = count(), last_seen = max(timestamp), by: {user.id, origin.address}| sort last_seen desc
For each integration you find, document the full URL it calls (including query parameters), what it does with the data, who owns it and where it runs, and how frequently it runs. Record where you stored the inventory in your migration ticket.
Once you complete the inventory, you will have a documented list of every integration that calls v2/auditlogs, including ownership and run frequency.
Classify before acting. Compliance-driven integrations often look idle between runs, so an apparently orphaned integration may be waiting for its next scheduled cycle. The following table provides a guideline for how to classify each integration based on the inventory from step 1.
| Classification | Signal |
|---|---|
Actively used | Runs at least weekly; last run within 30 days |
Periodic or scheduled | Runs monthly, quarterly, or per audit cycle |
Orphaned or unknown | No identified owner; no recent evidence of use |
These thresholds are examples, not rules. Review them against the real run cadence of each integration, especially compliance-driven workflows that may run monthly, quarterly, or less often.
Every DQL query for audit events starts from the same base.
fetch dt.system.events, from: <time-range>| filter event.kind == "AUDIT_EVENT"| filter <your-criteria>| sort timestamp desc| limit <page-size>
The from: time range replaces the classic from/to parameters, | filter lines replace the classic filter= expression, and | limit replaces pageSize.
Translate each classic filter= expression using this mapping.
Classic filter= expression | DQL | filter line |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Classic eventType values don't map one to one to Grail event.type values; see the field mapping below for the complete picture.
Translate the response fields your integration reads, using the field mapping between the classic API response and Grail DQL.
| Classic API field | Grail/DQL field | Notes |
|---|---|---|
|
| Stable unique identifier |
|
| Direct equivalent |
|
| May not be populated in every environment; see the FAQ |
|
| Primary user identifier in Grail |
|
| Value mapping differs; see the filter mapping above |
|
| Partial mapping; see the filter mapping above |
|
| Roughly equivalent; value format may differ |
|
| Format differs: classic uses a boolean, Grail a string ( |
|
| Format differs: classic uses a diff, Grail stores full before and after states |
To call DQL from a script or integration, use the Grail Query API.
POST https://{your-environment-id}.apps.dynatrace.com/platform/storage/query/v1/query:execute
Here's an example curl call:
curl -s -X POST \-H "Authorization: Bearer <your-platform-token>" \-H "Content-Type: application/json" \-d '{"query": "fetch dt.system.events | filter event.kind == \"AUDIT_EVENT\" | sort timestamp desc | limit 1000","defaultTimeframeStart": "now()-7d","defaultTimeframeEnd": "now()","fetchTimeoutMilliseconds": 60000}' \"https://{your-environment-id}.apps.dynatrace.com/platform/storage/query/v1/query:execute"
This synchronous call is a good fit for queries expected to finish within about 30 seconds. The default timeout is 300,000 ms (five minutes) and the maximum is 3,600,000 ms (1 hour). For large result sets, submit the same query and poll for results using the requestToken returned in the response, with GET .../query:poll?requestToken={requestToken}.
For each integration in your inventory, you now have a DQL query that reproduces its classic filter= logic and reads the equivalent response fields.
For each migrated integration, confirm all of the following before removing the classic API call.
Confirm the DQL query returns data for the expected time range and event types, and that the results match the expected volume and content from the classic API. Running both the classic API call and the DQL query in parallel during a validation window is a valid way to compare results directly before switching over.
Confirm the platform token works end-to-end, with no HTTP 401 or 403 responses, and that it has the minimum required Grail permissions. If it doesn't, see Why does my DQL query return a 403?.
Confirm every location where the classic API call exists (scripts, CI/CD pipeline variables, configuration files, and scheduled jobs) has been updated.
Don't remove classic API calls until the DQL replacement is confirmed working.
Once the DQL replacement is confirmed working:
Update the integration to call the Grail Query API instead of v2/auditlogs.
Wait at least one full job cycle before confirming retirement. For a monthly compliance export, that means waiting the full month.
Revoke the classic access token if it was used exclusively for audit log access. Disable it first, wait through at least one job cycle, then delete it using its unique ID, not its name.
You've completed the migration when:
dt.system.events in Grail instead of calling v2/auditlogs.No, for data already exported to external systems: this guide covers how new data is retrieved going forward. If you need classic API audit events from before your environment moved to Grail, contact Dynatrace Support. Once the classic API is removed, that data can't be recovered from Grail.
Grail audit events include both user.id (the UUID) and user.name (the email address), but whether user.name is always populated depends on your environment configuration. If it's missing, resolve the UUID with the IAM API.
GET https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/users/{userId}
This requires the ALLOW iam:users:read permission.
Yes. This guide covers the environment audit log: API calls and configuration changes within a specific environment (v2/auditlogs in classic, dt.system.events in Grail). IAM changes, group permissions, SSO configuration, and budget changes are captured separately in the Account Management audit log, accessed through the Account Management UI or the Account Audits API with the account-audit-logs-read scope, and retained for 10 years. Neither system captures changes to OAuth tokens.
Either the platform token doesn't have the required Grail permission statements, or the user or service user it belongs to doesn't have those permissions. Both must be in place; add the scoped policy statements from Prerequisites.
The built-in policies "Storage Default Monitoring Read," "Read System Events," and "All Grail data read access" each include an unconditional storage:system:read grant, which overrides any narrower WHERE clause in another policy. A user holding any of these sees all system events, not only audit events, regardless of a more restrictive policy also assigned to them. To enforce least-privilege audit access, make sure users in that role don't hold any of these built-in policies.
No. The classic audit log feature (Settings > Preferences > Log audit events) only controls whether data flows into v2/auditlogs. It has no effect on Grail, which is always on and stores audit events regardless of the classic setting.