Create an Access Level

Access levels define where and when cardholders can gain access. They’re the bridge between people and physical access points (readers). This guide shows you how to create and configure access levels.


Overview

What is an Access Level?

An access level is a collection of reader-schedule pairs that define access permissions. When you assign an access level to a person, they gain access to all readers in that access level during the specified schedules.

Access Level: "Engineering Team"
├── Reader: "Lab Door" + Schedule: "Business Hours"
├── Reader: "Office Entry" + Schedule: "24/7"
└── Reader: "Parking Garage" + Schedule: "Business Hours"

Access Level Components

Component Description Required
CommonName Display name for the access level ✅ Yes
AccessLevelEntries Reader + Schedule pairs ❌ No (add later)
ElevatorAccessLevelEntries Floor access for elevators ❌ No
StartsOn / EndsOn Optional date range for temporary access ❌ No

Create a Basic Access Level

Start by creating an access level with just a name. You’ll add readers and schedules after they exist.

C# Example

// Create a simple access level
var accessLevel = await client.AddAccessLevelAsync(currentInstance, new AccessLevelInfo
{
    CommonName = "General Access"
});

Console.WriteLine($"Created access level: {accessLevel.CommonName}");
Console.WriteLine($"Key: {accessLevel.Key}");

cURL Example

curl -X POST \
  https://api.us.acresecurity.cloud/api/f/INSTANCE_KEY/accesslevels \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "$type": "Feenics.Keep.WebApi.Model.AccessLevelInfo, Feenics.Keep.WebApi.Model",
    "CommonName": "General Access"
  }'

Create Access Level with Reader Entries

If you already have readers and schedules, you can include them at creation time.

C# Example

// First, get existing readers and schedules
var reader = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"MercuryReader\\",\\"_id\\":\\"{readerKey}\\"}}"))
    .OfType<MercuryReaderInfo>().FirstOrDefault();
var schedule = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"Schedule\\",\\"_id\\":\\"{scheduleKey}\\"}}"))
    .OfType<ScheduleInfo>().FirstOrDefault();

// Create access level with entries
var accessLevel = await client.AddAccessLevelAsync(currentInstance, new AccessLevelInfo
{
    CommonName = "Engineering Team",
    AccessLevelEntries = new[]
    {
        new AccessLevelEntryItem
        {
            ReaderId = reader.Key,
            ReaderCommonName = reader.CommonName,
            ScheduleId = schedule.Key,
            ScheduleCommonName = schedule.CommonName
        }
    }
});

cURL Example with Entries

curl -X POST \
  https://api.us.acresecurity.cloud/api/f/INSTANCE_KEY/accesslevels \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "$type": "Feenics.Keep.WebApi.Model.AccessLevelInfo, Feenics.Keep.WebApi.Model",
    "CommonName": "Engineering Team",
    "AccessLevelEntries": [
      {
        "$type": "Feenics.Keep.WebApi.Model.AccessLevelEntryItem, Feenics.Keep.WebApi.Model",
        "ReaderId": "READER_KEY",
        "ReaderCommonName": "Lab Door Reader",
        "ScheduleId": "SCHEDULE_KEY",
        "ScheduleCommonName": "Business Hours"
      }
    ]
  }'

Add Readers to Existing Access Level

The most common pattern is to create readers and schedules first, then link them to access levels.

C# Example

// Get the existing access level
var accessLevel = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"AccessLevel\\",\\"_id\\":\\"{accessLevelKey}\\"}}"))
    .OfType<AccessLevelInfo>().FirstOrDefault();

// Get the reader and schedule
var reader = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"MercuryReader\\",\\"_id\\":\\"{readerKey}\\"}}"))
    .OfType<MercuryReaderInfo>().FirstOrDefault();
var schedule = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"Schedule\\",\\"_id\\":\\"{scheduleKey}\\"}}"))
    .OfType<ScheduleInfo>().FirstOrDefault();

// Add new entry to the AccessLevelEntries array
var entries = accessLevel.AccessLevelEntries?.ToList() ?? new List<AccessLevelEntryItem>();
entries.Add(new AccessLevelEntryItem
{
    ReaderId = reader.Key,
    ReaderCommonName = reader.CommonName,
    ScheduleId = schedule.Key,
    ScheduleCommonName = schedule.CommonName
});
accessLevel.AccessLevelEntries = entries.ToArray();

// Update the access level with the new entries
await client.UpdateAccessLevelAsync(accessLevel);

Add Multiple Readers

// Get the existing access level
var accessLevel = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"AccessLevel\\",\\"_id\\":\\"{accessLevelKey}\\"}}"))
    .OfType<AccessLevelInfo>().FirstOrDefault();

// Get the schedule
var schedule = (await client.SearchAsync(currentInstance, $"{{\\"_t\\":\\"Schedule\\",\\"_id\\":\\"{businessHoursKey}\\"}}"))
    .OfType<ScheduleInfo>().FirstOrDefault();

// Initialize entries list from existing entries
var entries = accessLevel.AccessLevelEntries?.ToList() ?? new List<AccessLevelEntryItem>();

// Define a list of readers to add with the same schedule
var readers = new[] { "Lobby", "Elevator", "Parking" };

foreach (var readerName in readers)
{
    var results = await client.SearchAsync(currentInstance, 
        $"{{\"_t\":\"MercuryReader\",\"CommonName\":\"{readerName}\"}}");
    var reader = results.OfType<MercuryReaderInfo>().FirstOrDefault();
    
    if (reader != null)
    {
        entries.Add(new AccessLevelEntryItem
        {
            ReaderId = reader.Key,
            ReaderCommonName = reader.CommonName,
            ScheduleId = schedule.Key,
            ScheduleCommonName = schedule.CommonName
        });
        Console.WriteLine($"Added {readerName} to {accessLevel.CommonName}");
    }
}

// Update the access level once with all new entries
accessLevel.AccessLevelEntries = entries.ToArray();
await client.UpdateAccessLevelAsync(accessLevel);

Create Temporary Access Level

For contractors or visitors, create access levels with expiration dates.

C# Example

var tempAccessLevel = await client.AddAccessLevelAsync(currentInstance, new AccessLevelInfo
{
    CommonName = "Contractor - Project Alpha",
    StartsOn = DateTime.UtcNow,
    EndsOn = DateTime.UtcNow.AddMonths(3), // Expires in 3 months
    AccessLevelEntries = new[]
    {
        new AccessLevelEntryItem
        {
            ReaderId = lobbyReaderKey,
            ReaderCommonName = "Lobby Reader",
            ScheduleId = businessHoursKey,
            ScheduleCommonName = "Business Hours"
        }
    }
});

Console.WriteLine($"Temporary access expires: {tempAccessLevel.EndsOn}");

Create Elevator Access Level

For elevator floor access, use ElevatorAccessLevelEntries to link readers with elevator access levels.

C# Example

var elevatorAccessLevel = await client.AddAccessLevelAsync(currentInstance, new AccessLevelInfo
{
    CommonName = "Executive Floors",
    ElevatorAccessLevelEntries = new[]
    {
        new AccessLevelElevatorEntryItem
        {
            ReaderId = elevatorReaderKey,
            ReaderCommonName = "Elevator Card Reader",
            ElevatorAccessLevelId = executiveFloorsElevatorAccessLevelKey,
            ElevatorAccessLevelCommonName = "Executive Floors 10-12"
        }
    }
});

Common Patterns

Get All Access Levels

var accessLevels = await client.SearchAsync(currentInstance, "{\"_t\":\"AccessLevel\"}");

foreach (var al in accessLevels.OfType<AccessLevelInfo>())
{
    Console.WriteLine($"{al.CommonName}: {al.AccessLevelEntries?.Count ?? 0} readers");
}

Find Access Level by Name

var results = await client.SearchAsync(currentInstance,
    "{\"_t\":\"AccessLevel\",\"CommonName\":\"Engineering Team\"}");
var accessLevel = results.OfType<AccessLevelInfo>().FirstOrDefault();

if (accessLevel != null)
{
    Console.WriteLine($"Found: {accessLevel.Key}");
}

Delete Access Level

// Note: Remove all person assignments first
await client.DeleteAccessLevelAsync(accessLevel);

Best Practices

Practice Recommendation
Naming Convention Use descriptive names: “Engineering - Lab Access” not “AL1”
Minimal Access Grant only necessary access (principle of least privilege)
Group by Role Create access levels that match job functions
Use Schedules Must pair readers with appropriate schedules for the AccessLevel
Audit Regularly Review access level assignments periodically

Troubleshooting

Issue Cause Solution
Access level created but no access No reader entries added Add entries to AccessLevelEntries array and call UpdateAccessLevelAsync - may need to Push Controller DB
Person has access level but denied Schedule not active or AccessLevelEntries are empty Check Schedule and AccessLevel configuration - may need to Push Controller DB
Reader not appearing in entries Reader not linked or may no longer exist Verify reader key and add entry

See Also