Using Claude to Automate Cisco Meraki via the Dashboard API
Meraki Automation with Claude
The Cisco Meraki Dashboard API is one of the cleanest REST APIs in the networking industry — every organization, network, and device is accessible via authenticated HTTPS calls returning JSON. This makes it an ideal platform for AI-assisted automation.
Claude adds the reasoning layer that the API alone can’t provide: interpreting alert patterns, generating consistent configuration across hundreds of networks, diagnosing client connectivity issues, and building natural language interfaces for network management tasks that previously required expert knowledge of Meraki’s UI.
Setup
import anthropic
import requests
import json
client = anthropic.Anthropic()
MERAKI_API_KEY = "your-meraki-api-key"
BASE_URL = "https://api.meraki.com/api/v1"
def meraki_get(path: str, params: dict = None) -> dict | list:
r = requests.get(
f"{BASE_URL}{path}",
headers={"X-Cisco-Meraki-API-Key": MERAKI_API_KEY},
params=params or {},
timeout=30
)
r.raise_for_status()
return r.json()
def meraki_put(path: str, payload: dict) -> dict:
r = requests.put(
f"{BASE_URL}{path}",
headers={
"X-Cisco-Meraki-API-Key": MERAKI_API_KEY,
"Content-Type": "application/json"
},
json=payload,
timeout=30
)
r.raise_for_status()
return r.json()
def meraki_post(path: str, payload: dict) -> dict:
r = requests.post(
f"{BASE_URL}{path}",
headers={
"X-Cisco-Meraki-API-Key": MERAKI_API_KEY,
"Content-Type": "application/json"
},
json=payload,
timeout=30
)
r.raise_for_status()
return r.json()
def ask_claude(prompt: str, system: str = None) -> str:
kwargs = {
"model": "claude-opus-4-5",
"max_tokens": 2048,
"messages": [{"role": "user", "content": prompt}]
}
if system:
kwargs["system"] = system
return client.messages.create(**kwargs).content[0].text
Pattern 1: Natural Language Network Configuration
Describe a network policy in plain English, get a Meraki API payload back:
def generate_meraki_config(network_id: str, description: str) -> dict:
# Pull current network state for context
current_config = {
"vlans": meraki_get(f"/networks/{network_id}/appliance/vlans"),
"firewall_rules": meraki_get(f"/networks/{network_id}/appliance/firewall/l3FirewallRules"),
"ssids": meraki_get(f"/networks/{network_id}/wireless/ssids"),
}
prompt = f"""
Generate Cisco Meraki Dashboard API payload(s) for this request:
Request: {description}
Current network state:
{json.dumps(current_config, indent=2)}
Return a JSON array of API calls, each with:
- method: "GET" | "PUT" | "POST"
- path: the API path (e.g., /networks/{{network_id}}/appliance/vlans)
- payload: the request body (null for GET)
- description: what this call does
Use the current state to avoid conflicts.
Output ONLY valid JSON array.
"""
return json.loads(ask_claude(
prompt,
system="You are a Cisco Meraki expert. Generate only valid Meraki Dashboard API calls."
))
# Example: add a guest network with captive portal
api_calls = generate_meraki_config(
"N_123456789",
"""
Add a new Guest WiFi SSID called 'Guest-WiFi':
- WPA2 with password 'Welcome2024!'
- Isolated from corporate traffic (client isolation enabled)
- Bandwidth limit: 10 Mbps down, 5 Mbps up per client
- Splash page: click-through with terms of service
- VLAN 100 for guest traffic
- Available on all APs
- Enabled 8am-10pm daily using scheduled availability
"""
)
# Execute the API calls
for call in api_calls:
print(f"Executing: {call['description']}")
path = call['path'].replace('{network_id}', 'N_123456789')
if call['method'] == 'PUT':
result = meraki_put(path, call['payload'])
elif call['method'] == 'POST':
result = meraki_post(path, call['payload'])
print(f" ✓ Done")
Pattern 2: Client Connectivity Troubleshooter
When a user says “I can’t connect to WiFi,” this tool diagnoses the issue automatically:
def troubleshoot_client(network_id: str, client_identifier: str) -> dict:
"""
client_identifier: MAC address, IP address, or username
"""
# Gather client data from Meraki
clients = meraki_get(f"/networks/{network_id}/clients", params={
"timespan": 86400, # last 24 hours
"perPage": 100,
})
# Find matching client
client = next(
(c for c in clients if
c.get("mac", "").lower() == client_identifier.lower() or
c.get("ip", "") == client_identifier or
client_identifier.lower() in c.get("description", "").lower()),
None
)
client_data = {"search_term": client_identifier, "found": client is not None}
if client:
client_id = client["id"]
client_data.update({
"profile": client,
"events": meraki_get(f"/networks/{network_id}/events", params={
"clientMac": client.get("mac", ""),
"timespan": 3600,
"perPage": 50,
}).get("events", [])[:20],
"connectivity": client.get("status", "unknown"),
})
# Get network alerts
alerts = meraki_get(f"/networks/{network_id}/health/alerts")
prompt = f"""
Troubleshoot WiFi connectivity for client: {client_identifier}
Client data: {json.dumps(client_data, indent=2)}
Network alerts: {json.dumps(alerts[:10], indent=2)}
Return JSON with:
- client_found: true/false
- current_status: description of client's current state
- root_cause: specific reason for connectivity issue (or "No issue found")
- event_timeline: key events from the last hour in chronological order
- is_device_issue: true if problem is isolated to this device
- is_network_issue: true if problem affects multiple clients
- recommended_actions: ordered list of steps to resolve
- escalation_needed: true if issue requires on-site or TAC support
Output ONLY valid JSON.
"""
return json.loads(ask_claude(prompt))
result = troubleshoot_client("N_123456789", "aa:bb:cc:dd:ee:ff")
print(f"Status: {result['current_status']}")
print(f"Root cause: {result['root_cause']}")
for action in result['recommended_actions']:
print(f" → {action}")
Pattern 3: Multi-Site Security Audit
For MSPs or enterprises managing many Meraki networks, this audits all of them at once:
def audit_organization_security(org_id: str) -> list[dict]:
networks = meraki_get(f"/organizations/{org_id}/networks")
results = []
for network in networks:
nid = network["id"]
try:
# Gather security-relevant config
network_data = {
"name": network["name"],
"firewall_rules": meraki_get(f"/networks/{nid}/appliance/firewall/l3FirewallRules"),
"content_filtering": meraki_get(f"/networks/{nid}/appliance/contentFiltering"),
"intrusion_settings": meraki_get(f"/networks/{nid}/appliance/security/intrusion"),
"malware_settings": meraki_get(f"/networks/{nid}/appliance/security/malware"),
"ssids": meraki_get(f"/networks/{nid}/wireless/ssids"),
}
except Exception:
continue # Skip networks without MX/MR
prompt = f"""
Audit this Meraki network's security configuration.
Network: {network['name']}
Config: {json.dumps(network_data, indent=2)}
Return JSON with:
- security_score: 0-100
- critical_issues: list of immediate security risks
- warnings: list of non-critical concerns
- ssid_issues: list of SSIDs with weak security settings
- firewall_gaps: description of firewall rule weaknesses
- top_3_fixes: most impactful changes to improve security
Output ONLY valid JSON.
"""
audit = json.loads(ask_claude(prompt))
audit["network_name"] = network["name"]
audit["network_id"] = nid
results.append(audit)
# Sort by score ascending (worst first)
return sorted(results, key=lambda x: x["security_score"])
org_audits = audit_organization_security("123456")
print(f"Audited {len(org_audits)} networks")
print(f"\nLowest scoring networks:")
for network in org_audits[:5]:
print(f" {network['network_name']}: {network['security_score']}/100")
for issue in network['critical_issues']:
print(f" ⚠️ {issue}")
Pattern 4: Bulk Configuration Deployment
Deploy consistent configuration across all networks in an organization:
def deploy_policy_everywhere(org_id: str, policy_description: str, dry_run: bool = True) -> list[dict]:
networks = meraki_get(f"/organizations/{org_id}/networks")
deployment_results = []
for network in networks:
nid = network["id"]
# Generate network-specific payload
prompt = f"""
Generate Meraki API calls to implement this policy for network '{network['name']}' (ID: {nid}):
Policy: {policy_description}
Return a JSON array of API calls with method, path, and payload.
Make the path specific to network ID {nid}.
Output ONLY valid JSON array.
"""
try:
api_calls = json.loads(ask_claude(prompt))
except Exception as e:
deployment_results.append({"network": network["name"], "status": "error", "error": str(e)})
continue
if dry_run:
deployment_results.append({
"network": network["name"],
"status": "dry_run",
"planned_calls": len(api_calls),
"calls": api_calls
})
else:
# Execute calls
for call in api_calls:
try:
if call["method"] == "PUT":
meraki_put(call["path"], call["payload"])
elif call["method"] == "POST":
meraki_post(call["path"], call["payload"])
except Exception as e:
deployment_results.append({"network": network["name"], "status": "failed", "error": str(e)})
break
else:
deployment_results.append({"network": network["name"], "status": "success"})
return deployment_results
# Dry run first
results = deploy_policy_everywhere(
"123456",
"Block all social media (Facebook, Instagram, TikTok, Twitter/X) during business hours 8am-5pm Mon-Fri",
dry_run=True
)
print(f"Would deploy to {len(results)} networks")
Pattern 5: Natural Language NOC Dashboard
Give your NOC a conversational interface to Meraki:
class MerakiAssistant:
def __init__(self, org_id: str):
self.org_id = org_id
self.history = []
self.system = f"""You are a Meraki network management assistant for organization {org_id}.
You have access to the Meraki Dashboard API. When asked about network state,
tell the engineer exactly what API call to make and how to interpret the results.
When asked to make changes, generate the exact API payload and confirm before executing.
Be conversational but precise. Always mention the API path used."""
def ask(self, question: str) -> str:
self.history.append({"role": "user", "content": question})
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=self.system,
messages=self.history
).content[0].text
self.history.append({"role": "assistant", "content": response})
return response
# Natural language operations
assistant = MerakiAssistant("123456")
print(assistant.ask("How many clients are currently connected across all our networks?"))
print(assistant.ask("Which network has the most clients right now?"))
print(assistant.ask("Show me any networks that had connectivity issues in the last 24 hours"))
print(assistant.ask("Generate a config to add a new VLAN 200 called 'IoT-Devices' to all MX networks"))
Rate Limiting and Best Practices
The Meraki Dashboard API has rate limits (typically 10 requests/second per org). For bulk operations:
import time
def rate_limited_get(path: str, delay: float = 0.15) -> dict | list:
"""Add small delay between requests to stay under rate limit"""
time.sleep(delay)
return meraki_get(path)
Always use dry_run=True the first time you run any bulk operation. The Meraki API makes changes immediately — there’s no staging or commit step like IOS-XR.