This guide puts the Meraki Dashboard API calls I use for branch rollouts in one place: create or clone a network, fix the VLAN subnet the clone brings with it, and update MX Layer 3 firewall rules without wiping the existing ones. Every script below is a template. The IDs, API key and subnets are placeholders, so run it against a test network first and check the result in the Dashboard before you point it at production.
Before you start
- API key: generate it in the Dashboard under your profile (My profile, API access). The key needs write access to the organization. Keep it out of the script: export it as an environment variable,
MERAKI_API_KEY. - Organization ID and Network ID: see how to find your Meraki Organization ID and Network ID.
- Python: either
requestsfor raw calls or the official SDK (pip install meraki). The SDK retries on HTTP 429 for you. - Rate limit: Meraki allows roughly 10 calls per second per organization. A script that creates a network, updates a VLAN and rewrites firewall rules back to back can hit it.
1. Plan the IP schema first
Cloning a network copies its subnets. If two sites in an Auto VPN topology advertise the same subnet, routing breaks, so work out each branch’s address block before you create anything. A simple scheme is one /23 per branch, derived from a branch number:
| Branch | Subnet | Gateway |
| 1 | 10.200.0.0/23 | 10.200.0.1 |
| 2 | 10.200.2.0/23 | 10.200.2.1 |
| 3 | 10.200.4.0/23 | 10.200.4.1 |
A /23 gives 510 usable addresses, enough for data, voice, guest and IoT in one block for a typical branch. Doing the math in the script means two branches can never get the same subnet.
def calculate_branch_subnet(branch_id):
third_octet = (branch_id - 1) * 2
subnet = f"10.200.{third_octet}.0/23"
appliance_ip = f"10.200.{third_octet}.1"
return subnet, appliance_ip2. Create or clone a network
Creating a network gives you a blank network with default settings. Cloning, with copyFromNetworkId, copies the configuration of an existing “golden” network: VLANs, SSIDs, firewall rules. Both use the same endpoint:
POST /organizations/{organizationId}/networks
Supported product types are appliance (MX), switch (MS), wireless (MR), camera (MV), sensor (MT) and cellularGateway (MG). productTypes must be a list, not a string.
The JSON payload
{
"name": "New_Retail_Branch_05",
"timeZone": "America/New_York",
"productTypes": ["appliance", "switch", "wireless"],
"copyFromNetworkId": "N_1234567890123456"
}Python with requests
import os
import requests
api_key = os.environ["MERAKI_API_KEY"]
org_id = "YOUR_ORGANIZATION_ID"
url = f"https://api.meraki.com/api/v1/organizations/{org_id}/networks"
headers = {
"X-Cisco-Meraki-API-Key": api_key,
"Content-Type": "application/json",
"Accept": "application/json",
}
payload = {
"name": "New_Retail_Branch_05",
"timeZone": "America/New_York",
"productTypes": ["appliance", "switch", "wireless"],
"copyFromNetworkId": "N_1234567890123456",
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 201:
print("Network created:", response.json()["id"])
else:
print("Failed:", response.status_code, response.text)Python with the meraki SDK
import os
import meraki
dashboard = meraki.DashboardAPI(os.environ["MERAKI_API_KEY"], suppress_logging=True)
try:
new_network = dashboard.organizations.createOrganizationNetwork(
organizationId="YOUR_ORGANIZATION_ID",
name="New_Retail_Branch_05",
productTypes=["appliance", "switch", "wireless"],
timeZone="America/New_York",
copyFromNetworkId="N_1234567890123456",
)
print("New Network ID:", new_network["id"])
except meraki.APIError as e:
print(f"Meraki API Error: {e}")3. Fix the VLAN subnet after cloning
This is the step people miss. The clone is an exact copy, including the source site’s subnet, DHCP scope and fixed IP assignments. Update the VLAN straight away with the subnet you calculated, and clear the cloned static assignments so they do not conflict with the new range:
target_subnet, gateway_ip = calculate_branch_subnet(2)
dashboard.appliance.updateNetworkApplianceVlan(
new_network["id"],
vlanId="1",
subnet=target_subnet,
applianceIp=gateway_ip,
fixedIpAssignments={},
reservedIpRanges=[],
)If this call fails with a 400, read the error text. Meraki’s messages are specific, for example that the appliance IP must be inside the subnet.
Same VLAN update with raw requests
Without the SDK the call is the same. Send only the fields you are changing. Replacing subnet without clearing the old fixed IP assignments and reserved ranges is the usual cause of an HTTP 400, because every address in the payload has to fit inside the new subnet.
import os
import json
import requests
API_KEY = os.environ["MERAKI_API_KEY"]
NETWORK_ID = "YOUR_NETWORK_ID"
VLAN_ID = "10"
url = f"https://api.meraki.com/api/v1/networks/{NETWORK_ID}/appliance/vlans/{VLAN_ID}"
headers = {"X-Cisco-Meraki-API-Key": API_KEY, "Content-Type": "application/json", "Accept": "application/json"}
# Look at the current VLAN first
current = requests.get(url, headers=headers)
current.raise_for_status()
print(json.dumps(current.json(), indent=2))
# Change the subnet and clear the cloned fixed IPs
payload = {
"subnet": "10.200.4.0/24",
"applianceIp": "10.200.4.1",
"fixedIpAssignments": {},
"reservedIpRanges": [],
}
response = requests.put(url, headers=headers, json=payload)
print(response.status_code)
print(response.text)A 404 means the VLAN ID does not exist on that network, so create it first with a POST and then update it. To check that a new subnet and gateway fit together, use the free Subnet Calculator.
Same VLAN update with raw requests
Without the SDK the call is the same. Send only the fields you are changing. Replacing subnet without clearing the old fixed IP assignments and reserved ranges is the usual cause of an HTTP 400, because every address in the payload has to fit inside the new subnet.
import os
import json
import requests
API_KEY = os.environ["MERAKI_API_KEY"]
NETWORK_ID = "YOUR_NETWORK_ID"
VLAN_ID = "10"
url = f"https://api.meraki.com/api/v1/networks/{NETWORK_ID}/appliance/vlans/{VLAN_ID}"
headers = {"X-Cisco-Meraki-API-Key": API_KEY, "Content-Type": "application/json", "Accept": "application/json"}
# Look at the current VLAN first
current = requests.get(url, headers=headers)
current.raise_for_status()
print(json.dumps(current.json(), indent=2))
# Change the subnet and clear the cloned fixed IPs
payload = {
"subnet": "10.200.4.0/24",
"applianceIp": "10.200.4.1",
"fixedIpAssignments": {},
"reservedIpRanges": [],
}
response = requests.put(url, headers=headers, json=payload)
print(response.status_code)
print(response.text)A 404 means the VLAN ID does not exist on that network, so create it first with a POST and then update it. To check that a new subnet and gateway fit together, use the free Subnet Calculator.
4. Update MX Layer 3 firewall rules without deleting the rest
The endpoint is PUT /networks/{networkId}/appliance/firewall/l3FirewallRules. There is no PATCH for adding one rule, so PUT replaces the whole list. Send one rule to a firewall that has 50 and the 50 are gone. The safe order is always: GET the current rules, change the one you need in Python, PUT the full list back.
import os
import requests
API_KEY = os.environ["MERAKI_API_KEY"]
NETWORK_ID = "L_1234567890123456"
URL = f"https://api.meraki.com/api/v1/networks/{NETWORK_ID}/appliance/firewall/l3FirewallRules"
headers = {
"X-Cisco-Meraki-API-Key": API_KEY,
"Content-Type": "application/json",
"Accept": "application/json",
}
def update_firewall_rules():
# 1. Get the existing rules
response = requests.get(URL, headers=headers)
response.raise_for_status()
rules = response.json().get("rules", [])
# 2. Change one rule locally, matched by its comment
new_source_cidr = "10.50.10.0/24"
for rule in rules:
if rule.get("comment") == "Allow Printers to Internal":
rule["srcCidr"] = new_source_cidr
break
else:
print("Target rule not found, nothing changed.")
return
# 3. Put the complete list back
put_response = requests.put(URL, headers=headers, json={"rules": rules})
put_response.raise_for_status()
print("Rules updated, HTTP", put_response.status_code)
if __name__ == "__main__":
update_firewall_rules()A rule is an object with comment, policy, protocol, srcCidr, srcPort, destCidr and destPort. The change applies in the background like clicking Save in the Dashboard. The SDK equivalent is dashboard.appliance.updateNetworkApplianceFirewallL3FirewallRules().
If a rule you pushed opens a port to the outside, confirm it is reachable with our free Port Checker, not only accepted by the API.
5. Putting it together
A branch rollout is the four steps in order, with a short pause between them if you are running many sites:
- Calculate the subnet and gateway from the branch number.
- Create the network by cloning the golden template.
- Update the VLAN subnet and clear the cloned fixed IP assignments.
- Update the firewall rules so the new subnet is allowed or restricted the way the policy requires.
Wrap SDK calls in try/except meraki.APIError and print the error. If a call keeps returning 400, paste the payload your script generates into Postman. That tells you quickly whether the problem is the Python or the Meraki network.
Troubleshooting
| Error | Usual cause |
| 400 Bad Request | Malformed payload. productTypes and rules must be lists. After a clone, stale fixed IP assignments also cause 400 on a VLAN update. |
| 401 Unauthorized | API key missing, wrong, or without write access to the organization or network. |
| 404 Not Found | Wrong Organization ID or Network ID in the URL. |
| 429 Too Many Requests | Rate limit. Use the SDK, or add a short time.sleep() between calls. |
FAQ
What is the difference between creating and cloning a Meraki network?
Creating gives a blank network with default settings. Cloning with copyFromNetworkId copies the source network’s configuration, including VLAN subnets, so you must update the subnets afterwards.
Why did my API call delete all my other firewall rules?
The Layer 3 firewall endpoint uses PUT, which overwrites the whole list. Always GET first, edit the list, and PUT all of it back.
Does changing firewall rules by API cause downtime?
The change applies in the background, the same as saving in the Dashboard. Still test on a non-production network first.
Postman or Python?
Postman is good for exploring endpoints and testing a single payload. Python is for looping over sites and chaining calls.
Related guides: Python NETCONF with ncclient on Cisco IOS-XE
๐ง Free Network Tools
Handy while you're troubleshooting โ no signup needed.
