Events provide a complete audit trail of all access activity in your DoorFlow account. This guide explains event types, event codes, and how to build powerful monitoring and reporting tools.
What Are Events?
Events are immutable records of activity:
- Access attempts (successful and failed)
- Door status changes (online, offline, mode changes)
- System events (syncs, errors)
- Credential usage tracking
Events are read-only - they're automatically created by the system and cannot be modified or deleted.
Event Structure
{
"id": 123456,
"event_code": 12,
"person_id": 5678,
"person_name": "Jane Doe",
"channel_id": 1340,
"channel_name": "Front Door",
"credential_id": "cred_abc123",
"occurred_at": "2024-01-15T14:30:45Z",
"description": "Jane Doe accessed Front Door",
"additional_data": {
"credential_type": "Card",
"card_number": "1234567890"
}
}
Key fields
id: Unique event identifier
event_code: Numeric code indicating event type (see reference below)
person_id: Who was involved (null for non-person events)
channel_id: Which door (null for non-channel events)
credential_id: Which credential was used
occurred_at: Timestamp (UTC)
description: Human-readable description
additional_data: Extra context (varies by event type)
Event Codes Reference
Access Events (10-29)
Admit Events (10-18) - Successful Access
| Code | Name | Description |
| 10 | Access Granted | Person successfully accessed door with credential |
| 12 | Access Granted (Schedule) | Access granted during scheduled time |
| 13 | Access Granted (Reservation) | Access granted via time-limited reservation |
| 14 | Access Granted (Remote) | Door unlocked remotely via API |
| 15 | Access Granted (PIN) | Access with PIN credential |
| 16 | Access Granted (Mobile) | Access with mobile credential |
Common use cases
Example:
{
"event_code": 10,
"person_id": 12345,
"person_name": "John Smith",
"channel_id": 1340,
"channel_name": "Front Entrance",
"occurred_at": "2024-01-15T09:15:30Z",
"description": "John Smith accessed Front Entrance"
}
Reject Events (20-29) - Denied Access
| Code | Name | Description |
| 20 | Access Denied | Credential rejected (generic) |
| 21 | Access Denied (Disabled Person) | Person account is disabled |
| 22 | Access Denied (Disabled Credential) | Credential is disabled |
| 23 | Access Denied (No Permission) | Person lacks permission for this door |
| 24 | Access Denied (Outside Schedule) | Access attempted outside permitted hours |
| 25 | Access Denied (Invalid Credential) | Credential not recognized/invalid format |
| 26 | Access Denied (Expired Reservation) | Reservation has expired |
Common use cases
Example:
{
"event_code": 23,
"person_id": 12345,
"person_name": "John Smith",
"channel_id": 1350,
"channel_name": "Server Room",
"occurred_at": "2024-01-15T14:22:10Z",
"description": "John Smith denied access to Server Room - No Permission"
}
Troubleshooting denied access
Channel Status Events (40-49)
| Code | Name | Description |
| 40 | Channel Online | Channel came online |
| 41 | Channel Offline | Channel went offline |
| 42 | Channel Mode: Normal | Door set to normal operation |
| 43 | Channel Mode: Unlock | Door set to permanently unlocked |
| 44 | Channel Mode: Lockdown | Door set to lockdown (no access) |
| 45 | Channel Tamper Alert | Physical tampering detected |
| 46 | Door Forced Open | Door opened without valid credential |
| 47 | Door Held Open | Door left open too long |
Common use cases
Example:
{
"event_code": 41,
"channel_id": 1340,
"channel_name": "Front Door",
"occurred_at": "2024-01-15T03:15:00Z",
"description": "Front Door went offline"
}
System Events (60+)
| Code | Name | Description |
| 60 | Sync Started | Data synchronization began |
| 61 | Sync Completed | Data synchronization finished |
| 62 | Sync Failed | Data synchronization error |
| 70 | Configuration Change | System configuration updated |
Common use cases
Querying Events
Basic Query
Request:
curl -X GET "https://api.doorflow.com/api/3/events" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response:
{
"events": [
{
"id": 123456,
"event_code": 10,
"person_id": 12345,
"person_name": "Jane Doe",
"channel_id": 1340,
"channel_name": "Front Door",
"occurred_at": "2024-01-15T14:30:45Z",
"description": "Jane Doe accessed Front Door"
},
// ... more events
],
"pagination": {
"page": 1,
"per_page": 50,
"total_pages": 10,
"total_count": 487
}
}
Filtering by Person
Get all events for a specific person:
# By person ID
curl -X GET "https://api.doorflow.com/api/3/events?person_id=12345" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# By last name
curl -X GET "https://api.doorflow.com/api/3/events?last_name=Doe" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# By email
curl -X GET "https://api.doorflow.com/api/3/events?email=jane@example.com" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Use cases
Filtering by Channel
Get all events for a specific door:
# By channel ID
curl -X GET "https://api.doorflow.com/api/3/events?channel_id=1340" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Multiple channels
curl -X GET "https://api.doorflow.com/api/3/events?channel_id=1340&channel_id=1341" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Use cases
Filtering by Time Range
Get events within a specific time window:
# Since a specific time (UTC)
curl -X GET "https://api.doorflow.com/api/3/events?since_utc=2024-01-15T00:00:00Z" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Between two times
curl -X GET "https://api.doorflow.com/api/3/events?since_utc=2024-01-15T09:00:00Z&until_utc=2024-01-15T17:00:00Z" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Last 24 hours
curl -X GET "https://api.doorflow.com/api/3/events?since_utc=$(date -u -v-24H '+%Y-%m-%dT%H:%M:%SZ')" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Use cases
Filtering by Event Code
Get specific types of events:
# Only denied access events (codes 20-29)
curl -X GET "https://api.doorflow.com/api/3/events?event_code=20&event_code=21&event_code=22&event_code=23" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Only successful access (codes 10-18)
curl -X GET "https://api.doorflow.com/api/3/events?event_code=10&event_code=12&event_code=13" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Channel offline events
curl -X GET "https://api.doorflow.com/api/3/events?event_code=41" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Use cases
Combining Filters
Build complex queries by combining filters:
# Denied access at specific door in last 24 hours
curl -X GET "https://api.doorflow.com/api/3/events?channel_id=1340&event_code=20&event_code=21&event_code=22&event_code=23&since_utc=2024-01-14T14:00:00Z" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Specific person at specific door
curl -X GET "https://api.doorflow.com/api/3/events?person_id=12345&channel_id=1340" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Pagination
Handle large result sets:
# Default: 50 per page
curl -X GET "https://api.doorflow.com/api/3/events" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Custom page size
curl -X GET "https://api.doorflow.com/api/3/events?per_page=100&page=2" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Skip pagination (get all - use carefully!)
curl -X GET "https://api.doorflow.com/api/3/events?skip_pagination=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Best practice: Use pagination for large queries to avoid timeouts.
Common Use Cases
Use Case 1: Daily Access Report
Goal: Generate report of who accessed building today
async function generateDailyAccessReport() {
const today = new Date();
today.setHours(0, 0, 0, 0);
const since = today.toISOString();
const response = await fetch(
`/api/3/events?since_utc=${since}&event_code=10&event_code=12&event_code=13`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
// Group by person
const accessByPerson = events.reduce((acc, event) => {
const key = event.person_id;
if (!acc[key]) {
acc[key] = {
person: event.person_name,
firstAccess: event.occurred_at,
lastAccess: event.occurred_at,
accessCount: 0,
};
}
acc[key].accessCount++;
acc[key].lastAccess = event.occurred_at;
return acc;
}, {});
console.log('Daily Access Report:');
Object.values(accessByPerson).forEach(person => {
console.log(`${person.person}: ${person.accessCount} accesses (First: ${person.firstAccess}, Last: ${person.lastAccess})`);
});
return accessByPerson;
}
Use Case 2: Security Alert for Denied Access
Goal: Alert security team when access is denied
async function monitorDeniedAccess() {
// Check for denied access in last 5 minutes
const since = new Date(Date.now() - 5 * 60 * 1000).toISOString();
const response = await fetch(
`/api/3/events?since_utc=${since}&event_code=20&event_code=21&event_code=22&event_code=23&event_code=24&event_code=25`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
for (const event of events) {
// Alert on suspicious patterns
if (event.event_code === 25) {
// Invalid credential - potential security threat
await sendAlert({
priority: 'HIGH',
type: 'Invalid Credential',
person: event.person_name || 'Unknown',
channel: event.channel_name,
time: event.occurred_at,
});
} else if (event.event_code === 23) {
// No permission - might need support
await sendAlert({
priority: 'LOW',
type: 'Permission Issue',
person: event.person_name,
channel: event.channel_name,
message: `${event.person_name} tried to access ${event.channel_name} but lacks permission`,
});
}
}
}
// Run every 5 minutes
setInterval(monitorDeniedAccess, 5 * 60 * 1000);
Use Case 3: Attendance Tracking
Goal: Track employee attendance based on door access
async function getEmployeeAttendance(personId, date) {
const startOfDay = new Date(date);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(date);
endOfDay.setHours(23, 59, 59, 999);
const response = await fetch(
`/api/3/events?person_id=${personId}&since_utc=${startOfDay.toISOString()}&until_utc=${endOfDay.toISOString()}&event_code=10&event_code=12`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
if (events.length === 0) {
return { present: false };
}
// First and last access
const firstAccess = events[events.length - 1]; // Events are reverse chronological
const lastAccess = events[0];
return {
present: true,
checkIn: firstAccess.occurred_at,
checkOut: lastAccess.occurred_at,
totalAccessEvents: events.length,
};
}
// Usage
const attendance = await getEmployeeAttendance(12345, '2024-01-15');
console.log(`Present: ${attendance.present}`);
if (attendance.present) {
console.log(`Check-in: ${attendance.checkIn}`);
console.log(`Check-out: ${attendance.checkOut}`);
}
Use Case 4: Infrastructure Monitoring
Goal: Monitor channel health (online/offline status)
async function monitorChannelHealth() {
// Get channel status events from last hour
const since = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const response = await fetch(
`/api/3/events?since_utc=${since}&event_code=40&event_code=41`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
// Identify currently offline channels
const channelStatus = {};
events.forEach(event => {
channelStatus[event.channel_id] = {
channelName: event.channel_name,
status: event.event_code === 40 ? 'online' : 'offline',
lastChange: event.occurred_at,
};
});
// Alert on offline channels
Object.values(channelStatus).forEach(channel => {
if (channel.status === 'offline') {
console.error(`Channel offline: ${channel.channelName} (since ${channel.lastChange})`);
// sendMaintenanceAlert(channel);
}
});
return channelStatus;
}
Use Case 5: Incremental Sync
Goal: Efficiently sync only new events to external database
let lastSyncTime = null;
async function syncEventsToDatabase() {
// Get sync timestamp from database or use default
lastSyncTime = lastSyncTime || await db.query('SELECT last_sync_time FROM sync_status');
const since = lastSyncTime || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const response = await fetch(
`/api/3/events?since_utc=${since}&skip_pagination=true`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
console.log(`Syncing ${events.length} new events since ${since}`);
// Insert into local database
for (const event of events) {
await db.insert('events', event);
}
// Update sync timestamp
const newSyncTime = new Date().toISOString();
await db.update('sync_status', { last_sync_time: newSyncTime });
lastSyncTime = newSyncTime;
console.log(`[OK] Sync complete. Next sync from: ${lastSyncTime}`);
}
// Run every 5 minutes
setInterval(syncEventsToDatabase, 5 * 60 * 1000);
Event Analysis Patterns
Count Events by Type
async function analyzeEventTypes(since) {
const response = await fetch(
`/api/3/events?since_utc=${since}&skip_pagination=true`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
const eventCounts = events.reduce((acc, event) => {
const code = event.event_code;
const category =
code >= 10 && code < 20 ? 'Successful Access' :
code >= 20 && code < 30 ? 'Denied Access' :
code >= 40 && code < 50 ? 'Channel Status' :
'Other';
acc[category] = (acc[category] || 0) + 1;
return acc;
}, {});
console.log('Event Summary:', eventCounts);
return eventCounts;
}
Detect Unusual Patterns
async function detectUnusualActivity() {
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const response = await fetch(
`/api/3/events?since_utc=${since}&skip_pagination=true`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { events } = await response.json();
// Detect multiple failed attempts by same person
const failedAttempts = {};
events.filter(e => e.event_code >= 20 && e.event_code < 30).forEach(event => {
const key = event.person_id || 'unknown';
failedAttempts[key] = (failedAttempts[key] || 0) + 1;
});
// Alert if more than 5 failed attempts
Object.entries(failedAttempts).forEach(([personId, count]) => {
if (count > 5) {
console.warn(`Unusual activity: Person ${personId} has ${count} failed access attempts`);
}
});
// Detect after-hours access
const afterHoursEvents = events.filter(event => {
const hour = new Date(event.occurred_at).getHours();
return hour < 6 || hour > 20; // Before 6 AM or after 8 PM
});
console.log(`After-hours access events: ${afterHoursEvents.length}`);
return {
failedAttempts,
afterHoursCount: afterHoursEvents.length,
};
}
Best Practices
1. Use Incremental Sync
Don't query all events every time - use since_utc:
// Good: Only get new events
const lastSync = '2024-01-15T14:00:00Z';
const events = await fetchEvents({ since_utc: lastSync });
// Bad: Gets all events every time
const events = await fetchEvents();
2. Filter Early
Apply filters server-side, not client-side:
// Good: Filter in API request
const deniedEvents = await fetchEvents({ event_code: [20, 21, 22, 23] });
// Bad: Get all events then filter
const allEvents = await fetchEvents();
const deniedEvents = allEvents.filter(e => e.event_code >= 20 && e.event_code < 30);
3. Use Pagination for Large Queries
// Good: Paginate large queries
async function getAllEvents() {
let allEvents = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const { events, pagination } = await fetchEvents({ page, per_page: 100 });
allEvents = allEvents.concat(events);
hasMore = page < pagination.total_pages;
page++;
}
return allEvents;
}
// Bad: skip_pagination on unlimited time range
const events = await fetchEvents({ skip_pagination: true }); // Could timeout!
4. Cache Event Counts
// Cache event counts to avoid repeated queries
const cache = new Map();
async function getEventCount(filters) {
const cacheKey = JSON.stringify(filters);
if (cache.has(cacheKey)) {
const { count, timestamp } = cache.get(cacheKey);
// Cache for 5 minutes
if (Date.now() - timestamp < 5 * 60 * 1000) {
return count;
}
}
const { pagination } = await fetchEvents({ ...filters, per_page: 1 });
const count = pagination.total_count;
cache.set(cacheKey, { count, timestamp: Date.now() });
return count;
}
Quick Reference
Get events:
GET /api/3/events
Query parameters
person_id - Filter by person
channel_id - Filter by channel
event_code - Filter by event type
since_utc - Events after this time
until_utc - Events before this time
page - Page number (default: 1)
per_page - Results per page (default: 50, max: 500)
skip_pagination - Get all results (use carefully!)
Required OAuth scope: account.event.access.readonly
Event code ranges
Next Steps
- [Common Workflows] - See events used in real scenarios
- API Reference: Events - Complete endpoint documentation
- [Error Handling] - Handle API errors when querying events
- [Webhooks Complete Guide] - Get real-time event notifications
Need Help?
Common questions:
- Q: How long are events stored? A: Events are stored indefinitely.
- Q: Can I delete events? A: No, events are immutable for audit compliance.
- Q: What's the maximum time range? A: No hard limit, but use pagination for large ranges.
- Q: Can I get real-time events? A: Use webhooks for real-time notifications (see Webhooks guide).