Skip to main content
Security checkpoint illustrating PI Web API authentication methods
How-to Guide

Authentication

PI Web API supports Basic, Kerberos, NTLM, and Bearer (OpenID Connect) authentication. This guide covers each method with Python examples, explains SSL certificate handling, and covers the production patterns that matter -- service accounts, Kerberos delegation, and PI identity mapping.

Which authentication method should I use?

MethodWhen to usePython libraryCredentials in code?
BasicQuick tests, scripts on non-domain machines, service accountsrequests (built-in)Yes (username + password)
KerberosDomain-joined machines, production environments, SSOrequests-kerberosNo (uses Windows ticket)
NTLMWindows environments without Kerberos, legacy setupsrequests-ntlmYes (username + password)
Bearer (OpenID)Modern cloud apps, API gateways, PI Web API 2019+requests + token providerNo (uses OAuth token)

Server determines available methods

Your PI administrator configures which authentication methods PI Web API accepts. You cannot use a method that is not enabled on the server. To discover which methods are available, make an unauthenticated request and check the WWW-Authenticate response header.

discover_auth_methods.pypython
# Discover which auth methods the server accepts
import requests


response = requests.get(
    "https://your-server/piwebapi",
    verify=False,
)
print(f"Status: {response.status_code}")
print(f"WWW-Authenticate: {response.headers.get('WWW-Authenticate', 'Not present')}")

# Example output:
# Status: 401
# WWW-Authenticate: Basic realm="PI Web API", Negotiate, NTLM

Basic authentication

Basic auth sends credentials as a Base64-encoded header with every request. It is the simplest method and works from any platform, but credentials travel with every request -- always use HTTPS.

basic_auth.pypython
import requests
from requests.auth import HTTPBasicAuth

session = requests.Session()
session.auth = HTTPBasicAuth("DOMAIN\\username", "password")
session.verify = False  # Replace with CA bundle path in production
session.headers.update({"Accept": "application/json"})

response = session.get("https://your-server/piwebapi")
print(f"Status: {response.status_code}")
print(f"Server: {response.json()['ProductTitle']}")

When to include the domain prefix

Use DOMAIN\username format when your PI Web API server is in an Active Directory domain and Basic auth is configured against AD. If Basic auth is configured against local Windows accounts on the PI Web API server itself, use just username without the domain prefix. Ask your PI administrator which applies.

Kerberos authentication

Kerberos uses your Windows domain ticket automatically -- no password in your code. This is the preferred method for production because credentials never traverse the network.

Installbash
pip install requests-kerberos
kerberos_auth.pypython
import requests
from requests_kerberos import HTTPKerberosAuth, OPTIONAL

session = requests.Session()
session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
session.verify = False  # Replace with CA bundle path in production
session.headers.update({"Accept": "application/json"})

response = session.get("https://your-server/piwebapi")
print(f"Status: {response.status_code}")

Mutual authentication options

OptionBehavior
REQUIREDBoth client and server must prove their identity. Most secure, but fails if the server's SPN is not configured correctly.
OPTIONALClient authenticates to server, but does not require the server to prove its identity back. Works in more environments. Use this if REQUIRED fails.
DISABLEDNo mutual authentication. Client sends credentials, server does not prove identity. Least secure but most compatible.

Kerberos delegation (double-hop)

When PI Web API needs to access a PI Data Archive on a different server on your behalf, it must forward your Kerberos ticket. This is called "delegation" or the "double-hop" problem. Without proper delegation, you authenticate to PI Web API successfully, but PI Web API connects to the Data Archive anonymously.

kerberos_with_delegation.pypython
from requests_kerberos import HTTPKerberosAuth, OPTIONAL

session = requests.Session()
session.auth = HTTPKerberosAuth(
    mutual_authentication=OPTIONAL,
    delegate=True,  # Allow Kerberos ticket delegation
)

# With delegate=True, PI Web API can forward your credentials
# to the PI Data Archive on a different server

Delegation requires server-side configuration

For Kerberos delegation to work, your domain administrator must configure Constrained Delegation for the PI Web API service account in Active Directory. The service account needs "Trust this computer for delegation to specified services only" with the PI Data Archive SPN listed. This is a common source of "everything works on the PI Web API server but fails remotely" issues.

Debugging Kerberos failures

Debug Kerberos from the command linebash
# Check your current Kerberos tickets
klist

# Get a ticket for the PI Web API SPN
# (replace with your actual server and SPN)
kvno HTTP/pi-web-api-server.corp.com

# If klist shows no tickets:
kinit username@CORP.COM

# On Windows, check the Application event log for Kerberos errors:
# Event Viewer > Windows Logs > Security
# Look for Event ID 4771 (Kerberos pre-authentication failed)
# or Event ID 4769 (Kerberos service ticket requested)

NTLM authentication

NTLM is a legacy Windows authentication protocol. It requires explicit credentials but handles the challenge-response handshake automatically. Use it when Kerberos is not available.

Installbash
pip install requests-ntlm
ntlm_auth.pypython
import requests
from requests_ntlm import HttpNtlmAuth

session = requests.Session()
session.auth = HttpNtlmAuth("DOMAIN\\username", "password")
session.verify = False  # Replace with CA bundle path in production

response = session.get("https://your-server/piwebapi")
print(f"Status: {response.status_code}")

NTLM is being deprecated

Microsoft is phasing out NTLM in favor of Kerberos. If your environment supports Kerberos, prefer it over NTLM for new integrations. NTLM also does not support delegation, which means multi-server PI architectures may not work correctly with NTLM.

Bearer token authentication (OpenID Connect)

PI Web API 2019 and later supports Bearer token authentication via OpenID Connect. This is the modern approach for cloud applications, API gateways, and integrations that use OAuth 2.0 identity providers.

bearer_auth.pypython
import requests

# Obtain a token from your identity provider (Azure AD example)
# This step depends on your IdP configuration
token = "eyJ..."  # Your OAuth 2.0 access token

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {token}",
    "Accept": "application/json",
})
session.verify = False  # Replace with CA bundle path in production

response = session.get("https://your-server/piwebapi")
print(f"Status: {response.status_code}")

Bearer auth requires server configuration

Bearer authentication must be explicitly configured on the PI Web API server with a registered OpenID Connect provider (e.g., Azure Active Directory, Okta, or another OIDC-compliant identity provider). Ask your PI administrator if Bearer auth is available and which identity provider is configured.

Handling SSL certificates

PI Web API almost always uses a self-signed or internal CA certificate. This causes SSLCertVerificationError in Python by default. Here are three ways to handle it, from most to least secure.

Option 1: Provide your organization's CA bundle (recommended)

ca_bundle.pypython
# Point session.verify to your organization's root CA certificate
session = requests.Session()
session.verify = "/path/to/your-org-ca-bundle.pem"

# You can also set this via environment variable:
# export REQUESTS_CA_BUNDLE=/path/to/your-org-ca-bundle.pem
# Then session.verify = True will automatically use it

How to export the CA certificate

Extract certificate with opensslbash
# Method 1: Extract from the server directly using openssl
openssl s_client -connect your-server:443 -showcerts < /dev/null 2>/dev/null | \
  openssl x509 -outform PEM > pi-web-api-ca.pem

# Method 2: Extract the full chain (if there are intermediate CAs)
openssl s_client -connect your-server:443 -showcerts < /dev/null 2>/dev/null | \
  awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ print }' > pi-web-api-chain.pem

# Method 3: From a browser
# 1. Open https://your-server/piwebapi in a browser
# 2. Click the lock icon in the address bar
# 3. View the certificate chain
# 4. Export the ROOT certificate (top of the chain) as Base-64 PEM (.cer or .pem)

Option 2: Add to system trust store

Import the root CA certificate into your operating system's trusted certificate store. Then verify=True works automatically without specifying a file path.

Add to system trust storebash
# Windows: Import using certutil
certutil -addstore -user Root pi-web-api-ca.pem

# Windows: Or use python-certifi-win32 to trust Windows cert store
pip install python-certifi-win32
# After installing, requests automatically trusts certificates
# in the Windows certificate store

# Linux (Debian/Ubuntu):
sudo cp pi-web-api-ca.pem /usr/local/share/ca-certificates/pi-web-api-ca.crt
sudo update-ca-certificates

# Linux (RHEL/CentOS):
sudo cp pi-web-api-ca.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Option 3: Disable verification (testing only)

disable_verify.pypython
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

session = requests.Session()
session.verify = False

Never disable verification in production

Disabling SSL verification means your credentials travel over an unverified connection. An attacker could intercept your username and password with a man-in-the-middle attack. This is acceptable for a quick test on a trusted network, but must be resolved before production use.

Session reuse and connection pooling

Always use a requests.Session object. It reuses TCP connections and authentication state, which is significantly faster than creating a new connection for every request. For Kerberos and NTLM, session reuse also avoids re-authenticating on every call.

production_session.pypython
import requests
from requests.auth import HTTPBasicAuth
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_pi_session(base_url, username, password, ca_bundle=None):
    """Create a production-grade session for PI Web API.

    Features:
    - Connection pooling (reuses TCP connections)
    - Automatic retries on transient failures
    - Proper SSL certificate handling
    - Default headers for JSON
    """
    session = requests.Session()
    session.auth = HTTPBasicAuth(username, password)
    session.verify = ca_bundle if ca_bundle else False
    session.headers.update({
        "Accept": "application/json",
        "Content-Type": "application/json",
    })

    # Configure connection pooling and retries
    retry_strategy = Retry(
        total=3,
        backoff_factor=1,               # 1s, 2s, 4s between retries
        status_forcelist=[502, 503, 504],
        allowed_methods=["GET"],         # Only retry safe methods
    )
    adapter = HTTPAdapter(
        max_retries=retry_strategy,
        pool_connections=10,             # Number of connection pools
        pool_maxsize=10,                 # Connections per pool
    )
    session.mount("https://", adapter)

    # Verify the connection works
    resp = session.get(base_url)
    resp.raise_for_status()

    return session


# Usage
session = create_pi_session(
    base_url="https://your-server/piwebapi",
    username="DOMAIN\\username",
    password="password",
    ca_bundle="/path/to/ca-bundle.pem",  # Or None for testing
)

# All requests reuse the same connection pool
servers = session.get("https://your-server/piwebapi/dataservers").json()
points = session.get("https://your-server/piwebapi/search/query",
                      params={"q": "name:sinusoid"}).json()

Connection pool sizing

The default pool_maxsize=10 means up to 10 simultaneous connections to the PI Web API server. This is a good starting point for most integrations. If you use multithreading with more than 10 concurrent requests, increase pool_maxsize to match your thread count, otherwise threads will block waiting for a connection.

PI identity mapping

When you authenticate to PI Web API, your Windows identity is mapped to a PI Identity on the PI Data Archive. This mapping determines what PI points you can read and write. Understanding this chain is essential for troubleshooting permission errors.

Your Python code
    |
    | HTTPS (Basic/Kerberos/NTLM)
    v
PI Web API (IIS) -- authenticates you as DOMAIN\username
    |
    | PI Identity mapping (configured in PI SMT)
    v
PI Data Archive -- sees you as PI Identity "PIUser1"
    |
    | Point-level security
    v
Data access granted or denied per point

If you can authenticate to PI Web API but get 403 errors when accessing specific points, the problem is usually in the PI Identity mapping or point-level security, not in your authentication code. Work with your PI administrator to verify:

  1. Your Windows account is mapped to a PI Identity in PI System Management Tools
  2. That PI Identity has the correct Data Access level on the target points
  3. For write operations: the PI Identity has explicit write permission

Service account best practices

For production integrations that run as background services or scheduled jobs, use a dedicated service account rather than a personal account.

  • Create a dedicated Active Directory service account for your integration (e.g., SVC-PI-PIPELINE). Do not reuse personal accounts or shared accounts.
  • Grant minimum required permissions. If your integration only reads data, only grant read access. Never grant write access "just in case."
  • Store credentials securely. Use environment variables, a secrets manager (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault), or encrypted configuration. Never hardcode credentials in source code.
  • Rotate passwords on a schedule. Set a reminder to rotate the service account password and update the credential store. PI Web API sessions will fail if the password changes without updating the client.
  • Use Kerberos for service accounts on domain-joined machines. A service running as a domain account on a domain-joined Windows server can use Kerberos automatically with no password in configuration.
config_from_env.pypython
import os

# Read credentials from environment variables (never hardcode)
BASE_URL  = os.environ["PI_WEB_API_URL"]
USERNAME  = os.environ["PI_USERNAME"]      # e.g. DOMAIN\SVC-PI-PIPELINE
PASSWORD  = os.environ["PI_PASSWORD"]
CA_BUNDLE = os.environ.get("PI_CA_BUNDLE") # Path to CA cert, or empty

session = create_pi_session(
    base_url=BASE_URL,
    username=USERNAME,
    password=PASSWORD,
    ca_bundle=CA_BUNDLE or None,
)

Need help?