5.1. Call the same API from Python
This is a companion to the CLI request and Audit chapter.
Use the same Auth Site, client key and exact GET /tutorial/echo grant. The Python
application calls Auth over HTTPS; it does not call the private upstream directly.
Install the released Python SDK 0.2.3 from
PyPI in a trusted workload
environment:
python3 -m venv .venv. .venv/bin/activatepython -m pip install trustplane-auth-sdk==0.2.3In Control → Client Vault → Keys, select the active key and exact GET route, then open
Inspect command. Set the following variables from your active signing profile. For
TP_AUTH_BASE_URL, combine the profile’s https scheme and Auth Site authority;
for TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE, use the persistent private-key file created in
chapter 4. Do not paste its contents into Control or a shell command.
export TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE='./client-private-key'export TP_AUTH_BASE_URL='https://<Auth Site authority>'export TP_ISSUER='<issuer>'export TP_KID='<kid>'export TP_SUBJECT='<subject>'export TP_TRUST_DOMAIN='<trust domain>'export TP_AUDIENCE='<audience>'export TP_ROUTE_ID='<route ID>'Replace every angle-bracket value before running this complete Python request:
python - <<'PY'import osfrom pathlib import Pathfrom urllib.request import HTTPRedirectHandler, build_opener
from trustplane_auth import ProtectedClient, SigningProfile, private_key_from_base64url
class NoRedirect(HTTPRedirectHandler): def redirect_request(self, *args, **kwargs): return None
profile = SigningProfile.from_control({ "state": "active", "key_origin": "manual", "request_base_url": os.environ["TP_AUTH_BASE_URL"], "kid": os.environ["TP_KID"], "subject": os.environ["TP_SUBJECT"], "trust_domain": os.environ["TP_TRUST_DOMAIN"], "issuer": os.environ["TP_ISSUER"], "route_id": os.environ["TP_ROUTE_ID"], "canonical_route_id": os.environ["TP_ROUTE_ID"], "route_match_type": "Exact", "route_path": "/tutorial/echo", "method": "GET", "audience": os.environ["TP_AUDIENCE"],})
key_text = Path(os.environ["TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE"]).read_text().strip()private_key = private_key_from_base64url(key_text)client = ProtectedClient(profile, private_key)prepared = client.prepare( "GET", "/tutorial/echo", headers={"Content-Type": "application/json"})with build_opener(NoRedirect()).open(prepared.request, timeout=20) as response: print("HTTP", response.status) print(response.read(200).decode("utf-8").strip())print("Audit correlation JTI:", prepared.passport.jti)PYThe Content-Type header is intentional even though this GET has no body: this tutorial
Auth Site covers content-type and the SDK-generated x-trustplane-nonce in its request
transcript. A request that omits the covered header is denied before the upstream is
reached. For your own route, use the covered header set from its active profile; do not
copy this header set blindly to a different site. The SDK generates the passport, nonce,
body digest and proof. The standard-library transport above refuses redirects, so it
does not forward TrustPlane credentials to a changed destination.
Expect HTTP 200 and the private sample echo response. In Control → Audit, scope to
this site and the request time, open the allowed GET /tutorial/echo event, and compare
only the printed short-lived JTI with Raw evidence → Sanitized raw evidence. Confirm
the caller, exact route, Allowed — verified, target version and upstream HTTP 200.
The JTI is an audit identifier, not a bearer token; do not print or record the passport
or proof headers. If a request fails, inspect the Audit reason and the active profile’s
covered headers before retrying. A 403 transcript_build_failed can mean a required
covered header was omitted.
The isolated 14 October 2026 SDK verification using the released 0.2.3 wheel returned
HTTP 200; its JTI matched the allowed, verified Audit event for the same caller,
route and upstream response. No customer system was involved.