The APM API makes data collected and generated by AppNeta Performance Manager (APM) available for analysis, reporting, and presentation in third-party systems. Results are delivered in a lightweight JSON format that can be readily consumed by a broad range of clients. As well as collecting system data, the API also provides the ability to configure and control APM.

In addition to the APM API, there is an API for administering Enterprise Monitoring Points (EMPs) - the Admin API. For information on accessing the Admin API see Admin API.

Getting started with the APM API

The best place to start learning and experimenting with the APM API is through its interactive interface. It provides a good place to stage API calls before implementing them in your integration software. In addition to allowing you to experiment with the API, the interactive interface provides detailed documentation about each endpoint and its parameters.

As an example, to access the interactive APM API interface and obtain a simple response from APM:

  1. Log in to APM.
  2. If you have more than one organization, change the organization to the one you are interested in.
  3. Navigate to > API.
    • The interactive APM API interface appears.
  4. Navigate to path > GET /v3/path.
  5. Click Try it out!.
    • The Curl field shows the curl command string (except for the authentication information) that can be used from the command line to produce the same result. See Using curl for more information
    • The Request URL field shows the URL the request was made to.
    • The Response Body field shows the JSON formatted response sent by APM. In this case, all network paths within the selected organization.
    • The Response Code is the response code sent by APM. Successful responses have a “2xx” code. See Error codes for a list of error codes and their meaning.
    • The Response Headers is the header information sent by APM.

You can experiment by filling in a few parameters (to filter the results) and clicking Try it out!. You can also experiment with other endpoints. Care should be taken with PUT, POST, and DELETE requests as they affect your live data.

Authentication

The APM API supports Basic authentication. This requires an APM username and password be passed as part of the API request. See Using curl to see how this is achieved with curl. See the 401 codes in Error codes for the error codes associated with authentication issues.

Using curl

One of the outputs from the interactive APM API interface is a curl command that can be used to generate the same result as is in the Response Body, but from the command line. One caveat is that the command parameters provided do not include authentication information. For example, the curl command returned by GET /v3/path is:

   curl -X GET --header "Accept: application/json" "https://<your_APM_node>.pm.appneta.com/api/v3/path"

<your_APM_node> - the name of the APM node you are using.

Using the command directly would fail as there are no credentials included in the list of parameters.

Including basic authentication information

The simplest way to include authentication information in the curl command is with the -u parameter. For example:

   -u <email_address>:<password>

<email_address> - the email address you use to access APM.
<password> - the password you use to access APM.

The updated command looks as follows:

   curl -X GET --header "Accept: application/json" "https://<your_APM_node>.pm.appneta.com/api/v3/path" -u <email_address>:<password>

If you leave off “:<password>”, you will be prompted for your password.

This command provides output but it is unformatted and difficult to read.

Making the output easier to read

To make the curl output more intelligible, use | python -m json.tool after each curl command. For example:

   curl -X GET --header "Accept: application/json" "https://<your_APM_node>.pm.appneta.com/api/v3/path" -u <email_address>:<password> | python -m json.tool

The output from this command is properly formatted.

Obtaining “IDs”

A number of endpoints (for example, GET /v3/path/{id}/data) require an “id” parameter, to filter a result set (for example, “Path ID” or “Web Path ID”). The easiest way to obtain specific IDs is to navigate to a page in the APM user interface showing details of that object. For example, in APM, if you navigate to Delivery > Network Paths and hover over a path name, the “pathid” of the network path is shown at the bottom left of the page. Similarly, if you navigate to Experience > Web Paths and hover over a web path name, the “webpathid” of the web path is shown at the bottom left of the page.

Obtaining a timestamp in Unix time

There are endpoints (for example, GET /v3/path/{id}/data) that require a timestamp in “Unix time” (the number of seconds elapsed since midnight (UTC) on January 1, 1970). The simplest way to obtain Unix time is with a free online tool like the Unix timestamp tool.

Retrieving bulk data

There are three endpoints that can return potentially very large amounts of data: GET /v3/path/data, GET /v3/webPath/data, and GET /v3/dns/webPath/data. To limit the amount of data returned, a size restriction is in place. A single request can return a maximum of 20 path-days of data. For example:

  • 20 days of data for 1 path
  • 10 days of data for 2 paths
  • 1 day of data for 20 paths
  • 0.5 days of data for 40 paths
  • 1 hour of data for 480 paths
  • 15 minutes of data for 1,920 paths

For these endpoints, use the limit parameter to limit the number of paths returned, the from and to parameters to specify the time range, and the page parameter to specify the page of data to retrieve.

For example, to retrieve a day of data for 100 paths in five 20 path chunks, you would set:

  • limit = 20
  • from = start of day in UNIX time
  • to = end of day in UNIX time
  • page = 1 to 5 (i.e. make five requests with a different page number in each request)

Rate limit

AppNeta limits the number of API requests that can be made over a given period of time. Currently the rate limit is 50 requests every 10 seconds.

Sample code

As mentioned in previous sections, you can access the APM API using its interactive interface or by using the curl command. But, because it is a RESTful API, there are many ways to interact with the APM API programmatically. Virtually any modern programming language can be used. For our examples, we use Python.

Note: As you work through the examples below, use the interactive APM API interface to view documentation for each of the API endpoints. This information can be used to determine how to construct the request string and to understand what the response objects are expected to look like.

Loading code samples

Sample Python code files are available in a repository on GitHub. To use these files:

  1. Install git if it is not already installed on your computer.
  2. Sign up for a GitHub account if you don’t already have one.
  3. Clone the repository to your local system (e.g. git clone https://github.com/appneta/docs-apm-api-sample-code.git).
    • A local directory is created.
  4. Update the credentials.py file with your APM server name (e.g. app-01.pm.appneta.com) and APM credentials (username and password).
  5. Download and install Python.
  6. Run the sample programs and view the code files to see what they do and how they do it:
    • The “app-…” files (e.g. python app-organizations.py) retrieve a variety of APM system data.
    • The “path-…” files create/delete/show network paths identified in the paths.csv file.
      • Update paths.csv with valid information then run any of the “path-…” files (e.g. python path-create.py) to see what they do.
    • “aggregation-test.py” is used to show how data is aggregated over time.
    • Functions used in the program files are defined in api_fns.py.
  7. Modify any of these files or create your own code to use the APM API.

The sample files and the purpose of each are as follows:

Filename Description
credentials.py User credentials and server name
api_fns.py Functions to access the APM API
   
app-appliances.py Prints monitoring point info
app-mp-status.py Prints monitoring point status
app-network-path-info.py Prints network path info
app-network-path-stats.py Prints network path stats
app-network-path-status.py Prints network path status
app-network-path-status-group.py Prints network path status by group
app-network-path-status-saved-list.py Prints network path status by saved list
app-organizations.py Prints organization info
app-web-path-info.py Prints web path info
app-web-path-stats-group.py Prints web path stats info by group
app-web-path-stats.py Prints web path stats info
app-web-path-status-org.py Prints web path status by organization
app-web-path-status.py Prints web path status
   
paths.csv List of paths to create/delete/show
path-create.py Create paths in “paths.csv” file
path-delete.py Delete paths in “paths.csv” file
path-show.py Show paths in “paths.csv” file
   
aggregation-test.py Shows how data is aggregated

API access function with no parameters

An API access function is one that interacts with an APM API endpoint. All of the sample programs use at least one of the API access functions located in api_fns.py. In this section we review a simple API access function (get_org()) that accesses the GET /v3/organization endpoint, which requires no parameters.

Within api_fns.py you’ll find the get_org() code. It looks as follows:

def get_org():
    url = "https://{}/api/v3/organization".format(apm_server)
    return(requests.get(url, auth=(username, password)))

The function simply creates a request string (url) to access the GET /v3/organization endpoint.

  • apm_server - (defined in credentials.py) is the APM server you use.
  • username - (defined in credentials.py) is a valid username on your APM server.
  • password - (defined in credentials.py) is the password associated with the username.

requests.get() makes the GET request to the endpoint specified in the url using the username and password for authentication. All the sample API access functions defined in api_fns.py use the Python Requests library to access the API.

API access function with path parameters

In the previous section we looked at the get_org() function. This function required no parameters as the GET /v3/organization endpoint it accesses requires no parameters. In this section we look at GET /v3/webApplication/{web_app_grp_id}/monitor. This is an endpoint that requires a “path parameter” - a parameter (in this case the web app group ID: {web_app_grp_id}) included in the URL path. Path parameters are not optional. This endpoint is accessed using the get_web_path() function. Within api_fns.py you’ll find the get_web_path() code. It looks as follows:

def get_web_path(web_app_grp_id):
    url = "https://{}/api/v3/webApplication/{}/monitor".format(
          apm_server, web_app_grp_id)
    return(requests.get(url, auth=(username, password)))

As with get_org(), a request string (url) is created then the GET request is sent via requests.get(). In this case, the URL path contains the web app group ID as required by the endpoint.

API access function with query parameters

A query parameter is one that appears after the “?” in the endpoint URL. Query parameters are typically optional.

In this section we look at the GET /v3/path endpoint. It can take an optional organization ID parameter. If an organization ID is specified, the endpoint returns network path information for all network paths in the organization. Without the parameter it returns network path information for all paths in all organizations. Within api_fns.py you’ll find the get_network_path() code. It looks as follows:

def get_network_path(org_id=None):
    url = "https://{}/api/v3/path".format(apm_server)
    if org_id is not None:
        url += "?orgId={}".format(org_id)
    return(requests.get(url, auth=(username, password)))

As with the other functions we’ve looked at so far, a request string (url) is created then the GET request is sent via requests.get(). In this case, the org_id parameter is optional and defaults to None. If org_id is passed, it is included as a query parameter in the url string.

API access function with body parameters

In the previous examples we have reviewed endpoints (and the functions that call them) that use path parameters and query parameters. Another way that parameters are passed are in an object within the body of the request. The POST /v3/path endpoint is an example of this. Within api_fns.py you’ll find the create_network_path() code. It looks as follows:

def create_network_path(org_id, source_mp_name, target):
    url = "https://{}/api/v3/path".format(apm_server)
    headers = {
        "Content-Type": "application/json"
    }
    body = {
        "sourceAppliance": source_mp_name,
        "target": target,
        "orgId": org_id
    }
    return(requests.post(url, headers=headers, auth=(username, password),
           json=body))

In this case the url is very basic and the parameters are included as part of the body object. The headers object specifies the type of content being passed - in this case a JSON object. Also note that because the endpoint uses the POST command, this function uses requests.post() rather than requests.get().

Using the API access functions

You will notice that one of the things all the sample API access functions have in common is that they return the result of one of the requests functions (for example requests.get()). These return an object containing a variety of information including a status code (the HTTP response status) and the JSON response body. All sample programs that call an API access function check for a valid response before continuing. For example, within app-organizations.py you’ll see the following:

from api_fns import *

r = get_org()
if r.status_code == requests.codes.ok:
    for organization in r.json():
        print('Org ({}) name --> {} -- parent({})'.format(organization['id'],
              organization['displayName'], organization['parentId']))
        # pp_json(organization)
else:
    print_err(r)

get_org() is called and returns a response: r. If the response status (r.status_code) is okay, information about each organization returned is printed, otherwise an error is printed. If you want to see all the organization information returned, uncomment # pp_json(organization).

As another example, within app-mp-status.py you’ll see the following:

from api_fns import *

r1 = get_org()
if r1.status_code == requests.codes.ok:
    for organization in r1.json():
        print('Org ({}) name --> {}'.format(organization['id'],
              organization['displayName']))

        r2 = get_appliance(organization['id'])
        if r2.status_code == requests.codes.ok:
            for appliance in r2.json():
                print('   MP: {}, {}, {}, ({})'.format(appliance['id'],
                      appliance['resolvedIp'], appliance['name'],
                      appliance['connectionStatus']))
                # pp_json(appliance)
        else:
            print_err(r2)
else:
    print_err(r1)

This is similar to the first example in that get_org() is called, but for each organization get_appliance() is called using the organization information (organization['id']) retrieved. From this, a list of monitoring points and their connection status is printed. All the “app-…” program files are similar.

Bulk operations

One of the primary uses of the APM API is to facilitate bulk operations in cases where performing an operation from the Web Admin interface or the interactive APM API interface is impractical. For example, if you need to create hundreds or thousands of network paths, you’ll want to automate this task. paths-create.py is a sample program that creates network paths given a list of organization IDs, monitoring point names, targets, and target locations listed in paths.csv. The code looks as follows:

import csv
from api_fns import *

with open('paths.csv', mode='r') as csv_file:
    csv_reader = csv.DictReader(csv_file)
    for row in csv_reader:
        # pp_json(row)

        r1 = create_network_path(row['org_id'], row['source_mp'],
                                 row['target'])
        if r1.status_code == requests.codes.created:
            print('Created: org/src/targ {}/{}/{}'
                  .format(row['org_id'], row['source_mp'], row['target']))
        else:
            print('Unable to create: {}'.format(r1.json()['messages'][0]))

        network_path_id = get_network_path_id(row['org_id'], row['source_mp'],
                                              row['target'])

        r2 = add_network_path_location(network_path_id, row['location'])
        if r2.status_code == requests.codes.ok:
            print('Added location successfully: {}.'.format(row['location']))
        else:
            print('Unable to add location: {}.'.format(row['location']))

It simply opens the file then reads each row (using the CSV library) and calls API access function create_network_path() (which talks to the POST /v3/path endpoint) with the information retrieved. For each successful creation, it prints a message showing what was created. Otherwise, it prints an error message.

Note that the network path target location cannot be added when the network path is created. Instead, it must be added afterwards using the API access function add_network_path_location(). It uses the PUT /v3/path/{id}/targetLocation endpoint to add the target location information to the path.

If you run app-appliances.py (e.g. python app-appliances.py) you can find organization IDs and monitoring point names to use in the paths.csv file. Once paths.csv is updated, you can run paths-create.py (e.g. python paths-create.py) to create the specified paths.

paths-delete.py and paths-show.py are similar but they delete paths and show paths that match the information in paths.csv.

Data aggregation

In order to reduce the volume of data stored and returned by calls to the API, test results are aggregated over time. Depending on how far in the past you are looking at and the size of the time range you specify, the data will either not be aggregated, be aggregated by the hour, or be aggregated by the day. If you are using the API to extract test data you’ll need to take this into consideration. The aggregation-test.py program can be used to see aggregation within your data. The code looks as follows:

import math
from api_fns import *

count = 0
metric = 'availablecapacity'

org_id = '11111'      # organization ID
path_id = 222222      # network path ID
hours_back = 24*0     # the number of hours ago the range ends
hour_range = 24*10    # the number of hours in the range

if (hours_back == 0) & (hour_range == 0):
    start = end = None
else:
    if (hour_range == 0):
        hour_range = 1
    end = math.floor(time.time()-(60*60*hours_back))
    start = (end - math.floor(60*60*hour_range))

print('Start time: {} ({})'.format(time.ctime(start), start))
print('End time:   {} ({})'.format(time.ctime(end), end))
print('Org id:     ({})'.format(org_id))
print('Path id:    ({})'.format(path_id))

r1 = get_network_path_stats_id(org_id, path_id, start, end, metric)
if r1.status_code == requests.codes.ok:
    for network_path_stats in r1.json():
        print('   Network path ({})'.format(network_path_stats['pathId']))
        # pp_json(network_path_stats)

        if network_path_stats['pathId'] == path_id:
            # pp_json(network_path_stats)
            if network_path_stats['instrumentation'] == "ONE_WAY":
                # Single-ended paths have data within 'data'
                for test in network_path_stats['data']['availableCapacity']:
                    count += 1
                    print('      Single-ended path -> Start time={}' +
                          '   Period={}  Available Capacity value={} count={}'
                          .format(time.ctime(test['start']/1000),
                                  test['period'],
                                  math.floor(test['value']), count))
                    # pp_json(test)
            else:
                # Dual-ended paths have data within
                # 'dataInbound' and 'dataOutbound'
                for test in network_path_stats['dataInbound']['availableCapacity']:
                    count += 1
                    print('      Dual-ended path   -> Start time={}' +
                          '   Period={}  Available Capacity value={} count={}'
                          .format(time.ctime(test['start']/1000),
                                  test['period'],
                                  math.floor(test['value']), count))
                    # pp_json(test)

elif r1.status_code == requests.codes.bad_request:
    print_err_json(r1.json())
else:
    print_err(r1)

print('Start time: {} ({})'.format(time.ctime(start), start))
print('End time:   {} ({})'.format(time.ctime(end), end))

Run app-network-path-info.py (e.g. python app-network-path-info.py) to find an organization ID and corresponding network path ID. Update org_id and path_id with this information then run the program (e.g. python aggregation-test.py). Look at “Period=” and see if/how it changes. It will either show “None” (indicating non-aggregated data), “60” (indicating data aggregated over an hour), or “1440” (indicating data aggregated over a day). You can also update hours_back and hour_range to change the time range and see how the “Period=” results change.

Last hour of data returned by default

For endpoints where a time range can be specified with optional “to” and “from” parameters (for example GET /v3/path/data, called by API access function get_network_path_stats()), if “to” and “from” are not passed, the endpoint will return data for the last hour. To see this, in aggregation-test.py:

  1. Set org_id and path_id to valid values (use python app-network-path-info.py to find valid values).
  2. Set hours_back and hour_range to 0 (which means start and end are set to None when passed to get_network_path_stats()).
  3. Run the program (e.g. python aggregation-test.py).
    • You’ll see data from the past hour.

Differences between dual- and single-ended path data

For dual-ended paths, data is collected for both inbound and outbound directions. For single-ended paths, data is collected only in the outbound direction. This can be seen in the data returned by the GET /v3/path/data endpoint (called by API access function get_network_path_stats()). For a given network path, the test data can be seen under “dataInbound” and “dataOutbound” for dual-ended paths and under “data” for single-ended paths. Also, “instrumentation” is set to “TWO_WAY” for dual-ended paths and “ONE_WAY” for single-ended paths. To see this, in aggregation-test.py:

  1. Set org_id and path_id to valid values (use python app-network-path-info.py to find valid values).
    • Note whether the selected network path is dual-ended or single-ended.
  2. Set hours_back and hour_range to 0 (to reduce the amount of data returned).
  3. Uncomment the pp_json(network_path_stats) line.
  4. Run the program and redirect output into a file (e.g. python aggregation-test.py > outfile).
    • Open the output file.
    • You’ll see test data under “dataInbound” and “dataOutbound” if the path is dual-ended and under “data” if the path is single-ended. You will also see “instrumentation” set to either “TWO_WAY” or “ONE_WAY”.

Error codes

The error codes returned by APM include:

  • 400 - Bad Request
    • Indicates that the parameters sent with the request were incorrect.
  • 401 - Full authentication is required to access this resource
    • This indicates that no credentials were passed with the request.
  • 401 - Bad credentials
    • This indicates that credentials were passed correctly with the request but were invalid. Try using different credentials.
  • 401 - Failed to decode basic authentication token
    • This indicates that an encoded token could not be decoded. Try recreating the token.
  • 403 - Access is denied
    • This indicates that the server understood the request but will not fulfill it. It is likely that you do not have permissions to access the resource you were trying to access. For example, this can occur if you are trying to access the wrong organization.
  • 404 - Not found
    • This indicates that the specified API endpoint was invalid. Check the specified URL string.
  • 406 - Not Acceptable
    • This indicates, for example, incorrect information in the request header. Review the request.
  • 500 - Internal server error
    • This indicates that there is an issue on the system you are targeting. Try the request again later.
Call Support: 800-664-4401
Contact Us