The event history contains all access control events including card reads, door access grants/denials, alarms, and system events. This guide covers querying, filtering, and analyzing historical events for reporting, auditing, and troubleshooting purposes.
| Parameter | Type | Description |
|---|---|---|
forKeys |
string[] | Filter by specific keys using OR logic (any one must match) |
forAllKeys |
string[] | Filter by specific keys using AND logic (all must match) |
page |
int | Page number for pagination (0-based) |
pageSize |
int | Number of events per page (max 1000) |
startDate |
DateTime | Events after this date |
endDate |
DateTime | Events before this date |
includeSubFolders |
bool | Include events from child instances |
priorityThreshold |
int | Minimum priority level |
requiresAck |
bool | Only events requiring acknowledgment |
Key Differences:
forKeysuses OR logic - event matches if it contains ANY of the specified keys (e.g., AccessGranted OR AccessDenied)forAllKeysuses AND logic - event matches only if it contains ALL specified keys (e.g., AccessGranted AND specific Reader)
| Event Type | Description | Priority |
|---|---|---|
| Access Granted | Card read accepted | Low |
| Access Denied | Card read rejected | Medium |
| Door Held Open | Door exceeded hold time | High |
| Door Forced | Door opened without valid read | High |
| Alarm Active | Input triggered alarm state | High |
// Step 1: Get the Mercury application and event types
var apps = await client.GetAppsAsync(currentInstance);
var mercuryApp = apps.Single(a => a.CommonName == "Mercury Driver Service");
var eventTypes = await client.GetEventTypesAsync(mercuryApp);
// Step 2: Find specific event type
var accessGranted = eventTypes.Single(x => x.CommonName == "Access Granted");
// Step 3: Query events of this type
var events = await client.GetEventsAsync(
currentInstance,
forKeys: new[] { accessGranted.Key }
);
// Step 4: Process event data
foreach (var evt in events)
{
// Decode the event data from Base64 BSON
var bytes = Convert.FromBase64String(evt.EventDataBsonBase64);
var eventData = bytes.FromBson<ExpandoObject>();
Console.WriteLine($"Event at {evt.OccurredOn}:");
foreach (var kv in eventData)
{
Console.WriteLine($" {kv.Key}: {kv.Value}");
}
}
// Get events for the last 24 hours
var events = await client.GetEventsAsync(
currentInstance,
startDate: DateTime.UtcNow.AddHours(-24),
endDate: DateTime.UtcNow
);
Console.WriteLine($"Found {events.Count()} events in the last 24 hours");
// Find Access Denied and Granted event types
var accessDenied = eventTypes.Single(x => x.CommonName == "Access Denied");
var accessGranted = eventTypes.Single(x => x.CommonName == "Access Granted");
// Query both event types
var securityEvents = await client.GetEventsAsync(
currentInstance,
forKeys: new[] { accessDenied.Key, accessGranted.Key },
startDate: DateTime.UtcNow.AddDays(-7)
);
Console.WriteLine($"Found {securityEvents.Count()} security events this week");
For large event sets, use pagination:
public async Task<List<EventInfo>> GetAllEventsAsync(
InstanceInfo instance,
string[] eventTypeKeys,
DateTime startDate,
DateTime endDate)
{
var allEvents = new List<EventInfo>();
int page = 0;
int pageSize = 1000;
bool hasMore = true;
while (hasMore)
{
var events = await client.GetEventsAsync(
instance,
forKeys: eventTypeKeys,
startDate: startDate,
endDate: endDate,
page: page,
pageSize: pageSize
);
allEvents.AddRange(events);
hasMore = events.Count() == pageSize; // More pages if full page returned
page++;
Console.WriteLine($"Retrieved page {page}: {events.Count()} events");
}
return allEvents;
}
Access events contain detailed information in BSON format:
public class AccessEventDetails
{
public string CardNumber { get; set; }
public string PersonName { get; set; }
public string ReaderName { get; set; }
public DateTime EventTime { get; set; }
}
public AccessEventDetails ParseAccessEvent(EventInfo evt)
{
var bytes = Convert.FromBase64String(evt.EventDataBsonBase64);
var doc = MongoDB.Bson.BsonDocument.Parse(
System.Text.Encoding.UTF8.GetString(bytes));
return new AccessEventDetails
{
CardNumber = doc.GetValue("CardNumber", "").AsString,
PersonName = doc.GetValue("PersonName", "").AsString,
ReaderName = doc.GetValue("ReaderCommonName", "").AsString,
EventTime = evt.OccurredOn
};
}
// Usage
foreach (var evt in events)
{
var details = ParseAccessEvent(evt);
Console.WriteLine($"{details.EventTime}: {details.PersonName} at {details.ReaderName}");
}
public async Task GenerateAccessReportAsync(
InstanceInfo instance,
DateTime startDate,
DateTime endDate)
{
var apps = await client.GetAppsAsync(instance);
var mercuryApp = apps.Single(a => a.CommonName == "Mercury Driver Service");
var eventTypes = await client.GetEventTypesAsync(mercuryApp);
var accessGranted = eventTypes.Single(x => x.CommonName == "Access Granted");
var accessDenied = eventTypes.Single(x => x.CommonName == "Access Denied");
var events = await client.GetEventsAsync(
instance,
forKeys: new[] { accessGranted.Key, accessDenied.Key },
startDate: startDate,
endDate: endDate
);
// Group by event type
var granted = events.Where(e => e.EventTypeKey == accessGranted.Key);
var denied = events.Where(e => e.EventTypeKey == accessDenied.Key);
Console.WriteLine($"Access Report: {startDate:yyyy-MM-dd} to {endDate:yyyy-MM-dd}");
Console.WriteLine($" Granted: {granted.Count()}");
Console.WriteLine($" Denied: {denied.Count()}");
Console.WriteLine($" Denial Rate: {(double)denied.Count() / (granted.Count() + denied.Count()):P2}");
}
Filter events for a specific reader using forAllKeys to require both the event type and reader to match:
// Get the reader object
var reader = await client.GetByHrefAsync<MercuryReaderInfo>(readerHref);
// Get access granted event type
var apps = await client.GetAppsAsync(currentInstance);
var mercuryApp = apps.Single(a => a.CommonName == "Mercury Driver Service");
var eventTypes = await client.GetEventTypesAsync(mercuryApp);
var accessGranted = eventTypes.Single(x => x.CommonName == "Access Granted");
// Query events using forAllKeys to match BOTH access granted AND this specific reader
var events = await client.GetEventsAsync(
currentInstance,
forAllKeys: new[] { accessGranted.Key, reader.Key },
startDate: DateTime.UtcNow.AddDays(-1)
);
Console.WriteLine($"Found {events.Count()} Access Granted events at {reader.CommonName}");
Note:
forAllKeysenforces that ALL specified keys must match (AND logic), whileforKeysuses OR logic (any one must match).
curl -X GET \
"https://api.us.acresecurity.cloud/api/f/INSTANCE_KEY/events?forKeys=ACCESS_GRANTED_EVENT_TYPE_KEY&page=0&pageSize=100" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
curl -X GET \
"https://api.us.acresecurity.cloud/api/f/INSTANCE_KEY/events?startDate=2024-01-01T00:00:00Z&endDate=2024-01-31T23:59:59Z&page=0&pageSize=1000" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
curl -X GET \
"https://api.us.acresecurity.cloud/api/f/INSTANCE_KEY/apps/MERCURY_APP_KEY/eventtypes" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
| Practice | Recommendation |
|---|---|
| Pagination | Always paginate large queries (max 1000 per page) |
| Date Filtering | Use date ranges to limit result sets |
| Event Type Keys | Cache event type keys instead of looking up repeatedly |
| BSON Parsing | Handle missing fields gracefully in event data |
| Time Zones | All timestamps are UTC - convert for display |
| Rate Limiting | Add delays between large paginated requests |
| Issue | Cause | Solution |
|---|---|---|
| Empty results | Wrong event type key | Verify key matches desired event |
| Missing events | Date range too narrow | Expand date range |
| Timeout | Too many events | Use pagination, narrow filters |
| Parse errors | Malformed BSON | Check Base64 encoding |
| Wrong event data | Different event schema | Check event type documentation |