Audit
The Descope Audit Trail provides comprehensive logging of all security-relevant events within your Descope projects and Descope company. This guide explains how to access, search, and use the audit trail for security monitoring and compliance.
Descope supports streaming your audit trail to a third-party service. To learn more about this concept, review the Audit Trail Streaming knowledge base article.
You can search the Descope audit trail via the Descope SDK as shown below, or you can use the Descope console or the Search Audit API endpoint.
For a full list of audit events that are logged, refer to the Audit Events reference, which is split into project-level and company-level events.
For SCIM provisioning, Descope can emit detailed audit rows (SCIMEvent) that include request bodies, results, and field-level changes. See SCIM Audit Events.
Company-level Auditing
In addition to project-level events, Descope logs company-level events for actions taken across your Descope company — creating or deleting projects, managing management keys, and changing company settings. These events are surfaced in the same audit table as project events so you have a single place to review all administrative activity.
To view only company-level events, open the Audit page in any project within your company and set the Level filter to Company. The same filter is available in the Search Audit API.

For the full list of company-level events, see Company-level Audit Events. For where company-level configuration lives, see Company Settings.
Searching the Descope Audit Trail via SDK
Rate Limiting
Descope enforces a rate limit of 10 requests per minute for audit search operations.
Backend SDK
Install SDK
npm i --save @descope/node-sdkpip3 install descopego get github.com/descope/go-sdk// Include the following in your `pom.xml` (Maven)
<dependency>
<artifactId>java-sdk</artifactId>
<groupId>com.descope</groupId>
<version>sdk-version</version> // Check https://github.com/descope/descope-java/releases for the latest versions
</dependency>gem install descopecomposer require descope/descope-phpdotnet add package descopeImport and initialize SDK
import DescopeClient from '@descope/node-sdk';
try{
// baseUrl="<URL>" // When initializing the Descope client, you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize a custom domain within your Descope project.
const descopeClient = DescopeClient({ projectId: '__ProjectID__' });
} catch (error) {
// handle the error
console.log("failed to initialize: " + error)
}
from descope import (
REFRESH_SESSION_TOKEN_NAME,
SESSION_TOKEN_NAME,
AuthException,
DeliveryMethod,
DescopeClient,
AssociatedTenant,
RoleMapping,
AttributeMapping,
LoginOptions
)
try:
# You can configure the baseURL by setting the env variable Ex: export DESCOPE_BASE_URI="https://auth.company.com - this is useful when you utilize custom domain within your Descope project."
descope_client = DescopeClient(project_id='__ProjectID__')
except Exception as error:
# handle the error
print ("failed to initialize. Error:")
print (error)import "github.com/descope/go-sdk/descope"
import "github.com/descope/go-sdk/descope/client"
// Utilizing the context package allows for the transmission of context capabilities like cancellation
// signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
import (
"context"
)
// DescopeBaseURL // within the client.Config, you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize a custom domain within your Descope project.
descopeClient, err := client.NewWithConfig(&client.Config{ProjectID:"__ProjectID__"})
if err != nil {
// handle the error
log.Println("failed to initialize: " + err.Error())
}import com.descope.client.Config;
import com.descope.client.DescopeClient;
var descopeClient = new DescopeClient(Config.builder().projectId("__ProjectID__").build());require 'descope'
@project_id = ENV['__ProjectID__']
@client = Descope::Client.new({ project_id: @project_id})require 'vendor/autoload.php';
use Descope\SDK\DescopeSDK;
$descopeSDK = new DescopeSDK([
'projectId' => $_ENV['__ProjectID__'],
]);// appsettings.json
{
"Descope": {
"ProjectId": "__ProjectID__",
"ManagementKey": "DESCOPE_MANAGEMENT_KEY"
}
}
// Program.cs
using Descope;
using Microsoft.Extensions.Configuration;
// ... In your setup code
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var descopeProjectId = config["Descope:ProjectId"];
var descopeManagementKey = config["Descope:ManagementKey"];
var descopeConfig = new DescopeConfig(projectId: descopeProjectId);
var descopeClient = new DescopeClient(descopeConfig)
{
ManagementKey = descopeManagementKey,
};Search Audits
// Args:
// searchOptions: (AuditSearchOptions): A completed descope structure with the desired audit search options.
const searchOptions = {
userIDs: ["xxxxxx"],
actions: ["LoginSucceed"],
excludedActions: null, // List of actions to exclude
// from: time.Tim, // Retrieve records newer than given time. Limited to no older than 30 days.
// to: time.Time, // Retrieve records older than given time.
devices: null, // List of devices to filter by. Current devices supported are "Bot"/"Mobile"/"Desktop"/"Tablet"/"Unknown"
methods: null, // List of methods to filter by. Current auth methods are "otp"/"totp"/"magiclink"/"oauth"/"saml"/"password"
geos: null, // List of geos to filter by. Geo is currently country code like "US", "IL", etc.
remoteAddresses: null, // List of remote addresses to filter by
loginIDs: null, // List of login IDs to filter by
tenants: null, // List of tenants to filter by
noTenants: true, // Should audits without any tenants always be included
// text: "John" // Free text search across all fields
}
const resp = await descopeClient.management.audit.search(searchOptions)
if (!resp.ok) {
console.log("Failed to search audits.")
}
else {
console.log("Successfully searched audits.")
console.log(resp)
}# Args:
# user_ids (List[str]): Optional list of user IDs to filter by
user_ids = ["xxxxxx"]
# actions (List[str]): Optional list of actions to filter by
actions = ["LoginSucceed"]
# excluded_actions (List[str]): Optional list of actions to exclude
excluded_actions = None
# devices (List[str]): Optional list of devices to filter by. Current devices supported are "Bot"/"Mobile"/"Desktop"/"Tablet"/"Unknown"
devices = None
# methods (List[str]): Optional list of methods to filter by. Current auth methods are "otp"/"totp"/"magiclink"/"oauth"/"saml"/"password"
methods = None
# geos (List[str]): Optional list of geos to filter by. Geo is currently country code like "US", "IL", etc.
geos = None
# remote_addresses (List[str]): Optional list of remote addresses to filter by
remote_addresses = None
# login_ids (List[str]): Optional list of login IDs to filter by
login_ids = None
# tenants (List[str]): Optional list of tenants to filter by
tenants = None
# no_tenants (bool): Should audits without any tenants always be included
no_tenants = True
# text (str): Free text search across all fields
text = None
# from_ts (datetime): Retrieve records newer than given time but not older than 30 days
from_ts = None
# to_ts (datetime): Retrieve records older than given time
to_ts = None
try:
resp = descope_client.mgmt.audit.search(user_ids=user_ids, actions=actions, excluded_actions=excluded_actions, devices=devices, methods=methods, geos=geos, remote_addresses=remote_addresses, login_ids=login_ids, tenants=tenants, text=text, from_ts=from_ts, to_ts=to_ts)
print ("Successfully searched audits")
print (resp)
except AuthException as error:
print ("Failed to search audits")
print ("Status Code: " + str(error.status_code))
print ("Error: " + str(error.error_message))// Args:
// ctx: context.Context - Application context for the transmission of context capabilities like
// cancellation signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
ctx := context.Background()
// searchOptions: (AuditSearchOptions): A completed descope structure with the desired audit search options.
searchOptions := &descope.AuditSearchOptions{}
searchOptions.UserIDs = []string{"xxxxxx"}
searchOptions.Actions = []string{"LoginSucceed"}
searchOptions.ExcludedActions = nil // List of actions to exclude
// searchOptions.From = time.Time // Retrieve records newer than given time. Limited to no older than 30 days.
// searchOptions.To = time.Time // Retrieve records older than given time.
searchOptions.Devices = nil // List of devices to filter by. Current devices supported are "Bot"/"Mobile"/"Desktop"/"Tablet"/"Unknown"
searchOptions.Methods = nil // List of methods to filter by. Current auth methods are "otp"/"totp"/"magiclink"/"oauth"/"saml"/"password"
searchOptions.Geos = nil // List of geos to filter by. Geo is currently country code like "US", "IL", etc.
searchOptions.RemoteAddresses = nil // List of remote addresses to filter by
searchOptions.LoginIDs = nil // List of login IDs to filter by
searchOptions.Tenants = nil // List of tenants to filter by
searchOptions.NoTenants = true // Should audits without any tenants always be included
// searchOptions.Text = "John" // Free text search across all fields
// Pagination: use Limit (page size) and Page (zero-based page index) to page
// through large result sets instead of receiving a single capped response.
searchOptions.Limit = 100 // Number of records to return per page
searchOptions.Page = 0 // Zero-based page index to retrieve
// SearchAll returns the matching records for the requested page along with the total
// number of records that match the search, which you can use to drive pagination.
res, total, err := descopeClient.Management.Audit().SearchAll(ctx, searchOptions)
if err != nil {
fmt.Println("Unable to search audits: ", err)
} else {
fmt.Printf("Successfully searched audits (showing %d of %d total): \n", len(res), total)
for _, auditEvent := range res {
fmt.Println(auditEvent)
}
}
return errSearchAll vs. Search
Use SearchAll, which returns the matching records and the total result count so you can paginate. The previous Search function is deprecated and internally calls SearchAll, discarding the total count.
AuditService as = descopeClient.getManagementServices().getAuditService();
// Full text search on the last 10 days
try {
AuditSearchResponse resp = as.search(AuditSearchRequest.builder()
.from(Instant.now().minus(Duration.ofDays(10))));
} catch (DescopeException de) {
// Handle the error
}
// Search successful logins in the last 30 days
try {
AuditSearchResponse resp = as.search(AuditSearchRequest.builder()
.from(Instant.now().minus(Duration.ofDays(30)))
.actions(Arrays.asList("LoginSucceed")));
} catch (DescopeException de) {
// Handle the error
}// Any of the following arguments can be used as a search term for the function,
// not all of them need to be included in a search
$response = $descopeSDK->management->audit->search(
"userIds", // List of user IDs to filter by.
"actions", // List of actions to filter by.
"excludedActions", // List of actions to exclude.
"devices", // List of devices to filter by (e.g., "Bot", "Mobile", "Desktop").
"methods", // List of methods to filter by (e.g., "otp", "totp", "magiclink").
"geos", // List of geographical locations to filter by (country codes).
"remoteAddresses", // List of remote addresses to filter by.
"loginIds", // List of login IDs to filter by.
"tenants", // List of tenants to filter by.
"noTenants", // Whether to include audits without tenants.
"text", // Free text search across all fields.
"fromTs", // Retrieve records newer than this timestamp.
"toTs" // Retrieve records older than this timestamp.
);
print_r($response);User Update Audit Detail
It's crucial to see user configuration changes in your audit trail. To help you track these changes, Descope
logs the new values of the changed UserModified actions. Below is an example of the detail provided within
the audit trail of a UserModified action with various changes.
{
"Change": {
"added_multi_tenant_roles": [
"xx"
],
"added_roles": [
"xx"
],
"custom_attribute_emailConsent": true,
"custom_attribute_myAttribute": true,
"display_name": "Test Me",
"family_name": "Test",
"given_name": "Me",
"middle_name": "Middle",
"phone": "12223334455"
},
"correlation_id": "xx",
"request_details": {
"contentLength": "956",
"headers": {
"descope": {
"cf-bot-score": "99",
"cf-connecting-ip": "xx",
"cf-ja3-hash": "xx",
"cf-ray": "xx-DFW",
"cf-verified-bot": "false",
"x-request-id": "xx"
},
"http": {
"origin": "https://app.descope.com",
"referer": "https://app.descope.com/",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
},
"host": "console.descope.com",
"method": "POST",
"uri": "/console/v1/users/xx",
"url": "/console/v1/users/xx"
}
}