Practice Questions

Test your knowledge on this topic in the ENAUTO Exam Trainer — 186 questions across 5 interactive modes.

Cisco Meraki – Deep Dive for ENAUTO v2.0

Exam Relevance

Topic 3.0 Controller-Based Network Automation (30%) — Meraki is the cloud-managed controller. Unlike Catalyst Center, Meraki operations are synchronous and the API lives entirely in the cloud.

Exam Topics Covered:
3.1 – Day-0 provisioning (controller-based)
3.2 – Python to manage and monitor configurations
3.3 – Jinja2 templates (configuration templates)
3.5 – Security automation (group policies, firewall rules)
3.6 – Troubleshoot REST API automation solutions
4.4 – Monitor network health
4.6 – Webhook-based monitoring

Table of Contents


1 – Architecture Overview

Meraki is fundamentally different from Catalyst Center — there is no on-prem controller. Everything runs in the Meraki cloud.

┌─────────────────────────────────────────────┐
│              Your Python Script              │
│     (requests library  OR  meraki SDK)       │
└──────────────────┬──────────────────────────┘
                   │ HTTPS (port 443)
                   ▼
┌─────────────────────────────────────────────┐
│           Meraki Cloud Dashboard            │
│         https://api.meraki.com              │
│                                             │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐ │
│  │ Org API  │  │Network   │  │ Device    │ │
│  │ /orgs/   │  │ /networks│  │ /devices/ │ │
│  └──────────┘  └──────────┘  └───────────┘ │
└──────────────────┬──────────────────────────┘
                   │ Meraki Cloud Tunnel
                   ▼
┌─────────────────────────────────────────────┐
│         Managed Network Devices             │
│     MR APs · MS Switches · MX Firewalls     │
└─────────────────────────────────────────────┘

Key Differences from Catalyst Center

AspectCatalyst CenterMeraki
DeploymentOn-premises applianceCloud-hosted
Auth methodToken (Basic → X-Auth-Token)API Key in every request
API base URLhttps://<your-server>/dna/...https://api.meraki.com/api/v1
Write operationsAsynchronous (task polling)Synchronous (immediate response)
Rate limitingToken-based throttling5 calls/sec per org (429 retries)
Config modelIntent-basedDirect object manipulation

Rate Limiting – The #1 Meraki Gotcha

Meraki enforces 5 API calls per second per organization. Exceeding this returns 429 Too Many Requests with a Retry-After header. The SDK handles retries automatically; with requests you must implement backoff yourself.


2 – Authentication with Python requests

Exam Topic: 3.2, 3.6

Meraki uses the simplest auth model among Cisco controllers — a single API key in every request.

How It Works

  1. Generate an API key once from Meraki Dashboard → My Profile → API access.
  2. Include it as the X-Cisco-Meraki-API-Key header in every request.
  3. No token exchange. No session cookies. No expiry (unless manually regenerated).

Full Working Example

import requests
import os
 
# ── Best practice: store API key as environment variable ──────
# export MERAKI_DASHBOARD_API_KEY="your_key_here"
API_KEY = os.environ.get("MERAKI_DASHBOARD_API_KEY")
 
BASE_URL = "https://api.meraki.com/api/v1"
 
HEADERS = {
    "X-Cisco-Meraki-API-Key": API_KEY,
    "Content-Type": "application/json",
    "Accept": "application/json"
}
 
# ── Test: Get your organizations ──────────────────────────────
response = requests.get(f"{BASE_URL}/organizations", headers=HEADERS)
response.raise_for_status()
 
for org in response.json():
    print(f"Org: {org['name']} (ID: {org['id']})")

Exam Trap: Header Name

The header is X-Cisco-Meraki-API-Key — note the dashes and capitalization. Common wrong answers on the exam:

  • Authorization: Bearer <key> — wrong scheme
  • X-Meraki-API-Key — missing “Cisco”
  • X-Auth-Token — that’s Catalyst Center

What Happens on Failure?

Status CodeMeaningLikely Cause
401 UnauthorizedInvalid API keyWrong or revoked key
403 ForbiddenKey valid, no org accessKey lacks permissions for this org
404 Not FoundResource doesn’t existWrong org/network/device ID
429 Too Many RequestsRate limitedExceeded 5 calls/sec

Rate Limit Handling with requests

import time
 
def meraki_get(url, headers, max_retries=3):
    """GET with automatic 429 retry backoff."""
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
 
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 1))
            print(f"Rate limited. Retrying in {retry_after}s...")
            time.sleep(retry_after)
            continue
 
        response.raise_for_status()
        return response.json()
 
    raise Exception("Max retries exceeded due to rate limiting")

3 – Authentication with Meraki SDK

Exam Topic: 3.2

Installation

pip install meraki

Full Working Example

import meraki
import os
 
API_KEY = os.environ.get("MERAKI_DASHBOARD_API_KEY")
 
# The SDK handles:
#   - API key header injection
#   - Rate limit retries (automatic backoff)
#   - Pagination (returns all results)
#   - Logging (optional)
dashboard = meraki.DashboardAPI(
    api_key=API_KEY,
    suppress_logging=True,    # Quiet mode
    maximum_retries=3,        # Auto-retry on 429
    wait_on_rate_limit=True   # Pause instead of failing on 429
)
 
# ── Test authentication ───────────────────────────────────────
orgs = dashboard.organizations.getOrganizations()
for org in orgs:
    print(f"Org: {org['name']} (ID: {org['id']})")

SDK vs requests — When to Use What

Aspectrequestsmeraki SDK
Rate limitingManual (handle 429)Automatic backoff
PaginationManual (follow Link headers)Returns all pages
Method namingHTTP verbs + URLsdashboard.category.methodName()
API key handlingManual header in every callSet once in constructor
Exam relevanceMust understand raw callsMust know SDK constructor

4 – The Meraki Object Hierarchy

Understanding this hierarchy is critical — every API call requires IDs from the level above.

Organization (org_id)
├── Networks (network_id)
│   ├── Devices (serial)
│   │   └── Device-specific settings
│   ├── SSIDs (0-14)
│   ├── VLANs
│   ├── Firewall Rules
│   └── Group Policies
├── Licenses
├── Admins
└── Config Templates (template_id)
    └── Bound Networks

Key Identifier Types

LevelIdentifierFormatExample
OrganizationorganizationIdNumeric string"549236"
NetworknetworkIdL_ or N_ + digits"L_123456789012345678"
DeviceserialAlphanumeric"Q2HP-AJ22-UG72"
SSIDnumberInteger 0–140

5 – Get Organizations, Networks, and Devices

Exam Topic: 3.2

The Drill-Down Pattern (requests)

# ── Step 1: Get Organizations ────────────────────────────────
orgs = requests.get(
    f"{BASE_URL}/organizations", headers=HEADERS
).json()
org_id = orgs[0]["id"]
 
# ── Step 2: Get Networks in the Organization ─────────────────
networks = requests.get(
    f"{BASE_URL}/organizations/{org_id}/networks", headers=HEADERS
).json()
network_id = networks[0]["id"]
 
# ── Step 3: Get Devices in the Network ───────────────────────
devices = requests.get(
    f"{BASE_URL}/networks/{network_id}/devices", headers=HEADERS
).json()
 
for device in devices:
    print(f"{device.get('name', 'unnamed'):<20} "
          f"{device['model']:<15} "
          f"{device['serial']}")

The Same Flow with SDK

# ── Step 1: Organizations ────────────────────────────────────
orgs = dashboard.organizations.getOrganizations()
org_id = orgs[0]["id"]
 
# ── Step 2: Networks ─────────────────────────────────────────
networks = dashboard.organizations.getOrganizationNetworks(
    organizationId=org_id
)
network_id = networks[0]["id"]
 
# ── Step 3: Devices ──────────────────────────────────────────
devices = dashboard.networks.getNetworkDevices(networkId=network_id)
 
for device in devices:
    print(f"{device.get('name', 'unnamed'):<20} "
          f"{device['model']:<15} "
          f"{device['serial']}")

Get a Single Device by Serial

# requests
device = requests.get(
    f"{BASE_URL}/devices/Q2HP-AJ22-UG72", headers=HEADERS
).json()
 
# SDK
device = dashboard.devices.getDevice(serial="Q2HP-AJ22-UG72")

Get Organization Device Inventory (All Devices Across Networks)

# requests – useful for inventory audits
inventory = requests.get(
    f"{BASE_URL}/organizations/{org_id}/inventoryDevices",
    headers=HEADERS
).json()
 
# SDK
inventory = dashboard.organizations.getOrganizationInventoryDevices(
    organizationId=org_id
)
 
for device in inventory:
    print(f"{device['serial']:<15} "
          f"{device['model']:<15} "
          f"{device.get('networkId', 'unassigned')}")

6 – SSID Management

Exam Topics: 3.2, 3.5

Key Concept: SSIDs Are Slots, Not Created

Meraki networks have 15 SSID slots (0–14). You don’t “create” an SSID — you configure an existing slot. A slot with enabled: false and no name is effectively unused.

Configure an SSID (requests)

NETWORK_ID = "L_123456789012345678"
SSID_NUMBER = 2  # Use slot 2
 
payload = {
    "name": "Corporate-WiFi",
    "enabled": True,
    "authMode": "8021x-radius",     # Enterprise auth
    "encryptionMode": "wpa-eap",
    "wpaEncryptionMode": "WPA2 only",
    "radiusServers": [
        {
            "host": "10.0.0.100",
            "port": 1812,
            "secret": "RadiusSecret123"
        }
    ],
    "ipAssignmentMode": "Bridge mode",
    "defaultVlanId": 100
}
 
response = requests.put(
    f"{BASE_URL}/networks/{NETWORK_ID}/wireless/ssids/{SSID_NUMBER}",
    headers=HEADERS,
    json=payload
)
response.raise_for_status()
ssid = response.json()
print(f"Configured SSID {SSID_NUMBER}: {ssid['name']}")

Configure an SSID (SDK)

ssid = dashboard.wireless.updateNetworkWirelessSsid(
    networkId=NETWORK_ID,
    number=2,
    name="Corporate-WiFi",
    enabled=True,
    authMode="8021x-radius",
    encryptionMode="wpa-eap",
    wpaEncryptionMode="WPA2 only",
    radiusServers=[{
        "host": "10.0.0.100",
        "port": 1812,
        "secret": "RadiusSecret123"
    }],
    ipAssignmentMode="Bridge mode",
    defaultVlanId=100
)

SSID Auth Modes for the Exam

authMode ValueDescriptionUse Case
"open"No authenticationGuest splash pages
"psk"Pre-shared key (WPA2-Personal)Small office
"8021x-radius"RADIUS (WPA2-Enterprise)Corporate networks
"8021x-meraki"Meraki-hosted RADIUSMeraki-managed auth

Disable an SSID

# requests — send only the fields you want to change
requests.put(
    f"{BASE_URL}/networks/{NETWORK_ID}/wireless/ssids/{SSID_NUMBER}",
    headers=HEADERS,
    json={"enabled": False}
)
 
# SDK
dashboard.wireless.updateNetworkWirelessSsid(
    networkId=NETWORK_ID, number=SSID_NUMBER, enabled=False
)

PUT, Not PATCH

Meraki SSID updates use PUT, but they behave like a partial update — you only need to send the fields you want to change. This is unusual and a potential exam trick.


7 – Day-0 Provisioning – Device Claiming

Exam Topic: 3.1 – Construct a controller-based network automation solution for Day-0 provisioning

Meraki Day-0: Claim and Go

Meraki’s Day-0 is simpler than Catalyst Center’s PnP because devices auto-connect to the cloud. The provisioning steps are:

  1. Claim the device into a network using its serial number
  2. Device powers on → connects to Meraki cloud → downloads config
  3. Optionally set device name, address, tags
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Python API  │────►│ Meraki Cloud │────►│  New Device  │
│  Claim serial│     │ Assign to    │     │  Powers on   │
│  to network  │     │ network      │     │  Gets config │
└──────────────┘     └──────────────┘     └──────────────┘
    Step 1               Step 2               Step 3
  (your code)         (automatic)          (automatic)

Claim a Device into a Network (requests)

NETWORK_ID = "L_123456789012345678"
 
# ── Claim one or more devices by serial number ───────────────
claim_payload = {
    "serials": ["Q2HP-AJ22-UG72", "Q2HP-BK33-VH83"]
}
 
response = requests.post(
    f"{BASE_URL}/networks/{NETWORK_ID}/devices/claim",
    headers=HEADERS,
    json=claim_payload
)
response.raise_for_status()
print("Devices claimed successfully")

Claim a Device (SDK)

dashboard.networks.claimNetworkDevices(
    networkId=NETWORK_ID,
    serials=["Q2HP-AJ22-UG72", "Q2HP-BK33-VH83"]
)

Set Device Details After Claiming

# ── Update device name and location after claiming ────────────
device_payload = {
    "name": "Floor-2-AP-01",
    "address": "123 Main Street, Building A",
    "tags": [" floor2 ", " ap "],
    "notes": "Provisioned via automation"
}
 
# requests
requests.put(
    f"{BASE_URL}/devices/Q2HP-AJ22-UG72",
    headers=HEADERS,
    json=device_payload
)
 
# SDK
dashboard.devices.updateDevice(
    serial="Q2HP-AJ22-UG72",
    name="Floor-2-AP-01",
    address="123 Main Street, Building A",
    tags=[" floor2 ", " ap "],
    notes="Provisioned via automation"
)

Bulk Provisioning Pattern

import csv
 
def bulk_provision_from_csv(dashboard, network_id, csv_path):
    """Claim and configure devices from a CSV inventory file."""
 
    with open(csv_path, "r") as f:
        reader = csv.DictReader(f)
        devices = list(reader)
 
    # Step 1: Claim all serials at once (more efficient)
    serials = [d["serial"] for d in devices]
    dashboard.networks.claimNetworkDevices(
        networkId=network_id, serials=serials
    )
    print(f"Claimed {len(serials)} devices")
 
    # Step 2: Configure each device individually
    for device in devices:
        dashboard.devices.updateDevice(
            serial=device["serial"],
            name=device["name"],
            address=device.get("address", ""),
            tags=device.get("tags", "").split(",")
        )
        print(f"  Configured: {device['name']} ({device['serial']})")

Example CSV (inventory.csv):

serial,name,address,tags
Q2HP-AJ22-UG72,Floor-2-AP-01,123 Main St, floor2 ap
Q2HP-BK33-VH83,Floor-3-AP-01,123 Main St, floor3 ap

Meraki Day-0 vs Catalyst Center Day-0

AspectMerakiCatalyst Center PnP
ConnectionDevice → Cloud (automatic)Device → DHCP → Controller
Claim methodSerial-based claimingPnP device + claim + project
Config pushNetwork-level settings applyTemplate-based per device
ComplexityLow — claim and goHigher — DHCP, templates, projects
Provisioning timeMinutes (cloud)Minutes to hours (on-prem)

8 – Configuration Templates and Action Batches

Exam Topics: 3.3, 3.2

Meraki’s approach to templates differs from Catalyst Center’s Jinja2 templates, but both serve the same purpose: consistent configuration at scale.

Meraki Configuration Templates

Configuration templates in Meraki are network blueprints that you create in the org and then bind to networks. Bound networks inherit the template settings.

ORG_ID = "549236"
 
# ── List existing config templates ────────────────────────────
templates = requests.get(
    f"{BASE_URL}/organizations/{ORG_ID}/configTemplates",
    headers=HEADERS
).json()
 
for t in templates:
    print(f"Template: {t['name']} (ID: {t['id']})")
    print(f"  Product types: {t['productTypes']}")
 
# SDK
templates = dashboard.organizations.getOrganizationConfigTemplates(
    organizationId=ORG_ID
)

Bind a Network to a Template

TEMPLATE_ID = "L_123456789012345678"
NETWORK_ID = "N_987654321098765432"
 
# requests
response = requests.post(
    f"{BASE_URL}/networks/{NETWORK_ID}/bind",
    headers=HEADERS,
    json={
        "configTemplateId": TEMPLATE_ID,
        "autoBind": False
    }
)
 
# SDK
dashboard.networks.bindNetwork(
    networkId=NETWORK_ID,
    configTemplateId=TEMPLATE_ID,
    autoBind=False
)

Action Batches — Bulk Operations

Action Batches = Meraki's Answer to "How do I make 100 changes efficiently?"

Instead of 100 API calls (hitting rate limits), submit one action batch with 100 actions.

ORG_ID = "549236"
 
# ── Create an action batch to update multiple SSIDs ──────────
batch_payload = {
    "confirmed": True,
    "synchronous": False,
    "actions": [
        {
            "resource": f"/networks/{NETWORK_ID}/wireless/ssids/0",
            "operation": "update",
            "body": {"name": "Branch-Corp-WiFi", "enabled": True}
        },
        {
            "resource": f"/networks/{NETWORK_ID}/wireless/ssids/1",
            "operation": "update",
            "body": {"name": "Branch-Guest-WiFi", "enabled": True}
        },
        {
            "resource": f"/networks/{NETWORK_ID}/wireless/ssids/2",
            "operation": "update",
            "body": {"enabled": False}
        }
    ]
}
 
# requests
response = requests.post(
    f"{BASE_URL}/organizations/{ORG_ID}/actionBatches",
    headers=HEADERS,
    json=batch_payload
)
batch = response.json()
print(f"Batch ID: {batch['id']}, Status: {batch['status']['completed']}")
 
# SDK
batch = dashboard.organizations.createOrganizationActionBatch(
    organizationId=ORG_ID,
    confirmed=True,
    synchronous=False,
    actions=batch_payload["actions"]
)

Check Action Batch Status

# requests
status = requests.get(
    f"{BASE_URL}/organizations/{ORG_ID}/actionBatches/{batch['id']}",
    headers=HEADERS
).json()
print(f"Completed: {status['status']['completed']}")
print(f"Failed: {status['status']['failed']}")
 
# SDK
status = dashboard.organizations.getOrganizationActionBatch(
    organizationId=ORG_ID, actionBatchId=batch["id"]
)

Action Batch Limits

  • Max 100 actions per batch
  • Max 5 concurrent batches per org
  • Each action = one API call equivalent
  • This is the recommended approach for bulk changes

9 – Network Health Monitoring

Exam Topics: 4.4, 4.6

Organization-Wide Device Statuses

ORG_ID = "549236"
 
# ── Get status of all devices in the org ──────────────────────
statuses = requests.get(
    f"{BASE_URL}/organizations/{ORG_ID}/devices/statuses",
    headers=HEADERS
).json()
 
# Count by status
from collections import Counter
status_counts = Counter(d["status"] for d in statuses)
print(f"Online:  {status_counts.get('online', 0)}")
print(f"Offline: {status_counts.get('offline', 0)}")
print(f"Alerting: {status_counts.get('alerting', 0)}")
 
# SDK
statuses = dashboard.organizations.getOrganizationDevicesStatuses(
    organizationId=ORG_ID
)

Network Client Health

NETWORK_ID = "L_123456789012345678"
 
# ── Get clients on the network ────────────────────────────────
clients = requests.get(
    f"{BASE_URL}/networks/{NETWORK_ID}/clients",
    headers=HEADERS,
    params={"timespan": 86400}  # Last 24 hours (in seconds)
).json()
 
print(f"Total clients in last 24h: {len(clients)}")
for client in clients[:5]:
    print(f"  {client.get('description', 'unknown'):<20} "
          f"{client['mac']:<18} "
          f"VLAN: {client.get('vlan', 'N/A')}")
 
# SDK
clients = dashboard.networks.getNetworkClients(
    networkId=NETWORK_ID, timespan=86400
)
# ── Get uplink status for all MX appliances ──────────────────
uplinks = requests.get(
    f"{BASE_URL}/organizations/{ORG_ID}/appliance/uplink/statuses",
    headers=HEADERS
).json()
 
for device in uplinks:
    print(f"\nDevice: {device['serial']}")
    for uplink in device.get("uplinks", []):
        print(f"  {uplink['interface']}: {uplink['status']} "
              f"(IP: {uplink.get('publicIp', 'N/A')})")
 
# SDK
uplinks = dashboard.appliance.getOrganizationApplianceUplinkStatuses(
    organizationId=ORG_ID
)

Connection Stats per AP

# ── Wireless connection stats (useful for health dashboards) ─
stats = requests.get(
    f"{BASE_URL}/networks/{NETWORK_ID}/wireless/connectionStats",
    headers=HEADERS,
    params={"timespan": 3600}  # Last hour
).json()
 
print(f"Success: {stats.get('success', 0)}")
print(f"Auth failures: {stats.get('authFailure', 0)}")
print(f"DHCP failures: {stats.get('dhcpFailure', 0)}")
print(f"DNS failures: {stats.get('dnsFailure', 0)}")

10 – Webhook-Based Monitoring

Exam Topic: 4.6 – Implement webhook-based monitoring using controllers

Instead of polling, Meraki can push alerts to your server.

How Webhooks Work

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Meraki Cloud │────►│ Your HTTPS   │────►│ Process &    │
│ Detects      │     │ Endpoint     │     │ Alert        │
│ alert event  │     │ /webhook     │     │              │
└──────────────┘     └──────────────┘     └──────────────┘

Create a Webhook HTTP Server (requests)

NETWORK_ID = "L_123456789012345678"
 
# ── Step 1: Create a webhook receiver endpoint ────────────────
webhook_payload = {
    "name": "My-Alert-Server",
    "url": "https://my-server.example.com/meraki-webhook",
    "sharedSecret": "supersecretvalue123"
}
 
server = requests.post(
    f"{BASE_URL}/networks/{NETWORK_ID}/webhooks/httpServers",
    headers=HEADERS,
    json=webhook_payload
).json()
print(f"Webhook server ID: {server['id']}")
 
# SDK
server = dashboard.networks.createNetworkWebhooksHttpServer(
    networkId=NETWORK_ID,
    name="My-Alert-Server",
    url="https://my-server.example.com/meraki-webhook",
    sharedSecret="supersecretvalue123"
)

Configure Alert Types to Trigger Webhooks

# ── Step 2: Set which alerts trigger the webhook ─────────────
alert_settings = {
    "defaultDestinations": {
        "httpServerIds": [server["id"]],
        "emails": [],
        "allAdmins": False,
        "snmp": False
    },
    "alerts": [
        {
            "type": "gatewayDown",
            "enabled": True,
            "alertDestinations": {
                "httpServerIds": [server["id"]],
                "emails": []
            },
            "filters": {}
        },
        {
            "type": "applianceConnectsMerakiCloud",
            "enabled": True,
            "alertDestinations": {
                "httpServerIds": [server["id"]],
                "emails": []
            },
            "filters": {}
        }
    ]
}
 
requests.put(
    f"{BASE_URL}/networks/{NETWORK_ID}/alerts/settings",
    headers=HEADERS,
    json=alert_settings
)

Webhook Payload Structure (What Your Server Receives)

{
  "version": "0.1",
  "sharedSecret": "supersecretvalue123",
  "sentAt": "2026-02-17T10:30:00.000000Z",
  "organizationId": "549236",
  "organizationName": "My Org",
  "organizationUrl": "https://dashboard.meraki.com/o/abc123/manage",
  "networkId": "L_123456789012345678",
  "networkName": "Branch-Office-1",
  "alertType": "Gateway down",
  "alertData": {
    "deviceSerial": "Q2HP-AJ22-UG72",
    "deviceName": "MX-Branch1"
  }
}

Webhook Security

Always validate the sharedSecret in your webhook receiver to ensure the request actually came from Meraki. This is a security best practice the exam may test under topic 3.5.


11 – Common Patterns and Gotchas

Pattern: Complete Workflow (Auth → Discover → Configure → Verify)

import meraki
import os
 
API_KEY = os.environ.get("MERAKI_DASHBOARD_API_KEY")
dashboard = meraki.DashboardAPI(api_key=API_KEY, suppress_logging=True)
 
# ── 1. Discover the hierarchy ────────────────────────────────
orgs = dashboard.organizations.getOrganizations()
org_id = orgs[0]["id"]
print(f"Org: {orgs[0]['name']}")
 
networks = dashboard.organizations.getOrganizationNetworks(
    organizationId=org_id
)
# Find a wireless network
wireless_net = next(
    (n for n in networks if "wireless" in n["productTypes"]), None
)
if not wireless_net:
    print("No wireless network found!")
    exit()
net_id = wireless_net["id"]
print(f"Network: {wireless_net['name']}")
 
# ── 2. Get current SSID config ───────────────────────────────
ssids = dashboard.wireless.getNetworkWirelessSsids(networkId=net_id)
print(f"\nCurrent SSIDs:")
for ssid in ssids:
    status = "ENABLED" if ssid["enabled"] else "disabled"
    print(f"  [{ssid['number']}] {ssid['name']:<25} {status}")
 
# ── 3. Configure SSID slot 3 ────────────────────────────────
dashboard.wireless.updateNetworkWirelessSsid(
    networkId=net_id,
    number=3,
    name="Automated-Guest",
    enabled=True,
    authMode="open",
    splashPage="Click-through splash page"
)
print(f"\nConfigured SSID 3 as 'Automated-Guest'")
 
# ── 4. Verify ────────────────────────────────────────────────
updated = dashboard.wireless.getNetworkWirelessSsid(
    networkId=net_id, number=3
)
print(f"Verified: {updated['name']} (enabled={updated['enabled']})")

Gotchas for the Exam

Top Meraki Exam Pitfalls

1. SSIDs are slots (0–14), not created dynamically You PUT to configure a slot. There’s no POST to create an SSID.

2. SSID config uses PUT, not POST PUT /networks/{id}/wireless/ssids/{number} — the exam loves testing HTTP methods.

3. Rate limit = 5 calls/sec per organization Not per user, not per network — per organization. Multiple scripts hitting the same org stack up.

4. Pagination uses Link headers, not offset/limit Unlike Catalyst Center, Meraki pagination follows RFC 5988. The SDK handles this automatically.

5. Device identifier = serial number, not UUID Catalyst Center uses UUIDs. Meraki uses serial numbers like Q2HP-AJ22-UG72.

6. API base URL is always api.meraki.com Never changes. No on-prem option. If a question mentions “your Meraki server”, it’s a trap.

7. All Meraki write operations are synchronous Unlike Catalyst Center, there’s no task polling. When the response comes back, the change is applied (or failed).


12 – Exam-Style Scenarios

Scenario 1: Rate Limiting (Topic 3.6)

Your script loops through 500 networks to check SSID settings. After ~50 iterations, you get 429 Too Many Requests. What should you do?

for network in all_networks:
    ssids = requests.get(
        f"{BASE_URL}/networks/{network['id']}/wireless/ssids",
        headers=HEADERS
    ).json()

Scenario 2: Wrong HTTP Method for SSID (Topic 3.6)

You try to create a new SSID and get 405 Method Not Allowed. What's wrong?

response = requests.post(
    f"{BASE_URL}/networks/{net_id}/wireless/ssids",
    headers=HEADERS,
    json={"name": "NewSSID", "enabled": True}
)

Scenario 3: Missing Network ID (Topic 3.2)

What's the correct order of API calls to configure a device you just unboxed?


Scenario 4: Webhook Troubleshooting (Topic 4.6)

You've configured a webhook but your server never receives alerts. The webhook test from the Dashboard succeeds. What should you check?


Scenario 5: SDK Constructor (Topic 3.2)

Fill in the blanks to initialize the Meraki SDK with rate limit handling:

import meraki
dashboard = meraki._______(
    api_key=API_KEY,
    _______=True,
    maximum_retries=3
)

Scenario 6: Day-0 Claiming (Topic 3.1)

You need to add 3 new APs to the "Branch-5" network. Which single API call claims all three?


13 – Quick Reference Cheat Sheet

Authentication

Every request needs:
  Header: X-Cisco-Meraki-API-Key: <your_api_key>
  Base URL: https://api.meraki.com/api/v1
  No token exchange needed.

CRUD Operations Map

OperationMethodEndpointNotes
List orgsGET/organizationsTop of hierarchy
List networksGET/organizations/{orgId}/networksFilter by productTypes
List devicesGET/networks/{netId}/devicesIn a specific network
Get deviceGET/devices/{serial}By serial number
Claim devicePOST/networks/{netId}/devices/claimDay-0 provisioning
Update devicePUT/devices/{serial}Name, tags, address
List SSIDsGET/networks/{netId}/wireless/ssidsReturns all 15 slots
Update SSIDPUT/networks/{netId}/wireless/ssids/{num}Slot 0-14
Device statusesGET/organizations/{orgId}/devices/statusesHealth monitoring
ClientsGET/networks/{netId}/clientsRequires timespan
Action batchesPOST/organizations/{orgId}/actionBatchesBulk operations
WebhooksPOST/networks/{netId}/webhooks/httpServersAlert endpoint

SDK Quick Reference

import meraki
dashboard = meraki.DashboardAPI(api_key=KEY, suppress_logging=True)
 
# Hierarchy navigation
dashboard.organizations.getOrganizations()
dashboard.organizations.getOrganizationNetworks(organizationId=oid)
dashboard.networks.getNetworkDevices(networkId=nid)
dashboard.devices.getDevice(serial="Q2HP-...")
 
# Day-0
dashboard.networks.claimNetworkDevices(networkId=nid, serials=[...])
dashboard.devices.updateDevice(serial="Q2HP-...", name="AP-01")
 
# SSIDs
dashboard.wireless.getNetworkWirelessSsids(networkId=nid)
dashboard.wireless.updateNetworkWirelessSsid(networkId=nid, number=0, ...)
 
# Monitoring
dashboard.organizations.getOrganizationDevicesStatuses(organizationId=oid)
dashboard.networks.getNetworkClients(networkId=nid, timespan=86400)
 
# Bulk
dashboard.organizations.createOrganizationActionBatch(organizationId=oid, ...)

Compare: Meraki vs Catalyst Center at a Glance

ConceptMerakiCatalyst Center
AuthAPI Key header (every request)Basic → Token → X-Auth-Token
Device IDSerial numberUUID
Write opsSynchronous (immediate)Async (poll taskId)
Rate limit5/sec per org (429 + Retry-After)Token-based throttling
Day-0Claim serial → auto-configPnP: DHCP → discover → template
TemplatesOrg config templates (bind/unbind)Jinja2 template programmer
HealthGET device statuses + client statsSite/client health endpoints
WebhooksNative support (HTTP servers)Event notifications API
PaginationLink header (RFC 5988)offset + limit (starts at 1)
Base URLapi.meraki.com (always)Your controller IP/hostname

Further Study


enauto ccnp meraki python api controller-based day0 webhooks

See Also