
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?
| Method | When to use | Python library | Credentials in code? |
|---|---|---|---|
| Basic | Quick tests, scripts on non-domain machines, service accounts | requests (built-in) | Yes (username + password) |
| Kerberos | Domain-joined machines, production environments, SSO | requests-kerberos | No (uses Windows ticket) |
| NTLM | Windows environments without Kerberos, legacy setups | requests-ntlm | Yes (username + password) |
| Bearer (OpenID) | Modern cloud apps, API gateways, PI Web API 2019+ | requests + token provider | No (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 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, NTLMBasic 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.
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.
pip install requests-kerberosimport 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
| Option | Behavior |
|---|---|
REQUIRED | Both client and server must prove their identity. Most secure, but fails if the server's SPN is not configured correctly. |
OPTIONAL | Client authenticates to server, but does not require the server to prove its identity back. Works in more environments. Use this if REQUIRED fails. |
DISABLED | No 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.
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 serverDelegation 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
# 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.
pip install requests-ntlmimport 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.
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)
# 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 itHow to export the CA certificate
# 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.
# 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-trustOption 3: Disable verification (testing only)
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = requests.Session()
session.verify = FalseNever 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.
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 pointIf 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:
- Your Windows account is mapped to a PI Identity in PI System Management Tools
- That PI Identity has the correct Data Access level on the target points
- 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.
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,
)