Getting Started with the PI Web API

The PI Web API is a RESTful interface to the PI system. It gives client applications read and write access to their AF and PI data over HTTPS. This getting started guide starts with an overview of the constructs and principles that you'll encounter as you use the API, moves on to examples in the form of sample requests and a simple demo application, and concludes with a discussion of some more specific topics that may enrich your understanding of the API.

REST Principles

REST stands for 'representational state transfer.' In the context of the PI Web API, this means that the API is:

WebID

Resources that support CRUD operations are the primary objects in the PI Web API. To allow clients to address these resources, the PI Web API must identify them in a way that is both persistent (so that clients can address known resources consistently over time) and URL-safe (since resources are specified by URLs). Every primary resource in the PI Web API is associated with a WebID, which is an identifier that meets these criteria. Client applications should treat WebIDs as opaque identifiers. Because they are persistent, clients may cache URLs containing WebIDs.

HATEOAS

As discussed above, the resources exposed by the PI Web API are connected by links. This means that client applications should rarely need to construct resource URLs. Rather, they should use Hypermedia as the Engine of Application State (HATEOAS). There are several entry points to the application, including the root of the API and the various GetByPath methods. Once 'inside' the system, the client application should access related resources via the links.

HTTP Verbs

Every resource exposed by the PI Web API supports some subset of the CRUD operations discussed above. Create corresponds to POST, read to GET, update to PUT (when the method requires the complete resource definition) or PATCH (when the method accepts a partial resource definition), and delete to DELETE.

URL Parameters

Several PI Web API GET methods accept URL query parameters. In general, these specify options or serve as filters on the response. For example, element search (GET assetdatabase/{webId}/elements) takes various parameters that specify which elements to return and how to order the returned elements.

JSON

JavaScript Object Notation (JSON) is the primary media type supported by the PI Web API. You can get a feel for JSON by examining the samples provided in the documentation and exploring the API in your browser. Most common client application languages and frameworks provide libraries to convert between language/framework primitives and JSON strings.

Status Codes

HTTP status codes provide information about the success or failure of a request. The PI Web API adheres to the standard semantics of these codes as closely as possible. A coarse distinction is that 200-level status codes indicate success, 400-level status codes indicate user error, and 500-level status codes indicate server error. 400- and 500-level codes are generally accompanied by a response body providing a friendly error message to assist with debugging. One special case worth noting is that the 201 status code is returned in response to a POST, when a resource has been created. In this case, the response will contain a Location header with the WebID-based URL, which the client may use to access the newly-created resource.

The Structure of a PI Web API Request

In this section, we'll look at a GET request to the PI Web API. Client applications will generally use libraries to take care of the details of constructing HTTP requests, but an understanding of the information contained in a request, coupled with a request inspector like Fiddler, is useful for debugging. The following is a complete GET request to the API:

GET https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg HTTP/1.1
Host: myserver
Accept: application/json

The first line specifies the GET method, the resource URL, and the protocol version. The next two lines are request headers. The second line specifies the host addressed by the client, and the third line specifies that the client expects to receive a response formatted as JSON. The response looks like this:

HTTP/1.1 200 OK
Content-Length: 1783
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Wed, 01 Oct 2014 14:24:13 GMT

{
  "WebId": "D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg",
  "Id": "e759b840-d40a-4cf9-910d-09f307fcc654",
  "Name": "NuGreen",
  "Description": "PI BI Project Asset Model",
  "Path": "\\\\MyAFServer\\NuGreen",
  "Links": {
    "Self": "https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg",
    "Elements":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/elements",
    "ElementTemplates":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/elementtemplates",
    "EventFrames":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/eventframes",
    "AssetServer":"https://myserver/piwebapi/assetservers/S0NxzXSxtlKkGzAhZfHOB-KAUEhMQUZTMDQ",
    "ElementCategories":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/elementcategories",
    "AttributeCategories":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/attributecategories",
    "TableCategories":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/tablecategories",
    "EnumerationSets":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/enumerationsets",
    "Tables":"https://myserver/piwebapi/assetdatabases/D0NxzXSxtlKkGzAhZfHOB-KAQLhZ5wrU-UyRDQnzB_zGVAUEhMQUZTMDRcTlVHUkVFTg/tables"
  }
}

The first line specifies the protocol version and the response status code and reason phrase, indicating the overall success or failure of the request. The following four lines are response headers. Lines 2-3 provide information about the content of the response. Lines 4-5 contain information about the server and the time of the request. Below the headers is the response body, a JSON string providing information about the database. Notice the WebID and the links specifying the URLs of related resources and resource collections.

A Simple Application

In this section, we'll develop a JavaScript application for exploring the AF hierarchy of your PI System. A basic knowledge of JavaScript, HTML, and CSS is assumed. The following is the complete code. A lightly-modified version is hosted here.

<!DOCTYPE html>
<html>
<head>
  <title>AF Hierarchy Viewer</title>
  <script src="jquery.min.js"></script>
  <script type="text/javascript">
    var childrenMap = {
      PISystems: ['AssetServers'],
      AssetServers: ['Databases'],
      Databases: ['Elements'],
      Elements: ['Elements', 'Attributes'],
      Attributes: ['Attributes']
    };

    function node(name, type, links, parentDiv) {
      this.type = type;
      this.links = links;
      this.flipper = $('<span class="flipper">+</span>').click(flip.bind(this, this));
      parentDiv.append(this.flipper).append('<span class="' + type + '"> ' + name + '</span><br />');
      this.div = $('<div></div>').hide().appendTo(parentDiv);
    }

    function loadChildren(n) {
      n.loaded = true;
      childrenMap[n.type].forEach(function(childCollection) {
        $.get(n.links[childCollection], function(collection) {
          n[childCollection] = collection.Items.map(function (item) {
            return new node(item.Name, childCollection, item.Links, n.div);
          });
        });
      });
    }

    function flip(n) {
      if (!n.loaded) { loadChildren(n); }
      n.flipper.html(n.flipper.html() == '+' ? '-' : '+');
      n.div.toggle();
    }

    $(function() {
      root = new node('PI Systems', 'PISystems',
        { AssetServers: 'https://myserver/piwebapi/assetservers' }, $("#root"));
    });
  </script>
  <style type="text/css">
    div {
      left: 10px;
      position: relative;
    }
    .flipper {
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div id="root"></div>
</body>
</html>

The first few lines contain HTML boilerplate to set up the page and load jQuery. The section containing the JavaScript is the meat of the application.

childrenMap is a map from collection type to children collection types. For example, an 'AssetServers' collection contains objects (Asset Servers) whose child collections are 'Databases'. Note that the values in this map are chosen to correspond to the keys in the links collections returned by the Web API.

node creates a new node in the displayed AF hierarchy. It stores the collection type and links associated with the object used to create the node, then appends a new expandable element to the DOM.

loadChildren performs the AJAX GET requests to load the given node's children into the application. JavaScript automatically maps the JSON responses from the WebID to native JavaScript objects that we can access as in collection.Items and item.Name. A new node is created for each child object. As mentioned above, the values in childrenMap correspond to keys in the links collections. This property is used when determining the resource for the GET request: n.links[childCollection].

flip is the callback associated with clicking the '+'/'-' symbol next to each expandable node. If the node's children haven't been loaded yet, they are. Then, the '+'/'-' symbol and visibility of the child div are toggled.

The last bit of the script, $(function() {..., executes as soon as the page is loaded. It establishes the root node of the AF hierarchy. The page's source ends with some basic CSS styling and the HTML to set up the root node.

In this example, we can see several of the PI Web API principles listed above in action. Notice that the only hard-coded URL is the Asset Servers root. All other resources are retrieved by following links. You can use your browser console to examine the AJAX requests made by the application. You'll see the structure of the JSON objects returned by the Web API, including the links collections. You'll also notice the WebIDs embedded in the URLs. A richer application might include create, update, or delete functionality, the ability to do filtered reads using query parameters, and more robust error handling (the sample application fails silently for non-200-class responses).

Other Concepts

Caching and Concurrency

The PI Web API caches AF data to improve performance. It maintains up to two caches for each recently-connected user: one for reads and one for writes. The read cache can service multiple simultaneous requests, but each write request requires exclusive access to the write cache. Some notable consequences and caveats around this design are:

Timezones

On output, the PI Web API always displays times as ISO 8601 UTC strings. On input, several options are available. You may specify any ISO 8601 string as UTC, or with or without an offset. You may also specify PI times, such as '*' or 'T'. Time strings specified as UTC or with an offset are stored unaltered. Times without an offset and PI times are resolved relative to the timezone of the backend server on which the data is stored (i.e., not relative to the time zone of the client or the PI Web API).

Streams and Values

Data retrieval from attributes with a data reference and PI points is unified under the /streams... and /streamsets... (for bulk retrieval) endpoints. Use stream methods to retrieve recorded, plot, interpolated, and summary values for time series data. Values of attributes that do not reference time series data can be retrieved from /attributes/{webId}/value.

Stream Sets

For most use cases, the standard single-resource CRUD operations exposed by the PI Web API should be sufficient. To accommodate advanced use cases that require reading from or writing to multiple streams in a single request, the PI Web API exposes stream set resources. A stream set is a set of streams related by a common ancestor element, event frame, or attribute. The stream set endpoints allow reads and writes of time-series data to be made against multiple streams in a single call. The PI Web API also exposes 'ad-hoc' stream set endpoints, which permit reading and writing of data against multiple independent streams (i.e. streams that don't necessarily share a common ancestor).

Additional Resources

Additional information related to using and administering the PI Web API is available from the following sources:

Enabling Operational Intelligence