Skip to content

docs(private-ai): align Private AI docs with the ACI gateway and RedPill docs - #190

Open
kingsleydon wants to merge 13 commits into
mainfrom
docs/aci-release-links
Open

kingsleydon wants to merge 13 commits into
mainfrom
docs/aci-release-links

Conversation

@kingsleydon

@kingsleydon kingsleydon commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Align the Phala Private AI section with the ACI gateway contract and RedPill documentation while retaining Phala's TEE-only endpoint and account flows.

  • API reference: use absolute API metadata so generated requests target https://inference.phala.com/v1; add an API overview, endpoint policy, missing chat parameters, correct response headers, strict session-id validation, and host-forced TEE catalog semantics. Merge Signature into Receipts and Embedding Models into Embeddings, update links and navigation, and retain receipt ownership rules.
  • Verification and security: expand receipt verification to the full keyset/signature/hash/session/evidence procedure; explain refusal receipts, middleware events, exact SSE hashing, both E2EE suites, the exhaustive supported inference endpoint list, exact per-endpoint encrypted response fields, ciphertext response hashing, keyset expiry/digests, control-plane metadata, and middleware boundaries. Add verified-upstream inspection and provider claim/ZDR matrices, including the supplemental Phala/Chutes GPU checks and ArmetAI’s separate mandatory signed NRAS boundary. Restore legacy wrapper algorithm selection and signer binding, distinguish legacy quotes from canonical ACI quotes, document all legacy nonce cases (64-hex decoding, hashing other non-empty UTF-8 strings, and random generation for omitted/empty values), and state the KMS root-key publication exception. Restore NVIDIA CC in the provider technology column where supported by the verifier docs, explicitly marking Phala/Chutes and optional NEAR AI GPU checks as supplemental with unverified JWT signatures. Check the SDK identity verdict before sending traffic.
  • Guides and integrations: add SSE/cURL, base64/multiple-image, Pydantic parsing, and MCP tool examples; add OpenAI SDK, CrewAI/PydanticAI/OpenAI Agents, Vercel OpenAI-compatible provider, and Langfuse guides. Retain the existing Cloudflare, LiteLLM, LangChain, and agent configuration coverage. Correct VS Code token configuration and Goose custom-provider setup against their upstream docs.
  • Onboarding and layout: replace marketing blocks with a concise overview and two-proof-layer explanation, rewrite the FAQ for API users, move the existing GPU questions into GPU CVM navigation, qualify the Open WebUI data boundary, standardize API-key environment references, add the Phala security contact, and add Proxy installers, theme-aware screenshots, and Homebrew tap-trust guidance.

Companion: https://github.com/redpill-ai/redpill-docs/pull/50

Necessary differences retained

  • Phala branding, https://inference.phala.com/v1, and mandatory verified upstream serving; no mixed/routed-model product flow.
  • Phala dashboard, account login, keys, workspace Balance/billing, and existing support/rate-limit context. Budget status descriptions follow control-plane authorization code rather than the previous unsupported low-Balance 403/five-minute recovery claim.
  • No RedPill Chat, account MCP/CLI, or other RedPill-only products.
  • Release measurement values and changelog remain on RedPill; Phala links to published gateway releases and the shared notice policy.
  • Existing detailed attestation/JCS/independent-verifier guidance, SDK history/browser/release limits, Claude Code model-picker caveat, Phala Proxy profile creation, receipt ownership, tools/structured-output parameters, and complete agent configuration paths are preserved.

Redirects

  • /phala-cloud/confidential-ai/confidential-model/api-reference/signature → /phala-cloud/confidential-ai/confidential-model/api-reference/receipts (Legacy alias section).
  • /phala-cloud/confidential-ai/confidential-model/api-reference/embedding-models → /phala-cloud/confidential-ai/confidential-model/api-reference/embeddings (List embedding models section).

Verification

  • python3 .scripts/validate-redirects.py: passed, all 313 redirects valid.
  • mkdir -p tmp && python3 .scripts/extract_links.py: passed; temporary output removed.
  • git diff --check: passed. Checked ownership boundaries, all internal link paths, removed-page references, absolute API metadata, and API-key placeholders; parsed all 24 changed/new Python examples.
  • Mintlify 4.2.1200 local preview on port 3931: all 41 changed/new MDX pages have returned HTTP 200 and rendered headings in Chromium across the full and focused sweeps. All nine pages edited in the final accuracy pass were re-rendered. The generated attestation request uses https://inference.phala.com/v1/aci/attestation.
  • Checked representative Proxy, provider, API, and framework pages at 390px and 1440px in light/dark themes; both screenshot assets load and the Proxy page stays within the viewport. Rechecked the final edited pages after wording corrections.
  • Public live probes: model/embedding catalogs remain TEE-only with tee=false and tee=1; zdr=false returns 400; attestation advertises both X25519 and secp256k1 keys. Model capabilities and context values were checked against the catalog.
  • Claims checked against gateway b8808773, control-plane auth.ts/zdr.ts, and linked official integration documentation. The final full-diff sweep also checked service/receipts.rs, service/e2ee.rs, dstack.rs, handlers.rs, spec/e2ee-v2.md, service/claims.rs, and the NVIDIA/Chutes JWT decode paths. No authenticated inference, billing mutation, or live deployment performed.
  • Repository-wide scan confirms the removed KMS measurement names are absent. Re-ran redirect validation, link extraction, internal-path and diff checks. E2EE and receipt pages were checked at 390px/1440px in both themes without viewport overflow. The attestation and provider pages were also re-rendered at both widths and in both themes after the final nonce/GPU-column precision edits; redirects, link extraction, and diff checks passed again.

The local preview emits an existing ReferenceError: require is not defined from scripts/generate-cli-docs.js, including on the unchanged Phala Cloud overview. It did not prevent page rendering; that shared script is outside this task's ownership boundary.

Deliberately omitted or unconfirmed

  • Image/audio API reference pages inherited from main remain unchanged by the single-source conversion; no image/audio integration guides were added.
  • Positive ZDR claims for Privatemode, 0G, and ArmetAI: the control-plane ZDR allowlist does not include them. The matrix reports routing eligibility without inventing provider retention policies.
  • The former low-Balance 403 and fixed recovery interval: unsupported by the inspected authorization code. Account/key budget denials are documented as 402 insufficient_quota. Phala Cloud Balance behavior beyond these verified quota/budget denials is not asserted.
  • Generic Phala/Chutes GPU checks do not authenticate JWT signatures or GPU identity. Documentation states this limitation throughout the Private AI claim pages; an asserted verifier_derived label does not equate them to ArmetAI’s NRAS-signed gate. The moved GPU FAQ makes no such generic-proof claim.
  • E2EE rejection descriptions apply to authenticated requests on implemented inference endpoints. Image/audio handlers can reject missing bearer credentials with 401 before checking E2EE. No image/audio integration documentation was added.
  • Screenshots show the shared Proxy app with a RedPill profile; the page explicitly instructs Phala users to choose Phala. No screenshot was altered to imply otherwise.

Single source

Shared ACI text is synced verbatim from redpill-ai/redpill-docs at 91f482a2eb15a7699545f588227be05b612ed28b. Edit shared text there, regenerate its MANIFEST, and sync it here; do not edit snippets/aci/ in Phala. SOURCE records the canonical commit exactly as the automated mirror does. snippets/site.jsx owns Phala branding, base URL, API-key environment name, dashboard, Proxy, and page paths. Release measurements remain upstream. The release-asset/monitor companion is gateway PR #546.

CI now runs .scripts/check_private_ai.py: it verifies every shared file against MANIFEST (SOURCE is mirror metadata), rejects relative Private AI api: URLs, and rejects the removed KMS measurement names throughout repository files.

Per-page convergence from 76bafea (paths below are under phala-cloud/confidential-ai/):

Page Rendered change
confidential-model/how-it-works Canonical two-proof-layer cards, gateway request-flow diagram, and local verification checklist replace the site copy; Phala's mandatory verified-serving policy remains explicit.
verify/verify-attestation Canonical field table and ordered quote/JCS/nonce/expiry/RTMR3/release/channel checks; adds the absent-nonce statement and distinguishes legacy signer binding. Phala hostname/pinning framing remains.
verify/verify-signature Canonical Node/Bun example, browser limit, release-policy anchor, passing-audit criteria, and artifact inspection workflow replace the duplicated Phala walkthrough. Phala endpoint policy and SDK-reference links remain.
confidential-model/receipts-and-sessions Canonical event-author table, receipt ownership, refusal receipts, content-addressed sessions, TTL, and exact-stream hashing; retains Phala's required verified-serving context.
confidential-model/verified-upstreams Canonical channel/session/claims explanation and Phala-direct evidence example; absorbs the separate channel-binding and TCB/claims material, including their verified policy facts and stable topic anchors.
confidential-model/providers Canonical provider evidence, TEE/binding/ZDR, and claims matrices; includes the provider-specific GPU limitations and router boundary. Phala's confidential-only catalog framing remains.
confidential-model/trust-boundary Canonical plaintext-boundary, provider binding, retention, local-tool and control-plane metadata sections; keeps the Phala dedicated-GPU deployment link and required-serving framing.
confidential-model/e2ee-encryption Canonical E2EE concept, suites, endpoint limits, encrypted-field tables, headers, ciphertext/AAD, and restored-request/wire-response hashing; Phala verified-upstream context remains.
confidential-model/e2ee-guide (new) Imports the canonical request-encryption procedure as a separate guide after the concept page, avoiding duplicate receipt-hashing anchors.
confidential-model/attested-routing Canonical session pinning, rejection/refresh rules, removed-header guidance, validation and receipt inspection. Canonical catalog wording now distinguishes mixed and TEE-only hosts; Phala framing states forced TEE filtering.
confidential-model/error-handling Canonical shared error/type/status tables; retains Phala-specific TEE-only 404 behavior, Balance/key-budget framing, SDK examples, retry guidance, and common cases.
confidential-model/api-reference/attestation Canonical field/reference/legacy body; dynamic example hostname resolves to inference.phala.com, measured-compose/TLS-binding fields are explicit, and keyset-expiry/release-approval guidance is shared.
confidential-model/api-reference/receipts Canonical receipt schema, verification checklist, E2EE hash links and legacy-wrapper signing rules; Phala absolute API frontmatter and related links remain.
confidential-model/api-reference/sessions Canonical session schema, bindings, typed claims, listing filters and full-document content addressing; Phala absolute API frontmatter and related links remain.
verify/typescript-sdk Removes duplicated installation/example/release-policy prose in favor of canonical guide links. Retains SDK transport/API behavior, local receipt history, auditing requirement, browser limits, and package reference.
confidential-model/channel-binding, confidential-model/tcb-and-claims (removed) Redirect to verified-upstreams, which now owns the canonical topics; removed from navigation. Controlled links target its precise anchors. Hosted redirect destinations omit fragments.

The FAQ, compliance page and OpenAI SDK guide only change release-policy link destinations; their rendered prose is unchanged. All other non-shared pages were unchanged in the Phase 2 snippet conversion; the later fact-check corrections are listed below. Rendered-text diffs (content area, excluding navigation/pagination) are attached as PR comments rather than repository files.

Phase 2 verification (historical; cold browser verification below supersedes these browser results)

  • GitHub Documentation Validation passed on final commit e4a2eb6.

  • python3 .scripts/validate-redirects.py, link extraction (temporary output removed), .scripts/check_private_ai.py, and staged git diff --check: passed.

  • Guard smoke checks: valid mirror passes; altered, extra or missing shared files, relative API metadata, and both prohibited measurement-name spellings fail; SOURCE is excluded from MANIFEST.

  • Exact byte comparison: all shared files and MANIFEST equal upstream cd0be31; SOURCE is that full SHA plus one newline, matching the sync workflow.

  • Chromium with pinned Mintlify 4.2.819 and current 4.2.1204: all 56 final Private AI pages returned HTTP 200 and rendered at 390/1440px in light/dark (448 page/viewport/theme states). No viewport overflow. Sample screenshots were inspected.

  • Captured 57 pre-refactor pages. All 41 non-shared final page prose diffs are empty; the 15 shared/refactored pages have the intentional convergence changes listed above. Pinned/current rendered texts are identical. No unresolved site expressions or RedPill API credentials/base URLs appear in shared Phala content.

  • Every rendered local link and fragment on shared pages resolves, including release policy, manual artifact inspection, claims, sessions, transparency events and E2EE receipt hashing. All rendered preformatted code is free of curly quotes.

  • No new runtime errors. Both the unchanged baseline and final pages emit the existing ReferenceError: require is not defined from scripts/generate-cli-docs.js; that source remains outside this task’s ownership boundary.

  • Focused final re-check: attested routing and error handling rendered again on both CLI versions, both widths and themes after canonical model_not_found was corrected to 404. No non-shared page incorrectly claimed unknown models return 400.

  • Canonical convergence corrected the shared tee-filter description to distinguish mixed and TEE-only endpoints and the unknown-model error status to 404 model_not_found. No known Phala-incompatible shared text remains. No merge or deployment performed.

Fact-check corrections (Phala-owned pages)

Commit b64210f; Documentation Validation passed.

Checked P1, P2, R2, R3a and R3b independently against gateway 77fac126 (production) and db75886 (main), control-plane 86f8472, current upstream source, and the TypeSafe SDK package sources (typesafe-sdk 0.7.5 / @typesafe-ai/sdk 0.7.0). The gateway source diff is limited to src/middleware/audio.rs; the audio text below is valid for both. No authenticated inference or production changes were made.

Applied

  • Agent instructions (P1/R2): Goose uses its actual static-key/model/streaming/base-path prompts, with context limits edited in its provider JSON; Cline has no Verify action; Cherry Studio labels are Model Provider, API Host, Check and Sync models. Updated CrewAI's native OpenAI adapter explanation and troubleshooting, verifier minimums to Node 20.18.1/Bun 1.4.0, Hermes Windows paths, and the new-session instruction. Evidence: Goose configure source, Goose provider docs, Cline settings source, Cherry UI strings, CrewAI routing, gateway apps/desktop/agent-bridge/src/agents/discovery.rs:7-18 and clients/verifier-ts/package.json.
  • FAQ/GPU FAQ (P1/P2): current control-plane contract link and pre-report endpoint/model/provider/TEE fields; archived SDK/proxy notice with current GPU CVM deployment link; removed unsupported driver/CUDA versions and quote-attachment description. GitHub API confirmed nearai/private-ml-sdk, Phala-Network/private-ml-sdk and nearai/vllm-proxy archived; Phala-Network/vllm-proxy returned 404, so no claim about that repo was added. Gateway docs/control-plane-contract.md:34-61 confirms the metadata fields.
  • Runnable examples (P2/R3b): initialize independent vision examples; use APIStatusError when accessing status_code; import time in retry example; use stable client.chat.completions.parse. Sources: OpenAI exceptions, parse helper.
  • API compatibility (P2/R3a): distinguish OpenAI /v1 base from Anthropic host-only base; show complete gateway authentication-error shape and Anthropic envelope; disable compression in both TypeSafe examples; broaden Messages stop reasons to include refusal/provider-native values; clarify providers as provider IDs and keep is_tee separate; add missing absolute API frontmatter for chat, embeddings and models. Gateway error_payload.rs:74-92, response_transform.rs:1060-1110, handlers.rs JSON parsing, Cargo.toml tower-http features, control-plane modelService.ts:389-398, Anthropic SDK messages.py /v1/messages, and both published TypeSafe packages confirm these changes.
  • Provider-dependent behavior (P2/R3a/R3b): document image auto removal and mandatory usage, remove assumed output encoding/field presence, qualify parameter support, replace automatic-cache blanket wording with provider-specific activation/client markers, and enumerate every JSON ZDR endpoint plus audio's header carrier. Gateway images.rs:149-152,219-277, request_transform.rs:1044-1048,1159-1196,1760-1791,1863, handlers.rs:561-606, and control-plane zdr.ts confirm the source-supported behavior.
  • Release labels (P2): mark source-provenance JSON as illustrative and release-dependent.

Key-dependent claims rewritten or removed

  • FLUX always returning JPEG for png: removed the specific observed behavior and .jpg instruction; documentation now says the request is forwarded and returned bytes determine the encoding. images.rs passes through the encoding request and base64 image data without transcoding.
  • FLUX always omitting size/output_format: replaced with the sanitizer rule that upstream string fields are preserved (images.rs:222-227).
  • Voxtral's exact verbose_json error text/status: removed the upstream-specific promise and error row. The parser supports JSON/verbose JSON, but provider support is not established by unauthenticated source inspection.
  • Successful live Whisper verbose_json/missing no_speech_prob: removed the ahead-of-deploy optional-field sentence and made the response description conditional on success. Both commits require usable duration; production rejects string-only duration while main also parses numeric strings. The examples use json and no successful live verbose request is claimed.
  • Responses/image/audio example usage values: explicitly illustrative; no claim that those token counts were observed live.
  • Hermes session retention: softened to starting a new session, since the inspected docs do not prove mid-session provider behavior.
  • Model-specific max_tokens rejection: no keyed upstream rejection was asserted; descriptions defer to the selected model/route and actual request errors.

Disagreements, already-correct counterparts, and items not applied

  • P2 minor receipt finding: image/audio chat_id: null needs no key to establish. Gateway src/aggregator/service/middleware.rs:279-285 explicitly initializes it to None on those endpoints; image/audio sanitizers remove id, so finalization cannot add one. Kept this verified claim. Image missing token counts filled with zero are also proven by images.rs:248-277, not a live-only observation.
  • P2 vs R3b parse verdict: beta.chat.completions.parse remains a supported alias; this is an API spelling update to the stable path, not removal of a deprecated API.
  • R3b strict schema: Phala already lists every weather property in required and has additionalProperties: false; no strict-schema fix was needed. OpenAI's strict schema requirements confirm it.
  • R3b examples/claims already correct on Phala: streaming clients are defined; tool calling uses self-contained cURL; reasoning does not say unsupported controls are ignored; ZDR does not say all confidential providers are ZDR; the host always requires verified serving, so required: true is valid. No mixed-endpoint text was added to Phala framing.
  • R3a already-correct counterparts: no default model or exhaustive narrow role list, no GPT-5 sampling example, no outdated instruct model, correct embedding response model, Responses uses the public model alias (also established by response_transform.rs identity rewriting), existing /systemone and /embeddings/models endpoint rows, and correct #param-provider links. Phala already uses its inference hostname for all ACI playgrounds; changing these to relative URLs would violate Phala's guard. Phala has no counterpart of RedPill's unpublished openapi.yaml.
  • R2 non-applicable rows: Phala already links to Claude Code's current gateway-connect page and uses a tool-capable Phala model; it does not contain the stale LangChain retrieval link or the separate RedPill Cherry Studio UI walkthrough. Corrected the stale Cherry labels that did exist in its consolidated tools page. No optional Claude Fable alias was added.

Business/legal/product-owner confirmation — left unchanged

  • Workspace Balance, no top-up/minimum, gifted-credit eligibility and other billing promises in confidential-ai-api.mdx require Phala billing ownership/source confirmation.
  • “Windows Desktop is currently Preview” is a product release-status decision; installers alone do not establish intent. Left the label unchanged for its owner.
  • The Regulatory Fit table in compliance-and-open-source.mdx, provider non-retention contract/policy promises, and the existing image/audio and billing price statements remain unchanged for their owners. ZDR endpoint/carrier changes describe enforcement behavior only.

Verification

  • Guard, redirect validation, link extraction, and diff whitespace checks pass. Temporary extraction output was removed.
  • All 19 changed Python/JavaScript examples parse; independent vision examples define their clients. The weather strict schema was checked to require every property and disallow extra properties.
  • Pinned Mintlify 4.2.819 and current 4.2.1204: all 24 changed pages returned HTTP 200 at 390/1440px in light/dark (192 page/viewport/theme states). Rendered texts match across versions, code has no curly quotes, and both TypeSafe compression flags appear in rendered code blocks. No viewport overflow or new runtime errors; the existing site-wide require is not defined baseline error remains.
  • Sample mobile/desktop light/dark screenshots inspected. No keyed live inference was performed.

Shared snippets/aci/ were unchanged in b64210f. The separate canonical mirror and cold verification below complete that follow-up.

Canonical fact-check mirror and cold verification

Mirrors snippets/aci/ byte for byte from RedPill 91f482a2eb15a7699545f588227be05b612ed28b (the ten shared-topic changes are in 72bd7f7). SOURCE is the full canonical SHA followed by one newline, exactly matching the sync workflow. Site props and anchors are unchanged.

The shared corrections cover multipart endpoint capabilities, receipt ownership/error cases, session validity/reuse and claim fields, policy-dependent required verification, router evidence limits, and control-plane metadata.

Earlier browser evidence could have used a warm cache. The previews for f6df737, e4a2eb6 and b64210f reused the same source path/preview port without proving the generated snippet output was invalidated. The pinned CLI keeps generated public/ content when .mint-content-source equals the same source directory. HTTP 200 alone did not prove the snippet revision. Their prior browser assertions and historical rendered-diff comments are superseded by the cold checks below; the independent static/source checks remain valid.

For this pass, each CLI started from its own fresh git archive HEAD export with the exact new mirror overlaid. The pinned and current export paths were distinct, so each start forced the source marker to change and the generated public content to be rebuilt. The marker was explicitly checked against the expected export path. Rendering used repository-free temporary exports; the assigned task worktree contains only the canonical mirror update.

Commit 2127fa6; Documentation Validation passed:

  • Guard, redirect validation, link extraction (temporary output removed), manifest/source byte equality, and diff whitespace checks passed.
  • Cold Mintlify 4.2.819 and current 4.2.1204 each rendered all 57 pages covered by the earlier sweeps: the full 56-page non-GPU Private AI section plus the GPU FAQ. Every page returned HTTP 200 at 390/1440px in light/dark: 456 page/viewport/theme states, with no viewport overflow.
  • All ten new-topic assertions passed on both versions. Rendered page texts matched across versions. Every captured internal target/anchor resolved; preformatted code had no curly quotes. Sample screenshots were inspected.
  • Source markers were checked in both generated runtimes: pinned Next 3945/.../client/public/.mint-content-source and current Vite 3945-beta/.../client-vite/.mint-cli/public/.mint-content-source. Each matched its distinct fresh export directory.
  • No new runtime errors. Both cold runs reproduced only the pre-existing require is not defined error from the site's CLI-generator script, outside this change's ownership boundary.
  • Root-owned user previews still require their planned cold refresh before handoff.

Each changed topic had a positive rendered-text assertion that is absent from its old snippet:

Topic New text checked in the rendered page
API attestation supported_multipart_constraint_endpoints
API receipts “Anonymous receipts may be publicly retrievable”
API sessions “End of validity for new forwarding decisions”
Attested routing “Re-verification can reuse an unexpired session”
Errors “An owned receipt is fetched without the original bearer token”
How it works “When verified serving is required”
Providers “models can share an attested session”
Receipts “required records policy, not the verification result”
Trust boundary sessionKind
Verified upstreams “Every verified session carries at least one enforceable binding”

The cold pass also checked prior Phala-owned fixes in rendered output, including Goose/Cline/Cherry instructions, Hermes Windows paths, archived GPU SDK framing, conditional Whisper verbose output, image usage, Anthropic base URL, TypeSafe compression flags, APIStatusError, stable parse, provider-dependent caching, and ZDR endpoint coverage.

@kingsleydon kingsleydon changed the title docs(confidential-ai): link published gateway releases from attestation and open-source pages docs(private-ai): align Private AI docs with the ACI gateway and RedPill docs Nov 4, 2026
@kingsleydon

kingsleydon commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Historical Phase 2 rendering evidence: these previews did not prove cache freshness. The latest cold verification in the PR body supersedes the browser-validation claims here; this diff records the earlier conversion, not the final fact-checked text.

Single-source convergence, rendered text against 76bafea (part 1/2). Browser text includes page title/description/content; navigation/pagination is excluded. The two removed topic pages redirect to verified-upstreams. All 41 non-shared Private AI pages have an empty rendered prose diff (including the three approved href-only updates).

phala-cloud/confidential-ai/confidential-model/api-reference/attestation.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/api-reference/attestation.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/api-reference/attestation.mdx

@@ -40,4 +40,6 @@

 workload_keyset	object	subject, not_after, receipt_signing_keys, e2ee_public_keys, and tls_public_keys.
 evidence	object	quote, quote_report_data, event_log, vm_config, app_compose, key_custody, and downstream_tls_binding.
+evidence.app_compose	string	Booted compose whose SHA-256 must match the measured compose-hash in RTMR3.
+evidence.downstream_tls_binding	object	domain and spki_sha256 identify the keyset TLS entry to pin for this deployment’s hostname.
 ​
 Example (long values truncated)
@@ -69,6 +71,5 @@

   "service_capabilities": { "supported_e2ee_versions": ["2"], "serving": "aggregator" }
 }
-
-not_after is part of the JCS-encoded keyset. Changing it changes workload_keyset_digest, even when the public keys stay the same. Cache and verify the complete keyset.
+not_after is part of the JCS-encoded keyset. Changing it changes workload_keyset_digest, even when the public keys stay the same. Cache and verify the complete keyset; pin release approvals to reviewed compose hashes rather than the keyset digest.
 ​
 Verifying the report

phala-cloud/confidential-ai/confidential-model/api-reference/receipts.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/api-reference/receipts.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/api-reference/receipts.mdx

@@ -23,8 +23,11 @@

 id
 stringrequired
-A receipt id (rcpt-…) or the response id (chat id). The receipt id is returned in the x-receipt-id response header on /v1/chat/completions.
+A receipt id (rcpt-…) or the response id (chat id). The receipt id is returned in the x-receipt-id response header on /v1/chat/completions. Only unconstrained, non-E2EE streams on an endpoint that also serves non-TEE models can be early-committed and omit it; for those, use the response id after a successful stream. ACI-constrained and E2EE streams are never early-committed, including all inference streams on a TEE-only endpoint.
+​
+Authentication and ownership
+Use the same bearer API key that made the inference request. Receipt lookup without a bearer token returns 401; lookup with a different token returns 404, so another key cannot discover the receipt. This also applies to the legacy alias.
 ​
 Response
-200 OK, application/json. The bare canonical receipt. Use the same API key that made the inference request; a different key returns 404, and a missing key returns 401 for a credential-bound receipt:
+200 OK, application/json. The bare canonical receipt:
 FIELD	TYPE	DESCRIPTION
 api_version	string	ACI version token, for example aci/1.
@@ -56,5 +59,5 @@

   "chat_id": "d3ad808b20a444c29e798db35f112033",
   "workload_keyset_digest": "sha256:3eff0836…",
-  "model": "z-ai/glm-5.3",
+  "model": "phala/gemma-4-26b-a4b-uncensored",
   "endpoint": "/v1/chat/completions",
   "method": "POST",
@@ -62,7 +65,7 @@

   "event_log": [
     { "type": "request.received", "body_hash": "sha256:111d08a5…" },
-    { "type": "route.selected", "target_route_id": "<selected-route>" },
-    { "type": "upstream.verified", "model_id": "z-ai/glm-5.3",
-      "result": "verified", "required": true, "session_id": "3681736b…" },
+    { "type": "route.selected", "target_route_id": "phala:gemma-4" },
+    { "type": "upstream.verified", "model_id": "phala/gemma-4-26b-a4b-uncensored",
+      "result": "verified", "required": true, "session_id": "ecda679a01d29724ccb12f24e5007f536a50668b544d9c87d04c5ef8a1bddd3a" },
     { "type": "response.returned", "body_hash": "sha256:07703fb4…" }
   ],
@@ -71,4 +74,5 @@

 }
 
+The session id is a snapshot from the public sessions list; use the id in your own receipt.
 ​
 Verifying the receipt

phala-cloud/confidential-ai/confidential-model/api-reference/sessions.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/api-reference/sessions.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/api-reference/sessions.mdx

@@ -35,5 +35,5 @@

 expires_at	number	Retention deadline (Unix seconds).
 identity	object	Verified upstream identity keys (for example a signing address).
-channel_binding	array	The enforceable binding(s): TLS SPKI, E2EE public key, or measured co-deployed proxy image/credential.
+channel_binding	array	The enforceable binding(s): tls_spki_sha256, e2ee_public_key_sha256, or proxy_image_sha256 (Privatemode).
 claims	object	Typed claims (below).
 evidence	object	Byte-preserving evidence the verifier checked (digest + data URI).
@@ -69,5 +69,4 @@

 GET /v1/aci/sessions?upstream_name={upstream_name}&model={model} returns current attested sessions. The endpoint is public. upstream_name is a service-chosen route label such as chutes or phala-267, rather than a catalog provider id. Use it as a preflight survey: inspect the verified identity, channel binding, and claims for a model before sending a prompt.
 curl "https://inference.phala.com/v1/aci/sessions?upstream_name=chutes"
-
 Returns { "api_version": "aci/1", "sessions": [...] }. Each list entry includes its session_id but omits the bulky evidence.data; fetch the full record before verifying its content hash.
 ​

phala-cloud/confidential-ai/confidential-model/attested-routing.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/attested-routing.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/attested-routing.mdx

@@ -5,4 +5,5 @@

 Restrict a request to an upstream the gateway verified inside the TEE, and pin it to a specific attested channel.
 
+The Phala endpoint is TEE-only: upstream verification is required even when the provider block is omitted. No request option disables this policy. This host always filters the catalog to TEE-capable models, even with tee=false or tee=1; omit the query to get the same catalog. The separate zdr filter accepts only true.
 Attested routing restricts a request to an upstream that the gateway verified before sending the prompt. Verification runs inside the TEE, produces an enforceable channel binding, and fails closed: if no eligible upstream verifies, the prompt is never forwarded.
 Use it when a prompt must not reach a provider whose execution environment you cannot check. The examples below use a confidential model; see List the models you can use to find one.
@@ -11,8 +12,7 @@

 provider: {"aci_verified": true}	Any upstream that is currently verified and channel bound.
 provider: {"aci_session_ids": [...]}	One specific attested channel, by session id.
-The Phala endpoint is TEE-only: upstream verification is required even when this block is omitted. aci_verified can express that requirement explicitly, and aci_session_ids narrows it to specified verified channels. No request option disables the endpoint policy. See Provider Verification.
 ​
 Require an attested upstream
-Add the provider block to the request. Every inference endpoint accepts it: /v1/chat/completions, /v1/messages, /v1/responses, /v1/embeddings, and /v1/completions.
+Add the provider block to the request. The following JSON inference endpoints accept it: /v1/chat/completions, /v1/messages, /v1/responses, /v1/embeddings, and /v1/completions.
 curl https://inference.phala.com/v1/chat/completions \
   -H "Authorization: Bearer $PHALA_AI_API_KEY" \
@@ -23,16 +23,8 @@

     "provider": {"aci_verified": true}
   }'
-
-If no upstream for the requested model is both classified as attested and currently verified, the request fails before the prompt is sent. For example, an unavailable verified route can return:
-{
-  "error": {
-    "message": "upstream verification failed: no attested upstream available for model z-ai/glm-5.3",
-    "type": "upstream_verification_failed"
-  }
-}
-
-The status is 503 (upstream_verification_failed). Failover retries stay inside the attested set, so a plaintext upstream never receives the prompt as a fallback.
-Setting "aci_verified": false, or omitting the field, adds no request-level constraint. The endpoint still requires verified serving.
-A request that requires an attested upstream cannot also opt out of the attestation. No request header or option relaxes this constraint once aci_verified is set.
+When verification is required by the request or endpoint policy, a model that passes admission but has no eligible verified, attested upstream fails with 503 upstream_verification_failed before the prompt is sent. Failover retries stay inside the attested set, so an unverified upstream never receives the prompt as a fallback.
+Admission can reject a request earlier: a TEE-only endpoint rejects an existing non-TEE model with 404, and routing constraints such as provider.zdr can leave no eligible route (404). An unknown model returns 404 model_not_found.
+On an endpoint that also serves non-TEE models, setting "aci_verified": false or omitting it adds no request-level attestation constraint. A TEE-only endpoint forces verification even when the client sends false or omits the field. This does not waive validation: combining false with session pins is still rejected as contradictory.
+Requests that send the removed X-Upstream-Verification header return 400 with type invalid_request_error and the message X-Upstream-Verification is no longer supported; use provider.aci_verified. Remove the header and use the request-body controls above.
 ​
 Pin a request to one attested channel
@@ -42,8 +34,7 @@

   -H "Authorization: Bearer $PHALA_AI_API_KEY" \
   | jq -r '.sessions[] | "\(.session_id)  \(.upstream_name)  \(.endpoint)"'
-
-Each entry carries a session_id as a bare 64-character lowercase hex digest with no prefix, the verifier that produced it, its channel binding, and an expires_at timestamp. ?upstream_name= filters the same list by upstream. See Get Session.
+Each entry carries a session_id as 64 lowercase hex characters, the verifier that produced it, its channel binding, and an expires_at timestamp. ?upstream_name= filters the same list by upstream. See Attested sessions.
 The most reliable id is the one that already served you. Every response’s receipt names the session that served it, so a client can pin follow-up requests to the channel it just used.
-Pin a request to one or more of them. The example id below was obtained from the live sessions endpoint; replace it with a currently accepted id before sending:
+The example id is a snapshot from the public sessions list. Replace it with a current id for your model before sending the request. Pin a request to one or more of them:
 curl https://inference.phala.com/v1/chat/completions \
   -H "Authorization: Bearer $PHALA_AI_API_KEY" \
@@ -54,18 +45,19 @@

     "provider": {
       "aci_verified": true,
-      "aci_session_ids": ["50b1e4ac17b1f10906595d322269b69e75ba15e03adb147490fa28eba4de8fc1"]
+      "aci_session_ids": ["c352e3e6f871bcb370d89bec98785c9311ddb062e31b33c556bd2ece0b7d0d86"]
     }
   }'
-
-Listing several ids means any one of them may serve the request. If none of them matches a currently verified channel, the request fails with 412 (session_not_accepted) and no prompt is sent:
+Listing several ids means any one of them may serve the request. If none of them matches a currently verified channel, the request fails with 412 and no prompt is sent:
 {
   "error": {
-    "message": "upstream verification failed: none of the requested attested sessions is available for model z-ai/glm-5.3",
-    "type": "session_not_accepted"
+    "message": "none of the requested attested sessions is available for model z-ai/glm-5.3; pinned sessions may have been superseded by re-verification, so re-list /v1/aci/sessions and retry",
+    "type": "session_not_accepted",
+    "code": null,
+    "param": null
   }
 }
 
 A session id is tied to one verified channel, and the gateway re-verifies periodically. When it re-verifies, the evidence changes and the id changes with it. Read a current id shortly before you pin it, and expect a pinned id to stop matching. A pin that stops matching is a failure, never a silent fallback to a different channel.
-Every session id must be exactly 64 lowercase hexadecimal characters; duplicate ids are collapsed.
+Do not retry a 412 with the same unavailable pins; inspect current sessions and approve a new set before changing the request.
 aci_session_ids implies aci_verified. Passing session ids together with "aci_verified": false is rejected as a contradiction rather than resolved in either direction.
 ​
@@ -73,22 +65,22 @@

 curl -s "https://inference.phala.com/v1/models?tee=true" \
   | jq -r '.data[].id'
-
 Embedding models have their own catalog, which takes the same parameter:
 curl -s "https://inference.phala.com/v1/embeddings/models?tee=true" \
   | jq -r '.data[].id'
-
-This host always filters to TEE-capable models, even with tee=false or tee=1. Omit the query to get the same TEE-only catalog. The separate zdr filter accepts only true; other values return 400.
+On an endpoint that also serves non-TEE models, tee accepts only true; any other value, including false, returns 400. Omit the parameter for its full catalog. On a TEE-only endpoint, the filter is forced: any tee value, or omitting it, returns only TEE models.
+The independent zdr filter accepts only true on either endpoint type; omit it to leave retention unconstrained. ?zdr=true composes with the endpoint’s TEE filter. TEE-only serving does not imply zero data retention.
 ?tee=true and provider: {"aci_verified": true} read different properties. The catalog filter reports a model’s own is_tee capability flag. The routing constraint is enforced against the provider actually serving the request.
 A model listed under ?tee=true can still return 503 for an attested request when none of its live deployments is on an attested provider. The catalog tells you which models are offered confidentially. Only the gateway decides what a given request may reach.
+The requested model id, claims.extra.canonical_model_id, and measured --served-model-name may use different spellings; inspect the mapping under your policy rather than hard-coding a normalization rule.
 ​
 Request validation
-The gateway parses the attestation fields itself and rejects anything it cannot read, rather than treating it as an absent constraint. Every case below returns 400 with type invalid_request_error and forwards nothing.
+The gateway parses the attestation fields itself and rejects anything it cannot read, rather than treating it as an absent constraint. Invalid entries below return 400 with type invalid_request_error and forward nothing; the table also shows valid controls.
 REQUEST	RESULT
 "provider": {"aci_verified": true}	Constraint applied.
-"provider": {"aci_verified": false}	No extra request constraint; endpoint policy still applies.
+"provider": {"aci_verified": false}	No extra constraint on a mixed endpoint; a TEE-only endpoint still requires verification.
 "provider": {"aci_verified": "yes"}	400, expected a boolean.
 "provider": {"aci_session_ids": []}	400, must not be empty.
-"provider": {"aci_session_ids": [""]}	400, each id must be exactly 64 lowercase hexadecimal characters.
-"provider": {"aci_verified": false, "aci_session_ids": ["<64 lowercase hex>"]}	400, contradictory.
+"provider": {"aci_session_ids": [""]}	400, expected 64-character lowercase hex strings.
+"provider": {"aci_verified": false, "aci_session_ids": ["c352e3e6f871bcb370d89bec98785c9311ddb062e31b33c556bd2ece0b7d0d86"]}	400, contradictory.
 "provider": ["aci_verified"]	400, the block must be an object.
 aci_verified and aci_session_ids are read by the gateway and removed before the request reaches a provider. They compose with the other provider fields, such as zdr.
@@ -97,5 +89,5 @@

 The check runs before any prompt bytes leave the gateway:
 The route is classified as an attested provider.
-Verification is required for that route and cannot be waived by the request.
+Verification is required for that route and cannot be waived by a request header.
 Verification returns a verified result and an enforceable channel binding.
 The request is forwarded over the bound channel.
@@ -104,19 +96,17 @@

 ​
 Verify that it held
-A successful attested request returns x-receipt-id. Fetch that receipt and read its upstream.verified event:
+A completed attested request returns x-receipt-id. ACI-constrained streams, including all TEE-only endpoint streams, and E2EE streams are never early-committed. Fetch the receipt after completion and read its upstream.verified event:
 curl -s "https://inference.phala.com/v1/aci/receipts/$RECEIPT_ID" \
   -H "Authorization: Bearer $PHALA_AI_API_KEY" \
   | jq '.event_log[] | select(.type == "upstream.verified")'
-
 {
   "type": "upstream.verified",
-  "model_id": "z-ai/glm-5.3",
+  "model_id": "phala/gemma-4-26b-a4b-uncensored",
   "result": "verified",
   "required": true,
-  "session_id": "50b1e4ac17b1f10906595d322269b69e75ba15e03adb147490fa28eba4de8fc1"
+  "session_id": "ecda679a01d29724ccb12f24e5007f536a50668b544d9c87d04c5ef8a1bddd3a"
 }
 
-These fields record the gateway’s assertion that the constraint held. Inspection alone does not verify it; verify the receipt signature, hashes, and cited session with the TypeScript SDK or Private AI Proxy. session_id names the channel that served the request, and is the id to pin for follow-up requests. The cited session holds channel_binding, typed claims, and evidence; inspect its upstream_name rather than assuming a catalog provider name. See Verify a Response.
-For Phala-direct sessions, inspect extra.canonical_model_id as well as the public request model. Provider names may differ from catalog ids; accept a mapping only when it satisfies your policy.
+The example session id is a snapshot. In your receipt, session_id names the channel that served the request and is the id to pin for follow-up requests. Inspecting "result": "verified" and "required": true with curl and jq does not verify the receipt signature or the gateway’s attestation. Verify those and the request and response hashes before accepting the receipt as proof that the constraint held. See Verify a response.
 The request you sent is bound into the same receipt through request.received.body_hash, so you can prove which constraint you asked for alongside the result you got.
 ​

phala-cloud/confidential-ai/confidential-model/channel-binding.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/channel-binding.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/channel-binding.mdx

@@ -1,31 +0,0 @@

-Security Model
-Channel Binding
-Copy page
-
-Learn how the gateway binds confidential upstream verification to the actual request channel.
-
-Verifying an upstream’s attestation is not enough by itself. The gateway must also prove it sends the prompt to the same verified endpoint. Channel binding closes that gap.
-For a confidential response, gateway verification and binding are one fact: if the provider cannot be verified and bound, the prompt is not forwarded.
-​
-Binding Types
-BINDING	WHAT IT IS	HOW IT IS ENFORCED
-tls_spki_sha256	SHA-256 digest of the upstream TLS public key (SPKI).	The gateway pins the HTTPS connection to that key before forwarding.
-e2ee_public_key_sha256	SHA-256 digest of the upstream E2EE public key.	The gateway encrypts the provider-facing request to the verified enclave key.
-proxy_image_sha256	Privatemode proxy image digest and startup-checked shared credential digest.	The gateway requires the binding to match the measured co-deployed proxy and sends plaintext only to its configured internal origin; the proxy handles provider-facing encryption.
-The binding type depends on the provider. See Providers.
-​
-Fail-Closed Forwarding
-When upstream.verified.required is true, the gateway must verify the upstream and enforce the binding before forwarding. If verification fails, or if the binding cannot be enforced on the live connection, the gateway rejects instead of sending the prompt.
-On the Phala TEE-only endpoint, required is true for every routed request. A failed verification refuses forwarding; it does not fall back to an unverified provider.
-​
-How to Confirm Binding
-You do not enforce the provider binding from your client. You verify that the gateway did:
-Verify the Attestation Report.
-Fetch the Receipt.
-Verify the receipt signature and exact request/response body_hash values; require upstream.verified.result: verified, required: true, and a session_id.
-Fetch the full Session, verify its content address and evidence, and inspect channel_binding under your policy.
-The TypeScript SDK and Private AI Proxy implement client-side checks. cURL inspection alone does not establish channel binding.
-​
-Related
-Trust Boundary
-TCB and Claims

phala-cloud/confidential-ai/confidential-model/e2ee-encryption.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/e2ee-encryption.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/e2ee-encryption.mdx

@@ -5,5 +5,11 @@

 Field-level encryption to an attested gateway key, supported endpoints, and receipt hashing.
 
+Every model on Phala’s TEE-only endpoint uses a verified upstream. E2EE adds client-to-gateway field encryption to that serving policy.
 End-to-end encryption (E2EE) encrypts selected request and response fields between your client and the attested gateway, on top of TLS. Decryption happens inside that gateway so it can process the request. E2EE binds the decryption key to the measured workload; it does not hide plaintext from that workload or its in-process middleware. See Trust Boundary.
+​
+What E2EE adds
+Key provenance. The encryption key is published in the quote-bound keyset.
+Field-level encryption. AEAD binds encrypted fields to the request context.
+For setup and code, see the E2EE encryption guide.
 ​
 Get an attested encryption key
@@ -11,5 +17,4 @@

 curl -fsS "https://inference.phala.com/v1/aci/attestation?nonce=$(openssl rand -hex 32)" \
   | jq '.attestation.workload_keyset.e2ee_public_keys'
-
 The service advertises "2" in service_capabilities.supported_e2ee_versions.
 KEY ID	ALGORITHM	EPHEMERAL PUBLIC-KEY ENCODING
@@ -18,10 +23,10 @@

 X25519 is recommended for browser-native clients. The secp256k1 suite remains supported for existing EVM and dstack clients. Both use ECDH, HKDF-SHA256, and AES-256-GCM. Select the suite from the attested key entry’s algo; browser document verification alone cannot pin the actual TLS certificate SPKI.
 ​
-Supported endpoints
+Supported endpoints and suites
 ENDPOINT	E2EE V2
 /v1/chat/completions	Buffered and streaming
 /v1/completions	Buffered and streaming
 /v1/embeddings	Buffered
-Only these three inference endpoints support E2EE v2. Every other inference endpoint implemented by this gateway rejects authenticated E2EE requests with 400 e2ee_unsupported_endpoint. This includes Responses, Messages, System One, and the image and audio routes. Authentication checks can run first; the image and audio handlers return 401 when the bearer token is missing.
+Only these three inference endpoints support E2EE v2. Every other inference endpoint implemented by this gateway rejects authenticated E2EE requests with 400 e2ee_unsupported_endpoint. Authentication checks can run first; a missing bearer token can return 401.
 Encrypt at least one content-bearing request field. Use the protocol’s field locations for chat messages, completion prompts, and embedding input.
 ​

phala-cloud/confidential-ai/confidential-model/e2ee-guide.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/e2ee-guide.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/e2ee-guide.mdx

@@ -0,0 +1,48 @@

+Security Model
+Encrypt a request
+Copy page
+
+Send field-level encrypted requests using Phala’s attested gateway key.
+
+Use your Phala API key with https://inference.phala.com/v1. Every model on this endpoint requires verified upstream serving.
+E2EE encrypts selected request and response fields between your client and the attested gateway. TLS still protects the connection; E2EE adds field-level encryption whose public key comes from the gateway’s attested keyset. This guide shows how to send an E2EE request. For the trust model and what E2EE does and does not protect, read End-to-end encryption.
+E2EE is decrypted inside the attested gateway so it can process your request. It proves the decryption key belongs to the attested workload; it does not hide the request from that workload. For the upstream to be attested too, use a confidential model.
+​
+When to use it
+Use E2EE when you want field-level encryption bound to a key you can trace to the attestation report, in addition to TLS. If TLS is sufficient for your threat model, you can call the API normally without these headers.
+​
+Supported endpoints
+E2EE v2 is supported only on /v1/chat/completions and /v1/completions for buffered or streaming responses, and on /v1/embeddings for buffered responses. Every other inference endpoint rejects authenticated E2EE requests with 400 and type e2ee_unsupported_endpoint. A missing bearer token can be rejected with 401 before the E2EE check. See the gateway endpoint handlers.
+​
+1. Get the gateway E2EE key
+Fetch the attestation report and read the E2EE public key from the attested keyset. Verify the report first (see Verify a response) so you know the key is bound to a genuine TEE.
+curl -s "https://inference.phala.com/v1/aci/attestation?nonce=$(openssl rand -hex 32)" \
+  -H "Authorization: Bearer $PHALA_AI_API_KEY" \
+  | jq -r '.attestation.workload_keyset.e2ee_public_keys[] | select(.key_id=="dstack-kms-e2ee-x25519-v1") | .public_key'
+This selects x25519-aes-256-gcm-hkdf-sha256, recommended for browser-native clients. Existing secp256k1 clients can instead select dstack-kms-e2ee-v1, whose algorithm is secp256k1-aes-256-gcm-hkdf-sha256. Generate your client key on the same curve as the selected key.
+​
+2. Send E2EE headers
+Include these headers on your request. Do not send X-Signing-Algo; that selects the legacy path.
+HEADER	VALUE
+X-E2EE-Version	2
+X-Client-Pub-Key	Your public key for the selected suite, hex encoded.
+X-Model-Pub-Key	The gateway E2EE public key from step 1.
+X-E2EE-Nonce	32 fresh random bytes encoded as 64 hexadecimal characters, without 0x.
+X-E2EE-Timestamp	Current Unix seconds, within 300 seconds of gateway time.
+​
+3. Encrypt the selected fields
+Each encrypted field is the lowercase hex encoding of:
+ephemeral_public_key || aes_gcm_nonce (12 bytes) || ciphertext || tag (16 bytes)
+
+The ephemeral public key is 32 raw bytes for X25519 or 65 uncompressed SEC1 bytes for secp256k1. Use a fresh ephemeral key and AES-GCM nonce per field.
+The AEAD associated data binds the ciphertext to the protocol version, direction, algorithm, model, field path, nonce, and timestamp. Any mismatch fails decryption.
+Assembling the ciphertext and AAD by hand is error-prone. Use the official client or verifier helpers, which implement the key exchange, AAD construction, and field selection for you.
+​
+Receipts with E2EE
+An encrypted response carries X-E2EE-Applied: true, X-E2EE-Version: 2, and X-E2EE-Algo set to the selected key’s algorithm, alongside x-receipt-id. These headers are hints, not cryptographic proof.
+Reproduce request.received.body_hash by replacing encrypted request fields with their decrypted, restored values and compactly serializing the JSON body. Hash this serialization, not the encrypted request bytes.
+For response.returned.body_hash, hash the exact JSON or SSE wire bytes, including ciphertext and streaming framing, before decrypting response fields. Separately verify each field’s AEAD tag and associated data. See Receipts with E2EE.
+​
+Related
+E2EE concept
+Verify a response

phala-cloud/confidential-ai/confidential-model/error-handling.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/error-handling.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/error-handling.mdx

@@ -6,6 +6,6 @@

 
 ​
-Error Response Format
-Errors return a JSON body with an error object and an HTTP status code:
+Error response format
+Errors return a JSON body with an error object and an appropriate HTTP status:
 {
   "error": {
@@ -17,26 +17,30 @@

 }
 
-The type field is the machine-readable discriminator.
-TYPE	MEANING
-authentication_error	Missing or invalid API key.
-invalid_request_error	Malformed request body or unsupported parameter.
-model_not_found	The requested model id is unavailable.
-not_found_error	The model exists but no serving route can satisfy the request constraints, for example provider: {"zdr": true}.
-session_not_accepted	None of the requested attested session ids is currently accepted (412); refresh and verify the session before retrying.
-upstream_verification_failed	Required upstream verification failed or no eligible attested upstream is available (503); no prompt is forwarded.
-upstream_error	The upstream provider failed or timed out.
+The type field is the machine-readable discriminator. Common values:
+TYPE	WHEN
+authentication_error	Missing or invalid API key (401).
+invalid_request_error	Malformed body or an unsupported parameter (400).
+model_not_found	The model id is unknown or has no route for this endpoint (404).
+insufficient_quota	The account quota or key budget is exhausted (402).
+rate_limit_error	Over the requests or tokens per minute limit (429).
+not_found_error	An existing non-TEE model on a TEE-only endpoint, or a model with no provider under the request’s routing constraints, for example provider: {"zdr": true} (404).
+upstream_error	The upstream provider is unavailable (502).
+upstream_verification_failed	Required upstream verification did not produce an enforceable verified binding; no prompt was forwarded (503).
+session_not_accepted	None of the pinned attested sessions could serve the request; no prompt was forwarded (412).
 ​
-Status Codes
+Status codes
 STATUS	MEANING
-400	Malformed request or invalid parameter.
-401	Missing or invalid API key.
-402	Account quota or API key budget exhausted (insufficient_quota).
-403	Authenticated identity lacks permission.
-404	Unknown or non-TEE model, no eligible route under the request constraints, or an unknown/expired receipt or session.
-412	No requested attested session is currently accepted (session_not_accepted).
-429	Rate limited.
-500	Gateway server error.
-502	Upstream provider unavailable.
-503	Service temporarily unavailable, including required upstream verification failure (upstream_verification_failed).
+400	Bad request (invalid parameters)
+401	Unauthorized (missing or invalid API key)
+402	Account quota or key budget exhausted
+403	Forbidden
+404	An unknown model (model_not_found), an existing non-TEE model on a TEE-only endpoint, no eligible route under request constraints, or an unknown/expired receipt or session
+412	None of the pinned attested sessions could serve the request (session_not_accepted)
+429	Rate limit exceeded
+500	Server error
+502	Upstream provider unavailable
+503	Service temporarily unavailable, including required upstream verification failure (upstream_verification_failed)
+A TEE-only endpoint rejects an existing non-TEE model with 404; an unknown model returns 404 model_not_found. On an endpoint that also serves non-TEE models, requesting verified serving for a routed model fails with 503 upstream_verification_failed after admission. On either endpoint type, a model admitted for verified serving can return that 503 when no eligible upstream verifies. A separate routing constraint, such as provider.zdr, can instead reject the request with 404 before upstream verification.
+The Phala host is TEE-only: unknown or non-TEE models return 404. No request option relaxes the endpoint policy.
 Account quota or API key budget exhaustion returns 402 insufficient_quota. Check the Phala dashboard for workspace Balance and key limits.
 ​

phala-cloud/confidential-ai/confidential-model/how-it-works.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/how-it-works.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/how-it-works.mdx

@@ -5,21 +5,31 @@

 The two proof layers and request flow behind Phala’s TEE-only inference API.
 
-Phala’s Attested Confidential Inference (ACI) gateway runs inside an Intel TDX TEE. It publishes an attestation report that binds its keyset to hardware evidence, then signs a receipt that binds each request and response to that keyset.
+Every model on https://inference.phala.com/v1 requires verified upstream serving. Ordinary SDK calls use TLS; use a verification client to establish the gateway identity and pin the channel.
+The platform runs inference behind an Attested Confidential Inference (ACI) gateway. The gateway is a workload running inside a Trusted Execution Environment (TEE, Intel TDX). It does two things a normal API cannot:
+It proves which code is running, in a real TEE, with an attestation report.
+It signs a per-response receipt that ties your exact request and response to that attested workload.
+You do not have to trust the platform’s word. You fetch these artifacts and check the signatures and hashes yourself. This page explains the model. To run the checks, see Verify a response.
 ​
-Two proof layers
-ARTIFACT	WHAT IT ESTABLISHES	ENDPOINT
-Attestation report	Gateway workload evidence, nonce freshness, measured compose, key custody, and public keyset	GET /v1/aci/attestation?nonce=
-Signed receipt	Request and response hashes, routing events, and the verified upstream session for one exchange	GET /v1/aci/receipts/{id}
-A receipt is meaningful after you establish the gateway’s identity and appraise its measurements under your policy. The attestation report tells you which receipt-signing keys belong to the measured workload; a signature alone does not establish successful confidential serving.
+The two proof layers
+Attestation report
+Proves the gateway is a specific workload running in a genuine TEE, and publishes the keys it signs with. Fetch it from GET /v1/aci/attestation?nonce=.
+Signed receipt
+Proves what the gateway received and returned for one request, signed by a key from the attested report. Fetch it from GET /v1/aci/receipts/{id}.
+The report answers “who served me?”. The receipt answers “what did they do with my request?”. A receipt is only meaningful once you have verified the report, because the report is what tells you the receipt’s signing key is bound to a genuine TEE.
 ​
 Request flow
-Your client establishes the gateway identity and pins the connection when using an ACI-aware transport. Ordinary SDK calls only use TLS.
-The gateway receives your request, decrypts any E2EE fields, and records its hash.
-The backend verifies an eligible upstream and enforces its channel binding before forwarding. This Phala endpoint always requires verified serving. A failed required check refuses forwarding.
-The gateway observes the final response bytes and signs a receipt. Retrieve it using x-receipt-id, or the response id after completion when the stream committed headers before a receipt id was allocated.
+You call the API over TLS, or add E2EE for field-level encryption.
+The gateway records your request and computes its hash.
+For confidential serving, the backend verifies the upstream provider and enforces a channel binding before forwarding. If it cannot, it does not send your prompt (fail-closed).
+The response returns through the gateway, which signs the receipt after it has observed the final response bytes.
+On a TEE-only endpoint, every admitted inference request requires verified serving. On an endpoint that also serves non-TEE models, require provider.aci_verified: true or session pins to prevent an unverified routed provider from serving the request. The catalog’s is_tee flag alone does not prove how a response was served.
 ​
-Upstream sessions
-The receipt’s upstream.verified event must have result: "verified", required: true, and a session_id. Fetch the cited session to inspect the provider boundary, channel binding, claims, validity window, and evidence. Verify its content address and evidence digest before applying your policy.
-A model CVM, router, and broker establish different boundaries. Gateway attestation does not independently prove every provider’s model weights, CPU-to-GPU link, or software provenance. See Providers and Trust Boundary.
+What you can verify
+After one request you hold enough to check, locally:
+The gateway is a genuine TEE workload with a known identity (attestation report).
+The receipt was signed by a key that report endorses.
+The signed request hash matches the request you sent, and the signed response hash matches the response you received.
+For a confidential model, the upstream was verified and the channel was bound before your prompt was forwarded.
+Continue with the concept pages below, or jump to the hands-on Verify a response guide.
 ​
 Related

phala-cloud/confidential-ai/confidential-model/providers.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/providers.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/providers.mdx

@@ -5,9 +5,10 @@

 Understand the evidence and channel bindings for each provider in Phala’s live catalog.
 
-Every model served at https://inference.phala.com/v1 uses the TEE-only policy. The Phala gateway requires upstream verification and an enforceable channel binding before forwarding your prompt. Evidence coverage varies by provider: a verified router, a model instance, and a provider broker prove different boundaries.
-The public model catalog lists serving providers in providers. The receipt identifies the session that actually served a response; inspect that session’s upstream_name, verifier_id, channel_binding, claims, and evidence. A route label such as phala-267 is distinct from the catalog provider phala.
+Phala serves confidential models only. Check the current model catalog and the session cited by your receipt for the actual serving provider.
+A confidential model is served by an upstream provider whose enclave the gateway verifies before forwarding your prompt. Each provider produces one enforceable channel binding and a set of typed claims. This page summarizes what each provider proves.
+The receipt’s upstream.verified.session_id identifies the attested session that served a request. Fetch that session for the provider, channel binding, and verified claims. The platform adds confidential providers over time, so treat the cited session as the authoritative per-response record rather than this list.
 ​
 Provider evidence
-These are the providers in the live catalog. Availability changes; the catalog and each response’s verified session remain authoritative.
+Availability changes; the catalog and each response’s verified session remain authoritative.
 CATALOG PROVIDER	WHAT THE GATEWAY VERIFIER CHECKS AND BINDS	EVIDENCE BOUNDARY
 phala	dstack TDX quote, nonce and signing-address/TLS-SPKI report-data binding, measured compose, and available GPU evidence; pins upstream TLS.	A Phala dstack model CVM. GPU claims are supplemental nonce-matched checks; the generic GPU path does not authenticate JWT signatures or GPU identity.
@@ -28,14 +29,15 @@

 Privatemode	Contrast-attested secret path via a measured proxy	proxy_image_sha256	Not in the ZDR routing allowlist
 0G	See session evidence	e2ee_public_key_sha256	Not in the ZDR routing allowlist
-ArmetAI (fortanix)	Intel TDX + NVIDIA CC	tls_spki_sha256	Not in the ZDR routing allowlist
+Fortanix (ArmetAI)	Intel TDX + NVIDIA CC	tls_spki_sha256	Not in the ZDR routing allowlist
 The GPU qualifiers follow the Phala-direct, Chutes, and NEAR AI verifier paths. They describe supplemental GPU checks, not authenticated GPU identity; NEAR AI preserves its optional result in claims.extra.gateway_gpu_check while leaving gpu_attested unknown.
 A tls_spki_sha256 binding pins the upstream HTTPS connection to the verified TLS key.
 An e2ee_public_key_sha256 binding encrypts the request body to the verified enclave key, so only that enclave can decrypt it.
 A proxy_image_sha256 binding records provider, proxy_image_digest, and credential_sha256 for Privatemode’s co-deployed proxy. The gateway checks the credential and forwards only to the private proxy in its measured deployment; the proxy verifies and encrypts the provider hop.
-Zero data retention means the provider does not keep prompt or completion content after serving a request. It rests on the provider’s contract or published policy, not on the attestation report. The ZDR column describes the routing allowlist, not an attestation claim; absence from that list does not establish a provider’s retention policy. To enforce the property per request, send provider: {"zdr": true}. See Zero data retention.
+Zero data retention means the provider does not keep prompt or completion content after serving a request. It rests on the provider’s contract or published policy, not on the attestation report. The ZDR column describes the routing allowlist, which contains phala, tinfoil, near-ai, and chutes. Absence from that list does not establish a provider’s retention policy. To enforce the property per request, send provider: {"zdr": true}. See Zero data retention.
+The ZDR constraint applies independently on mixed and TEE-only endpoints; a verified TEE upstream is not automatically in the ZDR allowlist.
 ​
 What each provider attests
 Claims are honest about their source. A claim is asserted only when that provider’s verifier backs it; anything not established is Unknown. See Claims for the vocabulary.
-CLAIM	TINFOIL	NEAR AI	CHUTES	PHALA	PRIVATEMODE	0G	ARMETAI
+CLAIM	TINFOIL	NEAR AI	CHUTES	PHALA	PRIVATEMODE	0G	FORTANIX (ARMETAI)
 tee_attested	Hardware-proven	Hardware-proven	Hardware-proven	Hardware-proven	Verifier-derived	Hardware-proven	Hardware-proven
 tcb_up_to_date	Verifier-derived	Tri-state	Tri-state	Tri-state	Unknown	Tri-state	Tri-state
@@ -52,20 +54,6 @@

 ArmetAI’s armet-ai verifier asserts serving software against a configured operator measurement pin and GPU evidence against nonce-bound NRAS reports. The pin does not independently prove reproducible software or model-weight provenance. Its TCB claim is tri-state and OS provenance remains unknown. See the ArmetAI verification contract.
 ​
-Binding methods
-BINDING	MEANING
-tls_spki_sha256	Pins the provider-facing HTTPS connection to a verified TLS key.
-e2ee_public_key_sha256	Encrypts the provider-facing request to a verified key.
-proxy_image_sha256	Binds Privatemode forwarding to the co-deployed proxy’s measured image (proxy_image_digest) and startup-checked shared credential digest (credential_sha256); plaintext is sent only to its configured internal origin.
-Bindings and typed claims are in the session, referenced by the receipt’s upstream.verified.session_id. The receipt event records model_id, result, required, and session_id; do not assume it embeds all verification details.
-​
-Claim coverage
-Claims have lowercase statuses asserted, refuted, or unknown, and a source that distinguishes hardware proof, verifier derivation, and provider/operator assertions.
-tee_attested describes the verified TEE boundary above. Check its source as well as its status: Privatemode reports verifier_derived, based on the measured proxy and its Contrast-attested secret path, rather than hardware_proven.
-tcb_up_to_date can be refuted for a stale TCB; successful verification does not silently turn it into an up-to-date claim.
-serving_software_known_good depends on the verifier’s measurement/provenance policy.
-gpu_attested depends on the provider verifier and evidence. Supplemental Phala/Chutes results do not authenticate GPU identity; inspect the reason and evidence for the actual assurance boundary.
-model_weights_provenance is not generally proven. Treat unknown as unproven.
-Zero data retention is a separate property. Send provider: {"zdr": true} when the provider must not keep prompt or completion content after serving. See Zero Data Retention.
-Ordinary clients do not verify each response. Use the TypeScript ACI verifier or Private AI Proxy for client-side verification.
+Router-backed providers
+Some providers (NEAR AI, Tinfoil) front many models behind one TEE gateway. The gateway verifies that gateway channel, so it produces one attested session per router, and the served model is recorded on the receipt. Per-model coverage is delegated to the verified router, which attests its own backend model enclaves.
 ​
 Related

phala-cloud/confidential-ai/confidential-model/receipts-and-sessions.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/receipts-and-sessions.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/receipts-and-sessions.mdx

@@ -5,58 +5,64 @@

 Learn how per-response receipts and attested sessions prove what happened to a Private AI request.
 
-Receipts and sessions are the two response-level artifacts you use after verifying the gateway attestation report.
-A receipt is the signed record for one response.
-A session is the immutable verified security context for a confidential upstream channel.
-​
-Receipt Lifecycle
-A completed inference response identifies its receipt with x-receipt-id. A stream whose headers were committed before the id was allocated may instead require lookup by response id after completion. Fetch it with Get Receipt:
+On Phala’s TEE-only endpoint, require upstream.verified.result: verified and required: true; failed verification refuses forwarding.
+A receipt is the gateway’s signed record of one request. It is an ordered event log that captures what the gateway received, which upstream it selected and verified, what it forwarded, and what it returned, with a hash at each step. The gateway signs the receipt with a key published in its attestation report, so a verified receipt proves the response came from the attested workload and was not altered.
+Completed inference responses include the receipt id in x-receipt-id, except for early-committed, unconstrained, non-E2EE streams on a mixed endpoint; see Streaming. Fetch the receipt:
 curl "https://inference.phala.com/v1/aci/receipts/$RECEIPT_ID" \
   -H "Authorization: Bearer $PHALA_AI_API_KEY"
+{id} accepts the receipt id (rcpt-…) or the response id (chat id). See GET /v1/aci/receipts/{id} for the response shape. Use the same API key that made the request: a missing bearer token returns 401, and a different token returns 404.
+​
+What a receipt commits to
+GET /v1/aci/receipts/{id} returns the receipt directly, signed by a key from the attested keyset:
+FIELD	MEANING
+receipt_id, chat_id	The receipt id and the response/chat id.
+workload_keyset_digest	Must match the attestation report you verified.
+endpoint, method, served_at	The route, HTTP method, and time served.
+event_log	The ordered transparency events below.
+signature	Signature over the receipt, by a receipt_signing_keys entry in the attested keyset.
+The event_log carries the two hashes that matter most: request.received.body_hash (the request the gateway observed) and response.returned.body_hash (the response it returned). Comparing these to the bytes you sent and received proves the receipt covers your exact exchange.
+The legacy alias GET /v1/signature/{id} returns the same receipt wrapped in a compatibility envelope that adds a text field ("<request body hash>:<response hash>"), a top-level signature, and signing_address, for earlier API clients. The outer signature uses a separate legacy signer; it is not the embedded receipt’s ACI signature. Verify the embedded receipt with the signing key from the canonical ACI keyset. New integrations should use /v1/aci/receipts/{id} directly.
+​
+The transparency event log
+The event_log records the request’s path through the gateway. Each event carries a hash or a fact, never your prompt text.
+EVENT	AUTHOR	WHAT IT RECORDS
+request.received	Frontend	Hash of the request body the gateway observed.
+middleware.forwarded	Frontend	Effective body hash after any middleware rewrite.
+route.selected	Backend	The target route id the backend accepted.
+request.forwarded	Backend	Hash of the provider-facing request body.
+upstream.verified	Backend	The upstream verification result and session reference (see below).
+response.received	Backend	Hash of the provider response before any post-processing.
+response.returned	Frontend	body_hash of the final response bytes.
+The signed receipt includes request.received.body_hash and response.returned.body_hash, which lets a verifier bind the receipt to the exact bytes you sent and received.
+​
+The upstream.verified event
+This event records the upstream verification result and references the verified session. The provider boundary and its evidence are in that session:
+{
+  "type": "upstream.verified",
+  "model_id": "Qwen/Qwen3.6-35B-A3B-FP8",
+  "result": "verified",
+  "required": true,
+  "session_id": "5fbb0570097746289ee33ce69778bc917d9ec2db895788bc274a48c9771827fc"
+}
 
-The receipt records ordered events with hashes and facts, not prompt text.
-EVENT	WHAT IT RECORDS
-request.received	Hash of the request body observed by the gateway.
-middleware.forwarded	Hash of the body after middleware processing.
-route.selected	Selected backend route.
-request.forwarded	Hash of the provider-facing request.
-upstream.verified	model_id, result, required, and the session_id for verification details.
-response.received	Hash of the provider response before post-processing.
-response.returned	Hash of the final wire response.
-​
-What a Receipt Proves
-A verified receipt proves:
-The receipt was signed by a key in the attested gateway keyset.
-workload_keyset_digest matches the verified attestation report.
-The exact request bytes match request.received.body_hash, and the returned response bytes match response.returned.body_hash.
-The required upstream verification result is recorded. On this TEE-only endpoint required is true; a failed check refuses forwarding.
-Receipt signatures cover the JCS form of the full receipt with signature removed. Resolve key_id in the verified keyset’s receipt_signing_keys; the Phala key is dstack-kms-receipt-ed25519-v1 (ed25519). Hash comparisons alone do not verify the signature or attestation. Use the TypeScript SDK or Private AI Proxy.
-​
-Streaming Responses
-For streaming, x-receipt-id can arrive before the stream ends. Fetch the receipt after the stream completes because response.returned.body_hash covers the full response body.
-​
-Attested Sessions
-When the gateway verifies a confidential upstream, it records an attested session. A receipt references it from upstream.verified.session_id.
-A session captures:
-Verified upstream identity and endpoint.
-Enforced channel binding.
-Typed claims and reasons.
-Evidence digest and evidence payload.
-The session_id is the bare 64-character lowercase SHA-256 digest of the complete session document in JCS form, with no prefix. It is not a field in the full document. If the material changes, the id changes. The list endpoint returns { "api_version": "aci/1", "sessions": [...] }; entries include session_id but omit evidence.data. Fetch the full record before checking the content address. Filter the list with ?upstream_name=chutes or ?model=z-ai/glm-5.3.
-​
-Trace a Response
-response -> x-receipt-id
-receipt  -> upstream.verified.session_id
-session  -> provider identity, binding, claims, evidence
-
-The receipt records the attested gateway’s assertion about the upstream. Independent upstream auditing also verifies the cited session’s evidence and binding. Coverage varies by provider; apply the claims your policy requires.
-​
-Refusal receipts
-Security refusals can also carry a signed receipt and x-receipt-id, including failures of required upstream verification. Preserve and verify that receipt to inspect the refusal. A valid signature does not imply successful confidential serving; check the verification result explicitly.
+The session id is a snapshot from the public sessions list; use the id in your own receipt.
+For a confidential model, result is verified, required is true, and session_id references the attested session the channel was bound to.
+On a TEE-only endpoint, every successful inference requires result: "verified", required: true, and a session_id. On an endpoint that also serves non-TEE models, a non-attested routed response records result: "failed", required: false, and no session_id. Requiring verified serving prevents that response rather than allowing an unverified fallback.
+Signed refusal receipts for 412 session_not_accepted and 503 upstream_verification_failed also arrive via x-receipt-id: they record result: "failed", required: true, a reason, and no session_id or request.forwarded event; response.returned hashes the error body.
 ​
 Why hashes, not bodies
-Receipts retain hashes and request-path metadata rather than prompt and completion text. Recomputing the hashes detects changes without copying content into the receipt store. Keep your exact request and response bytes if you need to verify them later.
+The gateway does not store your request or response. Receipts hold hashes, so they prove integrity without warehousing your data. A request rewrite appears as a difference between request.received.body_hash and request.forwarded.body_hash; a response rewrite appears as a difference between response.received.body_hash and response.returned.body_hash.
+​
+Verify the signature and session
+Receipt signatures cover the JCS form of the full receipt with signature removed. Resolve key_id in the verified keyset’s receipt_signing_keys. Hash comparisons alone do not verify the signature or attestation. Use the ACI TypeScript SDK or Private AI Proxy.
+​
+Attested sessions
+The session endpoint returns the verified security context cited by upstream.verified.session_id: the upstream identity, binding, typed claims, validity window, and evidence. Its id is the bare 64-character lowercase SHA-256 digest of the complete session in JCS form, and is not a field in the full document. Fetch the full record before checking its content address; list entries omit evidence.data.
+The receipt records the gateway’s assertion about the upstream. Independent upstream auditing also verifies the session’s evidence and binding. Coverage varies by provider; apply your own claims policy.
+Receipts are kept in memory with a configurable TTL. Fetch them promptly and archive them if you need durable evidence.
 ​
 Streaming
-For SSE, hash the exact response-body bytes, including data: prefixes, newlines, and the final [DONE] frame. Concatenating token text or reserializing parsed JSON does not reproduce response.returned.body_hash. Fetch the receipt after the stream ends. For E2EE, hash the encrypted response bytes before decryption; the request hash uses the restored compact JSON described in E2EE.
+For normal streams, x-receipt-id is sent before the body finishes, and the signed receipt is stored after the terminal body is observed. On an endpoint that also serves non-TEE models, an unconstrained, non-E2EE stream can instead be committed as HTTP 200 before the upstream answers. That response has no x-receipt-id; after a successful stream, fetch the receipt by the response id. ACI-constrained and E2EE streams are never early-committed; a TEE-only endpoint forces the ACI constraint for every inference request.
+In both cases, fetch the receipt after the stream ends: response.returned.body_hash covers the full streamed body. An upstream failure after early commit arrives as an in-band error and creates no receipt.
+Hash the raw SSE bytes, including every data: line, blank line, and final [DONE] frame, rather than the reassembled text. For E2EE, hash response ciphertext before decryption; the request uses restored compact JSON. See Receipts with E2EE.
 ​
 Related

@kingsleydon

kingsleydon commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Historical Phase 2 rendering evidence: these previews did not prove cache freshness. The latest cold verification in the PR body supersedes the browser-validation claims here; this diff records the earlier conversion, not the final fact-checked text.

Single-source convergence, rendered text against 76bafea (part 2/2). Browser text includes page title/description/content; navigation/pagination is excluded. The two removed topic pages redirect to verified-upstreams. All 41 non-shared Private AI pages have an empty rendered prose diff (including the three approved href-only updates).

phala-cloud/confidential-ai/confidential-model/tcb-and-claims.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/tcb-and-claims.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/tcb-and-claims.mdx

@@ -1,61 +0,0 @@

-Verification
-TCB and Claims
-Copy page
-
-Interpret typed claims in receipts and attested sessions, including source, status, and TCB freshness.
-
-Confidential responses carry typed claims about the upstream that served the request, including TCB (Trusted Computing Base) freshness. Claims appear in the attested session referenced by the receipt’s upstream.verified.session_id; the canonical receipt event does not embed the full claim set.
-A claim is not just a boolean. It has a status, a source, and often a reason.
-{
-  "tee_attested": {
-    "status": "asserted",
-    "source": "hardware_proven",
-    "reason": "verified TEE quote and bound request channel"
-  }
-}
-
-​
-Claim Status
-STATUS	MEANING
-asserted	Evidence backs this claim.
-refuted	Evidence contradicts this claim, such as a stale TCB.
-unknown	The verifier did not establish this claim. Treat it as not proven.
-​
-Claim Source
-SOURCE	ASSURANCE
-hardware_proven	Comes from verified TEE quote or collateral.
-verifier_derived	Computed by the verifier from checked evidence.
-provider_asserted	Published by the provider, not independently proven.
-operator_asserted	Declared by the gateway operator.
-​
-Common Claims
-CLAIM	MEANING
-tee_attested	An attested TEE identity is bound to the channel, directly or through the measured proxy; inspect source.
-tcb_up_to_date	The platform Trusted Computing Base is current.
-os_known_good	The OS or platform image maps to reviewed source or known measurements.
-serving_software_known_good	Serving software meets the verifier’s acceptance policy, such as Sigstore provenance or an operator measurement pin. Inspect the reason.
-gpu_attested	Provider-specific GPU check outcome; inspect the verifier and reason before treating it as authenticated evidence.
-model_weights_provenance	Model weights and quantization match a reviewed provenance chain.
-Privatemode reports tee_attested: asserted with source: verifier_derived under verifier privatemode-proxy/co-deployed-contrast/v1. It relies on the measured co-deployed proxy and its Contrast-attested secret path, rather than a provider quote verified directly by the gateway. Its other common claims are unknown.
-​
-TCB Freshness
-For verifiers that expose a TCB status, tcb_up_to_date is tri-state:
-Up-to-date evidence asserts the claim.
-Stale evidence refutes the claim and records the stale status.
-Missing evidence leaves the claim unknown.
-Tinfoil is an exception: its verifier requires an up-to-date TCB but exposes no separate TCB status, so the claim is asserted as verifier_derived. Your policy decides whether a refuted TCB is acceptable; inspect the cited session’s reason and source.
-​
-GPU Attestation
-The assurance depends on the provider verifier. For Phala and Chutes, gpu_attested: asserted records an accepted, nonce-matched supplemental NVIDIA check. Those paths decode JWTs without verifying their signatures, so this claim does not authenticate GPU identity or bind it to the serving CPU TEE.
-ArmetAI instead requires an NRAS-signed GPU result and commits the exact GPU evidence into the CPU quote’s report data. Its claim has the same verifier_derived source label, but a different evidence boundary. Inspect the reason and raw session evidence. Neither path proves model-weight provenance or continued use of a particular GPU for your request. See Providers.
-​
-Suggested Policy
-For direct hardware proof, require tee_attested: asserted from hardware_proven. This policy excludes Privatemode’s verifier_derived sessions.
-Require upstream.verified.result = verified and required = true for sensitive prompts.
-Treat unknown as not proven.
-Decide whether to reject tcb_up_to_date: refuted.
-Appraise the reason and provider-specific evidence for serving_software_known_good and gpu_attested; an asserted status alone does not establish provenance or authenticated GPU identity.
-​
-Related
-Providers
-Get Session

phala-cloud/confidential-ai/confidential-model/trust-boundary.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/trust-boundary.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/trust-boundary.mdx

@@ -5,12 +5,13 @@

 Understand Phala’s attested gateway, provider evidence coverage, and where plaintext exists.
 
-Every model on https://inference.phala.com/v1 uses the TEE-only serving policy. The Phala gateway requires upstream verification and channel binding before forwarding; if that fails, it refuses the request. The evidence covers different boundaries for different providers.
+Phala’s endpoint requires verified serving for every request. Use a GPU TEE deployment when you need control over the complete serving workload.
+Confidential computing establishes specific workload and channel facts. This page describes what the attested gateway protects and where plaintext and metadata are visible.
 ​
 What the gateway protects
-Gateway identity: A fresh attestation report binds the gateway keyset to hardware evidence.
+Gateway identity: A fresh attestation report binds the gateway keyset to hardware evidence. A reviewed-release claim also requires an independently authenticated compose-hash allowlist.
 Per-response integrity: A signed receipt binds request and response hashes to that keyset.
-No body retention in receipts: Receipts contain hashes and verification facts rather than prompt or response bodies.
-Required upstream verification: The gateway verifies the provider boundary and enforces channel binding before forwarding.
-Optional retention constraint: provider: {"zdr": true} restricts routing to serving routes that do not retain prompt or completion content. See Zero Data Retention.
+No body retention: The gateway does not store request or response bodies. Receipts contain hashes and verification facts rather than prompt or response bodies.
+Confidential upstreams: For confidential models, the gateway verifies the provider boundary and enforces channel binding before forwarding, fail-closed.
+Optional retention constraint: provider: {"zdr": true} restricts routing to the ZDR allowlist on either endpoint type. TEE-only serving does not itself imply provider non-retention. See Zero Data Retention.
 ​
 Limits
@@ -36,5 +37,5 @@

 ​
 Apply a policy
-Verify the receipt and its session, then require the claims your workload needs. Add a compose allowlist from authenticated release metadata when you need reviewed gateway releases. Add zero data retention when post-request storage matters. Use a custom GPU TEE deployment when you need control over the complete serving workload.
+Verify the receipt and its session, then require the claims your workload needs. Add a compose allowlist from authenticated release metadata when you need reviewed gateway releases. Add zero data retention when post-request storage matters. Appraise the provider session when you need assurance about the complete serving workload.
 ​
 Middleware and control-plane metadata

phala-cloud/confidential-ai/confidential-model/verified-upstreams.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/confidential-model/verified-upstreams.mdx

+++ single-source/phala-cloud/confidential-ai/confidential-model/verified-upstreams.mdx

@@ -5,5 +5,6 @@

 How the gateway verifies a confidential provider before forwarding: the channel binding it enforces, the attested session it records, and the typed claims you read.
 
-For a confidential model, the gateway verifies the upstream enclave before it sends your prompt, and refuses to send if it cannot. Verification produces three things: a channel binding the gateway enforces on the wire, an attested session it records, and the typed claims inside that session. The receipt’s upstream.verified event ties a response to all three.
+Every model on https://inference.phala.com/v1 requires verified upstream serving.
+For confidential serving, the gateway verifies the upstream enclave before it sends your prompt, and refuses to send if it cannot. Verification produces three things: a channel binding the gateway enforces on the wire, an attested session it records, and the typed claims inside that session. The receipt’s upstream.verified event ties a response to all three.
 ​
 Channel binding
@@ -15,6 +16,11 @@

 The proxy binding covers the measured proxy deployment, not the specific provider workload that served a request. An observed provider manifest alone does not establish that workload binding.
 Every confidential provider produces exactly one binding, and it lives inside the verified attestation. A “verified” result without an enforceable binding is rejected in the gateway itself: a provider can never be verified but unpinned. Which provider uses which binding is on the Confidential providers page.
-For a confidential model, upstream.verified.required is true. If the gateway cannot verify the upstream or enforce the binding on the actual connection, it does not send your prompt. If the binding stops matching, the gateway re-verifies before forwarding; retries cannot bypass this.
-Every model on this Phala endpoint requires verified serving.
+For confidential serving, upstream.verified.required is true. If the gateway cannot verify the upstream or enforce the binding on the actual connection, it does not send your prompt. If the binding stops matching, the gateway re-verifies before forwarding; retries cannot bypass this.
+​
+Confirm the binding
+Verify the gateway attestation report and receipt signature, including the exact request and response hashes.
+Require upstream.verified.result: verified, required: true, and a session_id.
+Fetch the full session, verify its content address and evidence digest, and appraise channel_binding under your policy.
+The ACI TypeScript SDK and Private AI Proxy implement client checks. cURL inspection alone does not establish channel binding. On a TEE-only endpoint verification is required for every inference request; a mixed endpoint can also serve non-attested routed requests unless the client requires verified serving.
 ​
 Attested sessions
@@ -32,4 +38,5 @@

 ​
 Claims
+Claims are in the session cited by the receipt; the receipt’s upstream.verified event does not embed the complete claim set. TCB means Trusted Computing Base.
 A session never says “trusted”. It carries a fixed set of claims, each with a status, a source, and an optional reason, so a hardware-proven fact and an operator’s assertion never look alike.
 {
@@ -51,14 +58,18 @@

 operator_asserted	Declared by the gateway operator. Weakest.
 CLAIM	ASKS
-tee_attested	Is this a genuine CPU TEE with an identity bound to the channel?
+tee_attested	Is a TEE identity bound to the channel, directly or through a measured proxy? Inspect the source.
 tcb_up_to_date	Is the platform’s Trusted Computing Base current?
 os_known_good	Does the OS or platform image map to reviewed source?
 serving_software_known_good	Does the serving software meet this verifier’s software acceptance policy?
-gpu_attested	What provider-specific GPU check was accepted, and what does its evidence establish?
+gpu_attested	Which provider-specific GPU checks were accepted, and what do they establish?
 model_weights_provenance	Are the served weights and quantization what they claim to be?
-Two claims need care when you read them:
-When a verifier exposes a TCB status, UpToDate asserts tcb_up_to_date, another reported status refutes it, and an absent status leaves it unknown. Tinfoil instead asserts a verifier_derived claim because its verifier requires an up-to-date TCB without exposing a separate status.
-gpu_attested is provider-specific. Phala and Chutes record supplemental nonce-matched NVIDIA checks; those paths decode JWTs without verifying their signatures and do not authenticate GPU identity or bind it to the serving CPU TEE. ArmetAI uses a separate mandatory NRAS-signed check whose exact evidence is committed into the CPU quote. Both produce verifier_derived claims, so inspect the reason and evidence rather than inferring assurance from that label. Neither proves continued use of a particular GPU for the request.
-To use claims in a decision: require tee_attested to be asserted with source hardware_proven; treat serving_software_known_good: asserted as stronger than any provider_asserted claim about software; decide your own policy on tcb_up_to_date: refuted; read gpu_attested as supplemental; and treat every unknown as not proven. Which provider asserts which claim is on the Confidential providers page.
+Privatemode reports tee_attested: asserted with source: verifier_derived under privatemode-proxy/co-deployed-contrast/v1. It relies on the measured co-deployed proxy and its Contrast-attested secret path rather than a provider quote verified directly by the gateway. Its other common claims remain unknown.
+​
+TCB freshness
+When a verifier reports a separate TCB status, tcb_up_to_date is tri-state: UpToDate asserts it, another reported status refutes it, and an absent status leaves it unknown. Tinfoil is an exception: its verifier requires an up-to-date TCB but exposes no separate status, so a verified result asserts the claim with source verifier_derived.
+​
+GPU attestation
+gpu_attested is provider-specific. For Phala and Chutes, an asserted verifier_derived claim records an accepted, nonce-matched supplemental NVIDIA check. These paths decode JWTs without verifying their signatures. They do not authenticate GPU identity, bind it to the serving CPU TEE, or prove continued use of a particular GPU. ArmetAI uses a separate, mandatory signed NRAS check, with the GPU evidence committed in the CPU quote’s report data. Neither path proves model-weight provenance or continued use of a particular GPU for your request. See the claim mappings.
+For direct hardware proof, require tee_attested to be asserted with source hardware_proven (this policy excludes Privatemode’s verifier_derived sessions); treat serving_software_known_good: asserted as stronger than any provider_asserted claim about software; decide your own policy on tcb_up_to_date: refuted; appraise gpu_attested using the provider-specific limits above; and treat every unknown as not proven. Which provider asserts which claim is on the Confidential providers page.
 ​
 Fields to check in extra
@@ -72,5 +83,5 @@

 compose_hash_verified	true	The measured compose matches what was declared
 report_data_nonce_matched	true	The upstream evidence matches the upstream verifier’s challenge; this is separate from your gateway-attestation nonce
-gpu_evidence_nonce_matched	true when GPU evidence is present	Supplemental GPU payload matches the challenge; this does not authenticate its JWT signature or GPU identity
+gpu_evidence_nonce_matched	true when GPU evidence is present	Supplemental check reports a nonce match; not authenticated GPU identity or CPU binding
 tls_spki_from_report_data	true	The TLS channel binding comes from hardware measurement, not a spoken claim
 trust_boundary	phala-dstack-cvm	
@@ -85,5 +96,4 @@

           | .app_compose | fromjson | .docker_compose_file] | join("\n")' \
 | grep -E 'image:|--served-model-name'
-
 ​
 Related

phala-cloud/confidential-ai/verify/typescript-sdk.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/verify/typescript-sdk.mdx

+++ single-source/phala-cloud/confidential-ai/verify/typescript-sdk.mdx

@@ -7,49 +7,6 @@

 @phala/aci-verifier is the official TypeScript implementation of the ACI client checks. Node.js and Bun applications can use it to establish an instance-scoped, SPKI-pinned connection before sending model traffic. The package is ESM-only and supports Node.js 20.18.1 or newer and Bun 1.4.0 or newer.
 ​
-Install
-npm install @phala/aci-verifier openai
-
-​
 Create a verified connection
-import OpenAI from "openai";
-import { connectAci } from "@phala/aci-verifier/runtime";
-
-const apiKey = process.env.PHALA_AI_API_KEY;
-if (!apiKey) throw new Error("PHALA_AI_API_KEY is required");
-
-const aci = await connectAci({
-  baseURL: "https://inference.phala.com/v1",
-  serving: {
-    requireVerified: true,
-    requireReceipt: true,
-  },
-});
-
-try {
-  if (!aci.identity.transcript.verdict.verified) {
-    throw new Error(aci.identity.transcript.verdict.line);
-  }
-
-  const client = new OpenAI({
-    baseURL: aci.baseURL,
-    apiKey,
-    fetch: aci.fetch,
-  });
-
-  const response = await client.chat.completions.create({
-    model: "z-ai/glm-5.3",
-    messages: [{ role: "user", content: "Explain attestation." }],
-  });
-
-  const audit = await aci.verifyReceipt();
-  if (!audit.transcript.verdict.verified) {
-    throw new Error(audit.transcript.verdict.line);
-  }
-
-  console.log(response.choices[0].message.content);
-} finally {
-  await aci.close();
-}
-
+Follow Node and Bun setup for installation and a complete connection and receipt-audit example.
 connectAci() verifies a fresh nonce-bound workload report and creates a hostname-validated TLS transport pinned to the attested SPKI. Its scoped fetch rejects non-HTTPS and cross-origin requests, expired workload identities, TLS key mismatches, and missing receipt ids, and requires verified serving in JSON inference requests.
 ​
@@ -63,8 +20,5 @@

 ​
 Release policy
-The SDK does not check whether the gateway runs a production or development dstack OS image. For OS-image assurance, verify the same quote, event log, and VM configuration with an independent dstack verifier and require is_valid: true.
-Attestation proves which compose hash was measured, but not that your organization reviewed that release. When you have an authenticated allowlist, pass its hashes as policy.acceptedComposeHashes. Never copy a hash from the endpoint and trust it on first use.
-Each production release of the gateway at inference.phala.com is published as a gateway-prod-<date> release in Dstack-TEE/private-ai-gateway. It lists the compose hash, OS and KMS values, and TLS keys, and attaches the exact measured app_compose.json so you can recompute the compose hash yourself. A change to these values is published as a prerelease at least 3 days before the rollout, or sooner for an urgent change, with the reason in the release. The KMS root key is set by the KMS operator; if it changes, the new value is published when it changes, without the 3-day notice. The release measurements page lists the current values and the full notice policy.
-Without a compose allowlist, the client verifies that the measured compose ran in a genuine TDX workload. It does not claim that Phala reviewed that release. For a production security decision, pair compose appraisal with a dstack verifier result for the same quote, event log, and VM configuration, and require is_valid: true.
+See the canonical release policy for policy.acceptedComposeHashes and independent OS verification. See release measurements for published values and notice policy.
 For the complete API and security limits, see the @phala/aci-verifier package documentation.
 Verify Attestation

phala-cloud/confidential-ai/verify/verify-attestation.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/verify/verify-attestation.mdx

+++ single-source/phala-cloud/confidential-ai/verify/verify-attestation.mdx

@@ -5,44 +5,38 @@

 Verify the Phala gateway’s TDX evidence, nonce-bound keyset, expiry, and measured compose.
 
-The attestation report describes the Phala gateway, rather than every upstream model’s execution environment. It publishes the gateway’s receipt-signing, TLS, and E2EE keys. Upstream evidence is in the attested session cited by a response receipt, and its coverage varies by provider.
+On Phala, verify the gateway at https://inference.phala.com and pin TLS to that host’s SPKI in the verified keyset.
+The attestation report establishes a measured workload running inside an Intel TDX TEE and publishes the keys used for receipts, TLS, and end-to-end encryption. A reviewed-release allowlist is required to turn that measurement into a claim about approved software.
+This report covers the gateway. Upstream evidence is in the attested session cited by the receipt, and its coverage varies by provider. The endpoint is public.
+Fetch it with a fresh nonce:
+curl "https://inference.phala.com/v1/aci/attestation?nonce=$(openssl rand -hex 32)" \
+  -H "Authorization: Bearer $PHALA_AI_API_KEY"
+See GET /v1/aci/attestation for the full field reference. This page explains what each part proves.
 ​
-Inspect the report
-The endpoint is public. Generate a fresh 32-byte nonce, encoded as exactly 64 lowercase hex characters:
-NONCE=$(openssl rand -hex 32)
-curl --fail-with-body -sS "https://inference.phala.com/v1/aci/attestation?nonce=$NONCE" \
-  -o report.json
-
-jq '{api_version, workload_keyset_digest,
-     report_data: .attestation.report_data,
-     not_after: .attestation.workload_keyset.not_after,
-     provenance: .attestation.source_provenance}' report.json
-
-These commands inspect the document. They do not verify a hardware quote, the nonce binding, or a receipt signature. Applications should use connectAci(); agent tools can use Private AI Proxy.
+What the report contains
+FIELD	WHAT IT PROVES
+workload_keyset_digest	A SHA-256 digest over the published keyset. Receipts cite this digest.
+attestation.tee_type	The TEE technology, for example tdx.
+attestation.evidence.quote	The hardware-signed TDX quote. This is the root of trust for everything else in the report.
+attestation.report_data	The value bound into the quote. It commits to your nonce and the published keyset, which is how you prove the quote is fresh and matches these keys.
+attestation.workload_keyset	The keyset subject, expiry, receipt-signing keys, E2EE keys, and TLS SPKI pins.
+attestation.workload_keyset.not_after	Unix timestamp after which verifiers reject the keyset.
+attestation.source_provenance	Workload-declared source labels: repo_url, repo_commit, and optional image fields. These are not a release trust anchor.
+service_capabilities	Runtime facts, including supported_e2ee_versions and serving.
+The receipt-signing keys live in attestation.workload_keyset.receipt_signing_keys, and the E2EE keys in attestation.workload_keyset.e2ee_public_keys. The legacy /v1/attestation/report uses a different quote binding for a separate legacy signer and the nonce. Its top-level signing_address, signing_algo, and signing_public_key are not the ACI receipt-signing keys. Use the canonical /v1/aci/attestation for the keyset-digest/nonce checks on this page.
 ​
-Report fields
-FIELD	MEANING
-api_version	aci/1.
-workload_keyset_digest	sha256: digest of the RFC 8785 JCS form of the complete published keyset.
-attestation.tee_type	TEE technology, currently tdx for this gateway.
-attestation.workload_keyset	subject, not_after, receipt_signing_keys, e2ee_public_keys, and tls_public_keys.
-attestation.report_data	Digest of the nonce/keyset statement bound into the quote.
-attestation.evidence	Quote, report data, boot event log, VM configuration, app_compose, key custody, and downstream TLS binding.
-attestation.source_provenance	Workload-declared source/build labels; these are not a trust root.
-service_capabilities	Supported E2EE extension versions and serving: "aggregator".
-The published receipt key uses algo: "ed25519" and key_id: "dstack-kms-receipt-ed25519-v1". Select the key from the verified report rather than hardcoding its public key.
+Why the nonce matters
+You pass ?nonce=<random>, using 32 fresh random bytes encoded as 64 lowercase hex characters. The gateway mixes your nonce into report_data inside the hardware-signed quote. Because the quote is signed by the TEE, a report that commits to a nonce you just generated cannot be a replay of an old capture. Generate a new nonce every time you verify, and confirm the report’s report_data is derived from that nonce and the published keyset.
 ​
-Verification procedure
-Verify the TDX quote. Verify attestation.evidence.quote against Intel DCAP collateral, inspect TCB status, and apply your OS and key-custody policy. A local verifier such as dcap-qvl can verify Intel evidence. The ACI SDK verifies the quote but does not check whether the dstack OS image is production or development. For OS-image assurance, verify the same quote, event log, and VM configuration with an independent dstack verifier and require is_valid: true.
-Recompute the keyset digest. Canonicalize the complete attestation.workload_keyset with JCS (RFC 8785), hash those bytes with SHA-256, prefix the lowercase hex digest with sha256:, and require equality with workload_keyset_digest. Sorting keys with jq is not a general JCS implementation.
-Check nonce binding. Build this exact statement with the recomputed digest and your nonce, without whitespace and in this field order:
+How verification uses the report
+A verifier checks, in order:
+Verify the TDX quote. Verify attestation.evidence.quote against Intel DCAP collateral, inspect TCB status, and apply OS and key-custody policy. A local verifier such as dcap-qvl can verify Intel evidence. The ACI TypeScript SDK verifies the quote but does not check whether the dstack OS image is production or development. For that assurance, verify the same quote, event log, and VM configuration with an independent dstack verifier and require is_valid: true.
+Recompute the keyset digest. Canonicalize the complete attestation.workload_keyset using RFC 8785 JCS, hash those bytes with SHA-256, and prefix the lowercase hex digest with sha256:. Require equality with workload_keyset_digest. Sorting keys with jq or json.dumps is not a general JCS implementation. Changing any keyset field, including not_after, changes the digest.
+Check nonce binding. Build these exact UTF-8 statement bytes using the recomputed digest and your nonce, without whitespace and in this field order:
 {"keyset_digest":"sha256:<hex>","nonce":"<64 lowercase hex>","purpose":"aci.report_data.v1"}
 
-Require its SHA-256 digest to equal attestation.report_data and the quote’s first 32 report-data bytes, with the remaining 32 bytes zero. A report fetched without a nonce does not establish freshness.
-Check expiry. Require attestation.workload_keyset.not_after to be a future Unix timestamp under your policy. Reject implausibly distant expiry. Receipts must cite this verified workload_keyset_digest and a receipt key in this keyset.
-Replay measured evidence. Replay the boot event log to the quote’s RTMR3. Confirm the measured compose-hash equals sha256(app_compose). Inspect the measured launcher/configuration path that selects the gateway release.
-Apply release policy. For reviewed-release enforcement, compare the measured compose hash against an independently obtained, authenticated allowlist. Production values are published in gateway-prod-<date> releases in Dstack-TEE/private-ai-gateway; see the release policy. source_provenance.repo_url and repo_commit are inspection labels; trusting them without measured corroboration does not establish a reviewed release. Pair compose appraisal with the independent OS verification in step 1.
-Bind the client channel before sending traffic. Pin hostname-validated TLS to the SPKI listed for inference.phala.com in the attested keyset. The TypeScript SDK and Private AI Proxy implement this step.
-A valid hardware quote alone does not verify a particular response. Continue with Verify a Response.
-​
-Legacy compatibility
-GET /v1/attestation/report?nonce=… returns the gateway report with compatibility signer fields. signing_algo=ecdsa (default) or ed25519 selects the legacy signer. New integrations should use /v1/aci/attestation and the keyset directly.
+Require SHA-256 of that statement to equal attestation.report_data and bytes 0 through 31 of the quote’s report-data slot. Bytes 32 through 63 must be zero. An absent nonce is represented by JSON null in the statement and does not establish freshness. See the binding implementation.
+Check expiry. Require workload_keyset.not_after to be in the future under your policy. Reject implausibly distant expiry. Later receipts must cite this verified keyset digest and a signing key in that keyset.
+Replay measured evidence. Replay the boot event log to the quote’s RTMR3 and require the measured compose-hash to equal sha256(app_compose).
+Apply release policy. For reviewed-release enforcement, require the measured compose hash in an independently obtained, authenticated allowlist. source_provenance helps locate source, but the workload declares it; it is not a release trust anchor. Pair compose appraisal with independent OS verification in step 1.
+Bind the client channel. Before sending traffic, pin hostname-validated TLS to the SPKI listed for the host in the attested keyset. The SDK’s Node/Bun transport and Private AI Proxy implement this step; a browser cannot observe the server certificate directly.
+The gateway loads its identity and signing keys from dstack KMS inside the TEE. There is no ephemeral-key or stub-quote mode: a report always carries a real hardware quote.

phala-cloud/confidential-ai/verify/verify-signature.mdx

Rendered text diff
--- 76bafea/phala-cloud/confidential-ai/verify/verify-signature.mdx

+++ single-source/phala-cloud/confidential-ai/verify/verify-signature.mdx

@@ -5,40 +5,122 @@

 Verify a Phala response with a pinned client transport, signed receipt, and cited upstream session.
 
-https://inference.phala.com/v1 requires confidential routing through Phala’s attested gateway. Ordinary HTTP clients do not verify its attestation, pin its TLS key, or verify each response receipt. curl and jq can inspect the artifacts; client-side verification requires the TypeScript ACI verifier or Private AI Proxy.
+https://inference.phala.com/v1 is Phala’s TEE-only endpoint. Every request requires verified upstream serving. Ordinary SDK calls and cURL do not verify the attestation or pin the channel; use the verification client below or Private AI Proxy.
 ​
-Choose a verification client
-CLIENT	VERIFICATION
-Node.js or Bun application	Call connectAci() before traffic, use aci.fetch, and call verifyReceipt() after consuming the response. See the TypeScript example.
-Coding agent through Private AI Proxy	The proxy verifies attestation and pins the channel before traffic, then audits receipts after delivery. See Private AI Proxy.
-Ordinary OpenAI/Anthropic SDK or cURL	Base-URL configuration alone does not perform client-side verification.
-Agent tools, MCP servers, browser automation, and telemetry remain separate trust boundaries.
+Node and Bun
+npm install @phala/aci-verifier openai
+
+Node 20.18.1+ and Bun 1.4.0+ share the same API; the /runtime export selects the pinned transport.
+import OpenAI from "openai";
+import { connectAci } from "@phala/aci-verifier/runtime";
+
+const apiKey = process.env.PHALA_AI_API_KEY;
+if (!apiKey) throw new Error("PHALA_AI_API_KEY is required");
+
+const aci = await connectAci({
+  baseURL: "https://inference.phala.com/v1",
+  serving: { requireVerified: true, requireReceipt: true },
+});
+
+try {
+  if (!aci.identity.transcript.verdict.verified) {
+    throw new Error(aci.identity.transcript.verdict.line);
+  }
+
+  const client = new OpenAI({ apiKey, baseURL: aci.baseURL, fetch: aci.fetch });
+  const response = await client.responses.create({
+    model: "z-ai/glm-5.3",
+    input: "Reply with exactly: verified",
+  });
+
+  const audit = await aci.verifyReceipt();
+  if (!audit.transcript.verdict.verified) {
+    throw new Error(audit.transcript.verdict.line);
+  }
+  console.log(response.output_text);
+} finally {
+  await aci.close();
+}
+connectAci() verifies the attestation before sending model traffic and pins TLS to a key in the attested workload keyset. Pass aci.fetch only to the client that uses this gateway; it records the exact request and response wire digests. verifyReceipt() fetches the matching signed receipt, verifies those digests, and verifies the cited upstream session. Audit a response before treating its serving path as verified.
 ​
-What the verifier checks
-Verify a fresh nonce-bound gateway quote, recompute the JCS keyset digest, check not_after, and replay the measured compose evidence. See Attestation.
-Before sending model traffic, use hostname-validated TLS pinned to a key in the attested keyset.
-Fetch the receipt named by x-receipt-id using the same credential that made the request. Require workload_keyset_digest to match the verified keyset. Resolve key_id in receipt_signing_keys and verify the Ed25519 signature over the JCS receipt with signature removed.
-Match request.received.body_hash and response.returned.body_hash to the exact request and response bytes. For SSE, the response hash includes the complete in-order stream framing. E2EE request hashes follow the transport extension’s restored-plaintext rules.
-Require a successful upstream.verified event with result: "verified", required: true, and a session_id. Fetch the full session, recompute its bare SHA-256 JCS content address, verify its evidence and binding, and apply your claim policy. The receipt’s serving time must fall within that session’s validity interval.
-A receipt event by itself is an assertion by the attested gateway. Verifying the cited session’s evidence provides the independent upstream audit; coverage varies by provider.
+Browser use
+The browser export can verify attestation and receipt documents, but browser JavaScript cannot observe the server certificate SPKI. Use the Node.js or Bun runtime when model traffic must be bound to the attested TLS key.
 ​
-Inspect existing artifacts
-The following commands only display documents for a request you already made. They do not produce a verification verdict.
-# Set RECEIPT_ID to the x-receipt-id header from your inference response.
-curl --fail-with-body -sS "https://inference.phala.com/v1/aci/receipts/$RECEIPT_ID" \
-  -H "Authorization: Bearer $PHALA_AI_API_KEY" -o receipt.json
+Release policy
+The SDK does not check whether the gateway runs a production or development dstack OS image. The deprecated requireProductionOs option is ignored. For OS-image assurance, verify the same quote, event log, and VM configuration with an independent dstack verifier and require is_valid: true.
+Without acceptedComposeHashes, the client verifies that the measured compose ran in a genuine TDX workload; it does not claim that your organization reviewed that release.
+VALUE	MEANING
+sha256(app_compose)	Bound into RTMR3; suitable for a reviewed-release allowlist
+source_provenance.repo_url and repo_commit	Workload-declared labels, useful for inspection but not a trust anchor
+For reviewed-release enforcement, load accepted hashes from authenticated release metadata and pair compose appraisal with the independent OS verification above. Release measurements lists the current values, the GitHub release that publishes each one, and the notice you get before they change. Never copy a compose hash from the endpoint and trust it on first use.
+const aci = await connectAci({
+  baseURL: "https://inference.phala.com/v1",
+  policy: {
+    acceptedComposeHashes: ["<reviewed-compose-sha256>"],
+  },
+});
+​
+What a passing audit proves
+the TDX quote binds the fresh challenge, workload keyset, and measured compose;
+the model request used hostname-validated TLS bound to an attested SPKI;
+the signed receipt binds the exact request and response bytes to that workload; and
+a required confidential upstream was verified and its cited session is valid.
+It does not prove that a release was reviewed unless an independently obtained compose allowlist was enforced.
+​
+Inspect the artifacts by hand
+The curl and jq commands inspect artifacts; they do not verify hardware quotes, receipt signatures, or the actual TLS channel. Before sending sensitive data, use connectAci() above or Private AI Proxy, with the release policy your application requires.
+Get your API key from the dashboard. These examples use plain TLS and save the exact request and response bytes before formatting them with jq.
+export PHALA_AI_API_KEY=YOUR_API_KEY
+export BASE_URL=https://inference.phala.com
+1
 
-jq '{receipt_id, workload_keyset_digest, key_id, endpoint,
-     upstream: [.event_log[] | select(.type == "upstream.verified")]}' receipt.json
+Fetch the attestation with your own nonce
 
-# Stop if the receipt does not cite a successful upstream session.
-SESSION_ID=$(jq -er '.event_log[] | select(.type == "upstream.verified" and .result == "verified") | .session_id | select(type == "string" and test("^[0-9a-f]{64}$"))' receipt.json) && \
-  curl --fail-with-body -sS "https://inference.phala.com/v1/aci/sessions/$SESSION_ID" \
-    | jq '{upstream_name, endpoint, channel_binding, claims}'
+Fetch the gateway’s attestation report before sending a request. Generate a fresh 64-character lowercase hex nonce:
+NONCE=$(openssl rand -hex 32)
+curl -fsS "$BASE_URL/v1/aci/attestation?nonce=$NONCE" \
+  -H "Authorization: Bearer $PHALA_AI_API_KEY" > attestation.json
+2
 
-The session uses upstream_name (for example chutes or phala-267). The list endpoint accepts ?upstream_name= and ?model=; it omits evidence.data, so fetch the full record for a content-address check.
-​
-Release policy and limits
-A passing audit binds the exact exchange to a genuine attested gateway, pinned TLS channel, signed receipt, and verified upstream session. It establishes a reviewed release only when you enforce an independently obtained compose allowlist. Workload-declared source_provenance is not a trust root. See the SDK release policy.
-​
-Legacy signature endpoint
-GET /v1/signature/{id} wraps the same canonical receipt for older clients. New integrations should verify /v1/aci/receipts/{id} directly.
+Send a request and save the receipt id
+
+Choose a current model from the catalog. Set provider.aci_verified to require verified, channel-bound serving.
+cat > request.json <<'JSON'
+{
+  "model": "qwen/qwen3.8-27b",
+  "messages": [{"role": "user", "content": "Reply with exactly one word: hello"}],
+  "provider": {"aci_verified": true},
+  "max_tokens": 12
+}
+JSON
+
+curl -fsS -D headers.txt -o response.json "$BASE_URL/v1/chat/completions" \
+  -H "Authorization: Bearer $PHALA_AI_API_KEY" \
+  -H "Content-Type: application/json" \
+  --data-binary @request.json
+
+jq '.choices[0].message.content' response.json
+RECEIPT_ID=$(awk 'tolower($1) == "x-receipt-id:" {gsub("\r", "", $2); print $2}' headers.txt)
+printf '%s\n' "$RECEIPT_ID"
+3
+
+Fetch the receipt
+
+curl -fsS "$BASE_URL/v1/aci/receipts/$RECEIPT_ID" \
+  -H "Authorization: Bearer $PHALA_AI_API_KEY" > receipt.json
+jq . receipt.json
+See the receipt fields and transparency events for how to read it.
+4
+
+Follow the receipt to its session
+
+Extract the successful upstream.verified event’s session_id, then fetch the full record:
+if SID=$(jq -er '.event_log[] | select(.type == "upstream.verified" and .result == "verified") | .session_id' receipt.json); then
+  curl -fsS "$BASE_URL/v1/aci/sessions/$SID" \
+    -H "Authorization: Bearer $PHALA_AI_API_KEY" > session.json
+  jq '{upstream_name, channel_binding, evidence_digest: .evidence.digest, claims}' session.json
+else
+  echo "no verified upstream session" >&2
+fi
+Keep the full session.json for verification; the printed summary omits fields. See the session reference for field meanings and claims for how to appraise the evidence.
+The verification checklist covers the checks a verifier must perform on these artifacts.
+For runtime API details and local receipt history, see the TypeScript ACI Verifier.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant