# Attestation report Source: https://docs.redpill.ai/api-reference/attestation GET https://tee.redpill.ai/v1/aci/attestation The gateway TEE attestation report, bound to a caller-supplied nonce. Returns the gateway's attestation report: the hardware-backed proof of which TEE workload is serving the API, plus the public keys it signs with. Verify this report before trusting any [receipt](/api-reference/receipts). For what each field proves, see [Attestation report](/confidential-ai/attestation-report). ```http theme={null} GET /v1/aci/attestation?nonce={nonce} ``` ## Request Exactly 64 lowercase hex characters (32 random bytes). The gateway binds it into the quote's `report_data` so you can prove the report is not a replay. ## Response `200 OK`, `application/json`. The bare attestation report. ### Top-level fields | Field | Type | Description | | - | - | - | | `api_version` | string | ACI version token, for example `aci/1`. | | `workload_keyset_digest` | string | `sha256:…` digest over the published keyset. | | `attestation` | object | The attestation evidence and keyset (below). | | `service_capabilities` | object | `supported_e2ee_versions` and the `serving` mode. | ### `attestation` object | Field | Type | Description | | - | - | - | | `tee_type` | string | TEE technology, for example `tdx`. | | `report_data` | string | Value bound into the quote. Commits to your `nonce` and the keyset. | | `source_provenance` | object | Workload-declared `repo_url`, `repo_commit`, `image_digest`, and `image_provenance` labels. | | `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`, `key_custody`. | ### Example (long values truncated) ```json theme={null} { "api_version": "aci/1", "workload_keyset_digest": "sha256:3eff0836cd104d8cb5f8e8ee361789cf9…", "attestation": { "tee_type": "tdx", "report_data": "7b7daf62d7e9e31f5d17e70d32c010bda9ead273…", "source_provenance": { "repo_url": "https://github.com/Dstack-TEE/private-ai-gateway.git", "repo_commit": "9d45c7e3d48d2f74c31cd85f1fb5c6cee1435ef3", "image_digest": null, "image_provenance": null }, "workload_keyset": { "subject": null, "not_after": 1790498751, "receipt_signing_keys": [ { "key_id": "receipt-v1", "algo": "ed25519", "public_key": "211c…" } ], "e2ee_public_keys": [], "tls_public_keys": [ { "spki_sha256": "943c…", "domain": "tee.redpill.ai" } ] }, "evidence": { "quote": "040002008100…", "quote_report_data": "7b7daf…", "event_log": "[{…}]", "vm_config": "{…}", "key_custody": { "provider": "dstack-kms", "keys": [ … ] } } }, "service_capabilities": { "supported_e2ee_versions": ["2"], "serving": "aggregator" } } ``` ## Verifying the report 1. Verify `attestation.evidence.quote` against Intel DCAP collateral, and confirm its report data binds your `nonce` and the `workload_keyset`. 2. Recompute the JCS digest of `workload_keyset` and compare it with `workload_keyset_digest`. 3. Confirm `workload_keyset_digest` matches the receipts you verify. 4. Confirm `workload_keyset.not_after` is in the future. 5. Replay the event log to RTMR3 and confirm the measured compose hash equals `sha256(app_compose)`. 6. For reviewed-release enforcement, require that compose hash in an independently obtained allowlist. Do not treat `source_provenance` as the trust anchor. The end-to-end procedure is in [Verify a response](/guides/verify-a-response). ## Legacy alias `GET /v1/attestation/report?nonce=…` returns the same gateway attestation for clients written against earlier API versions, with two differences: top-level `signing_address`, `signing_algo`, and `signing_public_key` derived from the gateway keyset (`?signing_algo=ecdsa`, the default, or `ed25519` selects the key), and `all_attestations` collapsed to a single entry. Earlier API versions returned the upstream model's attestation here; the upstream verification for a response is now in the [receipt](/api-reference/receipts) and the [attested session](/api-reference/sessions). New integrations should use `/v1/aci/attestation`. ## Related The signed per-response receipt. What each field proves. # Chat completions Source: https://docs.redpill.ai/api-reference/chat-completions POST /chat/completions Create chat completion responses. Creates a model response for the given chat conversation. Requests are served through the attested TEE gateway, which does not retain request bodies. A confidential response additionally runs on a verified upstream enclave, confirmed per response from the receipt's `upstream.verified` event. The response includes an `x-receipt-id` header for [verification](/guides/verify-a-response). ```bash theme={null} POST https://api.redpill.ai/v1/chat/completions ``` ## Request body Model ID returned by [`GET /v1/models`](/api-reference/models). Array of message objects. Each message needs `role` and `content`. **Example**: ```json theme={null} [ {"role": "user", "content": "What is confidential AI?"} ] ``` **With system message**: ```json theme={null} [ {"role": "system", "content": "You are a helpful assistant"}, {"role": "user", "content": "Hello!"} ] ``` Sampling temperature (0-2), default 1 Maximum tokens to generate OpenAI `gpt-5`, `o3`, and `o4` models reject it; send `max_completion_tokens` instead. Maximum tokens to generate, for OpenAI `gpt-5`, `o3`, and `o4` models. Each model object's `supported_parameters` lists which of the two it accepts. Stream responses, default false Nucleus sampling (0-1) Number of completions, default 1 Presence penalty (-2 to 2) Frequency penalty (-2 to 2) Routing constraints for this request. Fields are applied together, and the request is served only by a provider that satisfies all of them. **`zdr`** (boolean): set `true` to route only to providers that operate under zero data retention, meaning they do not retain prompt or completion content after serving the request. `false`, or omitting the field, applies no retention constraint. ```json theme={null} {"provider": {"zdr": true}} ``` If no provider for the model satisfies the constraint, the request returns `404` and no prompt is sent. The constraint is enforced for the whole request, including failover, so a retry cannot fall back to a provider that does not satisfy it. Find models that can satisfy `zdr` ahead of time with [`GET /v1/models?zdr=true`](/api-reference/models#query-parameters). See [Zero data retention](/guides/zero-data-retention). **`aci_verified`** (boolean): set `true` to route only to an upstream the gateway verified inside the TEE before sending the prompt. `false`, or omitting the field, applies no attestation constraint. ```json theme={null} {"provider": {"aci_verified": true}} ``` If no upstream for the model is both classified as attested and currently verified, the request returns `503` and no prompt is sent. `X-Upstream-Verification: none` cannot relax this constraint. **`aci_session_ids`** (array of strings): restrict the request further to specific attested channels, identified by the session ids from [`GET /v1/aci/sessions`](/api-reference/sessions). Any one of the listed ids may serve the request; if none matches a currently verified channel, the request returns `503`. ```json theme={null} {"provider": {"aci_verified": true, "aci_session_ids": ["2a44...6290"]}} ``` Session ids imply `aci_verified`. Passing them with `"aci_verified": false` returns `400`, as does an empty array or a non-string entry. Both fields are read by the gateway and removed before the request reaches a provider. See [Attested routing](/guides/attested-routing). ### Message object ```json theme={null} { "role": "user" | "assistant" | "system", "content": "string" | array } ``` ## Examples ```bash Confidential theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -d '{ "model": "z-ai/glm-5.2", "messages": [ {"role": "user", "content": "What is confidential AI?"} ] }' ``` ```bash OpenAI theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -d '{ "model": "openai/gpt-5", "messages": [ {"role": "user", "content": "What is confidential AI?"} ] }' ``` ```bash With Parameters theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -d '{ "model": "anthropic/claude-sonnet-4.5", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Explain TEE"} ], "temperature": 0.7, "max_tokens": 500 }' ``` ```python Python theme={null} import os from openai import OpenAI client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1" ) response = client.chat.completions.create( model="openai/gpt-5", messages=[ {"role": "system", "content": "You are helpful."}, {"role": "user", "content": "Hello!"} ], temperature=0.7, max_tokens=100 ) print(response.choices[0].message.content) ``` ```javascript JavaScript theme={null} import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'https://api.redpill.ai/v1', apiKey: process.env.REDPILL_AI_API_KEY }); const response = await client.chat.completions.create({ model: 'openai/gpt-5', messages: [ { role: 'system', content: 'You are helpful.' }, { role: 'user', content: 'Hello!' } ], temperature: 0.7, max_tokens: 100 }); console.log(response.choices[0].message.content); ``` ## Response ```json theme={null} { "id": "chatcmpl-123", "object": "chat.completion", "created": 1677652288, "model": "openai/gpt-5", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Confidential AI is a privacy-first approach to inference..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 13, "completion_tokens": 25, "total_tokens": 38 } } ``` ## Related # Completions (legacy) Source: https://docs.redpill.ai/api-reference/completions POST /completions Create a text completion (legacy). This endpoint is legacy. Use [Chat completions](/api-reference/chat-completions) instead for better results. ```bash theme={null} POST https://api.redpill.ai/v1/completions ``` ## Request ```json theme={null} { "model": "gpt-3.5-turbo-instruct", "prompt": "Say this is a test", "max_tokens": 7, "temperature": 0 } ``` This endpoint accepts the same `provider` routing block as chat completions, including `zdr`, `aci_verified`, and `aci_session_ids`. See [Chat completions](/api-reference/chat-completions#body-provider). ## Response ```json theme={null} { "id": "cmpl-123", "object": "text_completion", "created": 1677652288, "model": "gpt-3.5-turbo-instruct", "choices": [{ "text": "\n\nThis is a test.", "index": 0, "finish_reason": "stop" }], "usage": { "prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12 } } ``` # Embeddings Source: https://docs.redpill.ai/api-reference/embeddings POST /embeddings Create text embeddings. Generate vector embeddings from text for semantic search, similarity, clustering, and retrieval. Requests are served through the attested TEE gateway, which does not retain request bodies. Use `is_tee` from the embedding catalog to find confidential-capable models and verify the receipt for the actual request. ```bash theme={null} POST https://api.redpill.ai/v1/embeddings ``` ## Request body Model id returned by `GET /v1/embeddings/models`. Text to embed. Pass a single string or an array of strings for batch embedding. `float` (default) or `base64`. Optional output dimension count. Supported by `openai/text-embedding-3-small` and `openai/text-embedding-3-large`, which can return fewer dimensions than their default. ## Example ```bash cURL theme={null} curl https://api.redpill.ai/v1/embeddings \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen/qwen3-embedding-8b", "input": "Confidential AI you can verify." }' ``` ```python Python theme={null} import os from openai import OpenAI client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1", ) resp = client.embeddings.create( model="qwen/qwen3-embedding-8b", input="Confidential AI you can verify.", ) print(len(resp.data[0].embedding)) ``` ```javascript JavaScript theme={null} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.redpill.ai/v1", apiKey: process.env.REDPILL_AI_API_KEY, }); const resp = await client.embeddings.create({ model: "qwen/qwen3-embedding-8b", input: "Confidential AI you can verify.", }); console.log(resp.data[0].embedding.length); ``` ### Batch input Pass an array to embed several strings in one request. Results are returned in input order, each with its `index`. ```bash theme={null} curl https://api.redpill.ai/v1/embeddings \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen/qwen3-embedding-8b", "input": ["first document", "second document"] }' ``` ## Response ```json theme={null} { "object": "list", "data": [ { "object": "embedding", "index": 0, "embedding": [0.0167, -0.0557, "…"] } ], "model": "openai/text-embedding-3-small", "usage": { "prompt_tokens": 8, "total_tokens": 8 } } ``` ## Model catalog The public `GET /v1/embeddings/models` catalog is authoritative. It accepts the same `zdr` filter as the chat model catalog: ```bash theme={null} curl -s "https://api.redpill.ai/v1/embeddings/models?zdr=true" \ | jq -r '.data[].id' ``` Embedding requests accept the same `provider` routing block as chat completions, including `zdr`, `aci_verified`, and `aci_session_ids`. See [Zero data retention](/guides/zero-data-retention). ## Custom dimensions `openai/text-embedding-3-small` and `openai/text-embedding-3-large` accept a `dimensions` parameter to return shorter vectors, which reduces storage and speeds up similarity search at some cost to quality. ```bash theme={null} curl https://api.redpill.ai/v1/embeddings \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/text-embedding-3-large", "input": "shorter vector", "dimensions": 1024 }' ``` Keep one model and one dimension count per index. Vectors from different models are not comparable. ## Related Chat and confidential models. What the gateway protects. # Messages Source: https://docs.redpill.ai/api-reference/messages POST /messages Create a message using the Anthropic Messages format. Creates a model response using the Anthropic Messages API request and response format. Like all inference endpoints, requests are served through the attested TEE gateway and return an `x-receipt-id` header for [verification](/guides/verify-a-response). ```bash theme={null} POST https://api.redpill.ai/v1/messages ``` ## Request body Model id, for example `anthropic/claude-haiku-4.5`. Maximum number of tokens to generate. Conversation turns. Each item has a `role` (`user` or `assistant`) and `content` (a string or an array of content blocks). A system prompt, sent as a top-level field rather than a message. Sampling temperature. Stream the response as server-sent events. Routing constraints for this request, applied together. `zdr` restricts routing to providers that do not retain content; `aci_verified` restricts it to an upstream verified inside the TEE; `aci_session_ids` pins it to specific attested channels. ```json theme={null} {"provider": {"aci_verified": true}} ``` If no provider satisfies the constraints the request fails before the prompt is sent, and failover retries stay inside the allowed set. Field reference: [Chat completions](/api-reference/chat-completions#body-provider). See [Attested routing](/guides/attested-routing) and [Zero data retention](/guides/zero-data-retention). ## Example ```bash theme={null} curl https://api.redpill.ai/v1/messages \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-haiku-4.5", "max_tokens": 100, "messages": [ {"role": "user", "content": "Say hello in 3 words."} ] }' ``` ## Response ```json theme={null} { "id": "msg_01ABC...", "type": "message", "role": "assistant", "model": "anthropic/claude-haiku-4.5", "content": [ { "type": "text", "text": "Hello, howdy, hi!" } ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 14, "output_tokens": 8 } } ``` Read assistant text from `content[].text` on blocks with `type: "text"`. `stop_reason` is one of `end_turn`, `max_tokens`, `stop_sequence`, or `tool_use`. ## Related # Models Source: https://docs.redpill.ai/api-reference/models GET /models List available models with providers, context windows, and pricing. Returns the current model catalog, including model IDs, context windows, providers, modalities, and token pricing. ```bash theme={null} GET https://api.redpill.ai/v1/models ``` No API key is required to list models. Embedding models are listed separately by [`GET /v1/embeddings/models`](/api-reference/embeddings#model-catalog). ## Query parameters Set to `true` to return only models that can be served under zero data retention, meaning the upstream provider does not retain prompt or completion content. A model is included when at least one provider that can serve it operates under zero data retention. Listing a model here means a request for it with `provider: {"zdr": true}` can be routed. See [Zero data retention](/guides/zero-data-retention). `true` is the only accepted value. Any other value, including `false`, returns `400`. Omit the parameter to list the full catalog. ```bash theme={null} curl "https://api.redpill.ai/v1/models?zdr=true" ``` Set to `true` to return only models that are offered confidentially, meaning the model object carries `is_tee: true`. This filter reports a model's own capability. It does not promise that a given request will reach an attested provider, which depends on the deployment serving it. To require attestation for a request, send `provider: {"aci_verified": true}`. See [Attested routing](/guides/attested-routing). `true` is the only accepted value. Any other value, including `false`, returns `400`. Omit the parameter to list the full catalog. ```bash theme={null} curl "https://api.redpill.ai/v1/models?tee=true" ``` Both filters compose. Combining them returns models that satisfy each constraint. Response entries have the same shape as the unfiltered catalog. There is no `zdr` field on a model object: the filter is the way to read this property. An invalid value returns `400`: ```json theme={null} { "error": { "message": "Invalid 'zdr' query parameter. The only accepted value is 'true'; omit it for unfiltered results.", "type": "invalid_request_error" } } ``` ## Example ```bash theme={null} curl https://api.redpill.ai/v1/models ``` ## Model object fields | Field | Description | | - | - | | `id` | Model identifier for API calls. | | `name` | Human-readable name. | | `is_tee` | `true` if the model can be served confidentially, on a verified TEE provider. | | `created` | Unix timestamp when the model was added. | | `context_length` | Maximum context window. | | `max_output_length` | Maximum output length. | | `input_modalities` | Accepted inputs, for example `text`, `image`, `file`, `video`. | | `output_modalities` | Produced outputs, for example `text`. System One models produce `decisions` and are called through [`POST /v1/systemone`](/api-reference/systemone), not chat. | | `pricing.prompt` | Price per prompt token. Multiply by 1,000,000 for the per-million-token price. | | `pricing.completion` | Price per completion token. Multiply by 1,000,000 for the per-million-token price. | | `pricing.input_cache_read` | Price per cached input token when the model exposes cache pricing. | | `supported_parameters` | Request parameters this model accepts, for example `max_tokens` or `max_completion_tokens`. | | `supported_features` | Higher-level capabilities declared for the model, for example `tools`, `reasoning`, or `structured_outputs`. | | `providers` | Confidential providers that can serve the model, for example `near-ai`, `tinfoil`, `chutes`. | ## Related Confidential versus standard models, and how to confirm which served a response. # Overview Source: https://docs.redpill.ai/api-reference/overview Base URL, authentication, and the full list of API endpoints. The API is OpenAI-compatible. Point an OpenAI or Anthropic client at the base URL and send your key as a bearer token. ## Base URL ``` https://api.redpill.ai/v1 ``` For a base URL that serves confidential models only, on attested providers, use `https://tee.redpill.ai/v1`. It rejects standard models with `404` and forces attested serving. See the [TEE-only endpoint](/guides/tee-endpoint). ## Authentication Send an API key as a bearer token for inference and private artifacts: ```bash theme={null} Authorization: Bearer $REDPILL_AI_API_KEY ``` Create keys in the [dashboard](https://www.redpill.ai/dashboard). Read the key from an environment variable or a secret manager, call the API from a backend rather than from browser or mobile code, and revoke a key in the dashboard if it is exposed. The model catalog and gateway attestation are public. ## Endpoints ### Inference | Endpoint | Method | Description | | - | - | - | | [`/v1/chat/completions`](/api-reference/chat-completions) | POST | Create a chat completion. | | [`/v1/responses`](/api-reference/responses) | POST | Create a response (Responses API format). | | [`/v1/messages`](/api-reference/messages) | POST | Create a message (Anthropic Messages format). | | [`/v1/embeddings`](/api-reference/embeddings) | POST | Create text embeddings. | | [`/v1/models`](/api-reference/models) | GET | List models with providers, context windows, and pricing. | Each model has an `is_tee` boolean: `is_tee: true` means it can be served confidentially. The receipt's `upstream.verified` event confirms a specific response was. See [Models](/get-started/models). ### Verification | Endpoint | Method | Description | | - | - | - | | [`/v1/aci/attestation`](/api-reference/attestation) | GET | Fresh TEE attestation report, bound to a caller-supplied `nonce`. | | [`/v1/aci/receipts/{id}`](/api-reference/receipts) | GET | Signed receipt for a response, by receipt id or chat id. | | [`/v1/aci/sessions/{id}`](/api-reference/sessions) | GET | Attested-session record referenced by a receipt. | | [`/v1/aci/sessions`](/api-reference/sessions) | GET | List current attested sessions (`?upstream_name=&model=`). | To verify a response: fetch the attestation report with a fresh `nonce`, read the `x-receipt-id` header on your response, fetch its signed receipt, then check the receipt signature under the attested keyset and compare the request and response hashes. See [Verify a response](/guides/verify-a-response). ### Legacy Legacy endpoints kept for backward compatibility with earlier API clients. New integrations should use the canonical endpoints above. | Endpoint | Method | Canonical replacement | | - | - | - | | [`/v1/completions`](/api-reference/completions) | POST | `/v1/chat/completions` | | [`/v1/signature/{id}`](/api-reference/receipts#legacy-alias) | GET | `/v1/aci/receipts/{id}` | | [`/v1/attestation/report`](/api-reference/attestation#legacy-alias) | GET | `/v1/aci/attestation` | ## Request format POST requests use JSON: ```bash theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Hello"}] }' ``` ## Response format ```json theme={null} { "id": "chatcmpl-123", "object": "chat.completion", "created": 1677652288, "model": "z-ai/glm-5.2", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "Hello! How can I help you?" }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 } } ``` ## Response headers Inference responses include verification headers: | Header | Description | | - | - | | `x-receipt-id` | Id of the signed [receipt](/api-reference/receipts) for the response. | | `x-aci-identity` | The attested workload identity that served the request. | | `x-aci-keyset-digest` | Digest of the workload's published keyset. | | `x-aci-version` | ACI version, for example `aci/1`. | To select a model, set the `model` field in the request body. For end-to-end encryption, send the `X-E2EE-*` request headers from the [E2EE guide](/guides/e2ee-encryption). ## Errors Errors return a JSON body with an appropriate HTTP status: ```json theme={null} { "error": { "message": "Invalid API key", "type": "invalid_request_error", "code": "invalid_api_key" } } ``` | Code | Meaning | | - | - | | 200 | Success | | 400 | Bad request (invalid parameters) | | 401 | Unauthorized (invalid API key) | | 402 | Out of credits or key budget (`insufficient_quota`) | | 404 | Endpoint or model not found | | 429 | Rate limit exceeded; see [Rate limits](/get-started/rate-limits) | | 500 | Server error | | 503 | Service unavailable | See [Error handling](/guides/error-handling) and [Rate limits](/get-started/rate-limits). ## Using existing client libraries The API follows the OpenAI request and response format, so OpenAI client libraries work by pointing the base URL at the gateway and using your API key: ```python theme={null} import os from openai import OpenAI client = OpenAI(api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1") resp = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "Hello"}], ) ``` See [OpenAI SDK](/guides/integrations/openai-sdk) and the other SDK guides. # Receipt Source: https://docs.redpill.ai/api-reference/receipts GET https://tee.redpill.ai/v1/aci/receipts/{id} The signed receipt for a response, with the full transparency event log. Returns the signed receipt for a response: a transparency event log that binds the request and response hashes to the attested gateway, signed by a key from the attested [keyset](/api-reference/attestation). This is the canonical per-response proof. Look up a receipt by the `x-receipt-id` header on the inference response, or by the response `id`. For how to read the event log, see [Receipts](/confidential-ai/receipts). ```http theme={null} GET /v1/aci/receipts/{id} ``` ## Request 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`. ## Response `200 OK`, `application/json`. The bare canonical receipt: | Field | Type | Description | | - | - | - | | `api_version` | string | ACI version token, for example `aci/1`. | | `receipt_id` | string | The receipt id (`rcpt-…`). | | `chat_id` | string or null | The response/chat id, when the upstream returns one. | | `workload_keyset_digest` | string | Must match the attestation report. | | `model` | string or null | Model requested by the client. | | `endpoint` | string | The route served, for example `/v1/chat/completions`. | | `method` | string | HTTP method. | | `served_at` | number | Unix seconds. | | `event_log` | array | Ordered transparency events (below). | | `key_id` | string | Selects a receipt-signing key from the attested keyset. | | `signature` | string | Signature over the receipt, by a key in the attested keyset. | ### `event_log` entries | `type` | Key fields | | - | - | | `request.received` | `body_hash` | | `middleware.forwarded` | `body_hash` | | `route.selected` | `target_route_id` | | `request.forwarded` | `body_hash` | | `transparency.request_modified` | (present when forwarded body differs) | | `upstream.verified` | `model_id`, `result`, `required`, `session_id` | | `response.received` | `body_hash` | | `transparency.response_modified` | (present when returned response differs) | | `response.returned` | `body_hash` | ### Example (truncated) ```json theme={null} { "api_version": "aci/1", "receipt_id": "rcpt-e0eefe63b3673e2a5984951f", "chat_id": "d3ad808b20a444c29e798db35f112033", "workload_keyset_digest": "sha256:3eff0836…", "model": "phala/gemma-4-26b-a4b-uncensored", "endpoint": "/v1/chat/completions", "method": "POST", "served_at": 1781588857, "event_log": [ { "type": "request.received", "body_hash": "sha256:111d08a5…" }, { "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": "3681736b…" }, { "type": "response.returned", "body_hash": "sha256:07703fb4…" } ], "key_id": "receipt-v1", "signature": "…" } ``` ## Verifying the receipt 1. Confirm `workload_keyset_digest` matches a verified [attestation report](/api-reference/attestation). 2. Verify `signature` under a `receipt_signing_keys` entry from the attested keyset. 3. Confirm `request.received.body_hash` matches the request you sent and `response.returned.body_hash` matches the response you received. 4. Read `upstream.verified`: `result = verified` and `required = true` indicate a confidential model served from a verified, channel-bound upstream. Follow `session_id` to the [attested session](/api-reference/sessions). See [Verify a response](/guides/verify-a-response) for the full walkthrough. ## Legacy alias `GET /v1/signature/{id}` returns the same receipt wrapped in a legacy envelope, for clients written against earlier API versions. `{id}` is a receipt id or a response id. | Field | Description | | - | - | | `api_version` | ACI version token, for example `aci/1`. | | `signing_algo` | Signature algorithm, for example `ecdsa`. | | `signing_address` | Address that signed `text`. | | `text` | `":"`, the signed message. | | `signature` | Signature over `text`. | | `receipt` | The full receipt object, identical to this endpoint's response. | New integrations should use `/v1/aci/receipts/{id}`. ## Related # Responses Source: https://docs.redpill.ai/api-reference/responses POST /responses Create a model response using the Responses API format. Creates a model response using the Responses API request and response format. Like all inference endpoints, requests are served through the attested TEE gateway and return an `x-receipt-id` header for [verification](/guides/verify-a-response). ```bash theme={null} POST https://api.redpill.ai/v1/responses ``` ## Request body Model id, for example `z-ai/glm-5.2` or `openai/gpt-4o`. The input prompt. A string, or an array of input items. Tool definitions the model may call. Sampling temperature. Nucleus sampling. Routing constraints for this request, applied together. `zdr` restricts routing to providers that do not retain content; `aci_verified` restricts it to an upstream verified inside the TEE; `aci_session_ids` pins it to specific attested channels. ```json theme={null} {"provider": {"aci_verified": true}} ``` If no provider satisfies the constraints the request fails before the prompt is sent, and failover retries stay inside the allowed set. Field reference: [Chat completions](/api-reference/chat-completions#body-provider). See [Attested routing](/guides/attested-routing) and [Zero data retention](/guides/zero-data-retention). ## Example ```bash theme={null} curl https://api.redpill.ai/v1/responses \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4o", "input": "Say hello in 3 words." }' ``` ## Response ```json theme={null} { "id": "resp_023cda4f5e268c44006a30f3e0b6f4819b87536574ae6ecd14", "object": "response", "status": "completed", "model": "gpt-4o-2024-08-06", "output": [ { "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "Hello, howdy, hi!", "annotations": [] } ] } ], "usage": { "input_tokens": 14, "output_tokens": 8, "total_tokens": 22 } } ``` The `output` array contains items by `type`. A `message` item holds the assistant content; a `reasoning` item appears for reasoning models. Read assistant text from `output[].content[].text` on the `message` item. ## Related # Session Source: https://docs.redpill.ai/api-reference/sessions GET https://tee.redpill.ai/v1/aci/sessions/{session_id} The immutable attested-session record behind a confidential response. Returns an [attested session](/confidential-ai/verified-upstreams#attested-sessions): the immutable, content-addressed record of the verified TEE channel a confidential request was bound to. A receipt's `upstream.verified.session_id` references it, so you can trace any confidential response back to the exact verified security context, with its typed claims and channel binding. ```http theme={null} GET /v1/aci/sessions/{session_id} GET /v1/aci/sessions?upstream_name={upstream_name}&model={model} ``` ## Get one session The 64-character lowercase hex id from a receipt's `upstream.verified.session_id`. ### Response | Field | Type | Description | | - | - | - | | `api_version` | string | ACI version token. | | `upstream_name` | string | The service-chosen upstream label for this channel. | | `endpoint` | string | The verified upstream origin. | | `verifier_id` | string | The verifier that produced this result. | | `established_at` | number | When the material was verified (Unix seconds). | | `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): a TLS SPKI digest or an E2EE public key. | | `claims` | object | Typed claims (below). | | `evidence` | object | Byte-preserving evidence the verifier checked (`digest` + data URI). | ### Typed claims Each claim carries a `status` (`asserted` / `refuted` / `unknown`), a `source` (`hardware_proven` / `verifier_derived` / `provider_asserted` / `operator_asserted`), and a `reason`. The claim set: `tee_attested`, `tcb_up_to_date`, `os_known_good`, `serving_software_known_good`, `gpu_attested`, `model_weights_provenance`, plus provider-specific entries in `extra`. See [Claims](/confidential-ai/verified-upstreams#claims). ```json theme={null} { "api_version": "aci/1", "upstream_name": "near-ai", "endpoint": "https://cloud-api.near.ai", "verifier_id": "private-ai-verifier/near-ai-gateway/v1", "established_at": 1781588840, "expires_at": 1781589140, "channel_binding": [ { "type": "tls_spki_sha256", "origin": "https://cloud-api.near.ai", "spki_sha256": "…" } ], "claims": { "tee_attested": { "status": "asserted", "source": "hardware_proven", "reason": "verified TDX quote" }, "tcb_up_to_date": { "status": "asserted", "source": "hardware_proven" }, "gpu_attested": { "status": "unknown" } }, "evidence": { "digest": "sha256:…", "data": "data:application/json;base64,…" } } ``` ## List current sessions `GET /v1/aci/sessions?upstream_name={upstream_name}&model={model}` returns current attested sessions. Use it as a preflight survey: inspect the verified identity, channel binding, and claims for a model before sending a prompt. ```bash theme={null} curl "https://tee.redpill.ai/v1/aci/sessions?upstream_name=near-ai" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" ``` 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. ## Why content-addressed `session_id` is the bare SHA-256 of the full session document in canonical JCS form. Any change produces a new id, and sessions are never updated in place. Recompute the id after fetching the full record to confirm it is exactly the document cited by the receipt. ## Related # System One Source: https://docs.redpill.ai/api-reference/systemone POST /systemone Ask a model typed questions about a state and get structured decisions. System One models answer typed questions about a piece of content instead of generating text. Each answer is a calibrated probability, a choice among options, or a level on a scale, so software can act on it directly. The endpoint follows the TypeSafe System One protocol, so the official TypeSafe SDKs work with RedPill by changing the base URL. System One models list `decisions` in `output_modalities` in [`GET /v1/models`](/api-reference/models). They do not serve chat: a chat completion request to one returns `404 model_not_found`. Responses are never streamed. ```bash theme={null} POST https://api.redpill.ai/v1/systemone ``` ## Request body Id of a model whose `output_modalities` include `decisions`, for example `jaredpalmer/kev-4b`. The content the questions are about: a message, a document, or any JSON value. Questions keyed by an id you choose. Each answer is returned under the same id. Every question has a `type` and usually `instructions` that state the question. A yes/no question. `criteria` optionally describes what `true` and `false` mean. ```json theme={null} { "type": "noul", "instructions": "Is this about billing?" } ``` Pick one option. `criteria` maps each option to a description, or to `null`. ```json theme={null} { "type": "choice", "instructions": "What is the tone?", "criteria": { "calm": null, "upset": null } } ``` Rate on an ordered scale. `criteria` lists the levels from lowest to highest. ```json theme={null} { "type": "score", "instructions": "How urgent is it?", "criteria": ["low", "medium", "high"] } ``` The same `provider` routing block as chat completions. It is used for routing and is not sent to the upstream. Other fields are not forwarded. ## Example ```bash cURL theme={null} curl https://api.redpill.ai/v1/systemone \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "jaredpalmer/kev-4b", "state": "I was charged twice this month. Please fix it today.", "questions": { "billing": { "type": "noul", "instructions": "Is this about billing?" }, "tone": { "type": "choice", "instructions": "What is the tone?", "criteria": { "calm": null, "upset": null } }, "urgency": { "type": "score", "instructions": "How urgent is it?", "criteria": ["low", "medium", "high"] } } }' ``` ```python Python theme={null} import os from typesafe_sdk import Choice, Noul, Score, TypeSafeClient client = TypeSafeClient( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai", model="jaredpalmer/kev-4b", ) result = client.system_one( state="I was charged twice this month. Please fix it today.", questions={ "billing": Noul(instructions="Is this about billing?"), "tone": Choice(instructions="What is the tone?", criteria={"calm": None, "upset": None}), "urgency": Score(instructions="How urgent is it?", criteria=["low", "medium", "high"]), }, ) print(result.nouls["billing"].noul, result.choices["tone"].choice, result.scores["urgency"].score) ``` ```javascript JavaScript theme={null} import { TypeSafeClient, choice, noul, score } from "@typesafe-ai/sdk"; const client = new TypeSafeClient({ baseURL: "https://api.redpill.ai", apiKey: process.env.REDPILL_AI_API_KEY, }); const result = await client.systemOne({ model: "jaredpalmer/kev-4b", state: "I was charged twice this month. Please fix it today.", questions: { billing: noul("Is this about billing?"), tone: choice("What is the tone?", { calm: null, upset: null }), urgency: score("How urgent is it?", ["low", "medium", "high"]), }, }); console.log(result.answers.billing.noul, result.answers.tone.choice, result.answers.urgency.score); ``` The SDKs append `/v1/systemone` themselves, so set their base URL to `https://api.redpill.ai` without `/v1`. Install them with `pip install typesafe-sdk` or `npm install @typesafe-ai/sdk`. ## Response ```json theme={null} { "model": "jaredpalmer/kev-4b", "answers": { "billing": { "type": "noul", "noul": 0.8828 }, "tone": { "type": "choice", "choice": "upset", "confidence": 0.4882, "probabilities": { "calm": 0.2559, "upset": 0.7441 } }, "urgency": { "type": "score", "score": 1.4055, "legend": { "0": "low", "1": "medium", "2": "high" }, "probabilities": { "0": 0.1268, "1": 0.3408, "2": 0.5323 }, "confidence": 0.1083 } }, "usage": { "input_tokens": 57, "output_tokens": 167, "cost": 0.000002394 } } ``` One answer per question id, in the same type as the question: * `noul`: `noul` is the probability that the answer is `true`. * `choice`: `choice` is the most likely option, with `probabilities` per option and a 0 to 1 `confidence`. * `score`: `score` is the probability-weighted level index, with `probabilities` per level, `legend` mapping each index to its level, and a 0 to 1 `confidence`. `input_tokens`, `output_tokens`, and `cost`, the charge for this request in USD. The response carries `x-typesafe-request-id`, which the SDKs read, and the gateway's `x-receipt-id` for [receipt verification](/api-reference/receipts). Errors use the same error format as the other endpoints. # Changelog Source: https://docs.redpill.ai/changelog Product changes, newest first. ## September 2026 **2026-09-19** * Private AI Proxy 0.1.4: Linux desktop and CLI builds run on glibc 2.35 and newer again (Ubuntu 22.04, Debian 12). [Release notes](https://github.com/Dstack-TEE/private-ai-gateway/releases/tag/desktop-v0.1.4). * The CLI installs on its own: `curl -fsSL https://redpill.ai/private-ai-proxy/install.sh | sh`, Homebrew tap `dstack-tee/private-ai`, or `npm install --global private-ai-proxy`. See [Private AI Proxy](/get-started/private-ai-proxy#install). * Account MCP server at `https://service.redpill.ai/mcp`: agents read models, balance, keys, workspaces, and usage, and make confirmed changes to keys and workspaces. See [MCP server](/get-started/mcp). * redpill.ai redesigned around two paths, Build with the API and Use in agents, with stable download links at `redpill.ai/download/private-ai-proxy/`. # Overview Source: https://docs.redpill.ai/chat/overview RedPill Chat runs only confidential models, with end-to-end encrypted conversations and private document knowledge bases. [RedPill Chat](https://chatgpt.redpill.ai/) is a chat app that offers only confidential models: every response is served by a verified TEE provider through the attested gateway, which does not retain request bodies. Conversations are end-to-end encrypted between your device and the gateway, and chat history is stored encrypted. ## Sign in Create an account at [chatgpt.redpill.ai](https://chatgpt.redpill.ai/) with an email address, Google, or GitHub. The free tier allows 10 messages per day on one device; Pro removes the message limit. ## Chat * Pick a model from the selector at the top of the conversation. Every model listed is confidential. * The lock icon confirms the conversation is end-to-end encrypted. * Attach PDF, DOCX, Markdown, or text files to a conversation, or build a reusable [knowledge base](/chat/private-knowledge). ## Related # Private knowledge Source: https://docs.redpill.ai/chat/private-knowledge Upload documents into a knowledge base, ask questions with citations, and generate summaries, FAQs, mind maps, and audio overviews. A knowledge base is a set of documents you upload once and query from chat. Files are encrypted before upload and processed inside the attested gateway. Answers cite the source passage; open a citation to read the original text. ## Create a knowledge base Choose **Create new** and give the knowledge base a name. Upload documents. Large files take up to a minute to process. Ask questions in chat, or open the **Studio** tab to generate an output from all sources. | Format | Extensions | Maximum size | | - | - | - | | Documents | `.pdf`, `.docx`, `.doc` | 50 MB | | Text | `.txt`, `.md`, `.rtf` | 10 MB | | Code | `.py`, `.js`, `.ts`, `.java`, and others | 10 MB | | Data | `.csv`, `.json` | 25 MB | ## Studio outputs | Output | Content | | - | - | | Summary | Overview of all sources | | Briefing doc | Detailed briefing for stakeholders | | FAQ | Questions and answers drawn from the sources | | Timeline | Events in chronological order | | Mind map | Concept map of topics and relationships | | Audio overview | Spoken summary | Generation takes from half a minute to a few minutes. Pin an output to keep it, or export it. ## Limits | Plan | Storage | Documents | | - | - | - | | Free | 10 MB | 10 | | Pro | 10 GB | 100 | | Enterprise | Custom | Unlimited | Enterprise plans can share a knowledge base within a team. Deleting a knowledge base deletes its files and generated outputs. ## Related # Attestation report Source: https://docs.redpill.ai/confidential-ai/attestation-report The gateway attestation report proves which TEE workload served your request and publishes the keys it signs with. 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. Fetch it with a fresh nonce: ```bash theme={null} curl "https://tee.redpill.ai/v1/aci/attestation?nonce=$(openssl rand -hex 32)" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" ``` See [`GET /v1/aci/attestation`](/api-reference/attestation) for the full field reference. This page explains what each part proves. ## 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`](/api-reference/attestation#legacy-alias) alias also adds top-level `signing_address` / `signing_algo` / `signing_public_key` for older clients.) ## Why the nonce matters You pass `?nonce=`. 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. ## How verification uses the report A verifier checks, in order: 1. `attestation.evidence.quote` verifies against Intel's DCAP collateral, and the quote's report data binds your `nonce` and the `workload_keyset`. 2. The quote-bound statement commits to the JCS digest of `workload_keyset`. 3. `workload_keyset_digest` matches what later receipts cite. 4. The keyset has not expired (`workload_keyset.not_after` is in the future). 5. The event log replays to the quote's RTMR3 and its measured `compose-hash` equals `sha256(app_compose)`. 6. For reviewed-release enforcement, the measured compose hash is in an independently obtained allowlist. `source_provenance` helps locate source for review, but the workload declares it. The compose hash is the value bound into RTMR3 and accepted by verifier policy. 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. ## Related How a single response is bound to this attested identity. Run the full check end to end. # Compliance Source: https://docs.redpill.ai/confidential-ai/compliance How the confidential computing architecture supports common compliance requirements, and how to request reports. The platform's architecture is built for workloads with strict data-handling requirements. This page explains what the technical controls give you and how to request compliance documentation. For reports or a security review, contact [support@redpill.ai](mailto:support@redpill.ai). Read [Trust boundary](/confidential-ai/trust-boundary) first. The gateway is attested and does not retain request bodies, but plaintext is visible to the attested gateway after TLS or E2EE decryption. Compliance posture depends on which model you use: confidential or routed. ## Technical controls relevant to compliance | Control | How the platform provides it | | - | - | | Data isolation | Inference runs in a TEE (Intel TDX). The gateway is attested with a hardware quote. | | No body retention | The gateway stores hashes in receipts, not request or response bodies. | | Upstream zero data retention | `provider: {"zdr": true}` restricts routing to providers that do not retain prompt or completion content. Enforced fail-closed, including on failover. See [Zero data retention](/guides/zero-data-retention). | | Verifiable processing | A signed [receipt](/confidential-ai/receipts) binds each request and response to the attested workload. | | Confidential upstreams | For a confidential response, the upstream enclave is verified and the channel bound before forwarding, confirmed by the receipt's `upstream.verified` event. | | Encryption in transit | TLS for all connections, with optional [E2EE](/confidential-ai/e2e-encryption) field-level encryption. | ## Regulatory requirements | Requirement | How the platform helps | | - | - | | Data minimization (GDPR) | No request or response body retention by default. Request `zdr` to extend the constraint to the upstream provider. | | Storage limitation (GDPR) | Zero-data-retention providers do not keep prompt or completion content after serving a request. | | Confidential processing | TEE isolation with attestation and per-response receipts. | | Auditability | Verifiable attestation and signed receipts you can check independently. | The platform supports Data Processing Agreements (DPA) and, for healthcare workloads, Business Associate Agreements (BAA). Certification status (for example SOC 2 and HIPAA) changes over time; contact [support@redpill.ai](mailto:support@redpill.ai) for current reports and scope. ## Choosing a model for regulated data * Use a [confidential model](/get-started/models) when the upstream that runs the model must be attested and the prompt must not reach a non-attested third party. * A routed model sends your prompt to a third-party provider that is not attested. Confirm that provider's terms meet your requirements before using it for regulated data. ## Requesting documents For enterprise customers and prospects, the platform provides security documentation, a DPA, and (where applicable) a BAA. Contact [support@redpill.ai](mailto:support@redpill.ai). # End-to-end encryption Source: https://docs.redpill.ai/confidential-ai/e2e-encryption Field-level encryption between your client and the attested gateway, on top of TLS. End-to-end encryption (E2EE) encrypts selected request and response fields between your client and the attested gateway. It sits on top of TLS: TLS still protects the HTTP connection, and E2EE adds field-level encryption whose key is published in the gateway's [attested keyset](/confidential-ai/attestation-report). This page explains the model. For setup and code, see the [E2EE encryption guide](/guides/e2ee-encryption). ## What E2EE adds * **Key provenance.** The encryption key (`X-Model-Pub-Key`) comes from the attested keyset, so you can prove the field is encrypted to a key bound to a genuine TEE, not an arbitrary endpoint. * **Field-level encryption.** Selected request fields and response fields are encrypted, with the ciphertext bound by AEAD associated data to the request context (listed under [Ciphertext format](#ciphertext-format)), so it cannot be replayed or moved to another field. The gateway reports E2EE support in the attestation report: `service_capabilities.supported_e2ee_versions` includes `"2"`. ## What E2EE does not change E2EE is decrypted **inside** the attested gateway so the gateway can process your request. It does not hide the request from the attested gateway; it proves the decryption key belongs to that gateway. This is the same boundary described in [Trust boundary](/confidential-ai/trust-boundary). Use a [confidential model](/get-started/models) when the upstream that runs the model must also be attested. ## Request headers | Header | Value | | - | - | | `X-E2EE-Version` | `2` | | `X-Client-Pub-Key` | Your secp256k1 public key, hex encoded. | | `X-Model-Pub-Key` | The gateway E2EE public key from the attested keyset. | | `X-E2EE-Nonce` | A unique per-request nonce. | | `X-E2EE-Timestamp` | Unix seconds, close to gateway time. | Do not send `X-Signing-Algo` with E2EE; that header selects the legacy compatibility path. ## Ciphertext format Each encrypted field is the lowercase hex encoding of: ```text theme={null} ephemeral_uncompressed_secp256k1_public_key || aes_gcm_nonce || ciphertext_tag ``` The AEAD associated data binds the ciphertext to the protocol version, direction, algorithm, model, field path, nonce, timestamp, and response identity. Any mismatch fails decryption, so use the official client helpers rather than assembling the bytes by hand. ## Receipts with E2EE Receipts attach to E2EE requests exactly as they do to plain TLS requests. The `request.received.body_hash` is the hash of the gateway-observed **decrypted** body, not the encrypted bytes you sent. When you [verify a response](/guides/verify-a-response), compare request hashes against the decrypted body your client holds. ## Related Headers, key exchange, and code. Where plaintext is visible. # FAQ Source: https://docs.redpill.ai/confidential-ai/faqs Common questions about the platform's attestation, receipts, confidential models, and trust boundary. It is inference served by a gateway running in a TEE that proves its own identity with an [attestation report](/confidential-ai/attestation-report) and signs a per-response [receipt](/confidential-ai/receipts) binding your request and response to that attested workload. You verify the proofs yourself rather than trusting an API's word. A confidential response runs on an upstream enclave that the gateway verifies before forwarding your prompt, fail-closed. A routed response passes through the TEE gateway but the upstream is not attested and sees your prompt. Confidentiality is a property of the verified upstream provider, not of the model id; confirm it per response from the receipt's `upstream.verified` event (`result = verified`, `required = true`). See [How it works](/confidential-ai/how-it-works) and [Trust boundary](/confidential-ai/trust-boundary). Plaintext is visible to the **attested** gateway after TLS or E2EE decryption, because it has to process the request. The gateway is a reviewed workload running in a TEE and does not retain request or response bodies. It does not mean no software ever sees the request. For the exact boundary, read [Trust boundary](/confidential-ai/trust-boundary). No. [E2EE](/confidential-ai/e2e-encryption) encrypts selected fields to a key from the attested keyset, and the gateway decrypts them inside the TEE to process the request. E2EE proves the decryption key belongs to the attested workload; it does not hide the request from that workload. Verify the [attestation report](/confidential-ai/attestation-report), then fetch the [receipt](/confidential-ai/receipts) by its `x-receipt-id` and confirm `upstream.verified.result = verified` and `required = true`. The full procedure is in [Verify a response](/guides/verify-a-response). The gateway binds your nonce into the hardware-signed quote's `report_data`. A report that commits to a nonce you just generated cannot be a replay of an older capture, which is how you prove freshness. Use a new random nonce each time. Receipts, which hold hashes of your request and response, not the content. The gateway does not store request or response bodies. Receipts are retained for a configurable window. Tinfoil, NEAR AI, Chutes, and Phala-backed enclaves, among others the platform adds over time. Each enforces a [channel binding](/confidential-ai/verified-upstreams#channel-binding) and attests a set of [typed claims](/confidential-ai/verified-upstreams#claims). The receipt cites an attested `session_id`; fetch that session to see which provider served the request. See [Providers](/confidential-ai/providers). The canonical endpoints live under `/v1/aci/`: `GET /v1/aci/attestation`, `GET /v1/aci/receipts/{id}`, and `GET /v1/aci/sessions/{id}`. The older `GET /v1/attestation/report` and `GET /v1/signature/{id}` remain as legacy compatibility aliases that return the same data in a legacy envelope. Use the `/v1/aci/*` endpoints for new integrations. # How confidential AI works Source: https://docs.redpill.ai/confidential-ai/how-it-works The platform serves an API from a TEE-attested gateway and proves each response with an attestation report and a signed receipt. 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: 1. It proves which code is running, in a real TEE, with an **attestation report**. 2. 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](/guides/verify-a-response). ## The two proof layers 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=`. 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 ```mermaid theme={null} flowchart LR user["Your app
OpenAI SDK"] upstream["Upstream model
provider"] subgraph gw["Gateway (TEE)"] direction TB fe["Frontend"] be["Backend
provider verification"] fe --> be end user <-->|"TLS or E2EE"| fe be <-->|"verified channel"| upstream ``` 1. You call the API over TLS, or add [E2EE](/confidential-ai/e2e-encryption) for field-level encryption. 2. The gateway records your request and computes its hash. 3. For a confidential model, the backend verifies the upstream provider and enforces a [channel binding](/confidential-ai/verified-upstreams#channel-binding) before forwarding. If it cannot, it does not send your prompt (fail-closed). 4. The response returns through the gateway, which signs the receipt after it has observed the final response bytes. ## Confidential vs routed models The platform serves two kinds of models through the same TEE gateway. The difference is whether the **upstream** that runs the model is itself attested. | | Confidential | Routed | | - | - | - | | Upstream verified | Yes, before forwarding | No | | Receipt `upstream.verified.result` | `verified` | `failed` | | Receipt `upstream.verified.required` | `true` (fail-closed) | `false` | | Attested session | Yes (`session_id` present) | No (`session_id` absent) | | Who can see your prompt | Only the attested gateway and the attested upstream enclave | The attested gateway, then the third-party provider (for example OpenAI) | Both kinds run through the TEE gateway, so the gateway protects your request in transit and does not retain request bodies. Only confidential responses extend that protection to the upstream that actually runs the model. For the precise limits, read [Trust boundary](/confidential-ai/trust-boundary). Whether a response is confidential depends on the upstream provider that serves it, not on the model id. Confirm it from the receipt: `upstream.verified.result` is `verified`, `required` is `true`, and `session_id` is present. Fetch that session for provider details. See [Models](/get-started/models). ## 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](/guides/verify-a-response) guide. # Open source Source: https://docs.redpill.ai/confidential-ai/open-source Review the open-source gateway and bind an approved deployment to its measured compose hash. The gateway and verifier are open source. Verification separates source labels from the hardware-bound measurement used to accept a release. ## Source labels and release identity Every attestation report includes `attestation.source_provenance`: ```json theme={null} { "repo_url": "https://github.com/Dstack-TEE/private-ai-gateway.git", "repo_commit": "9d45c7e3d48d2f74c31cd85f1fb5c6cee1435ef3", "image_digest": null, "image_provenance": null } ``` These fields identify the source claimed by the workload. They are useful for finding code to review, but are not cryptographic proof that the workload was built from that commit. The verifier instead checks that `sha256(app_compose)` matches the compose hash measured into RTMR3. After reviewing a release, publish that hash through an authenticated channel and require it through `acceptedComposeHashes`. A match then proves that the approved measured compose is running in the attested workload. ## Repositories The Attested Confidential Inference gateway: the API surface, attestation reports, receipts, and provider verification. The dstack TEE runtime and KMS the gateway uses for workload identity and quotes. The organization, clients, and tooling. ## What you can check from source * **Attestation handling.** How the gateway builds the report, binds the nonce and keyset into the quote, and endorses its signing keys. * **Provider verification.** How each confidential provider is verified and which [channel binding](/confidential-ai/verified-upstreams#channel-binding) is enforced before forwarding. * **Receipts.** How the [transparency event log](/confidential-ai/receipts) is built and signed, and that bodies are hashed, not stored. * **Fail-closed forwarding.** That a required upstream which cannot be verified or bound is rejected. ## Reporting a vulnerability Report security issues privately to [support@redpill.ai](mailto:support@redpill.ai) rather than in a public issue. Include reproduction steps. ## Related # Confidential providers Source: https://docs.redpill.ai/confidential-ai/providers The verified upstream providers behind the platform's confidential models, the binding each enforces, and what each attests. A [confidential model](/get-started/models) is served by an upstream provider whose enclave the gateway verifies before forwarding your prompt. Each provider produces one enforceable [channel binding](/confidential-ai/verified-upstreams#channel-binding) and a set of [typed claims](/confidential-ai/verified-upstreams#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. ## Providers and bindings | Provider | TEE | Channel binding | Zero data retention | | - | - | - | - | | Tinfoil | AMD SEV-SNP (or TDX) + NVIDIA CC | `tls_spki_sha256` | Yes | | NEAR AI | Intel TDX + NVIDIA CC | `tls_spki_sha256` | Yes | | Chutes | Intel TDX + NVIDIA CC | `e2ee_public_key_sha256` | Yes | | Phala | Intel TDX + NVIDIA CC | `tls_spki_sha256` | Yes | * 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. * 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. Third-party routed providers such as `openai` and `anthropic` are not in this set. To enforce the property per request, send `provider: {"zdr": true}`. See [Zero data retention](/guides/zero-data-retention). ## 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](/confidential-ai/verified-upstreams#claims) for the vocabulary. | Claim | Tinfoil | NEAR AI | Chutes | Phala | | - | - | - | - | - | | `tee_attested` | Hardware-proven | Hardware-proven | Hardware-proven | Hardware-proven | | `tcb_up_to_date` | Tri-state | Tri-state | Tri-state | Tri-state | | `serving_software_known_good` | Verifier-derived (Sigstore) | Unknown | Unknown | Unknown | | `gpu_attested` | Unknown | Unknown | Verifier-derived | Verifier-derived | | `model_weights_provenance` | Unknown | Unknown | Unknown | Unknown | Notes: * **`tee_attested` is hardware-proven** for every confidential provider: a genuine TEE quote is verified and the request channel is bound to it. * **`tcb_up_to_date` is a tri-state**, read from the verifier's reported TCB status. An up-to-date platform asserts the claim; a stale platform refutes it (the gateway records the stale status rather than silently passing); an absent status is `Unknown`. * **`serving_software_known_good`** is asserted for Tinfoil by comparing its launch measurement against published Sigstore golden values for the build. Other providers leave it `Unknown` today. * **`gpu_attested`** asserts when the provider's NVIDIA confidential-computing GPU attestation is verified and nonce-bound. It attests a genuine confidential GPU, not its binding to the serving CPU TEE, so it is verifier-derived and never gates a session. ## 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](/confidential-ai/verified-upstreams#attested-sessions) 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. ## Generic routed providers A routed response goes to a third-party provider with no confidential-inference verifier (for example OpenAI or Anthropic). The receipt records `upstream.verified.result = failed` and `required = false`. The request is served through the TEE gateway, but the upstream is not attested. See [Trust boundary](/confidential-ai/trust-boundary). ## Related Channel binding, attested sessions, and how to read a claim. Confirm a confidential response from its receipt. # Receipts Source: https://docs.redpill.ai/confidential-ai/receipts A signed receipt is a per-response transparency log that binds your request and response hashes to the attested gateway. 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](/confidential-ai/attestation-report), so a verified receipt proves the response came from the attested workload and was not altered. Every inference response includes the receipt id in the `x-receipt-id` header. Fetch the receipt: ```bash theme={null} curl "https://tee.redpill.ai/v1/aci/receipts/$RECEIPT_ID" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" ``` `{id}` accepts the receipt id (`rcpt-…`) or the response `id` (chat id). See [`GET /v1/aci/receipts/{id}`](/api-reference/receipts) for the response shape. ## 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 (`":"`), a top-level `signature`, and `signing_address`, for earlier API clients. New integrations should use the canonical `/v1/aci/receipts/{id}` response above. ## 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. | | `transparency.request_modified` | Backend | Present when the forwarded body differs from the received body. | | `upstream.verified` | Backend | The upstream verification result and binding (see below). | | `response.received` | Backend | Hash of the provider response before any post-processing. | | `transparency.response_modified` | Backend | Present when the returned response differs from the received response. | | `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 tells you whether the model that ran your prompt was itself attested: ```json theme={null} { "type": "upstream.verified", "model_id": "Qwen/Qwen3.6-35B-A3B-FP8", "result": "verified", "required": true, "session_id": "3681736b33b9b8191216968e37e350bf…" } ``` * For a [confidential model](/get-started/models), `result` is `verified`, `required` is `true`, and `session_id` references the [attested session](/confidential-ai/verified-upstreams#attested-sessions) the channel was bound to. * For a routed model, `result` is `failed`, `required` is `false`, and there is no `session_id`. The gateway still served the request, but the upstream provider was not attested. ## Why hashes, not bodies The gateway does not store your request or response. Receipts hold hashes, so they prove integrity without warehousing your data. If the response was modified in transit, a `transparency.*_modified` event records that the forwarded bytes differed, again by hash. ## Streaming For streaming responses the gateway can return `x-receipt-id` early, but the signed receipt is complete only after the stream finishes, because `response.returned.body_hash` covers the full streamed body. Fetch the receipt after the stream ends. ## Related Follow `session_id` to the verified security context behind a confidential response. Check a receipt signature and hashes yourself. # Trust boundary Source: https://docs.redpill.ai/confidential-ai/trust-boundary What the attested gateway protects, what it does not, and where your plaintext is visible. Confidential computing is precise about what it proves. This page states the boundary plainly so you can decide what to send through which model. It is the honest counterpart to [How it works](/confidential-ai/how-it-works). ## What the gateway protects * **Gateway identity.** You can prove that a measured workload is running in a genuine TEE. A reviewed-release claim additionally requires an accepted compose-hash allowlist. See the [attestation report](/confidential-ai/attestation-report). * **Per-response integrity.** A [signed receipt](/confidential-ai/receipts) binds your request and response hashes to that workload. Tampering is detectable. * **No body retention.** The gateway does not store request or response bodies. Receipts hold hashes, not content. * **Confidential upstreams.** For [confidential models](/get-started/models), the upstream enclave is verified and the channel is bound before your prompt is forwarded, fail-closed. * **Upstream retention, on request.** The gateway's own no-retention property says nothing about the provider it forwards to. Send `provider: {"zdr": true}` to restrict routing to providers that do not retain prompt or completion content. See [Zero data retention](/guides/zero-data-retention). ## What it does not protect * **Plain TLS is visible to the attested gateway.** After TLS terminates inside the TEE, the gateway processes your request in plaintext. The protection is that the gateway is attested and retains nothing, not that the gateway never sees the request. Add [E2EE](/confidential-ai/e2e-encryption) for field-level encryption, which is still decrypted inside the attested gateway. * **Routed models expose your prompt to the third party.** For a routed model (for example `openai/*` or `anthropic/*`), the gateway forwards to the provider over ordinary TLS. That provider sees your prompt and is not attested. Routing does not make a third-party provider confidential. Requesting [zero data retention](/guides/zero-data-retention) constrains whether such a provider stores your prompt, not whether it sees it. * **The gateway does not make an arbitrary upstream private.** A provider is only confidential when its adapter can verify a provider-specific identity and enforce the channel binding. * **Receipts are retained for a limited window.** Receipts are kept in memory with a configurable TTL; there is no durable public transparency log yet. ## Where plaintext exists | Stage | Who can see plaintext | | - | - | | Your device to gateway | Protected by TLS; with E2EE, selected fields are encrypted to the gateway's attested key. | | Inside the gateway TEE | The attested gateway, after TLS termination or E2EE decryption. It retains nothing. | | Gateway to confidential upstream | Only the attested upstream enclave (channel-bound). | | Gateway to routed upstream | The third-party provider, over ordinary TLS. | ## A note on optional middleware The gateway design allows an optional middleware layer for routing and business logic. If a deployment enables middleware, that middleware runs inside the same attested deployment and can see plaintext after gateway decryption, by design. Middleware never creates verification facts: upstream verification and channel binding remain the backend's responsibility, and the receipt records the verified result. ## Choosing a model for sensitive data * Use a [confidential response](/get-started/models) when the upstream that runs the model must be attested and your prompt must not reach a non-attested third party. Confidentiality is a property of the verified upstream provider, confirmed per response from the receipt's `upstream.verified` event, not from the model id. * Add [E2EE](/confidential-ai/e2e-encryption) when you want field-level encryption in addition to TLS. * Use a routed model when you accept that the third-party provider sees the prompt and you mainly want a single endpoint with a signed integrity receipt. ## Related # Verified upstreams Source: https://docs.redpill.ai/confidential-ai/verified-upstreams 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. ## Channel binding Verifying an upstream's attestation is not enough on its own: a genuine enclave could exist while the gateway talks to a different server. Channel binding closes that gap. The value the gateway verifies is the value it enforces on the connection before forwarding. | Binding | What it is | How the gateway enforces it | | - | - | - | | `tls_spki_sha256` | SHA-256 of the upstream's TLS public key (SPKI). | Pins the upstream HTTPS connection to that key. | | `e2ee_public_key_sha256` | SHA-256 of the upstream's end-to-end public key. | Encrypts the request body to that key, so only the attested enclave can decrypt it. | 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](/confidential-ai/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. ```mermaid theme={null} flowchart TD A["Confidential request"] --> B{"Upstream verified
and binding enforceable?"} B -->|"Yes"| C["Forward prompt over bound channel"] B -->|"No"| D["Reject. Prompt is not sent."] ``` For a standard model, `required` is `false`: the gateway forwards to the third-party provider over ordinary TLS, the receipt records `result: failed`, and your prompt leaves the gateway. See [Trust boundary](/confidential-ai/trust-boundary). ## Attested sessions When the gateway verifies an upstream, it records an **attested session**: an immutable snapshot of the verified channel. A session is the channel, not a model. One channel can serve many models, so a router-backed provider produces one session and the served model is recorded on the receipt. A session captures: * the verified upstream identity and endpoint; * the enforced channel binding; * the typed claims about the TEE, each with its source; and * the byte-preserving evidence the verifier checked. The `session_id` is the SHA-256 of the full session document in canonical JCS form, as 64 lowercase hex characters. Any change to the document produces a different id, so recomputing the hash proves the session you fetched is exactly the one the receipt cited. ```text theme={null} response → x-receipt-id receipt → upstream.verified { result, required, model_id, session_id } session → { binding, claims (+ reasons), evidence } ``` Fetch [`GET /v1/aci/sessions/{session_id}`](/api-reference/sessions) for the provider, binding, claims, and evidence. You do not enforce the binding yourself; you confirm after the fact that the gateway did: `result` is `verified`, `required` is `true`, and the cited session carries the binding. ## Claims A session never says "trusted". It carries a fixed set of claims, each with a `status`, a `source`, and a plain `reason`, so a hardware-proven fact and an operator's assertion never look alike. ```json theme={null} "tee_attested": { "status": "asserted", "source": "hardware_proven", "reason": "verified the TEE quote and bound the request channel" } ``` | `status` | Meaning | | - | - | | `asserted` | The verifier's evidence backs this claim. | | `refuted` | The evidence contradicts the claim, for example a stale platform TCB. Recorded, not hidden. | | `unknown` | The claim was not established. Never a silent pass. | | `source` | Assurance | | - | - | | `hardware_proven` | From the verified TEE quote or its collateral. Strongest. | | `verifier_derived` | Computed by the verifier from verified evidence. | | `provider_asserted` | Published by the provider, not independently proven. | | `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? | | `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 map to reviewed source? | | `gpu_attested` | Is a genuine confidential-computing GPU attested and nonce-bound? | | `model_weights_provenance` | Are the served weights and quantization what they claim to be? | Two claims need care when you read them: * `tcb_up_to_date` is read from the verifier's reported TCB status. An up-to-date platform asserts it, a stale platform refutes it, and an absent status leaves it `unknown`. Freshness comes from the evidence, never from policy. * `gpu_attested` is `asserted` with source `verifier_derived` when the provider's NVIDIA confidential-computing GPU attestation is verified and bound to the nonce. It proves a genuine confidential GPU exists for that nonce, not that the GPU is bound to the CPU TEE serving your request, so it never gates a session. 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](/confidential-ai/providers) page. ## Related Binding type and asserted claims per provider. The `upstream.verified` event that cites a session. Fetch a session by id, or list current sessions. What the gateway does and does not protect. # Billing Source: https://docs.redpill.ai/get-started/billing Pay-as-you-go credits in USD: what a request costs, how to add credits, key budgets, and where to read usage. RedPill is pay as you go. You add credits in USD, and each request deducts its cost from the workspace's balance. ## What a request costs Cost is the model's list price times tokens. The price per token is in the model object from [`GET /v1/models`](/api-reference/models), and every response reports what it was charged: | `usage` field | Meaning | | - | - | | `cost` | Amount charged for this response, in USD. | | `prompt_tokens`, `completion_tokens` | Billed at the model's `pricing.prompt` and `pricing.completion`. | | `completion_tokens_details.reasoning_tokens` | Part of `completion_tokens`; billed at the completion rate. | | `prompt_tokens_details.cached_tokens` | Billed at `pricing.input_cache_read` when the model lists one, otherwise at the prompt rate. | A request that is rejected before reaching a model, for example by a rate limit or a routing constraint, costs nothing. ## Add credits * **Card**: pay from the [Credits](https://www.redpill.ai/credits) page, 5 to 1,000 USD per payment. * **Auto-recharge**: choose a threshold and an amount, both at least 5 USD; when the balance drops below the threshold, the amount is charged to the saved card. Invoices and payment methods are on the [Billing](https://www.redpill.ai/billing) page. ## Limits on spend Set a budget on an API key when you create it. When the key reaches its budget, or the account's quota is used up, requests return `402` with `error.type` `insufficient_quota` and a message naming which limit was hit. ## Read usage The [Usage](https://www.redpill.ai/usage) page shows daily totals and a per-request log with model, tokens, and cost, with filters. An agent can read the same data through the [MCP server](/get-started/mcp). ## Related # RedPill CLI Source: https://docs.redpill.ai/get-started/cli Manage RedPill organizations, workspaces, models, billing, API keys, and usage from a terminal or script. The RedPill CLI manages your RedPill account. It is for people, shell scripts, and local agents that need a command-line interface; it does not send inference requests or replace [Private AI Proxy](/get-started/private-ai-proxy). ## Choose an account interface | Host | Use | | - | - | | Agent host with remote MCP support | [Account MCP](/get-started/mcp) | | Terminal, script, or agent host with a shell but no MCP client | RedPill CLI | Use one account interface on a given host, not both. Each keeps its own OAuth grant. MCP is the preferred agent integration because it provides typed tools and keeps API key creation in the browser; use the CLI when MCP is unavailable. ## Install Install Node.js 22.12 or newer, then install the CLI from npm. Node.js 24 LTS is recommended. ```bash theme={null} npm install --global @redpillai/cli redpill --version ``` npm is the supported installation channel. Standalone binaries and install scripts are not currently published. ## Sign in ```bash theme={null} redpill auth login redpill auth status ``` The CLI opens RedPill in your browser and uses OAuth Authorization Code with PKCE. One login grants access to your user account and discovers every organization you can use. Choosing an organization or workspace changes local command context; it does not start another OAuth authorization. ```bash theme={null} redpill org list redpill org use acme redpill workspace list redpill workspace use production ``` Flags override saved defaults for a single command: ```bash theme={null} redpill --org personal billing balance redpill --org acme --workspace sandbox usage summary ``` ## Capabilities | Area | Commands | | - | - | | Account | `account`, `org list`, `org current`, `org use` | | Workspaces | `workspace list`, `workspace current`, `workspace use`, `workspace create`, `workspace update`, `workspace archive` | | Models | `model list`, `model get`, `model uptime` | | Billing | `billing balance`, `billing history` | | API keys | `key list`, `key get`, `key create`, `key update`, `key revoke` | | Usage | `usage summary`, `usage logs`, `usage facets` | Run `redpill --help` or `redpill --help` for options. ## Automation and confirmations Human-readable output is the default. Use `--json` for one complete JSON document on stdout; diagnostics and errors stay on stderr. ```bash theme={null} redpill --json model list | jq '.data[] | .id' redpill --json --org acme usage summary ``` Commands that change remote data show a preview and ask for confirmation in an interactive terminal. In reviewed automation, use both `--json` and `--yes`: ```bash theme={null} redpill --json --org acme key revoke 42 --yes ``` `--json` disables prompts and fails before a mutation unless `--yes` is present. Use `--no-input` or `REDPILL_NO_INPUT=1` to disable prompts while retaining human-readable output. OAuth login is interactive, so an agent should ask the user to run `redpill auth login` before continuing. ## Configuration and credentials The CLI stores local state in the RedPill configuration directory, following the XDG convention on Unix systems: | Platform | Default directory | | - | - | | Linux and macOS | `${XDG_CONFIG_HOME:-$HOME/.config}/redpill` | | Windows | `%APPDATA%\\redpill` | Set `REDPILL_CONFIG_HOME` to override the directory. `config.json` contains organization and workspace defaults. `credentials.json` contains the OAuth grant and must not be shared or committed. On Unix systems, the directory is written with mode `0700` and both files with mode `0600`; credential updates use atomic replacement. The CLI does not store RedPill API key secrets. A newly created key is shown once in command output, so treat that output as a secret. Agents must not run `redpill key create` because the secret would enter model context; ask the user to create the key in the [dashboard](https://www.redpill.ai/keys) instead. ## Limits Payments, subscriptions, organization membership, and administrator operations are intentionally not available through the CLI. Receipt verification must run in the client that sent the request. ## Related # Get started with RedPill Source: https://docs.redpill.ai/get-started/introduction A confidential AI inference API you can verify. Every response is served from a TEE and backed by an attestation report and a signed receipt. Confidential AI RedPill is an OpenAI-compatible inference API served by a gateway that runs inside a Trusted Execution Environment (TEE). Every response comes with evidence you can verify: an attestation report identifying the measured workload, and a signed receipt binding your request and response to it. Confidential models go further and run on a verified upstream enclave, with the channel bound before your prompt is forwarded. ## How do you want to use RedPill? Point your OpenAI or Anthropic SDK at one endpoint. Confidential and standard models, with a signed receipt on every response. Run Claude Code, Codex, Pi, OpenCode, and more through Private AI Proxy, a desktop app that verifies the gateway and connects your agents. End-to-end encrypted chat with confidential models and private document knowledge bases. ## Onboard your agent Give an agent RedPill guidance and one account-management interface without changing the current project or its model provider. The button copies the same canonical prompt used on redpill.ai. The agent installs the RedPill skill and uses [Account MCP](/get-started/mcp) when the host supports remote MCP servers. Hosts with only a shell use the [RedPill CLI](/get-started/cli) instead. Use one account interface on a given host, not both. ## Verify, don't trust After one request you can check, locally, that the gateway is a measured workload running in a genuine TEE, and that the response came from that workload unaltered and, for a confidential model, ran on a verified upstream enclave. [How confidential AI works](/confidential-ai/how-it-works) explains the model; [Verify a response](/guides/verify-a-response) runs the checks. # Account MCP Source: https://docs.redpill.ai/get-started/mcp Let an agent read and manage your RedPill account, models, API keys, workspaces, and usage through the RedPill MCP server. Account MCP lets an agent manage your RedPill account through typed tools and user-level OAuth. It does not run inference or change the agent's model provider; for that, see [Use RedPill in agents](/get-started/use-in-agents). ## Choose an account interface | Host | Use | | - | - | | Agent host with remote MCP support | Account MCP | | Terminal, script, or agent host with a shell but no MCP client | [RedPill CLI](/get-started/cli) | Use one account interface on a given host, not both. Each keeps its own OAuth grant. MCP is the preferred agent integration because it provides typed tools and keeps API key creation in the browser; use the CLI when MCP is unavailable. ## Connect Add the account URL as a remote HTTP server. On first use the client opens your browser: sign in and approve access to your RedPill account. One OAuth grant covers every organization you can access; organization selection is an explicit tool argument, not another authorization. No API key, client secret, or app registration is needed. ```bash theme={null} claude mcp add --transport http redpill https://service.redpill.ai/mcp ``` Then run `/mcp` in Claude Code to sign in. ```bash theme={null} codex mcp add redpill --url https://service.redpill.ai/mcp codex mcp login redpill ``` Add to `opencode.json`: ```json theme={null} { "mcp": { "redpill": { "type": "remote", "url": "https://service.redpill.ai/mcp" } } } ``` Any client that supports remote HTTP MCP servers with OAuth works the same way. To configure the skill and account interface together, use [Onboard your agent](/get-started/introduction#onboard-your-agent). The public documentation MCP at `https://docs.redpill.ai/mcp` is optional and does not require sign-in. ## Capabilities | Area | Read | Write | | - | - | - | | Account | `get_account`, `list_workspaces` | `create_workspace`, `update_workspace`, `archive_workspace` | | Models | `list_models`, `get_model`, `get_model_uptime` | | | Billing | `get_balance`, `list_billing_history` | | | API keys | `list_api_keys`, `get_api_key` | `create_api_key`, `update_api_key`, `revoke_api_key` | | Usage | `get_usage_summary`, `get_usage_logs`, `get_usage_facets` | | Every call checks your current organization membership and the same permission the dashboard uses, so an agent can do exactly what you can do there, and no more. Key listings show metadata and limits only. Archiving a workspace revokes its keys. ## Confirmations and secrets A write returns a preview first. Clients that support MCP forms show a confirmation; other clients repeat the call with `confirm=true`. Creating an API key finishes in the browser. The agent receives a short-lived link; you open it signed in as the same user, click **Create**, and see the secret once. The secret never passes through MCP or the model. Clients without URL elicitation support create keys in the [dashboard](https://www.redpill.ai/keys) instead. ## Limits * Payments, top-ups, auto-recharge, and subscriptions. * Organization membership, identity, and administrator operations. * Receipt verification. Verification has to run in the client that sent the request; see [Verify a response](/guides/verify-a-response). ## Related # Models Source: https://docs.redpill.ai/get-started/models Model ids, confidential versus standard models, and how to confirm which kind served a response. One endpoint serves the whole catalog, from open-weight models to proprietary ones like GPT and Claude. Every available model by provider. `GET /v1/models` with pricing, context windows, and capabilities. ## Model ids Each model has a `provider/model` id, such as `openai/gpt-5` or `z-ai/glm-5.2`. Pass it as the `model` field in a request. The same model may be offered under more than one id. Embedding models have their own catalog, [`GET /v1/embeddings/models`](/api-reference/embeddings#model-catalog). ## Confidential and standard models A **confidential** model runs inside a TEE on a provider whose enclave the gateway verifies before forwarding your prompt, fail-closed. A **standard** model runs on a third-party provider that is not attested. Both are served through the attested gateway; only a confidential model extends that protection to the model itself. In the catalog, `is_tee: true` marks a model that can be served confidentially: ```bash theme={null} curl -s https://api.redpill.ai/v1/models | jq -r '.data[] | select(.is_tee == true) | .id' ``` To confine an application to confidential models, use the [TEE-only endpoint](/guides/tee-endpoint) `https://tee.redpill.ai` as the base URL. ## Confirm a confidential response `is_tee` says a model can be served confidentially. The [receipt](/confidential-ai/receipts) proves that a specific response was. Read the `x-receipt-id` header and inspect the `upstream.verified` event: ```bash theme={null} curl -s "https://api.redpill.ai/v1/aci/receipts/$RECEIPT_ID" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ | jq '.event_log[] | select(.type=="upstream.verified") | {model_id, result, required, session_id}' ``` | | Confidential | Standard | | - | - | - | | `result` | `verified` | `failed` | | `required` | `true` | `false` | | `session_id` | present | absent | The cited session names the provider and its verified claims. See [Verified upstreams](/confidential-ai/verified-upstreams). ## Confidentiality is not retention `is_tee` constrains who can read memory while the model runs. It says nothing about whether the provider stores the prompt afterwards. For that property, filter with [`GET /v1/models?zdr=true`](/api-reference/models#query-parameters) and send `provider: {"zdr": true}`. See [Zero data retention](/guides/zero-data-retention). ## Choosing a model Models differ in context length, accepted inputs, price, and supported parameters and features. Each model object in [`GET /v1/models`](/api-reference/models) carries these fields, so match a model to your application before you call it. ## Related # Private AI Proxy Source: https://docs.redpill.ai/get-started/private-ai-proxy The desktop app and CLI that verifies the RedPill gateway on your machine and connects your agents to a verified local API. Private AI Proxy runs on your computer. It signs in to your RedPill account, verifies the gateway's attestation before any prompt leaves your machine, and gives your agents a local API so they never hold your RedPill credential. It is open source in [Dstack-TEE/private-ai-gateway](https://github.com/Dstack-TEE/private-ai-gateway/tree/main/apps/desktop) and published by dstack TEE. ```text theme={null} your agent → Local API on 127.0.0.1:4180 (agent-scoped token) → Private AI Proxy, verified and pinned channel → RedPill gateway (attested), signed receipt per response ``` Private AI Proxy overview screen, light theme Private AI Proxy overview screen, dark theme The sections below follow the app's screens: Profiles, Privacy, Agents, Local API, Usage, and Settings, then the CLI. ## Install ### Desktop app [Start building](https://redpill.ai/start-building) detects your platform and downloads the current stable build. | Platform | Download | | - | - | | macOS, Apple Silicon | [`.dmg`](https://redpill.ai/download/private-ai-proxy/macos-arm64) | | macOS, Intel | [`.dmg`](https://redpill.ai/download/private-ai-proxy/macos-x64) | | Windows, x64 (Preview) | [`.exe`](https://redpill.ai/download/private-ai-proxy/windows-x64) | | Windows, ARM64 (Preview) | [`.exe`](https://redpill.ai/download/private-ai-proxy/windows-arm64) | | Linux, x64 | [`.deb`](https://redpill.ai/download/private-ai-proxy/linux-x64.deb) · [`.rpm`](https://redpill.ai/download/private-ai-proxy/linux-x64.rpm) · [Arch `.pkg.tar.zst`](https://redpill.ai/download/private-ai-proxy/linux-x64.pkg.tar.zst) | | Linux, ARM64 | [`.deb`](https://redpill.ai/download/private-ai-proxy/linux-arm64.deb) · [`.rpm`](https://redpill.ai/download/private-ai-proxy/linux-arm64.rpm) · [Arch `.pkg.tar.zst`](https://redpill.ai/download/private-ai-proxy/linux-arm64.pkg.tar.zst) | ```bash theme={null} brew install --cask dstack-tee/private-ai/private-ai-proxy ``` Linux requires glibc 2.35 or newer. Windows Desktop is currently Preview. ### CLI The official scripts are the recommended installation method. They detect the operating system and CPU, verify the release checksum, and install `pap`, `private-ai-proxy`, and `aci`. ```bash theme={null} curl -fsSL https://redpill.ai/private-ai-proxy/install.sh | sh ``` ```powershell theme={null} irm https://redpill.ai/private-ai-proxy/install.ps1 | iex ``` ```bash theme={null} brew install dstack-tee/private-ai/private-ai-proxy ``` ```bash theme={null} npm install --global private-ai-proxy ``` Do not use `--omit=optional`; npm selects the native package through an optional dependency. On Homebrew 6+, each fully qualified command above automatically taps the repository and trusts only the item being installed. To use short names such as `brew install private-ai-proxy` or `brew upgrade private-ai-proxy`, trust the whole tap first: ```bash theme={null} brew tap dstack-tee/private-ai brew trust dstack-tee/private-ai ``` Trust for an item installed by its fully qualified name persists, so later upgrades continue to work. Older Homebrew versions do not have tap trust; the fully qualified one-line commands still work there. Verify the installation: ```bash theme={null} pap --version pap doctor ``` Rerun an official script to update a script-managed installation. Homebrew and npm manage their own upgrades and removal. Release signatures and checksums are on the [GitHub Releases page](https://github.com/Dstack-TEE/private-ai-gateway/releases). ## Profiles and sign-in A profile is one RedPill workspace plus its credential. Add one with either: * **Account login**: the app opens your browser, you approve it once and pick a workspace, and the app receives an inference key for that workspace. The flow is a public OAuth client with PKCE and a loopback callback on `127.0.0.1:4181`; no client secret or refresh token, and the sign-in token stays in memory. If saving fails, **Retry** reuses the approval. * **API key**: **Get API key** opens the [dashboard](https://www.redpill.ai/keys); paste the key. Credentials live in the operating system's credential store, never in a plain file, and profile exports never contain them. Sign in again or paste a new key to replace one; revoke old keys in the dashboard. Phala Cloud profiles use the same form with Phala's device authorization; custom endpoints accept API keys only. ## Privacy: what Protected does Switching on **Protected** runs the ACI relying-party checks against the gateway with a fresh nonce: 1. the hardware quote verifies to the TEE vendor root and binds the report data; 2. the attested keyset digest matches the statement for that nonce; 3. the keyset has not expired; 4. source provenance names the public code the workload booted from; and 5. the TLS key actually used for the connection is in the attested keyset. On success the app pins the channel to that keyset for the session and starts the Local API. If any check fails, Protected stays off and no agent is connected. Stopping Protected, quitting the app, a transport failure that cannot be recovered, or a later verification failure disconnects the agents and restores their previous configuration. Verification is never restored from disk after a restart; it is redone. ## Agents The **Agents** screen lists the agents the app can configure. **Connect** writes RedPill into the agent's own provider settings, in the protocol the agent speaks, with an agent-scoped token for the Local API. **Disconnect** restores the file you had before; edits you made in the meantime are preserved, and an incomplete restore stays retryable. | Agent | Protocol | Where the app writes | After connecting | | - | - | - | - | | Claude Code | Messages | Claude Code settings | Restart `claude` | | Codex | Responses | Custom model provider in `config.toml` | Restart `codex` | | OpenCode | Chat Completions | `opencode.json` | Restart OpenCode | | Pi | Chat Completions | Pi provider settings | Restart Pi | | Hermes | Chat Completions | `config.yaml` | Restart Hermes | | OpenClaw | Chat Completions | Native-host provider with a local token reference | Restart OpenClaw | | Oh My Pi | Chat Completions | Local token and models file | Restart `omp` | Only agents that are both connected and currently protected receive the Local API configuration. Connecting from the CLI previews the change first and applies it with a revision, so a file that changed in between is rejected rather than overwritten. ## Local API While Protected is on, the app serves an OpenAI-compatible API on your machine: | Field | Value | | - | - | | Base URL | `http://127.0.0.1:4180/v1` | | API key | The client key shown on the Overview and Local API screens (`sk-pap-…`) | | Models | The verified catalog for the active profile | Any tool that accepts an OpenAI-compatible endpoint can use it, for example [Cline](/guides/integrations/coding-tools/cline). The Local API is plain HTTP on loopback; send ordinary request bodies. It rejects end-to-end encryption headers because the app already owns the verified channel, and it stops listening when Protected is off. Rotate the client key from the Local API screen or `token rotate`. ## Usage Responses stream to the agent immediately. The app fetches the signed receipt for each response afterwards and audits it against the attestation. **Usage** records every request with the agent, model, tokens, estimated cost, and the audit result; an audit failure is recorded but cannot recall bytes the agent already received. The Overview screen shows the current session's totals. Usage can be filtered, exported to CSV, and cleared. ## Settings * **Connect on launch** starts protection when the background service starts. * **Open at login** is an operating system preference set from the desktop app. * **Advanced > Reset** stops protection, disconnects agents, and restores defaults while keeping profiles, credentials, the local client key, and usage history. * Closing the window does not stop the service; the tray's Stop All and Quit action does. ## CLI `pap` drives the same background service without a window. It comes with the desktop app (on your PATH automatically on macOS, from Settings > **private-ai-proxy command** on Windows, system-wide from the Linux packages) or [on its own](#cli). `private-ai-proxy` and `aci` are the same executable. ```bash theme={null} pap doctor # every check, including credential store access pap service start # background service pap start --profile work # verify the gateway and turn on Protected pap agents connect codex # add --model MODEL to pick a model yourself pap status --json pap stop # stop Protected and restore agent configuration ``` | Task | Command | | - | - | | Service and protection | `service start`, `start`, `stop`, `status --watch` | | Profiles and credentials | `profiles list/show/add/edit/verify/use/remove`, `token clear-credential` | | Agents | `agents list/connect/disconnect/disconnect-all` | | Catalog and usage | `models list --refresh`, `usage list/show/export/clear` | | Local API settings and token | `settings show/set`, `token show/rotate` | | Verify any ACI service | `verify `, `sessions `, `serve ` | Credentials are entered through a hidden prompt or `--key-stdin`, never as an argument. For scripts, `--json --non-interactive` returns data and `--yes` approves a change; agent changes are previewed with `--dry-run` and applied with the returned `--revision`. Exit status is `0` on success, `2` for invalid arguments, `1` for operation failures. `verify`, `sessions`, and `serve` work against any ACI service without a profile; `serve` refuses to start unless verification passes. ## Troubleshooting | Symptom | Check | | - | - | | Protected will not turn on | Run `pap doctor`; it reports every check separately, including credential store access. | | An agent still uses its old provider | Confirm it shows **Connected**, that Protected is on, and restart the agent. | | A tool cannot reach `127.0.0.1:4180` | Protected must be on; the Local API only listens during a verified session. | | Settings look wrong after an update | Settings > Advanced > Reset. | # Quickstart Source: https://docs.redpill.ai/get-started/quickstart Make your first API call, then confirm it was served from an attested gateway. Create an API key in the [dashboard](https://www.redpill.ai/dashboard) and set it as the `REDPILL_AI_API_KEY` environment variable. Then call the API with any OpenAI client library: point it at `https://api.redpill.ai/v1` and send your key as a bearer token. ## Make a request ```bash cURL theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Explain attestation in one sentence."}] }' ``` ```python Python theme={null} import os from openai import OpenAI client = OpenAI(base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"]) resp = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "Explain attestation in one sentence."}], ) print(resp.choices[0].message.content) ``` ```javascript JavaScript theme={null} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.redpill.ai/v1", apiKey: process.env.REDPILL_AI_API_KEY, }); const resp = await client.chat.completions.create({ model: "z-ai/glm-5.2", messages: [{ role: "user", content: "Explain attestation in one sentence." }], }); console.log(resp.choices[0].message.content); ``` The response is a standard OpenAI chat completion, plus verification headers: | Header | Meaning | | - | - | | `x-receipt-id` | Id of the signed receipt for this response. | | `x-aci-identity` | The attested workload identity that served the request. | | `x-aci-keyset-digest` | Digest of the workload's published keyset. | ## Confirm it was attested Read the `x-receipt-id` header, then fetch the receipt and check how the response was served: ```bash theme={null} curl -s "https://api.redpill.ai/v1/aci/receipts/$RECEIPT_ID" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ | jq '.event_log[] | select(.type=="upstream.verified") | {model_id, result, required, session_id}' ``` A confidential response shows `result: verified` and `required: true`. To verify the gateway identity and the receipt signature end to end, see [Verify a response](/guides/verify-a-response). ## Related Browse the catalog and tell confidential models from standard ones. Every endpoint and parameter. Check attestation and receipts yourself. # Rate limits Source: https://docs.redpill.ai/get-started/rate-limits Requests per minute per workspace, per-model ceilings, key limits, and what a 429 response carries. Limits apply per workspace, on a sliding 60-second window. A request over the limit is rejected before it reaches a model, so it costs nothing. | Limit | Default | Where it is set | | - | - | - | | Requests per minute | 300 per workspace | Raised per account by RedPill; enterprise accounts are exempt | | Tokens per minute | None unless set | Per account or per model. Counts prompt and completion tokens; cached prompt tokens count less | | Per-model ceilings | None unless set | Requests or tokens per minute for one model, applied to every account | ## The 429 response ```json theme={null} { "error": { "message": "Rate limit exceeded. Please retry after the reset time.", "type": "rate_limit_error", "code": "rate_limit_exceeded" } } ``` | Header | Meaning | | - | - | | `X-RateLimit-Limit` | The limit that was hit. | | `X-RateLimit-Remaining` | `0` | | `X-RateLimit-Reset` | Unix time, in seconds, when the window resets. | | `Retry-After` | Seconds until then. | A model-specific ceiling says so in the message. Successful responses carry no rate-limit headers. Retry with backoff after `Retry-After`; see [Error handling](/guides/error-handling). ## Related # Use in agents Source: https://docs.redpill.ai/get-started/use-in-agents Connect Claude Code, Codex, OpenCode, Pi, and other agents to RedPill through Private AI Proxy, or with a plain base URL. [Private AI Proxy](/get-started/private-ai-proxy) verifies the gateway on your machine and connects your agents to a local API; the RedPill credential never leaves the app. Any tool that cannot use the Proxy can still point at RedPill directly. ## Connect an agent Download it from [Start building](https://redpill.ai/start-building), open it, and add a RedPill profile with your account or an API key. The app verifies the gateway's attestation and pins the channel. Traffic is protected while the session is active. On the **Agents** screen, connect the agent and restart it. The app has written RedPill into the agent's own provider settings; disconnecting restores them. | Agent | What changes | Guide | | - | - | - | | Claude Code | Messages provider in Claude Code settings | [Claude Code](/guides/integrations/coding-tools/claude-code) | | Codex | Custom Responses provider in `config.toml` | [Codex CLI](/guides/integrations/coding-tools/codex-cli) | | OpenCode | Provider in `opencode.json` | [OpenCode](/guides/integrations/coding-tools/opencode) | | Pi | Pi provider settings | [Pi](/guides/integrations/coding-tools/pi) | | Hermes | Provider in `config.yaml` | [Hermes](/guides/integrations/runtimes/hermes) | | OpenClaw | Native-host provider; restart required | [OpenClaw](/guides/integrations/runtimes/openclaw) | | Oh My Pi | Local token and models file; restart `omp` | | From the CLI: `pap start --profile NAME`, then `pap agents connect `. See [CLI](/get-started/private-ai-proxy#cli). ## Any other tool Tools with an OpenAI-compatible provider setting, such as [Cline](/guides/integrations/coding-tools/cline), use the Proxy's Local API while Protected is on: | Field | Value | | - | - | | Base URL | `http://127.0.0.1:4180/v1` | | API key | The client key from the app's Overview screen | | Model | A model from the app's verified catalog | ## Without the Proxy On servers, in CI, or in frameworks, point the client at RedPill directly. Requests still go through the attested gateway and carry an `x-receipt-id`, but nothing on your side verifies the channel. | Client | Protocol | Base URL | | - | - | - | | OpenAI clients and agent frameworks | Chat Completions | `https://api.redpill.ai/v1` | | Codex CLI and other Responses clients | Responses | `https://api.redpill.ai/v1` | | Claude Code and Anthropic clients | Messages | `https://api.redpill.ai` (the client appends `/v1/messages`) | ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" curl -s https://api.redpill.ai/v1/models \ | jq -r '.data[] | select((.supported_features // []) | index("tools")) | .id' ``` Use `https://tee.redpill.ai` only when every request must use an `is_tee: true` model; it returns `404` for standard models. See [TEE-only endpoint](/guides/tee-endpoint) and [Verify a response](/guides/verify-a-response). # Attested routing Source: https://docs.redpill.ai/guides/attested-routing Restrict a request to an upstream the gateway verified inside the TEE, and pin it to a specific attested channel. 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](#list-the-models-you-can-use) to find one. Two levels of constraint are available: | Constraint | What it selects | | - | - | | `provider: {"aci_verified": true}` | Any upstream that is currently verified and channel bound. | | `provider: {"aci_session_ids": [...]}` | One specific attested channel, by session id. | By default a request has no attestation constraint. The gateway still verifies confidential upstreams and still issues receipts, but a request without the constraint can be served by a routed provider whose upstream is not attested. See [Confidential and standard models](/get-started/models). To apply this to every request without a `provider` block, call the [TEE-only endpoint](/guides/tee-endpoint) at `https://tee.redpill.ai`. It serves confidential models only and forces attested serving for the whole application. ## 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`. ```bash theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Summarize this patient note."}], "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. A routed model such as `openai/gpt-4o-mini` always fails this check: ```json theme={null} { "error": { "message": "upstream verification failed: no attested upstream available for model openai/gpt-4o-mini", "type": "service_unavailable" } } ``` The status is `503`. 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, applies no attestation constraint. `X-Upstream-Verification: none` cannot relax this constraint. A request that asks to be pinned to an attested upstream cannot also opt out of the attestation. The header still applies to requests that do not set `aci_verified`. ## Pin a request to one attested channel `aci_verified` accepts any upstream that is verified right now. To require one specific attested channel, pass its session id. This is useful when you have already inspected a session's evidence and want subsequent requests to reach that exact channel or fail. List the sessions that can serve your model. The unfiltered list covers every upstream the gateway has verified, so filter it: a session belonging to another upstream will never match your request. ```bash theme={null} curl -s "https://api.redpill.ai/v1/aci/sessions?model=z-ai/glm-5.2" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ | jq -r '.sessions[] | "\(.session_id) \(.upstream_name) \(.endpoint)"' ``` 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](/confidential-ai/verified-upstreams#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: ```bash theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Summarize this patient note."}], "provider": { "aci_verified": true, "aci_session_ids": ["2a442c6ba4407b583152949de6331e7a5aa7df7285f7aa9dce810e5e24186290"] } }' ``` 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 `503` and no prompt is sent: ```json theme={null} { "error": { "message": "upstream verification failed: none of the requested attested sessions is available for model z-ai/glm-5.2", "type": "service_unavailable" } } ``` 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. `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. ## List the models you can use ```bash theme={null} curl -s "https://api.redpill.ai/v1/models?tee=true" \ | jq -r '.data[].id' ``` Embedding models have their own catalog, which takes the same parameter: ```bash theme={null} curl -s "https://api.redpill.ai/v1/embeddings/models?tee=true" \ | jq -r '.data[].id' ``` `true` is the only accepted value. Any other value, including `false`, returns `400`. Omit the parameter for the full catalog. `?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. ## 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. | Request | Result | | - | - | | `"provider": {"aci_verified": true}` | Constraint applied. | | `"provider": {"aci_verified": false}` | No constraint. Same as omitting the block. | | `"provider": {"aci_verified": "yes"}` | `400`, expected a boolean. | | `"provider": {"aci_session_ids": []}` | `400`, must not be empty. | | `"provider": {"aci_session_ids": [""]}` | `400`, expected non-empty strings. | | `"provider": {"aci_verified": false, "aci_session_ids": ["2a44..."]}` | `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`](/guides/zero-data-retention). ## What the constraint guarantees The check runs before any prompt bytes leave the gateway: 1. The route is classified as an attested provider. 2. Verification is required for that route and cannot be waived by a request header. 3. Verification returns a verified result and an enforceable channel binding. 4. The request is forwarded over the bound channel. Verification evidence is cached for a short interval and refreshed in the background, so a verified result may come from a recent check rather than one performed at that instant. The channel binding is enforced on every connection. If the upstream presents a key or certificate that does not match the binding, the gateway re-verifies and retries, then fails. A request is never served over a channel that does not match the evidence it was verified against. This constraint applies per request. There is no session to establish and reuse: every request is evaluated on its own, and each failover candidate is evaluated independently. ## Verify that it held A successful attested request returns `x-receipt-id`. Fetch that receipt and read its `upstream.verified` event: ```bash theme={null} curl -s "https://api.redpill.ai/v1/aci/receipts/$RECEIPT_ID" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ | jq '.event_log[] | select(.type == "upstream.verified")' ``` ```json theme={null} { "type": "upstream.verified", "model_id": "phala/gemma-4-26b-a4b-uncensored", "result": "verified", "required": true, "session_id": "941d3d5f..." } ``` `"result": "verified"` with `"required": true` is the proof that the constraint held. `session_id` names the channel that served the request, and is the id to pin for follow-up requests. The receipt is signed by a key in the attested keyset, so this is evidence rather than a log line. See [Verify a response](/guides/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. ## Related Enforce attestation for every request with a base URL instead of a per-request block. The binding, session, and claims behind a verified response. The retention constraint, which composes with this one. The full `provider` routing block. # Encrypt a request Source: https://docs.redpill.ai/guides/e2ee-encryption Add field-level encryption to requests, on top of TLS, using a key from the attested keyset. 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](/confidential-ai/attestation-report). 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](/confidential-ai/e2e-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](/get-started/models). ## 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. ## 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](/guides/verify-a-response)) so you know the key is bound to a genuine TEE. ```bash theme={null} curl -s "https://tee.redpill.ai/v1/aci/attestation?nonce=$(openssl rand -hex 32)" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ | jq -r '.attestation.workload_keyset.e2ee_public_keys[] | select(.key_id=="dstack-kms-e2ee-v1") | .public_key' ``` The matching key uses the `secp256k1-aes-256-gcm-hkdf-sha256` algorithm. ## 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 secp256k1 public key, hex encoded. | | `X-Model-Pub-Key` | The gateway E2EE public key from step 1. | | `X-E2EE-Nonce` | A unique per-request nonce. | | `X-E2EE-Timestamp` | Unix seconds, close to gateway time. | ## 3. Encrypt the selected fields Each encrypted field is the lowercase hex encoding of: ```text theme={null} ephemeral_uncompressed_secp256k1_public_key || aes_gcm_nonce || ciphertext_tag ``` 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 The response still carries an `x-receipt-id`. For an E2EE request, `request.received.body_hash` is the hash of the gateway-observed **decrypted** body. When you verify, compare request hashes against the decrypted body your client holds, not the encrypted bytes you sent. See [Receipts](/confidential-ai/receipts). ## Related # Error handling Source: https://docs.redpill.ai/guides/error-handling Understand the API error format and handle errors gracefully. Every error is a JSON body with an `error` object and an HTTP status. Branch on the status and `error.type`, not on the message text. ## Error response format Errors return a JSON body with an `error` object and an appropriate HTTP status: ```json theme={null} { "error": { "message": "Invalid API key provided", "type": "authentication_error", "code": null, "param": null } } ``` 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 not available (400). | | `insufficient_quota` | The workspace is out of credits or the key reached its budget (402). See [Billing](/get-started/billing). | | `rate_limit_error` | Over the requests or tokens per minute limit (429). See [Rate limits](/get-started/rate-limits). | | `not_found_error` | The model exists but no provider can serve it under the request's routing constraints, for example [`provider: {"zdr": true}`](/guides/zero-data-retention) (404). | | `upstream_error` | The upstream provider is unavailable (502). | ## Status codes | Status | Meaning | | - | - | | 400 | Bad request (invalid parameters or unknown model) | | 401 | Unauthorized (missing or invalid API key) | | 402 | Out of credits or key budget | | 403 | Forbidden | | 404 | No provider can serve the model under the request's routing constraints | | 429 | Rate limit exceeded | | 500 | Server error | | 502 | Upstream provider unavailable | | 503 | Service temporarily unavailable | ## Handle errors with the SDK OpenAI client libraries map these statuses to typed exceptions: ```python Python theme={null} import os, time from openai import OpenAI, AuthenticationError, RateLimitError, APIError client = OpenAI(base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"]) try: resp = client.chat.completions.create( model="openai/gpt-5", messages=[{"role": "user", "content": "Hello"}], ) except AuthenticationError: print("Invalid API key") except RateLimitError: print("Rate limited; back off and retry") except APIError as e: print(f"API error {e.status_code}: {e.message}") ``` ```javascript JavaScript theme={null} try { const resp = await client.chat.completions.create({ model: "openai/gpt-5", messages: [{ role: "user", content: "Hello" }], }); } catch (error) { if (error.status === 401) console.error("Invalid API key"); else if (error.status === 429) console.error("Rate limited"); else console.error("API error:", error.message); } ``` ## Retry transient errors Retry `429` and `5xx` responses with exponential backoff. Do not retry `400` or `401`: they will fail again until you fix the request or the key. ```python theme={null} import time from openai import RateLimitError, APIError def with_retry(call, max_retries=3): for attempt in range(max_retries): try: return call() except (RateLimitError, APIError) as e: status = getattr(e, "status_code", 500) if status in (429, 500, 502, 503) and attempt < max_retries - 1: time.sleep(2 ** attempt) else: raise ``` ## Common cases Check the key in the [dashboard](https://www.redpill.ai/dashboard), confirm the `Bearer ` prefix, and check for stray whitespace. The `model` id is not available. List valid ids with [`GET /v1/models`](/api-reference/models). Note that model ids are prefixed, for example `openai/gpt-5`. Some models reject a parameter. For example, newer OpenAI models (GPT-5, o3, o4) require `max_completion_tokens` instead of `max_tokens`. Check the model's `supported_parameters` in `/v1/models`. Back off and retry with exponential backoff, and reduce your request rate. The upstream provider failed to respond. Retry, or try another model. ## Related # Function calling Source: https://docs.redpill.ai/guides/function-calling Let a model call functions you define, using the standard OpenAI tools format. Function calling lets a model ask your code to run a function you declared, then continue with the result. It uses the standard OpenAI `tools` format. ## Define functions ```python theme={null} tools = [{ "type": "function", "function": { "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City name, e.g., San Francisco" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } }, "required": ["location"] } } }] ``` ## Complete example ```python Python theme={null} import os from openai import OpenAI import json client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1" ) # Define available functions tools = [{ "type": "function", "function": { "name": "get_weather", "description": "Get current weather", "parameters": { "type": "object", "properties": { "location": {"type": "string"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["location"] } } }] # Make request response = client.chat.completions.create( model="openai/gpt-5", messages=[{"role": "user", "content": "What's the weather in SF?"}], tools=tools ) # Check if model wants to call function tool_call = response.choices[0].message.tool_calls[0] if tool_call.function.name == "get_weather": args = json.loads(tool_call.function.arguments) # Call your actual function weather = get_weather(args["location"], args.get("unit", "celsius")) # Send result back to model response = client.chat.completions.create( model="openai/gpt-5", messages=[ {"role": "user", "content": "What's the weather in SF?"}, response.choices[0].message, { "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(weather) } ], tools=tools ) print(response.choices[0].message.content) ``` ```javascript JavaScript theme={null} import OpenAI from 'openai'; const tools = [{ type: 'function', function: { name: 'get_weather', description: 'Get current weather', parameters: { type: 'object', properties: { location: { type: 'string' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'] } }, required: ['location'] } } }]; const response = await client.chat.completions.create({ model: 'openai/gpt-5', messages: [{ role: 'user', content: "What's the weather in SF?" }], tools: tools }); const toolCall = response.choices[0].message.tool_calls[0]; if (toolCall.function.name === 'get_weather') { const args = JSON.parse(toolCall.function.arguments); const weather = await getWeather(args.location, args.unit); const finalResponse = await client.chat.completions.create({ model: 'openai/gpt-5', messages: [ { role: 'user', content: "What's the weather in SF?" }, response.choices[0].message, { role: 'tool', tool_call_id: toolCall.id, content: JSON.stringify(weather) } ], tools: tools }); console.log(finalResponse.choices[0].message.content); } ``` ## Supported models Function calling works on models whose `supported_features` include `tools`. Query [`GET /v1/models`](/api-reference/models) before selecting one. ## Model Context Protocol (MCP) [MCP](https://modelcontextprotocol.io/) clients work with this API out of the box. Convert each MCP tool definition to an OpenAI `tools` entry (the MCP `name`, `description`, and `inputSchema` map directly to `function.name`, `function.description`, and `function.parameters`), send the request, and execute the returned `tool_calls` against your MCP server. No special endpoint is required: MCP runs on the standard tool-calling shown above. ## Related # Agent frameworks Source: https://docs.redpill.ai/guides/integrations/agent-frameworks Run CrewAI, PydanticAI, or the OpenAI Agents SDK on RedPill through the Chat Completions API. Each framework takes an OpenAI-compatible base URL and key. Pick a model whose `supported_features` include `tools` in [`GET /v1/models`](/api-reference/models). For confidential-only calls, use `https://tee.redpill.ai/v1` with an `is_tee: true` model. ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" ``` CrewAI uses LiteLLM internally. Prefix the RedPill model id with `openai/` so LiteLLM selects its OpenAI-compatible adapter; RedPill receives `z-ai/glm-5.2`. ```bash theme={null} pip install crewai ``` ```python theme={null} import os from crewai import Agent, Crew, LLM, Process, Task llm = LLM( model="openai/z-ai/glm-5.2", base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"], temperature=0, ) reviewer = Agent( role="Code reviewer", goal="Identify correctness and security risks", backstory="A senior engineer who reports concrete findings.", llm=llm, max_iter=3, ) review = Task( description="Review the supplied change summary: {change_summary}", expected_output="A prioritized list of findings with suggested fixes.", agent=reviewer, ) crew = Crew(agents=[reviewer], tasks=[review], process=Process.sequential) result = crew.kickoff(inputs={"change_summary": "Adds token refresh without retry limits."}) print(result) ``` | Symptom | Check | | - | - | | LiteLLM cannot select a provider | Keep the `openai/` prefix before the RedPill model id. | | Crew loops too long | Reduce `max_iter` and add task-level execution limits. | [CrewAI LLM connections](https://docs.crewai.com/en/learn/llm-connections) PydanticAI exposes an OpenAI-compatible provider directly. Use `OpenAIChatModel` for the Chat Completions endpoint. ```bash theme={null} pip install pydantic-ai ``` ```python theme={null} import os from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIChatModel from pydantic_ai.providers.openai import OpenAIProvider from pydantic_ai.usage import UsageLimits model = OpenAIChatModel( "z-ai/glm-5.2", provider=OpenAIProvider( base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"], ), ) agent = Agent(model, system_prompt="Be concise and state uncertainty.") @agent.tool_plain def deployment_status(service: str) -> str: """Return a sample deployment status.""" return f"{service}: healthy" result = agent.run_sync( "Check the gateway deployment status.", usage_limits=UsageLimits(request_limit=3, tool_calls_limit=1), ) print(result.output) ``` | Symptom | Check | | - | - | | Provider initialization fails | Use `OpenAIProvider` with the `/v1` base URL. | | Tool calls exceed expectations | Lower `request_limit` and `tool_calls_limit`. | [PydanticAI OpenAI models](https://ai.pydantic.dev/models/openai/) The SDK uses the Responses API by default. Wrap a custom `AsyncOpenAI` client in `OpenAIChatCompletionsModel`. Tracing is disabled because the default exporter needs an OpenAI platform key; configure another trace processor if you need tracing. ```bash theme={null} pip install openai-agents ``` ```python theme={null} import os from agents import ( Agent, OpenAIChatCompletionsModel, Runner, function_tool, set_tracing_disabled, ) from openai import AsyncOpenAI set_tracing_disabled(True) client = AsyncOpenAI( base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"], ) model = OpenAIChatCompletionsModel(model="z-ai/glm-5.2", openai_client=client) @function_tool def deployment_status(service: str) -> str: """Return a sample deployment status.""" return f"{service}: healthy" agent = Agent( name="Operations assistant", instructions="Use the tool to check deployment status.", model=model, tools=[deployment_status], ) result = Runner.run_sync(agent, "Check the gateway deployment status.", max_turns=3) print(result.final_output) ``` | Symptom | Check | | - | - | | SDK calls the wrong API shape | Wrap the client with `OpenAIChatCompletionsModel`. | | Default tracing reports errors | Disable tracing or configure a compatible trace processor. | [OpenAI Agents SDK models](https://openai.github.io/openai-agents-python/models/) ## Related # Anthropic SDK Source: https://docs.redpill.ai/guides/integrations/anthropic-sdk Use the official Anthropic Python or TypeScript SDK with RedPill's Messages API. The Anthropic SDK can call RedPill's compatible [`/v1/messages`](/api-reference/messages) endpoint directly. Set the RedPill host without `/v1` because the SDK appends `/v1/messages`. Use `auth_token` in Python or `authToken` in TypeScript. The `api_key` option sends `x-api-key`, while RedPill expects `Authorization: Bearer`. ## Install ```bash Python theme={null} pip install anthropic ``` ```bash TypeScript theme={null} npm install @anthropic-ai/sdk ``` ## Send a message ```python Python theme={null} import os from anthropic import Anthropic client = Anthropic( auth_token=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai", ) message = client.messages.create( model="anthropic/claude-sonnet-5", max_tokens=128, messages=[{"role": "user", "content": "Explain attestation in one sentence."}], ) for block in message.content: if block.type == "text": print(block.text) ``` ```typescript TypeScript theme={null} import Anthropic from '@anthropic-ai/sdk'; const authToken = process.env.REDPILL_AI_API_KEY; if (!authToken) throw new Error('REDPILL_AI_API_KEY is required'); const client = new Anthropic({ authToken, baseURL: 'https://api.redpill.ai', }); const message = await client.messages.create({ model: 'anthropic/claude-sonnet-5', max_tokens: 128, messages: [{ role: 'user', content: 'Explain attestation in one sentence.' }], }); for (const block of message.content) { if (block.type === 'text') console.log(block.text); } ``` ## Choose a model Use an exact Anthropic model ID from the live catalog: ```bash theme={null} curl -s https://api.redpill.ai/v1/models \ | jq -r '.data[] | select(.id | startswith("anthropic/")) | .id' ``` Model availability and supported fields change. Test streaming or tool use with the selected model before depending on it in production. ## Privacy boundary Every request enters through RedPill's attested gateway. Check the model's `is_tee` value and the response [receipt](/confidential-ai/receipts) to determine whether the upstream model provider was also verified. Do not use the TEE-only base URL with a model whose catalog entry has `is_tee: false`. ## Troubleshooting | Symptom | Check | | - | - | | `401` authentication error | Pass the RedPill key as `auth_token` or `authToken`, not `api_key` or `apiKey`. | | Request path contains `/v1/v1/messages` | Set the base URL to `https://api.redpill.ai`, without `/v1`. | | Model not found | Refresh `GET /v1/models` and use the full `anthropic/...` ID. | | Unsupported field returns `400` | Update the SDK, then remove beta-only fields not accepted by the selected model. | ## Related * [Anthropic Python SDK](https://github.com/anthropics/anthropic-sdk-python) * [Anthropic TypeScript SDK](https://github.com/anthropics/anthropic-sdk-typescript) * [RedPill Messages API](/api-reference/messages) # Cherry Studio Source: https://docs.redpill.ai/guides/integrations/cherry-studio Chat with RedPill models in the Cherry Studio desktop app. Cherry Studio is a desktop client for Windows, macOS, and Linux. Add RedPill as a custom OpenAI-compatible provider to use its live model catalog. Download the current release from [Cherry Studio on GitHub](https://github.com/CherryHQ/cherry-studio/releases). ## Add RedPill Open **Settings**, select **Model Services**, and add a custom provider with these values: | Field | Value | | - | - | | Provider name | `RedPill AI` | | Provider type | **OpenAI** | | API address | `https://api.redpill.ai/v1` | | API key | Your RedPill API key | Use the base URL ending in `/v1`; Cherry Studio appends the Chat Completions path. Select **Test** to verify the key and endpoint. ## Add models Open **Manage** for the new provider and refresh the model list. If discovery is unavailable, add an exact ID from the live catalog: ```bash theme={null} curl -s https://api.redpill.ai/v1/models \ | jq -r '.data[] | [.id, .is_tee, (.supported_features | join(","))] | @tsv' ``` Choose a model that declares the capabilities you plan to use. Image upload requires `image` in `input_modalities`; tool-based assistants require `tools` in `supported_features`. ## Privacy choice Every request enters RedPill through the attested gateway. The selected model's `is_tee` field and the response [receipt](/confidential-ai/receipts) describe the upstream privacy boundary. To make the provider reject standard models, use `https://tee.redpill.ai/v1` as the API address and enable only current `is_tee: true` models. Cherry Studio may not expose RedPill's `x-receipt-id` response header; use a direct API client when receipt verification is required. ## Troubleshooting | Symptom | Check | | - | - | | Connection test fails | Confirm the API address ends in `/v1` and re-enter the RedPill key. | | Models do not load | Refresh **Manage** or add a full model ID from `GET /v1/models`. | | A model returns `404` | Refresh the catalog; model availability changes over time. | | Images or tools fail | Check the model's declared modalities and features before enabling them. | ## Related * [Cherry Studio documentation](https://docs.cherry-ai.com/) * [Cherry Studio releases](https://github.com/CherryHQ/cherry-studio/releases) * [RedPill model catalog](/get-started/models) # Claude Code Source: https://docs.redpill.ai/guides/integrations/coding-tools/claude-code Run Claude Code on RedPill through Private AI Proxy, or configure it by hand. Claude Code speaks the Anthropic Messages protocol. Private AI Proxy configures it automatically. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. On the **Agents** screen, connect **Claude Code**. 3. Start `claude`. The app writes the Messages provider into Claude Code's settings with an agent-scoped token for its local API, and restores the previous settings when you disconnect or stop Protected. From the CLI, the same connection is: ```bash theme={null} pap agents connect claude-code ``` ## Without the Proxy Set the base URL to the host without `/v1`; Claude Code appends `/v1/messages` itself. ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" export ANTHROPIC_BASE_URL="https://api.redpill.ai" export ANTHROPIC_AUTH_TOKEN="$REDPILL_AI_API_KEY" export ANTHROPIC_API_KEY="" export ANTHROPIC_MODEL="anthropic/claude-sonnet-5" export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 claude ``` `ANTHROPIC_AUTH_TOKEN` sends the key as a bearer token; clearing `ANTHROPIC_API_KEY` stops an existing Anthropic key from adding a conflicting `x-api-key` header. Gateway model discovery loads RedPill models into the `/model` picker. Map the family aliases if plan mode or background work needs them: `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL`. This path does not verify ACI locally. For confidential-only routing, use an `is_tee: true` model with `https://tee.redpill.ai`. See [Verify a response](/guides/verify-a-response). ## Check the connection Run `/status` in Claude Code. With the Proxy, the base URL is `http://127.0.0.1:4180`; without it, `https://api.redpill.ai`. Confirm the bearer credential source and the selected model. ## Troubleshooting | Symptom | Check | | - | - | | Claude Code ignores the Proxy | Confirm **Connected** in the app and that Protected is on, then restart `claude`. | | Request path contains `/v1/v1/messages` | Remove `/v1` from `ANTHROPIC_BASE_URL`. | | Authentication fails | Use `ANTHROPIC_AUTH_TOKEN` and clear `ANTHROPIC_API_KEY`. | ## Related * [Claude Code gateway configuration](https://docs.anthropic.com/en/docs/claude-code/llm-gateway) * [RedPill Messages API](/api-reference/messages) * [RedPill model catalog](/api-reference/models) # Cline Source: https://docs.redpill.ai/guides/integrations/coding-tools/cline Run Cline on RedPill through Private AI Proxy, or configure it by hand. Cline speaks Chat Completions through its OpenAI Compatible provider. Private AI Proxy does not configure it automatically; enter the app's Local API values by hand. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. In Cline's settings, choose **OpenAI Compatible** and enter the Local API values: | Field | Value | | - | - | | Base URL | `http://127.0.0.1:4180/v1` | | API key | The client key from the app's Overview screen | | Model ID | A model from the app's verified catalog | 3. Start a task. Requests succeed only while Protected is on. From the CLI, `pap token show` prints the client key and `pap models list` the catalog. ## Without the Proxy | Field | Value | | - | - | | API Provider | **OpenAI Compatible** | | Base URL | `https://api.redpill.ai/v1` | | API key | Your RedPill API key | | Model ID | An exact ID from [`GET /v1/models`](/api-reference/models) | Copy the model's context and output limits from the live catalog when you enter a model by hand. This path does not verify ACI locally. For confidential-only routing, use an `is_tee: true` model with `https://tee.redpill.ai/v1`. See [Verify a response](/guides/verify-a-response). ## Check the connection Use Cline's **Verify** action, then run a read-only request in a disposable repository and confirm the selected provider and model. ## Troubleshooting | Symptom | Check | | - | - | | Verify fails with the Proxy | Protected must be on; copy the client key again. | | Verify fails without the Proxy | Keep `/v1` in the base URL and refresh the API key. | | Tool calls fail | Choose a model that declares `tools` and repeat Verify. | ## Related * [Cline OpenAI-compatible providers](https://docs.cline.bot/provider-config/openai-compatible) * [RedPill model catalog](/api-reference/models) # Codex CLI Source: https://docs.redpill.ai/guides/integrations/coding-tools/codex-cli Run Codex CLI on RedPill through Private AI Proxy, or configure it by hand. Codex speaks the Responses protocol. Private AI Proxy configures it automatically. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. On the **Agents** screen, connect **Codex** and pick a model from the verified catalog. 3. Start `codex`. The app adds a custom model provider to `~/.codex/config.toml` that points at its local API with an agent-scoped token, and restores the file when you disconnect or stop Protected. From the CLI, the same connection is: ```bash theme={null} pap agents connect codex ``` ## Without the Proxy Add RedPill as a custom provider in the user-level configuration; Codex ignores `model_provider` and `model_providers` in a project-local `.codex/config.toml`. ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" ``` ```toml ~/.codex/config.toml theme={null} model = "openai/gpt-5.6-sol" model_provider = "redpill" [model_providers.redpill] name = "RedPill AI" base_url = "https://api.redpill.ai/v1" env_key = "REDPILL_AI_API_KEY" wire_api = "responses" ``` Pick the model from [`GET /v1/models`](/api-reference/models); choose one that declares `tools`. This path does not verify ACI locally. For confidential-only routing, use an `is_tee: true` model with `base_url = "https://tee.redpill.ai/v1"`. See [Verify a response](/guides/verify-a-response). ## Check the connection Start `codex` and run one bounded tool task. With the Proxy, the provider in `config.toml` points at `http://127.0.0.1:4180/v1`; without it, at `https://api.redpill.ai/v1`. ## Troubleshooting | Symptom | Check | | - | - | | Codex ignores the Proxy | Confirm **Connected** in the app and that Protected is on, then restart `codex`. | | Codex still uses the default provider | Confirm `model_provider = "redpill"` is at the top level of the user-level file. | | Chat works but tools fail | Choose a model that declares `tools` in the live catalog. | ## Related * [Codex custom model providers](https://learn.chatgpt.com/docs/config-file/config-advanced#custom-model-providers) * [RedPill Responses API](/api-reference/responses) * [RedPill model catalog](/api-reference/models) # OpenCode Source: https://docs.redpill.ai/guides/integrations/coding-tools/opencode Run OpenCode on RedPill through Private AI Proxy, or configure it by hand. OpenCode speaks Chat Completions. Private AI Proxy configures it automatically. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. On the **Agents** screen, connect **OpenCode**. 3. Restart OpenCode. The app writes the provider into `opencode.json` with an agent-scoped token for its local API, and restores the file when you disconnect or stop Protected. From the CLI, the same connection is: ```bash theme={null} pap agents connect opencode ``` ## Without the Proxy Install the RedPill plugin (OpenCode 1.18.24 or newer), restart OpenCode, then sign in and pick a model in its TUI. Omit `--global` for a project installation. ```bash theme={null} opencode plugin opencode-provider-redpill --global ``` ```text theme={null} /connect # select RedPill AI and enter your API key /models # search for redpill/ and choose a model ``` OpenCode keeps the plugin configuration and key in its own stores; do not add a separate `provider.redpill` block. `REDPILL_AI_API_KEY` supplies the key for one process. This plugin verifies ACI inside OpenCode: attestation and every response receipt are checked automatically, and `/redpill-attestation`, `/redpill-receipts`, `/redpill-receipt`, and `/redpill-session` show the evidence. ## Check the connection Run `/models` and confirm a `redpill/` model is selected, then run one bounded tool task. ## Troubleshooting | Symptom | Check | | - | - | | OpenCode ignores the Proxy | Confirm **Connected** in the app and that Protected is on, then restart OpenCode. | | RedPill is absent from `/connect` | Run `opencode debug config` and confirm `opencode-provider-redpill` is in `plugin`, then restart. | | No models appear | Run `opencode models redpill` and read the provider error. | ## Related * [OpenCode providers](https://opencode.ai/docs/providers/) * [OpenCode plugins](https://opencode.ai/docs/plugins/) * [RedPill model catalog](/api-reference/models) # Pi Source: https://docs.redpill.ai/guides/integrations/coding-tools/pi Run Pi on RedPill through Private AI Proxy, or configure it by hand. Pi speaks Chat Completions. Private AI Proxy configures it automatically. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. On the **Agents** screen, connect **Pi**. 3. Start `pi`. The app writes the provider into Pi's settings with an agent-scoped token for its local API, and restores the previous settings when you disconnect or stop Protected. From the CLI, the same connection is: ```bash theme={null} pap agents connect pi ``` ## Without the Proxy Install the RedPill provider package, then sign in and pick a model inside Pi: ```bash theme={null} pi install npm:pi-provider-redpill pi ``` ```text theme={null} /login redpill # paste your RedPill API key /model # search for redpill/, choose a model, press Ctrl+S to save it as the default ``` Pi stores the key in `~/.pi/agent/auth.json`, the live catalog in `~/.pi/agent/models-store.json`, and the saved default in `~/.pi/agent/settings.json`. `REDPILL_AI_API_KEY` supplies the key for one process; Pi does not copy it into its store. This package verifies ACI inside Pi: attestation and every response receipt are checked automatically, and `/redpill-attestation`, `/redpill-receipts`, `/redpill-receipt`, and `/redpill-session` show the evidence. Its default endpoint is `https://tee.redpill.ai/v1`. ## Check the connection Run `/model` in Pi and confirm a `redpill/` model is selected. With the provider package, wait for the footer to show `aci-verified` before sending work. ## Troubleshooting | Symptom | Check | | - | - | | Pi ignores the Proxy | Confirm **Connected** in the app and that Protected is on, then restart `pi`. | | No `redpill/` models in `/model` | Run `/login redpill` again; the catalog loads after login. | | Footer never shows `aci-verified` | Run `/redpill-attestation` and read the failing check. | ## Related * [pi-provider-redpill on npm](https://www.npmjs.com/package/pi-provider-redpill) * [RedPill model catalog](/api-reference/models) # LangChain Source: https://docs.redpill.ai/guides/integrations/langchain Use RedPill for LangChain chat, streaming, retrieval, and agents. LangChain connects to RedPill through its OpenAI integration. Use the Chat Completions base URL and an exact model ID from the live catalog. ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" pip install -U langchain langchain-openai npm install @langchain/openai @langchain/core ``` ## Send a message ```python Python theme={null} import os from langchain_openai import ChatOpenAI model = ChatOpenAI( model="z-ai/glm-5.2", base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"], ) response = model.invoke("Explain attestation in one sentence.") print(response.content) ``` ```typescript TypeScript theme={null} import { ChatOpenAI } from "@langchain/openai"; const apiKey = process.env.REDPILL_AI_API_KEY; if (!apiKey) throw new Error("REDPILL_AI_API_KEY is required"); const model = new ChatOpenAI({ model: "z-ai/glm-5.2", apiKey, configuration: { baseURL: "https://api.redpill.ai/v1" }, }); const response = await model.invoke("Explain attestation in one sentence."); console.log(response.content); ``` Use `model.stream(...)` instead of `model.invoke(...)` to render tokens as they arrive. ## Build an agent LangChain's `create_agent` runtime is built on LangGraph. Bound each run and validate tool input: ```python theme={null} from langchain.agents import create_agent from langchain.tools import tool @tool def lookup_service(name: str) -> str: """Return the owner of an internal service.""" owners = {"gateway": "inference", "dashboard": "product"} return owners.get(name, "unknown") agent = create_agent( model=model, tools=[lookup_service], system_prompt="Use tools when a service owner is requested.", ) result = agent.invoke( {"messages": [{"role": "user", "content": "Who owns the gateway service?"}]}, config={"recursion_limit": 4}, ) print(result["messages"][-1].content) ``` Choose a model whose `supported_features` includes `tools`. ## Add retrieval Query the embedding catalog, then configure `OpenAIEmbeddings` with a current model: ```python theme={null} from langchain_core.vectorstores import InMemoryVectorStore from langchain_openai import OpenAIEmbeddings embeddings = OpenAIEmbeddings( model="qwen/qwen3-embedding-8b", base_url="https://api.redpill.ai/v1", api_key=os.environ["REDPILL_AI_API_KEY"], ) store = InMemoryVectorStore.from_texts( ["Receipts bind a request and response to the serving path."], embedding=embeddings, ) print(store.similarity_search("What does a receipt prove?", k=1)[0].page_content) ``` ## Verification boundary This base-URL integration does not verify ACI locally. TypeScript applications that require an attested, TLS-bound connection can inject `connectAci().fetch` through LangChain's `configuration` hook and audit the retained receipt, using the same pattern as the [OpenAI SDK](/guides/integrations/openai-sdk#verified-node-and-bun-transport). ## Related * [LangChain models](https://docs.langchain.com/oss/python/langchain/models) * [LangChain agents](https://docs.langchain.com/oss/python/langchain/agents) * [LangChain retrieval](https://docs.langchain.com/oss/python/langchain/retrieval) * [RedPill embeddings API](/api-reference/embeddings) # Langfuse Source: https://docs.redpill.ai/guides/integrations/langfuse Trace RedPill API calls with Langfuse and an explicit data boundary. Langfuse can trace RedPill calls through its OpenAI SDK wrapper. It records latency, usage, errors, and, by default, model inputs and outputs. Langfuse is a separate observability destination. Traced prompts and responses leave RedPill's trust boundary and are subject to your Langfuse deployment, retention, and access controls. ## Configure tracing Install the current Python SDKs and set both services' credentials: ```bash theme={null} pip install -U langfuse openai export REDPILL_AI_API_KEY="YOUR_API_KEY" export LANGFUSE_PUBLIC_KEY="pk-lf-..." export LANGFUSE_SECRET_KEY="sk-lf-..." export LANGFUSE_BASE_URL="https://cloud.langfuse.com" ``` Use your own `LANGFUSE_BASE_URL` for a self-hosted deployment. ## Trace a request Import `OpenAI` from `langfuse.openai`, then configure RedPill as the API endpoint: ```python theme={null} import os from langfuse import get_client from langfuse.openai import OpenAI client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1", ) response = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "Reply with: connected"}], name="redpill_connection_check", ) print(response.choices[0].message.content) # Flush explicitly in short-lived jobs and scripts. get_client().flush() ``` The wrapper accepts the same streaming and async calls as the OpenAI SDK. Add trace names, session identifiers, and low-sensitivity metadata only when they are useful for debugging or aggregation. ## Decide what to export Before enabling tracing in production: 1. Classify prompts, responses, tool arguments, retrieved documents, and metadata. 2. Configure Langfuse masking before those values leave the application, or omit content entirely. 3. Set retention and access controls for the Langfuse project. 4. Use a self-hosted Langfuse deployment when its data must remain inside your own boundary. Masking changes telemetry only; it does not change the request sent to RedPill. If another OpenTelemetry exporter is active, configure its redaction separately. ## Costs and receipts Langfuse cost estimates depend on a matching model definition. RedPill model IDs or prices may need a custom definition under **Project Settings > Models**. Treat the RedPill dashboard as the billing source of truth. Langfuse does not verify RedPill's signed receipts. Fetch and verify a receipt separately when the serving path is part of your security decision. ## Related * [Langfuse OpenAI integration](https://langfuse.com/docs/integrations/openai) * [Langfuse masking](https://langfuse.com/docs/observability/features/masking) * [Langfuse cost tracking](https://langfuse.com/docs/observability/features/token-and-cost-tracking) * [RedPill receipts](/confidential-ai/receipts) # OpenAI SDK Source: https://docs.redpill.ai/guides/integrations/openai-sdk Use RedPill through the OpenAI SDK, with optional local ACI verification on Node and Bun. RedPill supports the OpenAI SDK's Responses, Chat Completions, and Embeddings resources. Set the base URL to `https://api.redpill.ai/v1` and use an exact RedPill model ID. Changing only the base URL does not make the SDK a local ACI verifier. ```bash Python theme={null} pip install openai ``` ```bash TypeScript theme={null} npm install openai ``` ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" ``` ## Responses ```python Python theme={null} import os from openai import OpenAI client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1", ) response = client.responses.create( model="z-ai/glm-5.2", input="Explain attestation in one sentence.", ) print(response.output_text) ``` ```typescript TypeScript theme={null} import OpenAI from "openai"; const apiKey = process.env.REDPILL_AI_API_KEY; if (!apiKey) throw new Error("REDPILL_AI_API_KEY is required"); const client = new OpenAI({ apiKey, baseURL: "https://api.redpill.ai/v1", }); const response = await client.responses.create({ model: "z-ai/glm-5.2", input: "Explain attestation in one sentence.", }); console.log(response.output_text); ``` ## Chat completions Use Chat Completions when an existing application expects that API shape: ```typescript theme={null} const response = await client.chat.completions.create({ model: "z-ai/glm-5.2", messages: [{ role: "user", content: "Reply with: connected" }], }); console.log(response.choices[0]?.message.content); ``` Pass `stream=True` in Python or `stream: true` in TypeScript for server-sent events. See [Streaming](/guides/streaming) and [Function calling](/guides/function-calling) for complete loops. ## Embeddings Embedding models use a separate catalog: ```bash theme={null} curl -s https://api.redpill.ai/v1/embeddings/models \ | jq -r '.data[] | [.id, .is_tee, .context_length] | @tsv' ``` ```python theme={null} response = client.embeddings.create( model="qwen/qwen3-embedding-8b", input="Receipts bind requests and responses to the serving path.", ) embedding = response.data[0].embedding ``` ## Verified Node and Bun transport Node 20.18+ and Bun 1.4+ can inject the same ACI runtime `fetch`: ```bash theme={null} npm install openai @phala/aci-verifier ``` ```typescript theme={null} import OpenAI from "openai"; import { connectAci } from "@phala/aci-verifier/runtime"; const apiKey = process.env.REDPILL_AI_API_KEY; if (!apiKey) throw new Error("REDPILL_AI_API_KEY is required"); const aci = await connectAci({ baseURL: "https://tee.redpill.ai/v1", policy: { requireProductionOs: true }, serving: { requireVerified: true, requireReceipt: true }, }); try { const client = new OpenAI({ apiKey, baseURL: aci.baseURL, fetch: aci.fetch }); const response = await client.chat.completions.create({ model: "z-ai/glm-5.2", messages: [{ role: "user", content: "Explain attestation briefly." }], }); 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(); } ``` The connection verifies the TDX quote, measured compose, identity expiry, hostname, and attested TLS SPKI before model traffic. `verifyReceipt()` checks the latest retained exchange, signed receipt, wire hashes, and cited session. Add `acceptedComposeHashes` only from authenticated release metadata. Without that allowlist, the client verifies the measured hardware workload but does not claim that RedPill reviewed the release. ## Production checks Use `https://tee.redpill.ai/v1` only with an `is_tee: true` model when standard upstreams must be rejected. Set timeouts and bounded retries, and do not expose raw upstream errors to users. ## Related * [OpenAI Python SDK](https://github.com/openai/openai-python) * [OpenAI TypeScript SDK](https://github.com/openai/openai-node) * [Verify a response](/guides/verify-a-response) * [RedPill Responses API](/api-reference/responses) # Hermes Source: https://docs.redpill.ai/guides/integrations/runtimes/hermes Run Hermes Agent on RedPill through Private AI Proxy, or configure it by hand. Hermes speaks Chat Completions. Private AI Proxy configures it automatically. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. On the **Agents** screen, connect **Hermes**. 3. Start a new Hermes session; existing sessions stay on their original provider. The app writes the provider into `~/.hermes/config.yaml` with an agent-scoped token for its local API, and restores the file when you disconnect or stop Protected. From the CLI, the same connection is: ```bash theme={null} pap agents connect hermes ``` ## Without the Proxy Define and select a named provider. Hermes reads the key from its process environment or `~/.hermes/.env`; do not put it in `config.yaml`. ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" ``` ```yaml ~/.hermes/config.yaml theme={null} providers: redpill: api: https://api.redpill.ai/v1 key_env: REDPILL_AI_API_KEY transport: chat_completions default_model: z-ai/glm-5.2 discover_models: true model: provider: custom:redpill default: z-ai/glm-5.2 ``` This changes the main text and tool-calling loop only; image, speech, embedding, search, and browser providers stay separate. This path does not verify ACI locally. For confidential-only routing, use an `is_tee: true` model with `https://tee.redpill.ai/v1`. See [Verify a response](/guides/verify-a-response). ## Check the connection Start `hermes`, run `/model`, and confirm the active provider, then run one harmless tool task. ## Troubleshooting | Symptom | Check | | - | - | | Hermes ignores the Proxy | Confirm **Connected** in the app and that Protected is on, then start a new session. | | Provider is not selected | Start a new session and choose `custom:redpill` with `/model`. | | Authentication fails | Export `REDPILL_AI_API_KEY` in the Hermes process environment. | ## Related * [Hermes model providers](https://hermes-agent.nousresearch.com/docs/integrations/providers) * [RedPill model catalog](/api-reference/models) # OpenClaw Source: https://docs.redpill.ai/guides/integrations/runtimes/openclaw Run OpenClaw on RedPill through Private AI Proxy, or configure it by hand. OpenClaw speaks Chat Completions. Private AI Proxy configures it automatically. ## With Private AI Proxy 1. Install [Private AI Proxy](/get-started/private-ai-proxy), sign in, and turn on **Protected**. 2. On the **Agents** screen, connect **OpenClaw**. 3. Restart OpenClaw; it reads the native-host provider and local token on start. The app writes a native-host provider into OpenClaw's gateway configuration with an agent-scoped token for its local API, and restores the file when you disconnect or stop Protected. From the CLI, the same connection is: ```bash theme={null} pap agents connect openclaw ``` ## Without the Proxy Merge a named provider into the gateway configuration and keep your existing agent, channel, tool, and provider settings. Export the key where the OpenClaw gateway process can read it. ```bash theme={null} export REDPILL_AI_API_KEY="YOUR_API_KEY" ``` ```json5 ~/.openclaw/openclaw.json theme={null} { agents: { defaults: { model: { primary: "redpill/z-ai/glm-5.2" } } }, models: { mode: "merge", providers: { redpill: { baseUrl: "https://api.redpill.ai/v1", apiKey: "${REDPILL_AI_API_KEY}", api: "openai-completions", models: [ { id: "z-ai/glm-5.2", name: "GLM 5.2", reasoning: true, input: ["text"], contextWindow: 1048576, maxTokens: 131072 }, ], }, }, }, } ``` This changes the main model loop only; image, speech, embedding, search, and browser providers stay separate. This path does not verify ACI locally. For confidential-only routing, use an `is_tee: true` model with `https://tee.redpill.ai/v1`. See [Verify a response](/guides/verify-a-response). ## Check the connection Run `openclaw config validate` and `openclaw models list`, confirm the RedPill model is listed, then run one harmless tool task. ## Troubleshooting | Symptom | Check | | - | - | | OpenClaw ignores the Proxy | Confirm **Connected** in the app and that Protected is on, then restart OpenClaw. | | Configuration is rejected | Run `openclaw config validate` and preserve JSON5 syntax. | | Key is unavailable | Export `REDPILL_AI_API_KEY` for the gateway process, not only an interactive shell. | ## Related * [OpenClaw model providers](https://docs.openclaw.ai/concepts/model-providers) * [OpenClaw configuration](https://docs.openclaw.ai/gateway/configuration) * [RedPill model catalog](/api-reference/models) # Vercel AI SDK Source: https://docs.redpill.ai/guides/integrations/vercel-ai-sdk Generate and stream RedPill responses in TypeScript applications. Use the AI SDK's OpenAI-compatible provider with RedPill. ```bash theme={null} npm install ai @ai-sdk/openai-compatible export REDPILL_AI_API_KEY="YOUR_API_KEY" ``` ## Configure the provider ```typescript theme={null} import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; const apiKey = process.env.REDPILL_AI_API_KEY; if (!apiKey) throw new Error("REDPILL_AI_API_KEY is required"); export const redpill = createOpenAICompatible({ name: "redpill", apiKey, baseURL: "https://api.redpill.ai/v1", }); ``` Use `.chatModel()` with an exact model ID. The provider sends that ID without adding a prefix. ## Generate text ```typescript theme={null} import { generateText } from "ai"; import { redpill } from "./redpill"; const { text } = await generateText({ model: redpill.chatModel("z-ai/glm-5.2"), prompt: "Explain attestation in one sentence.", }); console.log(text); ``` ## Stream from a route ```typescript app/api/generate/route.ts theme={null} import { streamText } from "ai"; import { redpill } from "../../../redpill"; export async function POST(request: Request) { const body: unknown = await request.json().catch(() => null); if ( typeof body !== "object" || body === null || !("prompt" in body) || typeof body.prompt !== "string" || body.prompt.trim().length === 0 ) { return Response.json({ error: "A non-empty prompt is required" }, { status: 400 }); } const result = streamText({ model: redpill.chatModel("z-ai/glm-5.2"), prompt: body.prompt.trim(), }); return result.toTextStreamResponse(); } ``` Keep the RedPill key on the server. Validate tool arguments and bound multi-step tool loops. ## Verification boundary The base-URL configuration does not verify ACI locally. Node and Bun applications can pass `connectAci().fetch` to `createOpenAICompatible`, then audit the retained receipt after generation. See the [verified Node and Bun transport](/guides/integrations/openai-sdk#verified-node-and-bun-transport). ## Related * [AI SDK OpenAI-compatible provider](https://ai-sdk.dev/providers/openai-compatible-providers) * [AI SDK `streamText`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) * [AI SDK UI chat](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot) * [RedPill function calling](/guides/function-calling) # Migrate from OpenAI or OpenRouter Source: https://docs.redpill.ai/guides/migrate Point an existing OpenAI-compatible integration at RedPill: a new base URL, a new key, and provider-prefixed model ids. RedPill is OpenAI-compatible, so a migration is a base URL and a key. Model ids carry a provider prefix, the same format OpenRouter uses. ```python Python theme={null} import os from openai import OpenAI client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1", ) response = client.chat.completions.create( model="openai/gpt-5", messages=[{"role": "user", "content": "Hello"}], ) ``` ```javascript JavaScript theme={null} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.REDPILL_AI_API_KEY, baseURL: "https://api.redpill.ai/v1", }); ``` | | From OpenAI | From OpenRouter | | - | - | - | | Base URL | `https://api.openai.com/v1` becomes `https://api.redpill.ai/v1` | `https://openrouter.ai/api/v1` becomes `https://api.redpill.ai/v1` | | Model id | `gpt-5` becomes `openai/gpt-5` | Same `provider/model` format; confirm each id in [`GET /v1/models`](/api-reference/models) | | Extra headers | None | Drop `HTTP-Referer` and `X-Title` | Streaming, function calling, vision, and error shapes are unchanged. What RedPill adds: every response carries an `x-receipt-id` you can [verify](/guides/verify-a-response), and confidential (`is_tee`) models run on a verified upstream enclave. See [Models](/get-started/models). # Prompt caching Source: https://docs.redpill.ai/guides/prompt-caching Providers that cache a repeated prompt prefix report cached tokens in usage, and RedPill bills them at the model's cache rate. Prompt caching happens at the provider and needs nothing in the request. When a provider serves part of a prompt from its cache, the response reports it in `usage`, and the cached tokens are billed at `pricing.input_cache_read` when the model lists one, otherwise at the normal prompt rate. ```json theme={null} "usage": { "prompt_tokens": 1905, "prompt_tokens_details": {"cached_tokens": 1792}, "completion_tokens": 64 } ``` To benefit, keep the stable part of a prompt, such as the system message and reference material, at the start, and vary only the end. OpenAI models cache prefixes of 1,024 tokens or more on repeated requests. Which models expose a cache rate is visible in the catalog: look for `input_cache_read` in `pricing`. ## Related # Reasoning Source: https://docs.redpill.ai/guides/reasoning Control how much a model thinks before it answers, and whether the reasoning text is returned. Reasoning models think before they answer. Ask for more or less of it with the `reasoning` object, and choose whether the reasoning text comes back. A model supports it when its `supported_features` in [`GET /v1/models`](/api-reference/models) include `reasoning`; other models ignore the parameter. ## Set the effort ```json theme={null} { "model": "openai/gpt-5", "messages": [{"role": "user", "content": "What is 17 * 23?"}], "reasoning": {"effort": "low"} } ``` `effort` is one of `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. The OpenAI-style `reasoning_effort` field is accepted as an alias; if both are sent they must match. Instead of an effort, `reasoning.max_tokens` sets a token budget for thinking. The two are mutually exclusive, and a model that cannot express a budget returns `400`. ## Read or hide the reasoning Models that return their reasoning put it in `message.reasoning_content`, or `delta.reasoning_content` while streaming. OpenAI models return no reasoning text. Either way, `usage.completion_tokens_details.reasoning_tokens` counts the thinking, and it is billed as completion tokens. To keep the reasoning out of the response, send `"reasoning": {"exclude": true}` or `"include_reasoning": false`. The tokens are still generated and billed. ## Related # Streaming Source: https://docs.redpill.ai/guides/streaming Stream a chat completion as server-sent events with stream: true. Streaming allows you to receive responses in real-time as they're generated, instead of waiting for the complete response. ## Enable streaming Set `stream: true` in your request: ```python Python theme={null} import os from openai import OpenAI client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1" ) stream = client.chat.completions.create( model="openai/gpt-5", messages=[{"role": "user", "content": "Write a long story"}], stream=True ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) ``` ```javascript JavaScript theme={null} import OpenAI from 'openai'; const stream = await client.chat.completions.create({ model: 'openai/gpt-5', messages: [{ role: 'user', content: 'Write a long story' }], stream: true }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content || ''); } ``` ```bash cURL theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5", "messages": [{"role": "user", "content": "Write a story"}], "stream": true }' ``` ## Stream format Responses are sent as Server-Sent Events (SSE): ``` data: {"id":"chatcmpl-123","choices":[{"delta":{"content":"Hello"}}]} data: {"id":"chatcmpl-123","choices":[{"delta":{"content":" world"}}]} data: [DONE] ``` All chat models support streaming, including GPU TEE confidential models. ## Related # Structured outputs Source: https://docs.redpill.ai/guides/structured-outputs Constrain a response to a JSON schema with response_format, from a Pydantic model or a raw schema. Structured outputs constrain the response to a JSON schema you supply in `response_format`, using the standard OpenAI parameter. Pass a Pydantic model through the SDK, or a raw JSON schema. ## Pydantic models ```python theme={null} import os from openai import OpenAI from pydantic import BaseModel client = OpenAI( api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://api.redpill.ai/v1" ) class CalendarEvent(BaseModel): name: str date: str participants: list[str] response = client.beta.chat.completions.parse( model="openai/gpt-5", messages=[ {"role": "system", "content": "Extract calendar events from text"}, {"role": "user", "content": "Team meeting tomorrow at 2pm with Alice and Bob"} ], response_format=CalendarEvent ) event = response.choices[0].message.parsed print(event.name) # "Team meeting" print(event.participants) # ["Alice", "Bob"] ``` ## JSON schema ```python theme={null} schema = { "type": "object", "properties": { "name": {"type": "string"}, "email": {"type": "string", "format": "email"}, "age": {"type": "integer", "minimum": 0}, "interests": { "type": "array", "items": {"type": "string"} } }, "required": ["name", "email"], "additionalProperties": False } response = client.chat.completions.create( model="openai/gpt-5", messages=[ {"role": "user", "content": "Extract user info: John Doe, john@example.com, 30 years old, likes hiking and coding"} ], response_format={ "type": "json_schema", "json_schema": { "name": "user_profile", "strict": True, "schema": schema } } ) import json user = json.loads(response.choices[0].message.content) ``` ## Supported models Structured outputs work on models whose `supported_features` include `structured_outputs`. Check [`GET /v1/models`](/api-reference/models). For models without native structured output support, use function calling with a single function whose parameters match your desired schema. ## Related # TEE-only endpoint Source: https://docs.redpill.ai/guides/tee-endpoint Point your base URL at tee.redpill.ai to serve confidential models only, on attested providers, with no per-request configuration. `https://tee.redpill.ai` is a base URL that serves confidential models only. Every request is restricted to a model that can run in a TEE and is forced onto an attested provider, without a per-request `provider` block. Point your client at it when an application must never reach a standard, non-attested model. It is the same gateway as `https://api.redpill.ai`, with the same API key, receipts, and verification headers. The host changes what the gateway will serve, not how you call it. ## Use it Change the base URL. Everything else stays the same. ```bash theme={null} curl https://tee.redpill.ai/v1/chat/completions \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Summarize this patient note."}] }' ``` ```python theme={null} import os from openai import OpenAI client = OpenAI(api_key=os.environ["REDPILL_AI_API_KEY"], base_url="https://tee.redpill.ai/v1") resp = client.chat.completions.create( model="z-ai/glm-5.2", messages=[{"role": "user", "content": "Summarize this patient note."}], ) ``` ## What the endpoint enforces | Behavior | `api.redpill.ai` (default) | `tee.redpill.ai` | | - | - | - | | `GET /v1/models` | Full catalog | Confidential (`is_tee`) models only | | Request for a confidential model | Served | Served on an attested provider | | Request for a standard model | Served | `404`, not available | | Attested serving | Opt in per request with `provider: {"aci_verified": true}` | Always on, cannot be waived | The endpoint applies two constraints together: * The catalog is filtered to confidential models, the same set as [`GET /v1/models?tee=true`](/guides/attested-routing#list-the-models-you-can-use). A standard model such as `openai/gpt-4o-mini` is not listed and returns `404` if requested. * Serving is forced onto an attested upstream, the same guarantee as [`provider: {"aci_verified": true}`](/guides/attested-routing). A client cannot opt out on this host: `provider: {"aci_verified": false}` and `X-Upstream-Verification: none` are ignored. List exactly what this endpoint will serve: ```bash theme={null} curl -s https://tee.redpill.ai/v1/models \ | jq -r '.data[].id' ``` ## When to use it Use `tee.redpill.ai` when the whole application must stay confidential and you do not want to add a `provider` block to every request. It removes the risk of one request omitting the constraint and reaching a standard model. Use the default `api.redpill.ai` with a per-request [`provider` block](/guides/attested-routing) when the application mixes standard and confidential traffic, or when you pin individual requests to one specific attested channel with `aci_session_ids`. ## Statuses to expect | Request on `tee.redpill.ai` | Result | | - | - | | Confidential model with a live attested provider | `200`, served and attested | | Standard (non-`is_tee`) model | `404`, not available on this endpoint | | Confidential model with no attested provider available right now | `503`, no prompt is sent | The `503` case is the same failure as an attested request on the default endpoint: the model is confidential in the catalog, but none of its live deployments is on an attested provider at that moment. See the [catalog and routing note](/guides/attested-routing#list-the-models-you-can-use). ## Verify a response The endpoint issues the same signed receipt as the default one. A response carries `x-receipt-id`. Fetch the receipt and read its `upstream.verified` event to confirm the request was served on a verified upstream. ```bash theme={null} curl -s "https://tee.redpill.ai/v1/aci/receipts/$RECEIPT_ID" \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ | jq '.event_log[] | select(.type == "upstream.verified")' ``` `"result": "verified"` with `"required": true` is the proof. See [Verify a response](/guides/verify-a-response). ## Related Per-request attestation with the `provider` block, and pinning to one channel. How confidential and standard models differ. Check the receipt for a specific response. Browse the catalog and read the `is_tee` flag. # Verify a response Source: https://docs.redpill.ai/guides/verify-a-response Verify the gateway workload, pinned TLS channel, signed receipt, and cited upstream session from your own client. Using `https://tee.redpill.ai` forces confidential routing, but a base URL alone does not verify the TEE from your machine. A verified client checks the workload attestation before sending model traffic, binds TLS to a key in the attested keyset, and verifies the signed receipt for the exact request and response bytes. `curl` and `jq` can inspect the artifacts; they cannot perform these checks. | Client | Verification | | - | - | | Pi | Built in, through [`pi-provider-redpill`](/guides/integrations/coding-tools/pi) | | OpenCode | Built in, through [`opencode-provider-redpill`](/guides/integrations/coding-tools/opencode) | | Node or Bun application | `connectAci()` from `@phala/aci-verifier`, below | | Claude Code, Codex CLI, Cline, and other base-URL-only tools | None; they cannot inject the pinned transport | Agent tools, MCP servers, browser automation, and telemetry remain separate trust boundaries. ## Node and Bun ```bash theme={null} npm install @phala/aci-verifier openai ``` Node 20.18+ and Bun 1.4+ share the same API; the `/runtime` export selects the pinned transport. ```typescript theme={null} import OpenAI from "openai"; import { connectAci } from "@phala/aci-verifier/runtime"; const apiKey = process.env.REDPILL_AI_API_KEY; if (!apiKey) throw new Error("REDPILL_AI_API_KEY is required"); const aci = await connectAci({ baseURL: "https://tee.redpill.ai/v1", policy: { requireProductionOs: true }, serving: { requireVerified: true, requireReceipt: true }, }); try { const client = new OpenAI({ apiKey, baseURL: aci.baseURL, fetch: aci.fetch }); const response = await client.responses.create({ model: "z-ai/glm-5.2", 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. ## Release policy `requireProductionOs` rejects development and unknown OS measurements. Without `acceptedComposeHashes`, the client verifies that the measured compose ran in a genuine TDX workload; it does not claim that RedPill 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 a dstack verifier result for the same quote (`is_valid: true`). Never copy a compose hash from the endpoint and trust it on first use. ```typescript theme={null} const aci = await connectAci({ baseURL: "https://tee.redpill.ai/v1", policy: { requireProductionOs: true, acceptedComposeHashes: [""], }, }); ``` ## 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. ## Related # Vision Source: https://docs.redpill.ai/guides/vision Send images to multimodal models by URL or as base64. Vision models can analyze images, screenshots, diagrams, and more. ## Supported models Vision works on any model whose `input_modalities` include `image`. Filter [`GET /v1/models`](/api-reference/models) to find them. ## Basic usage ```python Python theme={null} response = client.chat.completions.create( model="qwen/qwen3-vl-30b-a3b-instruct", messages=[{ "role": "user", "content": [ { "type": "text", "text": "What's in this image?" }, { "type": "image_url", "image_url": { "url": "https://example.com/image.jpg" } } ] }] ) ``` ```javascript JavaScript theme={null} import OpenAI from 'openai'; const response = await client.chat.completions.create({ model: 'qwen/qwen3-vl-30b-a3b-instruct', messages: [{ role: 'user', content: [ { type: 'text', text: "What's in this image?" }, { type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } } ] }] }); ``` ## Base64 images ```python theme={null} import base64 def encode_image(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode('utf-8') base64_image = encode_image("photo.jpg") response = client.chat.completions.create( model="qwen/qwen3-vl-30b-a3b-instruct", messages=[{ "role": "user", "content": [ {"type": "text", "text": "Describe this image"}, { "type": "image_url", "image_url": { "url": f"data:image/jpeg;base64,{base64_image}" } } ] }] ) ``` ## Multiple images ```python theme={null} response = client.chat.completions.create( model="qwen/qwen3-vl-30b-a3b-instruct", messages=[{ "role": "user", "content": [ {"type": "text", "text": "Compare these images"}, {"type": "image_url", "image_url": {"url": "https://example.com/1.jpg"}}, {"type": "image_url", "image_url": {"url": "https://example.com/2.jpg"}} ] }] ) ``` `qwen/qwen3-vl-30b-a3b-instruct` can be served by a verified upstream provider for confidential image processing. Confirm a confidential response per request from the receipt. ## Related # Zero data retention Source: https://docs.redpill.ai/guides/zero-data-retention Restrict a request to providers that do not retain prompt or completion content, and list the models that support it. Zero data retention (ZDR) means the upstream provider that serves your request does not keep the prompt or the completion after returning it. Use it when a prompt carries data you cannot leave on a third party's disk. Two surfaces implement it: * [`GET /v1/models?zdr=true`](/api-reference/models#query-parameters) lists the models a ZDR request can reach. * `provider: {"zdr": true}` in a request body restricts routing for that request. The two agree by construction. A model listed under `?zdr=true` is one that `provider: {"zdr": true}` can route. ## Route a request under zero data retention Add the `provider` block to the request. Every inference endpoint accepts it: `/v1/chat/completions`, `/v1/messages`, `/v1/responses`, `/v1/embeddings`, and `/v1/completions`. ```bash theme={null} curl https://api.redpill.ai/v1/chat/completions \ -H "Authorization: Bearer $REDPILL_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "z-ai/glm-5.2", "messages": [{"role": "user", "content": "Summarize this contract clause."}], "provider": {"zdr": true} }' ``` If no provider for that model operates under zero data retention, the request fails before the prompt is sent: ```json theme={null} { "error": { "message": "No zero-data-retention providers are available for the selected model.", "type": "not_found_error" } } ``` The status is `404`. This is a fail-closed check: the gateway does not fall back to a non-ZDR provider, and failover retries stay inside the ZDR set. Setting `"zdr": false`, or omitting the field, applies no retention constraint and behaves exactly as a request with no `provider` block. ## List the models you can use ```bash theme={null} curl -s "https://api.redpill.ai/v1/models?zdr=true" \ | jq -r '.data[].id' ``` Embedding models have their own catalog, which takes the same parameter: ```bash theme={null} curl -s "https://api.redpill.ai/v1/embeddings/models?zdr=true" \ | jq -r '.data[].id' ``` `true` is the only accepted value. Any other value, including `false`, returns `400`. Omit the parameter for the full catalog. Rejecting other values is deliberate: a typo cannot silently return an unfiltered list that looks filtered. A model object has no `zdr` field. The filter is how you read this property, so query the filtered catalog rather than inspecting individual model objects. ## How zero data retention differs from confidential inference These are different properties and you may need either, or both. | | What it constrains | How to select it | How to verify it | | - | - | - | - | | Confidential inference (`is_tee`) | Who can read memory while the model runs. The upstream is attested and the channel is bound before the prompt is sent. | `is_tee: true` on a model object | The per-response [receipt](/api-reference/receipts) records `upstream.verified.result` | | Zero data retention (`zdr`) | Whether the upstream stores prompt and completion content after serving. | `?zdr=true`, `provider: {"zdr": true}` | Provider contract and published policy | The distinction matters because a TEE constrains execution, not storage. A provider could run a model inside an enclave and still write request logs to disk. RedPill tracks the two properties separately for that reason. Today the two filters happen to select the same models, because every confidential provider RedPill routes to also operates under zero data retention, and the routed third-party providers do not. Do not depend on that. Filter with `?zdr=true` when you need the retention property, rather than inferring it from `is_tee`. Zero data retention is a property of the upstream provider, backed by its contract or published policy. It is not proven by the attestation report or the receipt, which cover workload identity and response integrity. Confidential inference is the property the receipt does prove. See [Verify a response](/guides/verify-a-response). ## The gateway's own retention The gateway does not store request or response bodies at any point, for ZDR and non-ZDR requests alike. Receipts hold hashes, not content. See [Trust boundary](/confidential-ai/trust-boundary). Setting `zdr` changes which upstream serves the request. It does not change gateway behavior. ## Related The `zdr` query parameter and model object fields. The `provider` routing block. What each upstream provider attests. How retention maps to GDPR and HIPAA obligations.