Skip to main content
Developer connecting to PI System via REST API gateway
Tutorial

Start Here: Your First PI Web API Request

This tutorial walks you through making your first successful PI Web API request using Python. You will verify connectivity, explore the API structure, find a PI point, and read live data -- all in under 10 minutes.

Before you begin

Before writing any code, confirm you have the following. If any of these are missing, the rest of this tutorial will not work.

RequirementDetailsHow to verify
PI Web API URLThe full base URL, e.g. https://your-server/piwebapi. Must include /piwebapi at the end.Open it in a browser -- you should see a JSON response with server info
CredentialsA username and password with at least read access to PI data. Format: DOMAIN\username for Basic auth.Ask your PI administrator
Python 3.8+With the requests library installed.python --version and pip install requests
PI Web API version2017 SP1 or later. Older versions are missing endpoints used in this tutorial.The root endpoint response includes ProductVersion

Common URL mistake

The PI Web API base URL must end with /piwebapi, not just the server hostname. For example: https://myserver.corp.com/piwebapi -- not https://myserver.corp.com. If you use the wrong URL, you will get a 404 or an IIS default page instead of the API.

Step 1: Verify connectivity

The PI Web API root endpoint returns metadata about the server. Hit it first to confirm your URL, credentials, and network connectivity all work.

verify_connection.pypython
import requests
from requests.auth import HTTPBasicAuth

BASE_URL = "https://your-server/piwebapi"

response = requests.get(
    BASE_URL,
    auth=HTTPBasicAuth("DOMAIN\\username", "password"),
    verify=False,  # See the Authentication guide for proper cert handling
)

print(f"Status:  {response.status_code}")

if response.status_code == 200:
    data = response.json()
    print(f"Product: {data['ProductTitle']}")
    print(f"Version: {data['ProductVersion']}")
    print(f"Server:  {data['MachineName']}")
    print(f"\nAvailable resources:")
    for name, url in data["Links"].items():
        if name != "Self":
            print(f"  {name}: {url}")
else:
    print(f"Error:   {response.text[:200]}")

Expected output (your values will differ):

Expected outputtext
Status:  200
Product: PI Web API
Version: 2021 SP3
Server:  YOURSERVER

Available resources:
  AssetServers: https://your-server/piwebapi/assetservers
  DataServers: https://your-server/piwebapi/dataservers
  Search: https://your-server/piwebapi/search
  System: https://your-server/piwebapi/system
  ...

About verify=False

Setting verify=False disables SSL certificate verification. This is acceptable for a quick connectivity test, but you must configure proper certificate handling before moving to production. Without it, your credentials travel over an unverified connection. See the Authentication guide for proper setup.

What if it does not work?

SymptomFix
ConnectionError / timeoutCheck the URL, firewall rules, and VPN. Try opening the URL in a browser.
SSLErrorThe server uses a self-signed or internal CA certificate. Add verify=False for now, then fix it properly later.
Status 401Wrong credentials or auth method. Check username format (DOMAIN\user). Try the URL in a browser with the same credentials.
Status 404 or HTML responseURL is wrong. Make sure it ends with /piwebapi, not /piwebapi/ (no trailing slash).

Step 2: Understand the API structure

PI Web API is a hypermedia API. The root response contains links to all major resource collections. You never need to guess endpoint URLs -- the API tells you where everything is.

The most important resources for getting started are:

ResourceURLWhat it does
Data Servers/dataserversLists PI Data Archive servers (where time-series data lives)
Asset Servers/assetserversLists PI AF Servers (where the element/attribute hierarchy lives)
Points/pointsLook up individual PI points by path
Streams/streamsRead and write time-series values for a specific point
Search/search/queryFull-text search across points, elements, and attributes
list_data_servers.pypython
# List all PI Data Archive servers this PI Web API can reach
response = requests.get(
    f"{BASE_URL}/dataservers",
    auth=HTTPBasicAuth("DOMAIN\\username", "password"),
    verify=False,
)

servers = response.json()["Items"]
for server in servers:
    print(f"Server: {server['Name']}")
    print(f"  WebId:   {server['WebId']}")
    print(f"  Path:    {server['Path']}")
    print(f"  IsConnected: {server['IsConnected']}")
    print()

What is a WebId?

Every resource in PI Web API has a WebId -- a unique, URL-safe identifier. You need a resource's WebId to read or write its values. Think of it as a primary key. Learn more in the WebID & Lookup guide.

Step 3: Find a PI point

The most reliable way to find a PI point is by its full path. This does not require the Indexed Search crawler to be running (which is a common configuration issue with the search endpoint).

find_point_by_path.pypython
# Look up a PI point by its full path
# Replace YOUR-SERVER with your PI Data Archive server name
response = requests.get(
    f"{BASE_URL}/points",
    params={"path": "\\\\YOUR-SERVER\\sinusoid"},
    auth=HTTPBasicAuth("DOMAIN\\username", "password"),
    verify=False,
)

if response.status_code == 200:
    point = response.json()
    print(f"Name:      {point['Name']}")
    print(f"WebId:     {point['WebId']}")
    print(f"PointType: {point['PointType']}")
    print(f"Descriptor: {point.get('Descriptor', 'N/A')}")
    print(f"EngUnits:  {point.get('EngineeringUnits', 'N/A')}")

    # Save the WebId for the next step
    POINT_WEB_ID = point["WebId"]
elif response.status_code == 404:
    print("Point not found. Check the server name and point name.")
    print("Tip: Server name is case-sensitive on some configurations.")
else:
    print(f"Error {response.status_code}: {response.text[:200]}")

The sinusoid point

Most PI Data Archive installations include a default point called sinusoid that generates a continuous sine wave. It is the standard test point for learning. If your server does not have it, ask your PI administrator for the name of any active numeric point you can read.

Alternatively, you can search by name. This requires the PI Web API Indexed Search crawler to be configured and running.

find_point_by_search.pypython
# Search for points by name (requires Indexed Search to be enabled)
response = requests.get(
    f"{BASE_URL}/search/query",
    params={"q": "name:sinusoid", "count": 5},
    auth=HTTPBasicAuth("DOMAIN\\username", "password"),
    verify=False,
)

if response.status_code == 200:
    results = response.json()
    items = results.get("Items", [])
    if items:
        for item in items:
            print(f"{item['Name']} ({item['ItemType']}) - WebId: {item['WebId']}")
    else:
        print("No results. The Indexed Search crawler may not be configured.")
        print("Use the path-based lookup above instead.")
elif response.status_code == 502:
    print("Search service unavailable. Indexed Search may not be enabled.")
    print("Use the path-based lookup above instead.")

Search requires configuration

The /search/query endpoint requires the PI Web API Indexed Search crawler to be enabled and running. Many PI Web API installations do not have this configured, which causes search to return empty results or a 502 error. If search does not work, use the path-based lookup method above -- it always works.

Step 4: Read the current value

With a WebId, you can read the current (snapshot) value of any PI point. This is the most recent value in the PI Data Archive.

read_current_value.pypython
# Read the current snapshot value
response = requests.get(
    f"{BASE_URL}/streams/{POINT_WEB_ID}/value",
    auth=HTTPBasicAuth("DOMAIN\\username", "password"),
    verify=False,
)

data = response.json()
print(f"Value:     {data['Value']}")
print(f"Timestamp: {data['Timestamp']}")
print(f"Good:      {data['Good']}")
print(f"Units:     {data.get('UnitsAbbreviation', 'N/A')}")

# The full response contains more fields:
# - Substituted: whether the value was manually overridden
# - Annotated: whether the value has a note attached
# - Good: whether the data quality is acceptable

Expected response structure:

Response bodyjson
{
  "Timestamp": "2026-03-15T14:30:00Z",
  "Value": 87.345,
  "UnitsAbbreviation": "",
  "Good": true,
  "Questionable": false,
  "Substituted": false,
  "Annotated": false
}

Understanding the Good field

The Good field indicates data quality. When Good is false, the value may be stale, from a disconnected sensor, or otherwise unreliable. Always check this field before using the value in calculations or displays. In production code, you should handle bad-quality values explicitly.

Step 5: Read historical values

The real power of PI is historical data. Let's read the last hour of recorded values.

read_recorded_values.pypython
# Read recorded values from the last hour
response = requests.get(
    f"{BASE_URL}/streams/{POINT_WEB_ID}/recorded",
    params={
        "startTime": "*-1h",   # 1 hour ago
        "endTime": "*",        # Now
        "maxCount": 100,       # Limit results
    },
    auth=HTTPBasicAuth("DOMAIN\\username", "password"),
    verify=False,
)

data = response.json()
items = data.get("Items", [])

print(f"Retrieved {len(items)} recorded values:\n")
for item in items[:5]:  # Show first 5
    quality = "Good" if item["Good"] else "BAD"
    print(f"  {item['Timestamp']}: {item['Value']:.3f} [{quality}]")

if len(items) > 5:
    print(f"  ... and {len(items) - 5} more")

# Important: if len(items) == maxCount, there may be more data
# that was truncated. See the Pagination guide for how to handle this.
if len(items) == 100:
    print("\nWarning: results may be truncated. Increase maxCount or paginate.")

PI time syntax

PI Web API accepts relative time expressions: * (now), *-1h (1 hour ago), *-7d (7 days ago), t (today at midnight), y (yesterday at midnight). You can also use ISO 8601 format: 2026-03-15T00:00:00Z. These expressions work on all time-based endpoints.

Recorded values are compressed

PI Data Archive uses exception-based compression, so "recorded" values are not raw sensor readings at fixed intervals. They are the values that passed the compression filter -- typically when the value changed significantly. This is why you may get 20 values for one hour of a slowly-changing point but 500 for a volatile one. If you need evenly-spaced values, use the /streams/{webId}/interpolated endpoint instead. See the Reading Values guide for details.

Data streams flowing from industrial equipment to monitoring screens

Complete working example

Here is the entire tutorial as a single, self-contained script you can copy and run. Replace the placeholder values at the top with your actual server details.

pi_web_api_quickstart.pypython
"""PI Web API Quick Start -- your first requests in Python.

Replace the configuration values below with your actual server details.
Run: python pi_web_api_quickstart.py
"""

import requests
from requests.auth import HTTPBasicAuth
import urllib3


# Suppress SSL warnings for this quick test
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# ── Configuration (replace these) ──────────────────────────────
BASE_URL   = "https://your-server/piwebapi"
USERNAME   = "DOMAIN\\username"
PASSWORD   = "password"
PI_SERVER  = "YOUR-PI-SERVER"   # PI Data Archive server name
POINT_NAME = "sinusoid"         # PI point to read
# ───────────────────────────────────────────────────────────────

session = requests.Session()
session.auth = HTTPBasicAuth(USERNAME, PASSWORD)
session.verify = False
session.headers.update({"Accept": "application/json"})

# Step 1: Verify connectivity
print("1. Connecting to PI Web API...")
resp = session.get(BASE_URL)
if resp.status_code != 200:
    print(f"   FAILED: {resp.status_code} - {resp.text[:200]}")
    exit(1)

info = resp.json()
print(f"   Connected to {info['ProductTitle']} {info['ProductVersion']}")
print(f"   Server: {info['MachineName']}")

# Step 2: Find the PI point
print(f"\n2. Looking up point '{POINT_NAME}' on {PI_SERVER}...")
resp = session.get(
    f"{BASE_URL}/points",
    params={"path": f"\\\\{PI_SERVER}\\{POINT_NAME}"},
)
if resp.status_code != 200:
    print(f"   FAILED: {resp.status_code} - {resp.text[:200]}")
    exit(1)

point = resp.json()
web_id = point["WebId"]
print(f"   Found: {point['Name']} (Type: {point['PointType']})")
print(f"   WebId: {web_id}")

# Step 3: Read the current value
print("\n3. Reading current value...")
resp = session.get(f"{BASE_URL}/streams/{web_id}/value")
data = resp.json()
quality = "Good" if data["Good"] else "BAD"
print(f"   Value: {data['Value']} [{quality}]")
print(f"   Timestamp: {data['Timestamp']}")

# Step 4: Read the last hour of history
print("\n4. Reading last hour of recorded values...")
resp = session.get(
    f"{BASE_URL}/streams/{web_id}/recorded",
    params={"startTime": "*-1h", "endTime": "*", "maxCount": 100},
)
items = resp.json().get("Items", [])
print(f"   Retrieved {len(items)} recorded values")

if items:
    values = [item["Value"] for item in items if item["Good"]]
    if values:
        print(f"   Min: {min(values):.3f}")
        print(f"   Max: {max(values):.3f}")
        print(f"   Avg: {sum(values)/len(values):.3f}")

print("\nDone! You have successfully connected to PI Web API.")

What you learned

  • How to connect to PI Web API and verify your credentials
  • The API structure: root endpoint, data servers, points, and streams
  • How to find a PI point by path (more reliable than search)
  • How to read the current snapshot value and historical recorded values
  • The importance of quality flags and data compression in PI

Next steps

Now that you can connect and read data, here is the recommended path forward:

  1. 1

    Set up proper authentication

    Learn about Kerberos, NTLM, and certificate handling for production use. Stop using verify=False.

  2. 2

    Understand WebID and lookup patterns

    Learn how to find any PI resource by path, name, or search query. Understand WebID encoding.

  3. 3

    Read all value types

    Master recorded, interpolated, and summary values. Learn about compression, quality flags, and digital states.

  4. 4

    Write values back to PI

    Send single or bulk value updates to PI points. Understand buffering, update options, and error handling.

Need help?