5. Send and audit the first managed request
The request now crosses the real Auth boundary. Auth checks the caller, short-lived passport, proof bound to this request, route policy and replay state before forwarding to the identified private upstream. Control Audit provides the attributed runtime decision; the upstream’s own business outcome is separate.
Previous: Create a client and grant. Next: Call the same API from Python, then change access and troubleshoot.
-
In Client Vault, select the exact site, client, key and active granted route. Inspect the canonical signing profile and Inspect command. The Copy and run smoke command is available only when that key’s profile is active for the acknowledged target version. It reads
TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE; it must not contain private key bytes. Use the checksum-verified CLIv0.2.5release for this tutorial. -
From a trusted workload environment, use the persistent
client-private-keyfrom chapter 4. Copy the values for issuer, KID, subject, trust domain, audience, route ID and Auth authority from Inspect command for your active profile. Replace every angle-bracket value below. For this exact bodylessGET /tutorial/echoroute, the following single Python command runs the released CLI once in its JSON mode, sends that same signed request, and prints only its HTTP status and JTI. It holds the CLI JSON (including the bearer token, proof and private key field) in process memory; it never prints or writes that JSON. Do not use shell tracing, a public terminal recorder, a redirected output file, or an upstream URL that bypasses Auth. A body-bearing operation needs the exact body-file handling shown in Control’s generated command and is outside this read-only example.Terminal window export TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE="$PWD/client-private-key"export TP_AUTH_BASE_URL='https://<Auth Site authority>'export TP_ISSUER='<issuer>' TP_KID='<kid>' TP_SUBJECT='<subject>'export TP_TRUST_DOMAIN='<trust domain>' TP_AUDIENCE='<audience>'export TP_ROUTE_ID='<route ID>'test -r "$TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE" || exit 1python3 - <<'PY'import base64import jsonimport osimport subprocessfrom urllib.error import HTTPErrorfrom urllib.parse import urlsplitfrom urllib.request import HTTPRedirectHandler, Request, build_openerclass NoRedirect(HTTPRedirectHandler):def redirect_request(self, *args, **kwargs):return Nonebase = urlsplit(os.environ["TP_AUTH_BASE_URL"])if base.scheme != "https" or not base.netloc or base.path not in ("", "/") or base.query or base.fragment or base.username:raise SystemExit("Use only the exact HTTPS Auth Site authority")args = ["./trustplane", "sign","--private-key-file", os.environ["TRUSTPLANE_CLIENT_PRIVATE_KEY_FILE"],"--issuer", os.environ["TP_ISSUER"], "--kid", os.environ["TP_KID"],"--subject", os.environ["TP_SUBJECT"],"--trust-domain", os.environ["TP_TRUST_DOMAIN"],"--audience", os.environ["TP_AUDIENCE"],"--route-id", os.environ["TP_ROUTE_ID"],"--key-binding", "software", "--method", "GET","--scheme", "https", "--authority", base.netloc,"--path", "/tutorial/echo", "--query", "", "--ttl", "5m"]signed = json.loads(subprocess.run(args, capture_output=True, text=True,check=True).stdout)headers = signed["headers"]token = headers["Authorization"].removeprefix("Bearer ")claims = json.loads(base64.urlsafe_b64decode(token.split(".")[1] + "=="))jti = claims["jti"]if not isinstance(jti, str) or not jti or signed["method"] != "GET" or signed["url"] != os.environ["TP_AUTH_BASE_URL"].rstrip("/") + "/tutorial/echo":raise SystemExit("CLI output does not match the selected GET route")request = Request(signed["url"], headers=headers, method="GET")try:with build_opener(NoRedirect()).open(request, timeout=20) as response:status = response.statusexcept HTTPError as error:status = error.codeprint("HTTP", status)print("Audit correlation JTI:", jti)PYThe CLI v0.2.5 JSON result has
headers,methodandurl; its--curlmode emits a ready-to-run shell command containing the bearer token, so this example deliberately does not use--curloreval. The JTI decode is for correlation only; Auth verifies the token. The transport refuses redirects so it cannot forward signed headers to another destination. -
Send a separate unsigned request to the same protected method and path. It should be denied at Auth, normally
401 missing_bearer_token. For the signed request, record the actual HTTP status, Auth decision and upstream result.Auth allowedmeans the authorization boundary passed; it does not prove the upstream business action succeeded.Terminal window curl --silent --show-error --output /dev/null --write-out '%{http_code}\n' \'https://<Auth Site hostname from active profile>/tutorial/echo' -
In Control → Audit, select the exact site and time window, then inspect the attributed decision. Compare the printed JTI from this same CLI-generated request to the
jtiin Raw evidence → Sanitized raw evidence. Confirm the subject, method, canonical path, route ID, Auth outcome, target bundle version and upstream outcome from the fields actually present. The current raw event does not include the client key ID or adapter version; verify key and active target separately in Client Vault and Auth Site status. An HTTP status alone is not Audit correlation. Early gateway denials can happen before a passport is parsed and may have no JTI; correlate those by site, time, method and reason without claiming an attributed client decision. If the signed call is redirected, fails before reaching Auth, or its Audit row is absent, do not infer authorization from the locally decoded JTI.
The 14 October 2026 first-run call returned HTTP 200 and the private sample echo response.
Its local jti exactly matched the allowed, verified Control Audit event for the same
site, caller, GET /tutorial/echo route and upstream HTTP 200. A separate unsigned call
returned 401; its Audit event reported missing_bearer_token, route not evaluated, and
upstream not reached. No customer system was involved.
If using a product-owned mock API as the private upstream, identify it as a sample upstream. It is still a deployed API reached through the real Auth Site; it is not a customer’s banking system.
Next: Show how a policy or key change propagates and how to diagnose a denial.