API not retrieving all networks

Solved
Deadman
Here to help

API not retrieving all networks

Hello all! Hope you are doing well. 

 

I have been trying to retrieve my networking information from Postman, but I only get two networks out of the four when I run the GET. I'm able to access all the networks on the Meraki dashboard, but having no luck with APIs. I have created different API keys, but no luck. Any advice?

1 Accepted Solution
RaphaelL
Kind of a big deal
Kind of a big deal

Then the queries would be simply : 

 

 https://api.meraki.com/api/v1/organizations/   ( with a valid API key )

https://api.meraki.com/api/v1/organizations/{organizationId}/networks ( with a valid Org ID obtained from the previous call ) 

 

Simply log into any Org that you have a API key in your browser and paste the urls in the url bar :

RaphaelL_0-1644444873258.png

This is how I test my calls before scripting.

View solution in original post

14 Replies 14
RaphaelL
Kind of a big deal
Kind of a big deal

Could you precise what API call you using ?

Deadman
Here to help

GET  {{baseUrl}}/organizations/

 

I got the organization ID from the Meraki Dashboard, but when I try to add it to the URL I get a 401.

 

{
"errors": [
"Invalid API key"
]
}
RaphaelL
Kind of a big deal
Kind of a big deal

So you are trying to Get https://api.meraki.com/api/v1/organizations/  which should return you a list of Orgs that you have access. Then you will be able to retrieve the ''id'' ( which is the OrgID ) and try https://api.meraki.com/api/v1/organizations/{organizationId}/networks  with the correct ID. 

 

My favorite way to test these API calls , is by using Chrome and logging to the desired Org and then typing the API url in the url bar.

Deadman
Here to help

Thanks for the advice. I am unsure how to test API calls from Chrome. Can you please provide a site that gives instructions? 

And I have followed the correct steps to get the Organizations back. I noticed that the n# is different for the Organizations I am trying to access. I wonder if that is where the problem is. 

Deadman
Here to help

No, I am not using v0. I am using v1. The GET request works for the networks that are on the same shard, but not the other two that are on different shards. 

RaphaelL
Kind of a big deal
Kind of a big deal

All networks from the same Org are on the same shard. Do not mix shards ID and Orgs. 

 

That is why it is recommended to use the mega proxy api.meraki.com instead of the shard. If you really want to use the shard , you have to make sure that it is the right one.

Deadman
Here to help

I am using the mega proxy api.meraki.com. 

 

I am only seeing the difference in the shard from the dashboard, so I wasn't sure if that was causing the issue. I have never used the shard in the API call. 

RaphaelL
Kind of a big deal
Kind of a big deal

Then the queries would be simply : 

 

 https://api.meraki.com/api/v1/organizations/   ( with a valid API key )

https://api.meraki.com/api/v1/organizations/{organizationId}/networks ( with a valid Org ID obtained from the previous call ) 

 

Simply log into any Org that you have a API key in your browser and paste the urls in the url bar :

RaphaelL_0-1644444873258.png

This is how I test my calls before scripting.

Deadman
Here to help

Wow! That was awesome. Thanks for sharing that. I got the results and the Organization was present. However, when I add the organizationId( or the organizationId/networks) I get a 404 error. 

 

I tested the method with the other networks and only one network returned with 200.

RaphaelL
Kind of a big deal
Kind of a big deal

Has the Org API settings been enabled ?  :

 

RaphaelL_0-1644446939015.png

 

Deadman
Here to help

Yes, it has. 

RaphaelL
Kind of a big deal
Kind of a big deal

 However, when I add the organizationId( or the organizationId/networks) I get a 404 error. 

 

I tested the method with the other networks and only one network returned with 200.

 

I'm not sure I'm following you. The call https://api.meraki.com/api/v1/organizations/{organizationId}/networks    It needs a OrgID not a NetworkID. You should be able to list all the networks from that given Org. If it still fails for the same Org , I would open a ticket.

Deadman
Here to help

It is working! Thank you!

 

IronBones
Getting noticed

Please direct me to the correct Group if this is incorrect.

After searching, this thread is the closest I have found to my issue. 
I am using the Automation-scripts-Master Python Script for retrieving an inventory of each of my organizations networks but it has started to miss entire organizations. My API key is valid and I can pull the orgs missed if I use py inventorycvs.py -k <myapikey> -o "name" -f c:\temp\listofitems.csv
I get a list of the Org/networks/items but it is missing orgs. 

import sys, getopt, requests, time, datetime, os, smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from urllib.parse import urlencode
from requests import Session, utils

class NoRebuildAuthSession(Session):
    def rebuild_auth(self, prepared_request, response):
        """Prevent auth header stripping on redirect."""

API_MAX_RETRIES             = 3
API_CONNECT_TIMEOUT         = 60
API_TRANSMIT_TIMEOUT        = 60
API_STATUS_RATE_LIMIT       = 429
API_RETRY_DEFAULT_WAIT      = 3

FLAG_REQUEST_VERBOSE        = True

API_BASE_URL                = "https://api.meraki.com/api/v1"
API_KEY_ENV_VAR_NAME        = "MERAKI_DASHBOARD_API_KEY"

def send_email(smtp_server, port, sender_email, sender_password, recipient_email, subject, body, attachment_path):
    try:
        msg = MIMEMultipart()
        msg['From'] = sender_email
        msg['To'] = recipient_email
        msg['Subject'] = subject

        msg.attach(MIMEText(body, 'plain'))

        if attachment_path:
            with open(attachment_path, "rb") as attachment:
                part = MIMEBase('application', 'octet-stream')
                part.set_payload(attachment.read())
            encoders.encode_base64(part)
            part.add_header(
                "Content-Disposition",
                f"attachment; filename={attachment_path.split('/')[-1]}",
            )
            msg.attach(part)

        server = smtplib.SMTP(smtp_server, port)
        server.starttls()
        server.login(sender_email, sender_password)
        server.send_message(msg)
        server.quit()

        print(f"Email sent successfully to {recipient_email}")
    except Exception as e:
        print(f"Failed to send email: {e}")

def merakiRequest(p_apiKey, p_httpVerb, p_endpoint, p_additionalHeaders=None, p_queryItems=None, 
        p_requestBody=None, p_verbose=False, p_retry=0):
    if p_retry > API_MAX_RETRIES:
        if(p_verbose):
            print("ERROR: Reached max retries")
        return False, None, None, None
        
    bearerString = "Bearer " + str(p_apiKey)
    headers = {"Authorization": bearerString}
    if not p_additionalHeaders is None:
        headers.update(p_additionalHeaders)
        
    query = ""
    if not p_queryItems is None:
        qArrayFix = {}
        for item in p_queryItems:
            if isinstance(p_queryItems[item], list):
                qArrayFix["%s[]" % item] = p_queryItems[item]
            else:
                qArrayFix[item] = p_queryItems[item]
        query = "?" + urlencode(qArrayFix, True)
    url = API_BASE_URL + p_endpoint + query
    
    verb = p_httpVerb.upper()
    
    session = NoRebuildAuthSession()
    
    verbs   = {
        'DELETE'    : { 'function': session.delete, 'hasBody': False },
        'GET'       : { 'function': session.get,    'hasBody': False },
        'POST'      : { 'function': session.post,   'hasBody': True  },
        'PUT'       : { 'function': session.put,    'hasBody': True  }
    }

    try:
        if(p_verbose):
            print(verb, url)
            
        if verb in verbs:
            if verbs[verb]['hasBody'] and not p_requestBody is None:
                r = verbs[verb]['function'](
                    url,
                    headers =   headers,
                    json    =   p_requestBody,
                    timeout =   (API_CONNECT_TIMEOUT, API_TRANSMIT_TIMEOUT)
                )
            else: 
                r = verbs[verb]['function'](
                    url,
                    headers =   headers,
                    timeout =   (API_CONNECT_TIMEOUT, API_TRANSMIT_TIMEOUT)
                )
        else:
            return False, None, None, None
    except:
        return False, None, None, None
    
    if(p_verbose):
        print(r.status_code)
    
    success         = r.status_code in range (200, 299)
    errors          = None
    responseHeaders = None
    responseBody    = None
    
    if r.status_code == API_STATUS_RATE_LIMIT:
        retryInterval = API_RETRY_DEFAULT_WAIT
        if "Retry-After" in r.headers:
            retryInterval = r.headers["Retry-After"]
        if "retry-after" in r.headers:
            retryInterval = r.headers["retry-after"]
        
        if(p_verbose):
            print("INFO: Hit max request rate. Retrying %s after %s seconds" % (p_retry+1, retryInterval))
        time.sleep(int(retryInterval))
        success, errors, responseHeaders, responseBody = merakiRequest(p_apiKey, p_httpVerb, p_endpoint, p_additionalHeaders, 
            p_queryItems, p_requestBody, p_verbose, p_retry+1)
        return success, errors, responseHeaders, responseBody        

    try:
        rjson = r.json()
    except:
        rjson = None
        
    if not rjson is None:
        if "errors" in rjson:
            errors = rjson["errors"]
            if(p_verbose):
                print(errors)
        else:
            responseBody = rjson  

    if "Link" in r.headers:
        parsedLinks = utils.parse_header_links(r.headers["Link"])
        for link in parsedLinks:
            if link["rel"] == "next":
                if(p_verbose):
                    print("Next page:", link["url"])
                splitLink = link["url"].split("/api/v1")
                success, errors, responseHeaders, nextBody = merakiRequest(p_apiKey, p_httpVerb, splitLink[1], 
                    p_additionalHeaders=p_additionalHeaders, 
                    p_requestBody=p_requestBody, 
                    p_verbose=p_verbose)
                if success:
                    if not responseBody is None:
                        responseBody = responseBody + nextBody
                else:
                    responseBody = None
    
    return success, errors, responseHeaders, responseBody

# Other functions here...

def main(argv):    
    arg_apikey = None
    arg_orgname = "/all"
    arg_file = None
    arg_email = None  # New email argument

    try:
        opts, args = getopt.getopt(argv, 'hk:o:f:e:')
    except getopt.GetoptError:
        print(readMe)
        sys.exit()
    
    for opt, arg in opts:
        if opt == '-h':
            print(readMe)
        elif opt == '-k':
            arg_apikey = arg
        elif opt == '-o':
            arg_orgname = arg
        elif opt == '-f':
            arg_file = arg
        elif opt == '-e':
            arg_email = arg

    # Validate API key
    apiKey = getApiKey(arg_apikey)
    if apiKey is None:
        print("Error: API key missing!")
        sys.exit()

    filepath = generateFileName() if arg_file is None else arg_file
    # Generate CSV logic...

    # Send email if email argument provided
    if arg_email:
        smtp_server = "smtp.office365.com"
        port = 587
        sender_email = "dwarren@clearlinktech.com"
        sender_password = "There is no spoon."
        subject = "Meraki Inventory CSV File"
        body = f"Attached is the CSV file for {arg_orgname}."

        send_email(smtp_server, port, sender_email, sender_password, arg_email, subject, body, filepath)

 I am curious if I am missing some recent changes or if my rate limiting is incorrect.

Thanks,

IronBones 

Get notified when there are additional replies to this discussion.