Audit with Management SDKs
You can search the Descope audit trail and emit custom audit events from your backend using the Management SDK or the Search Audit and Create Audit Event API endpoints.
For filtering in the Descope Console, see Filtering Audit Events.
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 errNote
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);Search filter parameters
All parameters are optional. Combine them to build precise queries.
| Parameter | Type | Description |
|---|---|---|
actions | string[] | Filter to specific event actions (maps 1:1 to event types). See Audit Events for valid values. |
excludedActions | string[] | Exclude specific event actions from results. |
userIDs | string[] | Filter by Descope User ID(s). |
loginIDs | string[] | Filter by Login ID(s) (e.g., email address, phone number). |
from | timestamp | Return events newer than this time. Cannot be older than 30 days. |
to | timestamp | Return events older than this time. |
devices | string[] | Filter by device type: "Bot", "Mobile", "Desktop", "Tablet", "Unknown". |
methods | string[] | Filter by authentication method: "otp", "totp", "magiclink", "oauth", "saml", "password". |
geos | string[] | Filter by country code, e.g. "US", "IL". |
remoteAddresses | string[] | Filter by originating IP address(es). |
tenants | string[] | Filter by tenant ID(s). |
noTenants | boolean | When true, always includes events with no associated tenant alongside tenant-scoped results. |
text | string | Free-text search across all audit fields. |
Example: failed logins for a user (last 7 days)
const searchOptions = {
userIDs: ["U2abc123xyz"],
actions: ["LoginFailed"],
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
}
const resp = await descopeClient.management.audit.search(searchOptions)from datetime import datetime, timedelta
try:
resp = descope_client.mgmt.audit.search(
user_ids=["U2abc123xyz"],
actions=["LoginFailed"],
from_ts=datetime.utcnow() - timedelta(days=7),
)
print(resp)
except AuthException as error:
print("Error: " + str(error.error_message))ctx := context.Background()
from := time.Now().AddDate(0, 0, -7)
searchOptions := &descope.AuditSearchOptions{
UserIDs: []string{"U2abc123xyz"},
Actions: []string{"LoginFailed"},
From: from,
}
res, total, err := descopeClient.Management.Audit().SearchAll(ctx, searchOptions)AuditService as = descopeClient.getManagementServices().getAuditService();
try {
AuditSearchResponse resp = as.search(AuditSearchRequest.builder()
.userIds(Arrays.asList("U2abc123xyz"))
.actions(Arrays.asList("LoginFailed"))
.from(Instant.now().minus(Duration.ofDays(7))));
} catch (DescopeException de) {
// Handle the error
}$response = $descopeSDK->management->audit->search(
userIds: ["U2abc123xyz"],
actions: ["LoginFailed"],
fromTs: (new DateTime())->modify('-7 days'),
);
print_r($response);Create Audit Event
Beyond the events Descope logs automatically, you can emit custom audit rows from your backend. See Creating Custom Audit Events on the Audit Events reference.
// Args:
// auditOptions: (AuditCreateOptions): A completed descope structure with the desired audit creation options.
const auditOptions = {
userId: "xxxxxx", // Optional audit user ID
action: "LoginSucceed", // The action that was performed.
type: "info", // Choose from three severity levels: info, warn, or error
actorId: "xxxxxx", // The user that performed the action
tenantId: "xxxxxx", // The tenant that the action was performed in
data: { // Optional additional data to include in the audit event
key1: "value1",
key2: "value2"
}
}
await descopeClient.management.audit.createEvent(auditOptions)# Args:
# user_id (str): Optional audit user ID
user_id = "xxxxxx"
# action (str): Audit action that was performed
action = "LoginSucceed"
# audit_type (str): Choose from three severity levels: info, warn, or error
audit_type = "info"
# actor_id (str): The user that performed the action
actor_id = "xxxxxx"
# tenant_id (str): The tenant that the action was performed in
tenant_id = "xxxxxx"
# data (dict): Optional additional data to include in the audit event
data = {
"key1": "value1",
"key2": "value2"
}
try:
resp = descope_client.mgmt.audit.create_event(user_id=user_id, action=action, type=audit_type, actor_id=actor_id, tenant_id=tenant_id, data=data)
print ("Successfully created audit event")
print (resp)
except AuthException as error:
print ("Failed to create audit event")
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()
// createOptions: (AuditCreateOptions): A completed descope structure with the desired audit create options.
createOptions := &descope.AuditCreateOptions{}
// createOptions.UserID: Optional audit user ID
createOptions.UserID = "xxxxxx"
// createOptions.Action: Audit action that was performed
createOptions.Action = "LoginSucceed"
// createOptions.Type: Choose from three severity levels: info, warn, or error
createOptions.Type = "info"
// createOptions.ActorID: The user that performed the action
createOptions.ActorID = "xxxxxx"
// createOptions.TenantID: The tenant that the action was performed in
createOptions.TenantID = "xxxxxx"
// createOptions.Data: Optional additional data to include in the audit event
createOptions.Data = map[string]interface{}{
"key1": "value1",
"key2": "value2",
}
// CreateEvent returns nil if the event was successfully created, or an error if it failed.
err := descopeClient.Management.Audit().CreateEvent(ctx, createOptions)
if err != nil {
fmt.Println("Unable to create audit event: ", err)
} else {
fmt.Printf("Successfully created audit event")
}
return errAuditService as = descopeClient.getManagementServices().getAuditService();
try {
as.createEvent(AuditCreateRequest.builder()
.userId("some-id") // Audit user ID
.action("some-action-name") // The action that was performed
.type(AuditType.INFO) // Choose from three severity levels: info, warn, or error
.actorId("some-actor-id") // The user that performed the action
.tenantId("some-tenant-id") // The tenant that the action was performed in
.data(Map.of("key1", "value1", "key2", "value2")) // Optional additional data to include in the audit event
.build());
} catch (DescopeException de) {
// Handle the error
}
client.audit_create_event(
user_id: "UXXX", # optional, the ID of the user associated with the event
actor_id: "UXXX", # required, the actor that performed the action
tenant_id: "tenant-id", # required, the tenant that the action was performed in
action: "pencil.created", # required, the action that was performed
type: "info", # either: info/warn/error # required
data: { # optional, additional data to include in the audit event
pencil_id: "PXXX",
pencil_name: "Pencil Name"
}
)$response = $descopeSDK->management->audit->createEvent(
"action", // The action that was performed
"type", // Choose from three severity levels: info, warn, or error
"actorId", // The actor that performed the action
"tenantId", // The tenant that the action was performed in
"userId", // Optional, the ID of the user associated with the event
["key1" => "value1", "key2" => "value2"] // Optional, additional data to include in the audit event
);
print_r($response);Filtering Audit Events
Learn how to filter Descope audit events by event type, user, date range, and more using the Descope Console, Backend SDKs, or REST API.
Audit Trail Streaming
This guide will cover the fundamentals and one use case regarding streaming your Descope audit trail to a third-party service.