my first API

SOLVED
Adrian4
A model citizen

my first API

Hello,

 

I am trying to learn APIs for the first time whilst also trying to get a specific job done and need a little guidance.

We have some WiFi monitoring and reporting software that can see all the BSSID's flying around, but at the moment it cant give us any clear info about them. In order to do that I need to feed it an xml that will tell it which BSSIDs belong to which AP's (and the associated SSID and the band).

We have hundreds of AP's so going through the meraki portal and copying this data manually is painful - I'm hoping I can pull this data with a GET request?

So far I have setup postman with a new environment and set a API key and baseURL variables.

At this point I wanted to turn to some pre-made examples from here but its v0 not v1
https://documenter.getpostman.com/view/7928889/SVmsVg6K#9019f8d0-a1db-4eb1-b45d-c348dc800e32

 

will these examples be unusable in v1? If so is there a v1 set of basic API's like this somewhere? In any case I coudlnt find something that provided the data fields I'm after 😞


Also - we have lots of different networks and Id need this info for each network - does that complicate things? IE can you do a API call on all networks or is it per network etc?

Thanks!

1 ACCEPTED SOLUTION
sungod
Head in the Cloud

Quick and dirty example.

 

I just tested it on an org with over 12000 active AP-BSSIDs (took 2-3 minutes), it works ok.

 

You'll need Python 3 and the Meraki Python library installed.

 

Most of the code is setting things up and error detection, the action all happens in just a few lines.

 

If you want to add extra output fields, just edit the print statement.

 

import os
import sys


import meraki.aio
import asyncio


#import the org id and api key from the environment
#or you could hard code them, but that's less desirable
ORG_ID = os.environ.get("PARA0")
API_KEY = os.environ.get("PARA1")


async def processAp(aiomeraki: meraki.aio.AsyncDashboardAPI, ap):

    try:
        # get list of statuses for an AP
        statuses = await aiomeraki.wireless.getDeviceWirelessStatus(ap['serial'])
    except meraki.AsyncAPIError as e:
        print(f'Meraki API error: {e}', file=sys.stderr)
        sys.exit(0)
    except Exception as e:
        print(f'some other error: {e}', file=sys.stderr)
        sys.exit(0)
    
    for bss in statuses['basicServiceSets']:
        if bss['enabled']:
            print(f"{ap['name']},{bss['ssidName']},{bss['bssid']},{bss['band']}")
        
    return

async def main():
    async with meraki.aio.AsyncDashboardAPI(
        api_key=API_KEY,
        base_url='https://api.meraki.com/api/v1/',
        print_console=False,
        output_log=False,
        suppress_logging=True,
        wait_on_rate_limit=True,
        maximum_retries=100
    ) as aiomeraki:

        #get the wireless devices
        try:
            aps = await aiomeraki.organizations.getOrganizationDevices(ORG_ID, perPage=1000, total_pages="all", productTypes = ["wireless"])
        except meraki.AsyncAPIError as e:
            print(f'Meraki API error: {e}', file=sys.stderr)
            sys.exit(0)
        except Exception as e:
            print(f'some other error: {e}', file=sys.stderr)
            sys.exit(0)

        # process devices concurrently
        apTasks = [processAp(aiomeraki, ap) for ap in aps]
        for task in asyncio.as_completed(apTasks):
            await task

if __name__ == '__main__':
    asyncio.run(main())


 

View solution in original post

29 REPLIES 29
Adrian4
A model citizen

ok cool, just realised that this page will actually run the API for you as well! 😛

It runs ok but I have to specify the serial number of a specific AP. Is there a way to to run it against all AP's in all networks?

found this -  /organizations/{organizationId}/deviceStatuses

now just to figure out where to get teh org id from? I found old posts about a v0 api call but the actual post with an explanation isnt up anymore 😘

aah but it doesn't show what i need 😞 

need a "connection stats" call for organisation - which I cant find atm

sungod
Head in the Cloud

I use https://developer.cisco.com/meraki/api-v1/#!get-network-wireless-connection-stats

 

Assuming you want details at SSID level, You need to call it on each SSID on each network (that has at least one AP) in the organization.

 

To get the list of networks... https://developer.cisco.com/meraki/api-v1/#!get-organization-networks

 

To see if a network has any APs, check the devices... https://developer.cisco.com/meraki/api-v1/#!get-network-devices

 

To find the SSIDs in a network... https://developer.cisco.com/meraki/api-v1/#!get-network-wireless-ssids

 

 

AxL1971
Building a reputation

I have written plenty of custom applications using the API and what I found to be very useful is to get all your networks and devices (switches, access points) into a back end SQL database. As these do not change very often no point in running APi calls to get this info to pass to another API

 

I then grab the device info and network ID from database and then run the needed API to do what ever I need to do

 

Again for monitoring and reporting, I write to a back end SQL database and use PowerBI to create fancy graphs

 

Example of what I have written

 

AP channel utilisation. An alert sent out when utilisation is high. A PowerBI report to show AP utilisation over time

 

AP client connections to diffrent SSID's, or Access Point, again written to back end SQL database and PowerBI reports to see usage over period of time can be grouped into network ID, SSID . An alert sent out if client counts is high so can be proactive as end users complain about performance.

 

I think an SQL database is a bit much for me at the moment - Id have to install and run it from my laptop (getting it installed on a full blown SQL server might be difficult, esp as we are in teh process of getting rid of as many servers as possible).

Passing info from one API call to another seems ok - is it difficult to do?

I have about 47 networks each with anything from 5 to 100 AP devices : / - I have managed to do an API call that returns the serial numbers of every AP device in all networks - so presumably I just need to pass all of these serial numbers to a 

/devices/{serial}/wireless/status 

 

?

 

thanks!

AxL1971
Building a reputation

yes that is correct, what you could use instead of SQL is a CSV text file to hold the devices you have and with the API call, read the CSV file and pass the device details to the API

 

so for example if you want to run API on a switch, only pass the device type that is a switch (see example below of a CSV file format)

 

<device name>,<device serial number>,<device type>,<device network ID>,<device  IP Address>

 

so...I have a json (that I could convert to csv) with the following (iv removed some details for posting here);

{
"mac": "mac ",
"serial": "serial number",
"name": "GD - AP - 2",
"model": "MR33",
"networkId": "networkid",
"orderNumber": "ordernumber",
"claimedAt": "2018-07-30T10:30:08.789690Z",
"tags": [ "recently-added" ],
"productType": "wireless"
},

the file contains hundreds of these - I would need to pull out just the serial numbers into (I guess) a query parameter array - not sure how thats done

sungod
Head in the Cloud

I recommend install Python, version 3.11, then add the Meraki Python library (which handles a lot of low-level stuff for you).

 

https://www.python.org/downloads/release/python-3110/

 

https://developer.cisco.com/meraki/api-v1/#!python

 

 

It makes working with the API simpler once you get the hang of things.

 

There are many online sources on learning/using Python. If you get stuck, search with google for what you want to do, i.e. "python array", or "python dictionary" - calls typically return dictionaries, which may have arrays of dictionaries inside them.

 

Loads of examples here...

https://developer.cisco.com/codeexchange/platforms/meraki#search=meraki&lang=Python

 

AxL1971
Building a reputation

yes convert the contents of the json out into a csv file

 

once you have the data in CSV file, you loop thru the file and pass the serial number as a parameter tto the API call.

 

is that something you have to do in python?

AxL1971
Building a reputation

you can write code in Python or any other language

 

 

Adrian4
A model citizen

I think I might be chasing down the wrong route here with the CSV. I think I should clarify my end-goal but not sure if it would be better as  new post or keep going here do you think?

AxL1971
Building a reputation

what is your end goal ?

takes a breath...

ok, so I need a CSV file that contains data for each and every access point in our organisation.
For each AP I need a list of that AP's name and every enabled BSSID with the BSSIDs band and SSID.


I can see I can get the BSSID/band/SSID info from /devices/serialnumber/wireless/status


{
"basicServiceSets": [
{
"ssidName": "Unconfigured SSID 1",
"ssidNumber": 0,
"enabled": false,
"band": "2.4 GHz",
"bssid": "00:00:00:00:00:00",
"channel": 6,
"channelWidth": "20 MHz",
"power": "16 dBm",
"visible": true,
"broadcasting": false
},
{
"ssidName": "Unconfigured SSID 1",
"ssidNumber": 0,
"enabled": false,
"band": "5 GHz",
"bssid": "00:00:00:00:00:00",
"channel": 44,
"channelWidth": "80 MHz",
"power": "19 dBm",
"visible": true,
"broadcasting": false
},

 

 

however you have to feed the API the AP serial number. I assume if I tried to feed it all the serial numbers in one API call, the return would just give me all the "basicServiceSets" without letting me know which belongs to which AP.


I can get the AP serial numbers and names from

/organizations/orgID/devices/statuses?productTypes[]=wireless

{
"name": "GD - AP - 2",
"serial": "0000-0000-0000",
"mac": "00:00:00:00:00:00",
"publicIp": "0.0.0.0",
"networkId": "N_0000000000000000",
"status": "online",
"lastReportedAt": "2022-12-07T11:21:44.044000Z",
"productType": "wireless",
"model": "MR33",
"tags": [ "recently-added" ],
"lanIp": "0.0.0.0",
"gateway": "0.0.0.0",
"ipType": "dhcp",
"primaryDns": "0.0.0.0",
"secondaryDns": "0.0.0.0"
},

 

I am struggling to imagine the necessary workflow. The serial number and name come from one API and all the other info I need come from another API, but the field that links the two API is the serial number which I don't actually need but its the only thing that ties the AP name to the other bits of info : /

AxL1971
Building a reputation

The workflow would be as follows 

 

The first API calls would be to get all the AP's serial number and save these details to a text file, so example of your text file could be

 

xxxxxx

yyyyyy

zzzzzz

aaaaa

bbbbb

cccccc

 

So call /organizations/orgID/devices/statuses?productTypes[]=wireless

 

 

 

once you have got this the next API call would be passing the serial number of the AP to the API call

 

You would read the text file one line at a time and once you have the data in json format, process this to extract the necessary details, write it to a text file and move onto the next serial number in the text file .

 

You are basically looping thru the text file, reading the serial number calling the API to get the data you need

 

Hope this makes sense

thanks,

so, would I be doing a single API call per serial number?
Since the data returned from /devices/serialnumber/wireless/status doesn't actually include the serial number or AP name, how would I link the API return data to the serial number that was used in the call?

AxL1971
Building a reputation


@Adrian4 wrote:

thanks,

so, would I be doing a single API call per serial number?
Since the data returned from /devices/serialnumber/wireless/status doesn't actually include the serial number or AP name, how would I link the API return data to the serial number that was used in the call?




once you export the data from the 2nd API call, where will this data be stored/saved ?

 

As I stated earlier, I write to a back end SQL database and this is how I would link the AP name or serial number to the data extracted. For your case you could write it to a text file with the AP serial number or name the header of the text file.

sungod
Head in the Cloud

Quick and dirty example.

 

I just tested it on an org with over 12000 active AP-BSSIDs (took 2-3 minutes), it works ok.

 

You'll need Python 3 and the Meraki Python library installed.

 

Most of the code is setting things up and error detection, the action all happens in just a few lines.

 

If you want to add extra output fields, just edit the print statement.

 

import os
import sys


import meraki.aio
import asyncio


#import the org id and api key from the environment
#or you could hard code them, but that's less desirable
ORG_ID = os.environ.get("PARA0")
API_KEY = os.environ.get("PARA1")


async def processAp(aiomeraki: meraki.aio.AsyncDashboardAPI, ap):

    try:
        # get list of statuses for an AP
        statuses = await aiomeraki.wireless.getDeviceWirelessStatus(ap['serial'])
    except meraki.AsyncAPIError as e:
        print(f'Meraki API error: {e}', file=sys.stderr)
        sys.exit(0)
    except Exception as e:
        print(f'some other error: {e}', file=sys.stderr)
        sys.exit(0)
    
    for bss in statuses['basicServiceSets']:
        if bss['enabled']:
            print(f"{ap['name']},{bss['ssidName']},{bss['bssid']},{bss['band']}")
        
    return

async def main():
    async with meraki.aio.AsyncDashboardAPI(
        api_key=API_KEY,
        base_url='https://api.meraki.com/api/v1/',
        print_console=False,
        output_log=False,
        suppress_logging=True,
        wait_on_rate_limit=True,
        maximum_retries=100
    ) as aiomeraki:

        #get the wireless devices
        try:
            aps = await aiomeraki.organizations.getOrganizationDevices(ORG_ID, perPage=1000, total_pages="all", productTypes = ["wireless"])
        except meraki.AsyncAPIError as e:
            print(f'Meraki API error: {e}', file=sys.stderr)
            sys.exit(0)
        except Exception as e:
            print(f'some other error: {e}', file=sys.stderr)
            sys.exit(0)

        # process devices concurrently
        apTasks = [processAp(aiomeraki, ap) for ap in aps]
        for task in asyncio.as_completed(apTasks):
            await task

if __name__ == '__main__':
    asyncio.run(main())


 

Adrian4
A model citizen

thank you so much! this looks great. I cant test it at the moment as I'm having trouble installing the meraki module, but thats an entirely different problem.

 

Thanks again!

Adrian4
A model citizen

sorry, just one more follow up question....

it looks like you have put your key and org id in a module called "os"?

Where do you put modules you make like this? I am trying to use PyCharm but I'm a bit lost with the interface and where things should go, esp if they are modules I want to call in scripts.


sungod
Head in the Cloud

The 'os' module is part of the base Python install, it does a bunch of things, in this case I'm using it to get the value of runtime environment variables.

 

I'm running on Unix-like systems, so I pull in environment variables from the shell, PARA0 and PARA1. Of course you can choose your own names, there is nothing special about these names, they just need to match the names you use in the environment.

 

In the shell that runs the script, I set these two environment variable like this...

 

PARA0="the org ID value"

PARA1="the API key value"

 

...then the script can pull in the values by variable name.

 

If you are on windows, you can set environment variables with the same names and it should work just the same, the way you set them depends where you are running the scripts from, there are some examples here of using Windows variables with scripts with the 'os' module...

 

https://www.twilio.com/blog/environment-variables-python

 

 

In general, if you install modules using pip, I think they should go in the right place automatically, for instance...

 

pip install meraki

 

...or to update the version...

 

pip install --upgrade meraki

 

...but I don't use Python on Windows so maybe there are some other/better ways!

macwilson
Conversationalist

Hey sungod, this was very helpful, thank you! Did you build this script from scratch or did you use documentation to assist?

sungod
Head in the Cloud

It's mostly from adapting from another script I wrote, the basics of set-up, iterating by something, error handling etc. are common across scripts, so whenever I do something new I tend to see which old script I have that is closest in structure to what I need and then adapt it, and I'm lazy 😀

 

This one is using async I/O as it is generally more efficient, but it does affect readability.

 

If you look on the github site, there are a few examples you can use as starting points to experiment...

 

https://github.com/meraki/dashboard-api-python/tree/main/examples

 

macwilson
Conversationalist

I apreciate you sharing that. Thanks again!

PS- I used ChatGPT. I too start of my base script of calling up all my networks, and go from there. 

Jamieinbox
Getting noticed

I have just a bit of code here:  jadexing/Merakicode: Code for Meraki Wi-Fi (github.com)
All mine is Wi-Fi based. 
I open Powershell, go to the script directory and execute.

Get notified when there are additional replies to this discussion.