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:
-
Stateless
The PI Web API is stateless. This means that the service retains no observable knowledge of clients across requests. Each request is an independent transaction between the client and the server. One important consequence of this property is that the check out - make changes - check in transaction pattern found in many database-related APIs (e.g. the AFSDK) is not exposed by the PI Web API. Rather, each request encapsulates this pattern internally.
-
Resource-oriented
Interaction with the PI Web API is organized around resources. A resource is a structured piece of data that represents an object in one of the PI Systems connected to the Web API. Most important PI and AF objects, such as Asset Servers, Data Servers, points, elements, attributes, event frames, and so on, map to resources in the PI Web API. Four primary operations are used to interact with these resources: create, read, update, and delete (often abbreviated as CRUD).
-
Navigable by links
Links (also called hypermedia) capture the organization of the resources exposed by the Web API. You're probably familiar with the hierarchical structure of AF objects: Asset Servers contain databases, which contain elements, which contain attributes, and so on. Links express these relationships. For example, an Asset Server resource links to the collection of databases it contains, and each database links to its parent Asset Server.
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. If you're viewing this on a live instance of PI Web API, click here to view a live, lightly-modified version of this application.
<!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:
- If you're using anonymous authentication, every read/write request is served from the same read/write cache.
- The
Cache-Control: no-cache
request header can be used to force the AF cache to refresh from the backend Asset Server. This operation requires an exclusive lock on the cache, however, so it should be used sparingly. - A request that writes time series data is not a writer request, because time series data reads and writes are always served directly from the backend Data Server.
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:
-
The PI Developers Club community, which has subscription-based resources to help you with the programming and integration of OSIsoft products, including tutorials, forums, and sample code. Many samples and tutorials hosted on PI Developers Club assume the presence of the NuGreen database, which you can run yourself by importing the NuGreen XML into your PI Asset Server.
-
The PI Web API Users Guide, hosted in the PI Live Library, contains detailed information on how to run and administer the product. The PI Live Library also contains information on how to install, configure, maintain, and use other components in the PI System.