# 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.
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
```
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.