> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tinfoil.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Proxy encrypted requests through your backend

> Keep your API key server-side while preserving end-to-end encryption between clients and the inference enclave.

When building production applications with Tinfoil, you typically need to keep your `TINFOIL_API_KEY` on your backend — exposing it to client browsers would let anyone use your API quota. At the same time, you want prompts and completions encrypted end-to-end to the attested enclave, not just to your server.

A proxy server running on your backend solves this. It sits between your client and the Tinfoil enclave, adding your API key, authenticating users, tracking usage, or applying rate limiting — while the [Encrypted HTTP Body Protocol (EHBP)](/resources/ehbp) ensures request and response bodies stay encrypted end-to-end. EHBP encrypts at the application layer using [HPKE](https://datatracker.ietf.org/doc/html/rfc9180), completely separate from TLS, so your proxy can read and modify HTTP headers without ever seeing the plaintext data. Every Tinfoil SDK also automatically verifies that the HPKE key comes from an attested secure enclave.

## Client Setup

<Note>
  The credential passed to the client is a token for your proxy, not your backend's `TINFOIL_API_KEY`. Your proxy should authenticate the token, remove it, and add `TINFOIL_API_KEY` before forwarding the request.
</Note>

JavaScript, Python, Go, and Swift route both inference and attestation requests through your proxy when you set its URL as both the API base URL and attestation bundle URL. The SDK still verifies the attestation bundle client-side regardless of where it was fetched from.

When the SDK sends requests through a proxy (i.e., `baseURL` differs from the enclave URL), it includes the enclave URL in the `X-Tinfoil-Enclave-Url` header. Your proxy should use this header to determine where to forward requests, ensuring the encrypted payload reaches the same enclave that the client verified.

<Tabs>
  <Tab title="JavaScript">
    ```bash theme={"dark"}
    npm install tinfoil
    ```

    ```typescript theme={"dark"}
    import { TinfoilAI } from "tinfoil";

    const client = new TinfoilAI({
      bearerToken: "<PROXY_AUTH_TOKEN>",
      baseURL: "https://your-proxy-server.com/v1/",
      attestationBundleURL: "https://your-proxy-server.com",
    });

    const response = await client.chat.completions.create({
      model: "<MODEL_NAME>",
      messages: [{ role: "user", content: "Hello!" }],
    });
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={"dark"}
    pip install tinfoil
    ```

    ```python theme={"dark"}
    from tinfoil import TinfoilAI

    client = TinfoilAI(
        api_key="<PROXY_AUTH_TOKEN>",
        base_url="https://your-proxy-server.com/v1/",
        attestation_bundle_url="https://your-proxy-server.com",
    )

    response = client.chat.completions.create(
        model="<MODEL_NAME>",
        messages=[{"role": "user", "content": "Hello!"}],
    )
    ```
  </Tab>

  <Tab title="Go">
    ```bash theme={"dark"}
    go get github.com/tinfoilsh/tinfoil-go
    ```

    ```go theme={"dark"}
    import (
        "log"

        "github.com/openai/openai-go/v3/option"
        "github.com/tinfoilsh/tinfoil-go"
    )

    client, err := tinfoil.NewClientWithOptions(
        tinfoil.WithBaseURL("https://your-proxy-server.com/v1/"),
        tinfoil.WithAttestationBundleURL("https://your-proxy-server.com"),
        tinfoil.WithOpenAIOptions(option.WithAPIKey("<PROXY_AUTH_TOKEN>")),
    )
    if err != nil {
        log.Fatal(err)
    }
    ```
  </Tab>

  <Tab title="Swift">
    ```swift theme={"dark"}
    import TinfoilAI

    let client = try await TinfoilAI.create(
        apiKey: "<PROXY_AUTH_TOKEN>",
        baseURL: "https://your-proxy-server.com",
        attestationBundleURL: "https://your-proxy-server.com"
    )
    ```
  </Tab>

  <Tab title="Rust">
    ```bash theme={"dark"}
    cargo add tinfoil --git https://github.com/tinfoilsh/tinfoil-rs --branch feat/ehbp-proxy
    ```

    ```rust theme={"dark"}
    let client = tinfoil::Client::new_with_proxy(
        "inference.tinfoil.sh",
        "tinfoilsh/confidential-model-router",
        "<PROXY_AUTH_TOKEN>",
        "https://your-proxy-server.com",
    )
    .await?;
    ```

    Rust verifies the enclave directly, then sends encrypted inference requests through the proxy. The client therefore needs network access to both the enclave and your proxy; it does not use the proxy's `/attestation` endpoint.

    <Note>
      Rust proxy support is currently available from the `feat/ehbp-proxy` branch and will move to `main` in the next SDK release.
    </Note>
  </Tab>
</Tabs>

`baseURL` or `base_url` must point at your proxy, not a Tinfoil enclave. Include `/v1/` for JavaScript, Python, and Go. Swift and Rust append API paths themselves.

## Proxy Server Setup

Your proxy's job is to preserve the [EHBP protocol headers](/resources/ehbp#protocol-headers) that coordinate encryption between client and enclave, add your authentication credentials, and forward the encrypted payload. No special cryptographic libraries are needed — the proxy never participates in encryption or decryption.

While we provide an example implementation in Go below, the proxy pattern works with any language that can handle HTTP requests. The [example repository](https://github.com/tinfoilsh/encrypted-request-proxy-example) provides a complete reference implementation with a Go proxy and a TypeScript client.

### Required Endpoints

Your proxy needs to implement these routes:

| Path           | Method | Description                                                                                 |
| -------------- | ------ | ------------------------------------------------------------------------------------------- |
| `/attestation` | GET    | Proxy to `https://atc.tinfoil.sh/attestation` for JavaScript, Python, Go, and Swift clients |
| `/v1/*`        | POST   | Forward API requests to the enclave URL from the `X-Tinfoil-Enclave-Url` header             |

The example below forwards every `/v1/` path so chat completions, responses, embeddings, audio, and other supported APIs use the same encrypted route.

### Required Headers to Preserve

The EHBP protocol uses specific headers to coordinate encryption keys between the client and enclave.
Your proxy must forward these headers unchanged in both directions.

For requests, you need to preserve `Ehbp-Encapsulated-Key` (the HPKE encapsulated key, hex-encoded, 64 characters).
For responses, you need to preserve `Ehbp-Response-Nonce` (the 32-byte nonce used in response key derivation, hex-encoded, 64 characters).

Understanding the [body framing format](/resources/ehbp#body-framing) isn't necessary for proxy implementation, but it helps explain why these headers are critical—they contain the cryptographic material needed for key derivation and decryption.

### CORS Configuration

If your proxy will be called from browser-based applications, you'll need to configure CORS headers to allow the browser to send and read the encryption headers.
The key requirement is exposing the EHBP headers in both directions:

```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Accept, Authorization, Content-Type, Ehbp-Encapsulated-Key, X-Tinfoil-Enclave-Url
Access-Control-Expose-Headers: Ehbp-Response-Nonce
```

### Implementation Example

```go theme={"dark"}
package main

import (
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
)

var (
	ehbpRequestHeaders  = []string{"Ehbp-Encapsulated-Key"}
	ehbpResponseHeaders = []string{"Ehbp-Response-Nonce"}
)

func main() {
	http.HandleFunc("/v1/", proxyHandler)
	http.HandleFunc("/attestation", attestationHandler)
	log.Println("Proxy listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

// attestationHandler proxies attestation bundle requests to the Tinfoil ATC
func attestationHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")

	if r.Method == http.MethodOptions {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	resp, err := http.Get("https://atc.tinfoil.sh/attestation")
	if err != nil {
		http.Error(w, "Failed to fetch attestation bundle", http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()

	if ct := resp.Header.Get("Content-Type"); ct != "" {
		w.Header().Set("Content-Type", ct)
	}

	w.WriteHeader(resp.StatusCode)
	io.Copy(w, resp.Body)
}

// proxyHandler forwards encrypted API requests to the enclave
func proxyHandler(w http.ResponseWriter, r *http.Request) {
	// Set CORS headers
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Ehbp-Encapsulated-Key, X-Tinfoil-Enclave-Url")
	w.Header().Set("Access-Control-Expose-Headers", "Ehbp-Response-Nonce")

	if r.Method == http.MethodOptions {
		w.WriteHeader(http.StatusNoContent)
		return
	}

	// Get upstream URL from the X-Tinfoil-Enclave-Url header
	upstreamBase := r.Header.Get("X-Tinfoil-Enclave-Url")
	if upstreamBase == "" {
		http.Error(w, "X-Tinfoil-Enclave-Url header required", http.StatusBadRequest)
		return
	}
	if !isAllowedEnclave(upstreamBase) {
		http.Error(w, "Unapproved enclave URL", http.StatusBadRequest)
		return
	}
	upstreamURL := upstreamBase + r.URL.Path

	// Create upstream request
	req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, upstreamURL, r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if contentType := r.Header.Get("Content-Type"); contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	if accept := r.Header.Get("Accept"); accept != "" {
		req.Header.Set("Accept", accept)
	}

	// Add your API key
	apiKey := os.Getenv("TINFOIL_API_KEY")
	if apiKey == "" {
		http.Error(w, "TINFOIL_API_KEY not set", http.StatusInternalServerError)
		return
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)

	// Copy encryption headers
	copyHeaders(req.Header, r.Header, ehbpRequestHeaders...)

	// Forward request
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()

	// Copy encryption headers from response
	copyHeaders(w.Header(), resp.Header, ehbpResponseHeaders...)

	if ct := resp.Header.Get("Content-Type"); ct != "" {
		w.Header().Set("Content-Type", ct)
	}

	// Handle chunked transfer encoding for streaming responses
	if te := resp.Header.Get("Transfer-Encoding"); te != "" {
		w.Header().Set("Transfer-Encoding", te)
		w.Header().Del("Content-Length")
	}

	w.WriteHeader(resp.StatusCode)

	// Stream response with flushing for SSE/streaming support
	if flusher, ok := w.(http.Flusher); ok {
		buf := make([]byte, 1024)
		for {
			n, err := resp.Body.Read(buf)
			if n > 0 {
				w.Write(buf[:n])
				flusher.Flush()
			}
			if err != nil {
				break
			}
		}
		return
	}

	io.Copy(w, resp.Body)
}

func isAllowedEnclave(rawURL string) bool {
	upstream, err := url.Parse(rawURL)
	if err != nil || upstream.Scheme != "https" || upstream.User != nil {
		return false
	}
	host := upstream.Hostname()
	return host == "tinfoil.sh" || strings.HasSuffix(host, ".tinfoil.sh")
}

func copyHeaders(dst, src http.Header, keys ...string) {
	for _, key := range keys {
		if value := src.Get(key); value != "" {
			dst.Set(key, value)
		}
	}
}
```

## Usage Metrics for Billing

Since EHBP encrypts request and response bodies, your proxy cannot read token counts from the JSON. Tinfoil provides usage metrics via HTTP headers so you can track usage and bill your users.

To enable this, add the `X-Tinfoil-Request-Usage-Metrics: true` header when forwarding requests to the enclave. Tinfoil returns token counts in the `X-Tinfoil-Usage-Metrics` response header for non-streaming requests, or as an HTTP trailer after the body completes for streaming requests. The format is:

```
prompt=<prompt_tokens>,completion=<completion_tokens>,total=<total_tokens>
```

For example:

```go theme={"dark"}
// When building the upstream request:
req.Header.Set("X-Tinfoil-Request-Usage-Metrics", "true")

// After receiving the response:
// Non-streaming: read from response header
if usage := resp.Header.Get("X-Tinfoil-Usage-Metrics"); usage != "" {
    log.Printf("Usage: %s", usage)  // "prompt=67,completion=42,total=109"
}

// Streaming: read from trailer after body is consumed
io.Copy(w, resp.Body)
if usage := resp.Trailer.Get("X-Tinfoil-Usage-Metrics"); usage != "" {
    log.Printf("Usage: %s", usage)
}
```

See the [example repository](https://github.com/tinfoilsh/encrypted-request-proxy-example#usage-metrics-for-billing) for a complete implementation.

## Custom Headers

Beyond the required EHBP headers, you can use custom HTTP headers to build your own application-level protocols between the client and proxy. Since EHBP only encrypts bodies, headers remain visible to the proxy — which means you can authenticate users, track requests, implement rate limiting, or pass feature flags without any of that metadata reaching the enclave.

### Request Headers

The JavaScript SDK's lower-level `SecureClient` lets you send arbitrary headers and inspect the raw response:

```typescript theme={"dark"}
import { SecureClient } from "tinfoil";

const secureClient = new SecureClient({
  baseURL: "https://your-proxy-server.com/",
  attestationBundleURL: "https://your-proxy-server.com",
});
await secureClient.ready();

const response = await secureClient.fetch("/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: "Bearer <PROXY_AUTH_TOKEN>",
    "Content-Type": "application/json",
    "X-User-ID": "user-123",
    "X-Request-ID": crypto.randomUUID(),
    "Your-Custom-Request-Header": "custom-value",
  },
  body: JSON.stringify({
    model: "<MODEL_NAME>",
    messages: [{ role: "user", content: "Hello!" }],
  }),
});
```

Your proxy can then read and strip these headers for logging, routing decisions, or authentication checks before forwarding the encrypted request to the enclave:

```go theme={"dark"}
// In your proxy server
if customHeader := r.Header.Get("Your-Custom-Request-Header"); customHeader != "" {
    log.Printf("Custom request header received: %s", customHeader)
    // These headers are not forwarded to the enclave
}
```

Just remember to add any custom headers you use to your CORS `Access-Control-Allow-Headers` configuration so browsers can send them.

### Response Headers

Your proxy can add custom headers to responses that the client can read. This is useful for communicating rate limit information, cost tracking, or request IDs for debugging:

```go theme={"dark"}
// In your proxy server
w.Header().Set("Your-Custom-Response-Header", "response-value")
w.Header().Set("X-Rate-Limit-Remaining", "100")
```

The client can then access these headers:

```typescript theme={"dark"}
// Optional: Read custom headers from the response
const customHeader = response.headers.get("Your-Custom-Response-Header");
const remaining = response.headers.get("X-Rate-Limit-Remaining");
```

As with request headers, you'll need to expose custom response headers in your CORS `Access-Control-Expose-Headers` configuration for browsers to access them.

## Security Considerations

While EHBP encrypts the request and response bodies, the HTTP headers remain visible throughout the proxy chain. This design is intentional—it's what allows proxies to route and manage requests—but it has important security implications.

<Warning>
  Never expose your `TINFOIL_API_KEY` to client applications. The entire purpose of the proxy architecture is to keep this key on your backend.
</Warning>

* **Use HTTPS in production.** While request and response bodies are encrypted at the application layer, HTTP headers (including custom headers like user IDs) are only protected by transport-layer encryption.
* **Allowlist enclave destinations.** Treat `X-Tinfoil-Enclave-Url` as untrusted input and only forward to approved HTTPS Tinfoil enclave origins. This prevents clients from using your proxy to reach arbitrary hosts.
* **Validate and sanitize custom headers** from clients. Treat them as untrusted input — check formats, guard against injection attacks, and authenticate before trusting client-provided values.
* **Always preserve and forward the EHBP headers** (`Ehbp-Encapsulated-Key` for requests, `Ehbp-Response-Nonce` for responses). Dropping or modifying them will cause decryption to fail.

## How It Works

The data flow shows how encryption is maintained throughout the request lifecycle:

```mermaid theme={"dark"}
sequenceDiagram
    participant Client
    participant Proxy as Your Proxy Server
    participant ATC as Tinfoil ATC
    participant Enclave as Tinfoil Enclave

    Client->>Proxy: GET /attestation
    Proxy->>ATC: GET /attestation
    ATC-->>Proxy: Attestation bundle
    Proxy-->>Client: Attestation bundle
    Note over Client: Verifies enclave attestation<br/>Extracts encryption keys

    Note over Client: Encrypts body with<br/>enclave's public key
    Client->>Proxy: Encrypted request<br/>+ X-Tinfoil-Enclave-Url<br/>+ EHBP headers
    Note over Proxy: Reads headers only<br/>Cannot decrypt body
    Proxy->>Enclave: Encrypted request<br/>+ TINFOIL_API_KEY<br/>+ EHBP headers
    Note over Enclave: Decrypts request body<br/>Processes inference<br/>Encrypts response
    Enclave-->>Proxy: Encrypted response<br/>+ EHBP headers
    Note over Proxy: Reads headers only<br/>Cannot decrypt body
    Proxy-->>Client: Encrypted response<br/>+ EHBP headers
    Note over Client: Decrypts response body
```

<Note>
  This diagram shows the proxied attestation flow used by JavaScript, Python, Go, and Swift. Rust performs attestation directly against the enclave before routing encrypted inference requests through your proxy.
</Note>

## Next Steps

For a complete working example, check out the [encrypted-request-proxy-example](https://github.com/tinfoilsh/encrypted-request-proxy-example) repository. It includes a Go proxy implementation with streaming support, a TypeScript browser client demonstrating the `SecureClient`, and custom header handling examples.

<CardGroup cols={2}>
  <Card title="Example Repository" icon="github" href="https://github.com/tinfoilsh/encrypted-request-proxy-example">
    Go proxy server with TypeScript browser client
  </Card>

  <Card title="Encrypted HTTP Body Protocol" icon="lock" href="/resources/ehbp">
    Deep dive into the EHBP specification
  </Card>
</CardGroup>
