I don’t have a way to test python directly, but here is what chatgpt 5.6 sol says (below). If you continue to have issues, if you want to point to code repository or use the code blocking (triple ticks at start and finish ```) here on the forum, it might make further debugging a little easier.
The failure is caused by two signing errors in the Python code. The “works about half the time” behavior has a particularly strong explanation: urlsafe_b64encode() is incorrect for PAPI.
Polaris expects:
HMAC key: API access key
Signed text: HTTP method + exact URL + date + AccessSecret
Encoding: standard Base64
The screenshot instead uses the AccessSecret as the HMAC key and omits it from the signed text.
Use this implementation:
import base64
import hashlib
import hmac
from datetime import datetime, timezone
from email.utils import format_datetime
def papi_date():
return format_datetime(datetime.now(timezone.utc), usegmt=True)
def make_signature(
api_access_key,
method,
url,
date,
access_secret="",
):
# Protected requests append AccessSecret to the signed text.
message = f"{method}{url}{date}{access_secret}".encode("utf-8")
digest = hmac.new(
api_access_key.encode("utf-8"), # Always the permanent API access key
message,
hashlib.sha1,
).digest()
# PAPI requires standard Base64, not URL-safe Base64.
return base64.b64encode(digest).decode("ascii")
The authentication request does not include an AccessSecret:
date = papi_date()
auth_url = (
"https://catalog.onlib.org/PAPIService/REST/"
"protected/v1/1033/100/1/authenticator/staff"
)
signature = make_signature(
api_access_key,
"POST",
auth_url,
date,
)
headers = {
"Authorization": f"PWS {api_access_id}:{signature}",
"PolarisDate": date,
"Content-Type": "application/xml",
"Accept": "application/json",
}
response = http.request(
"POST",
auth_url,
body=body,
headers=headers,
)
After retrieving the token and secret, sign the protected request like this:
auth_result = json.loads(response.data.decode("utf-8"))
access_token = auth_result["AccessToken"]
access_secret = auth_result["AccessSecret"]
url = (
"https://catalog.onlib.org/PAPIService/REST/"
f"protected/v1/1033/100/1/{access_token}/"
"recordsets/257846/records"
"?userid=2376&wsid=943&startIndex=0&numRecords=100"
)
date = papi_date()
signature = make_signature(
api_access_key,
"GET",
url,
date,
access_secret, # Appended to the message, not used as the HMAC key
)
headers = {
"Authorization": f"PWS {api_access_id}:{signature}",
"PolarisDate": date,
"Accept": "application/json",
}
response = http.request("GET", url, headers=headers)
print(response.status)
print(response.data.decode("utf-8"))
Why the original sometimes succeeds:
base64.urlsafe_b64encode(...)
replaces + with - and / with _. Polaris expects ordinary Base64. For a SHA-1 digest, the URL-safe and standard encodings happen to be identical roughly 44% of the time—almost exactly the reported “half the time.” The official examples use standard Base64 and show signatures containing characters such as +. Polaris 7.8 authorization overview
Also:
- Put the real
AccessToken in the URI; do not use none.
- Generate one date value per request and use that exact value in both the signature and
PolarisDate header. Polaris requires RFC 1123 GMT and accepts only a ±30-minute window.
- Sign the exact URL that is transmitted, including query order and encoding.
- Pass headers directly to each request instead of mutating
http.headers, which can retain stale request state.
- Confirm that
userid=2376 is the authenticated staff user’s PolarisUserID. A mismatched user can produce -8000 or -8001.
- Polaris 7.8 can be configured for
HMACSHA256; if the server’s KeyedHashAlgorithm is set to that, change hashlib.sha1 to hashlib.sha256.
The protected-method documentation explicitly says the token belongs in the URI and the AccessSecret is appended to the data being hashed. Protected-method signing instructions The endpoint itself requires userid, wsid, startIndex, and numRecords. RecordSetRecordsGet reference
Any success using /none/ is likely the separate Polaris server-side bug support has escalated; it should not be relied upon.