I don't think that is the issue. But something appears to be different from the GUI to what the API can return. In an effort to slim this down I added logic to search for a specific username, on a specific network as well as debug to show a match which works, but no match no data. And that is the problem I cannot get it to match and pull every single EAP success for a 24 hour period for some users and no idea why. Matched Event:
{'occurredAt': '2025-10-28T23:47:45.480374Z', 'networkId': 'L_609111849601866880', 'type': '8021x_eap_success', 'description': 'Successful authentication (EAP success)', 'clientId': 'k1e8554', 'clientDescription': 'devicename', 'clientMac': '56:0a:ab:1c:e1:2f', 'category': '8021x', 'deviceSerial': 'xxxxxxx', 'deviceName': 'ap1', 'ssidNumber': 0, 'ssidName': 'wireless', 'eventData': {'radio': '1', 'vap': '0', 'client_mac': '56:0A:AB:1C:E1:2F', 'identity': 'user1'}} If I have 2 log entries for 2 different users 1 after the other the script finds the first one user1 but not the second user2 when its clear as day there are entries for both. :~/meraki-automation$ python3 meraki-eap-v1.py
Enter the date to run (YYYY-MM-DD): 2025-10-28
Enter the identity to search for (e.g., user or user@domain.com): user1
Enter the network name to search in (or part of it): hq
Searching HQ...
Matches found in HQ: 1
Saved 1 matching events to auth-user1-2025-10-28.csv
:~/meraki-automation$ python3 meraki-eap-v1.py
Enter the date to run (YYYY-MM-DD): 2025-10-28
Enter the identity to search for (e.g., user or user@domain.com): user2
Enter the network name to search in (or part of it): hq
Searching HQ...
Matches found in HQ: 0
Saved 0 matching events to auth-user2-2025-10-28.csv import requests
import csv
from datetime import datetime, timedelta, timezone
import re
API_KEY = 'MY-API-KEY'
BASE_URL = 'https://api.meraki.com/api/v1'
headers = {
'X-Cisco-Meraki-API-Key': API_KEY,
'Content-Type': 'application/json'
}
def main():
# Prompt for date, identity, and network name filter
date_input = input("Enter the date to run (YYYY-MM-DD): ")
identity_input = input("Enter the identity to search for (e.g., user or user@domain.com): ")
network_filter = input("Enter the network name to search in (or part of it): ").lower()
try:
target_date = datetime.strptime(date_input, "%Y-%m-%d").date()
except ValueError:
print("Invalid date format. Please use YYYY-MM-DD.")
return
normalized_target_identity = identity_input.split('@')[0].lower()
start_time = datetime.combine(target_date, datetime.min.time()).replace(tzinfo=timezone.utc)
end_time = start_time + timedelta(days=1)
orgs = requests.get(f'{BASE_URL}/organizations', headers=headers).json()
org_id = orgs[0]['id']
networks = requests.get(f'{BASE_URL}/organizations/{org_id}/networks', headers=headers).json()
# Filter networks based on user input
filtered_networks = [net for net in networks if network_filter in net['name'].lower()]
if not filtered_networks:
print(f"No networks found matching '{network_filter}'.")
return
matching_events = []
for network in filtered_networks:
network_id = network['id']
network_name = network['name']
print(f"\nSearching {network_name}...")
url = f'{BASE_URL}/networks/{network_id}/events'
params = {
'productType': 'wireless',
'includedEventTypes[]': '8021x_eap_success',
'occurredAfter': start_time.isoformat().replace('+00:00', 'Z'),
'occurredBefore': end_time.isoformat().replace('+00:00', 'Z'),
'perPage': 1000
}
match_count = 0
while url:
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
data = response.json()
events = data.get('events', [])
for event in events:
occurred_at_str = event.get('occurredAt', '')
try:
occurred_at = datetime.fromisoformat(occurred_at_str.replace('Z', '+00:00'))
except ValueError:
continue
if not (start_time <= occurred_at < end_time):
continue
client_mac = event.get('clientMac', '')
identity_raw = event.get('eventData', {}).get('identity', 'N/A')
normalized_identity = identity_raw.split('@')[0].lower()
if normalized_identity != normalized_target_identity:
continue
matching_events.append([network_name, occurred_at.isoformat(), client_mac, identity_raw])
match_count += 1
# Check for rel="next" in Link header
link_header = response.headers.get('Link', '')
match = re.search(r'<([^>]+)>;\s*rel="next"', link_header)
url = match.group(1) if match else None
params = None # Clear params for next URL
print(f" Matches found in {network_name}: {match_count}")
filename = f"Meraki-auth-{normalized_target_identity}-{target_date.isoformat()}.csv"
with open(filename, 'w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
writer.writerow(['Network Name', 'Occurred At', 'Client MAC', 'Identity'])
writer.writerows(matching_events)
print(f"\nSaved {len(matching_events)} matching events to {filename}")
if __name__ == '__main__':
main()
... View more