Skip to main content
Venice offers privacy-enhanced models that run in Trusted Execution Environments (TEE) and support End-to-End Encryption (E2EE). These models provide cryptographic guarantees that your data remains private—even from Venice.

Understanding the Privacy Levels

TypePrefixWhat It Means
TEEtee-*Model runs in a hardware-secured enclave. Venice cannot access the computation. You can verify this with attestation.
E2EEe2ee-*Full end-to-end encryption. Your prompts are encrypted client-side before being sent. Only the TEE can decrypt them.
E2EE models include TEE protection plus client-side encryption. TEE models provide enclave security without requiring client-side encryption.

Available Models

Loading…
Check the Models page for the full list with pricing and context limits.

TEE Models

TEE models run inside hardware-secured enclaves (Intel TDX, NVIDIA Confidential Computing). The model weights and your data are protected from the host system—including Venice’s infrastructure.

Basic Usage

TEE models work exactly like regular models:
from openai import OpenAI

client = OpenAI(
    api_key="your-venice-api-key",
    base_url="https://api.venice.ai/api/v1"
)

response = client.chat.completions.create(
    model="tee-qwen3-5-122b-a10b",
    messages=[{"role": "user", "content": "Explain quantum computing"}]
)

print(response.choices[0].message.content)

Verifying TEE Attestation

You can cryptographically verify that a model is running in a genuine TEE by fetching its attestation report:
# Generate a random nonce (prevents replay attacks)
NONCE=$(openssl rand -hex 16)

# Fetch attestation
curl "https://api.venice.ai/api/v1/tee/attestation?model=tee-qwen3-5-122b-a10b&nonce=$NONCE" \
  -H "Authorization: Bearer $API_KEY_VENICE"
The attestation response includes:
FieldDescription
verifiedWhether the attestation passed server-side verification
nonceYour nonce, confirming freshness
modelThe attested model ID
tee_providerTEE provider identifier
intel_quoteRaw Intel TDX quote (base64) for client-side verification
nvidia_payloadNVIDIA GPU attestation data (if applicable)
signing_keyPublic key for verifying response signatures (typically required for E2EE flows; may be omitted for some plain TEE models)
signing_addressEthereum address derived from signing key
For production use, verify the attestation client-side by parsing the Intel TDX quote and checking the NVIDIA attestation.
For plain TEE model verification, signing_address and server-side verification fields are sufficient for baseline attestation checks. A signing_key is required when you need client-side E2EE key agreement and strict key-binding checks.

Response Signatures

TEE models can sign their responses, proving the output came from the attested enclave:
# After getting a completion, verify the signature
curl "https://api.venice.ai/api/v1/tee/signature?model=tee-qwen3-5-122b-a10b&request_id=chatcmpl-abc123" \
  -H "Authorization: Bearer $API_KEY_VENICE"

E2EE Models

E2EE models add client-side encryption on top of TEE protection. Your prompts are encrypted before leaving your device, and only the TEE can decrypt them. Venice E2EE uses:
  • ECDH (Elliptic Curve Diffie-Hellman) on secp256k1 for key exchange
  • HKDF-SHA256 for key derivation
  • AES-256-GCM for symmetric encryption
  • TEE attestation to verify the model runs in a secure enclave
E2EE requires client-side implementation. The examples below show the complete protocol.

How E2EE Works

1

Generate Ephemeral Key Pair

Client generates a secp256k1 key pair for this session.
2

Fetch TEE Attestation

Client requests /api/v1/tee/attestation and receives the model’s public key, attestation evidence, and nonce.
3

Verify Attestation

Client checks nonce match, debug mode disabled, and attestation validity.
4

Encrypt Messages

Client encrypts prompts using ECDH shared secret → HKDF → AES-GCM.
5

Send Request

Client sends request with E2EE headers (X-Venice-TEE-Client-Pub-Key, X-Venice-TEE-Model-Pub-Key, X-Venice-TEE-Signing-Algo).
6

TEE Processing

TEE decrypts request, processes it, and encrypts the response.
7

Decrypt Response

Client receives encrypted chunks and decrypts with private key.

Prerequisites

JavaScript (Node.js ESM):
npm install elliptic @noble/ciphers @noble/hashes
Python:
pip install cryptography ecdsa requests

Step 1: Check Model E2EE Support

First, verify the model supports E2EE by checking the /models endpoint.
async function getE2EEModels(apiKey) {
  const response = await fetch('https://api.venice.ai/api/v1/models', {
    headers: { Authorization: `Bearer ${apiKey}` },
  })
  const { data } = await response.json()

  return data.filter(model => model.model_spec?.capabilities?.supportsE2EE === true)
}

// Example usage
const models = await getE2EEModels('your-api-key')
console.log('E2EE Models:', models.map(m => m.id))
// Output: ['e2ee-qwen3-5-122b-a10b', 'e2ee-glm-5', ...]

Step 2: Generate Ephemeral Key Pair

Generate a new key pair for each session. The private key should be kept in memory only and securely zeroed after use.
import { ec as EC } from 'elliptic'

function generateEphemeralKeyPair() {
  const ec = new EC('secp256k1')
  const keyPair = ec.genKeyPair()

  return {
    privateKey: new Uint8Array(keyPair.getPrivate().toArray('be', 32)),
    publicKeyHex: keyPair.getPublic('hex'), // Uncompressed format (65 bytes hex)
  }
}

// Security: Zero-fill private key when done
function zeroFill(arr) {
  arr.fill(0)
}

Validation Helpers

Use these helper functions to validate keys and encrypted content before sending requests.
function validateClientPubkey(pubkeyHex) {
  if (pubkeyHex.length !== 130 || !pubkeyHex.startsWith('04')) {
    throw new Error(`Client pubkey must be 130 hex chars starting with '04' (got ${pubkeyHex.length})`)
  }
}

function isValidEncrypted(s) {
  // Minimum: ephemeral_pub (65) + nonce (12) + tag (16) = 93 bytes = 186 hex chars
  return s.length >= 186 && /^[0-9a-fA-F]+$/.test(s)
}

Step 3: Fetch and Verify TEE Attestation

The attestation proves the model is running in a genuine TEE. Always verify the attestation before trusting the model’s public key.
Important: Nonce Length - The client nonce must be 32 bytes (64 hex characters). Some TEE providers require exactly 32 bytes and will reject shorter nonces.
import crypto from 'crypto'

async function fetchAndVerifyAttestation(modelId, apiKey) {
  // Generate client nonce for replay protection (32 bytes = 64 hex chars)
  const clientNonce = crypto.randomBytes(32).toString('hex')

  const response = await fetch(
    `https://api.venice.ai/api/v1/tee/attestation?model=${encodeURIComponent(modelId)}&nonce=${clientNonce}`,
    { headers: { Authorization: `Bearer ${apiKey}` } }
  )

  const attestation = await response.json()

  // Verify attestation
  if (attestation.verified !== true) {
    throw new Error('TEE attestation verification failed on server')
  }

  if (attestation.nonce !== clientNonce) {
    throw new Error('Attestation nonce mismatch - possible replay attack')
  }

  // Get model's public key for encryption
  const modelPublicKey = attestation.signing_key || attestation.signing_public_key
  if (!modelPublicKey) {
    throw new Error('No signing key in attestation response')
  }

  return {
    modelPublicKey,
    signingAddress: attestation.signing_address,
    attestation,
  }
}

Step 4: Encrypt Messages

Encrypt user and system messages before sending. Only user and system role messages need encryption.
When E2EE headers are present, all user and system role messages must be encrypted. Sending any plaintext content in these roles will result in an “Encrypted field is not valid hex” error.
import { gcm } from '@noble/ciphers/aes.js'
import { hkdf } from '@noble/hashes/hkdf.js'
import { sha256 } from '@noble/hashes/sha2.js'
import { ec as EC } from 'elliptic'
import crypto from 'crypto'

const HKDF_INFO = new TextEncoder().encode('ecdsa_encryption')

function encryptMessage(plaintext, modelPublicKeyHex) {
  const ec = new EC('secp256k1')

  // Normalize public key (add 04 prefix if needed)
  let normalizedKey = modelPublicKeyHex
  if (!normalizedKey.startsWith('04') && normalizedKey.length === 128) {
    normalizedKey = '04' + normalizedKey
  }

  const modelPublicKey = ec.keyFromPublic(normalizedKey, 'hex')

  // Generate ephemeral key pair for this message
  const ephemeralKeyPair = ec.genKeyPair()

  // ECDH shared secret
  const sharedSecret = ephemeralKeyPair.derive(modelPublicKey.getPublic())
  const sharedSecretBytes = new Uint8Array(sharedSecret.toArray('be', 32))

  // Derive AES key using HKDF
  const aesKey = hkdf(sha256, sharedSecretBytes, undefined, HKDF_INFO, 32)

  // Generate random nonce
  const nonce = crypto.randomBytes(12)

  // Encrypt with AES-GCM
  const cipher = gcm(aesKey, nonce)
  const encrypted = cipher.encrypt(new TextEncoder().encode(plaintext))

  // Get ephemeral public key (uncompressed)
  const ephemeralPublic = new Uint8Array(ephemeralKeyPair.getPublic(false, 'array'))

  // Combine: ephemeral_public (65 bytes) + nonce (12 bytes) + ciphertext
  const result = new Uint8Array(65 + 12 + encrypted.length)
  result.set(ephemeralPublic, 0)
  result.set(nonce, 65)
  result.set(encrypted, 65 + 12)

  return Buffer.from(result).toString('hex')
}

function encryptMessagesForE2EE(messages, modelPublicKey) {
  return messages.map(msg => {
    if (msg.role === 'user' || msg.role === 'system') {
      return {
        ...msg,
        content: encryptMessage(msg.content, modelPublicKey),
      }
    }
    return msg
  })
}

Step 5: Send Request with E2EE Headers

Include the required headers to enable E2EE processing.
HeaderDescription
X-Venice-TEE-Client-Pub-KeyYour ephemeral public key (uncompressed hex, 130 chars)
X-Venice-TEE-Model-Pub-KeyModel’s public key from attestation
X-Venice-TEE-Signing-AlgoAlways ecdsa
async function sendE2EERequest(messages, model, e2eeContext, apiKey) {
  // Encrypt messages
  const encryptedMessages = encryptMessagesForE2EE(messages, e2eeContext.modelPublicKey)

  const response = await fetch('https://api.venice.ai/api/v1/chat/completions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      // E2EE headers
      'X-Venice-TEE-Client-Pub-Key': e2eeContext.publicKeyHex,
      'X-Venice-TEE-Model-Pub-Key': e2eeContext.modelPublicKey,
      'X-Venice-TEE-Signing-Algo': 'ecdsa',
    },
    body: JSON.stringify({
      model,
      messages: encryptedMessages,
      stream: true, // E2EE requires streaming
    }),
  })

  return response
}

Step 6: Decrypt Response Chunks

Responses from E2EE models are hex-encoded encrypted chunks. Decrypt each chunk using your private key.
import { gcm } from '@noble/ciphers/aes.js'
import { hkdf } from '@noble/hashes/hkdf.js'
import { sha256 } from '@noble/hashes/sha2.js'
import { ec as EC } from 'elliptic'

const HKDF_INFO = new TextEncoder().encode('ecdsa_encryption')

function hexToBytes(hex) {
  const h = hex.startsWith('0x') ? hex.slice(2) : hex
  const bytes = new Uint8Array(h.length / 2)
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16)
  }
  return bytes
}

function isHexEncrypted(s) {
  // Minimum: ephemeral_pub (65) + nonce (12) + tag (16) = 93 bytes = 186 hex chars
  if (s.length < 186) return false
  return /^[0-9a-fA-F]+$/.test(s)
}

function decryptChunk(ciphertextHex, clientPrivateKey) {
  const raw = hexToBytes(ciphertextHex)

  // Parse components
  const serverEphemeralPubKey = raw.slice(0, 65)
  const nonce = raw.slice(65, 65 + 12)
  const ciphertext = raw.slice(65 + 12)

  // ECDH with server's ephemeral key
  const ec = new EC('secp256k1')
  const clientKey = ec.keyFromPrivate(Buffer.from(clientPrivateKey))
  const serverKey = ec.keyFromPublic(Buffer.from(serverEphemeralPubKey))
  const sharedSecret = clientKey.derive(serverKey.getPublic())
  const sharedSecretBytes = new Uint8Array(sharedSecret.toArray('be', 32))

  // Derive AES key
  const aesKey = hkdf(sha256, sharedSecretBytes, undefined, HKDF_INFO, 32)

  // Decrypt
  const cipher = gcm(aesKey, nonce)
  const plaintext = cipher.decrypt(ciphertext)

  return new TextDecoder().decode(plaintext)
}

// Process streaming response
async function processE2EEStream(response, clientPrivateKey) {
  const reader = response.body.getReader()
  const decoder = new TextDecoder()
  let fullContent = ''

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    const text = decoder.decode(value)
    const lines = text.split('\n')

    for (const line of lines) {
      if (!line.startsWith('data: ')) continue
      const data = line.slice(6)
      if (data === '[DONE]') continue

      try {
        const chunk = JSON.parse(data)
        const content = chunk.choices?.[0]?.delta?.content

        if (content && isHexEncrypted(content)) {
          const decrypted = decryptChunk(content, clientPrivateKey)
          fullContent += decrypted
          process.stdout.write(decrypted) // Real-time output
        } else if (content) {
          fullContent += content
          process.stdout.write(content)
        }
      } catch (e) {
        // Skip malformed chunks
      }
    }
  }

  return fullContent
}

Complete Working Example

import elliptic from 'elliptic';
import { gcm } from '@noble/ciphers/aes.js';
import { hkdf } from '@noble/hashes/hkdf.js';
import { sha256 } from '@noble/hashes/sha2.js';
import crypto from 'crypto';

const EC = elliptic.ec;

const API_KEY = process.env.API_KEY_VENICE;
const BASE_URL = 'https://api.venice.ai/api/v1';
const MODEL = 'e2ee-qwen3-5-122b-a10b';
const HKDF_INFO = new TextEncoder().encode('ecdsa_encryption');

function hexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
  }
  return bytes;
}

async function main() {
  // Step 1: Generate ephemeral key pair
  console.log('🔑 Generating ephemeral key pair...');
  const ec = new EC('secp256k1');
  const keyPair = ec.genKeyPair();
  const clientPublicKeyHex = keyPair.getPublic('hex');

  // Step 2: Fetch and verify attestation
  console.log('🔍 Fetching TEE attestation...');
  const clientNonce = crypto.randomBytes(32).toString('hex'); // 32 bytes required
  const attestationRes = await fetch(
    `${BASE_URL}/tee/attestation?model=${MODEL}&nonce=${clientNonce}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const attestation = await attestationRes.json();

  if (attestation.verified !== true || attestation.nonce !== clientNonce) {
    throw new Error('Attestation verification failed');
  }

  const modelPublicKey = attestation.signing_key || attestation.signing_public_key;
  console.log('✅ TEE attestation verified');

  // Step 3: Encrypt message
  console.log('🔐 Encrypting message...');
  const plaintext = 'What is 2+2? Answer briefly.';

  // Normalize and parse model's public key
  let normalizedKey = modelPublicKey;
  if (!normalizedKey.startsWith('04') && normalizedKey.length === 128) {
    normalizedKey = '04' + normalizedKey;
  }

  const modelKey = ec.keyFromPublic(normalizedKey, 'hex');
  const ephemeralKeyPair = ec.genKeyPair();
  const sharedSecret = ephemeralKeyPair.derive(modelKey.getPublic());
  const sharedSecretBytes = new Uint8Array(sharedSecret.toArray('be', 32));
  const aesKey = hkdf(sha256, sharedSecretBytes, undefined, HKDF_INFO, 32);
  const nonce = crypto.randomBytes(12);
  const cipher = gcm(aesKey, nonce);
  const encrypted = cipher.encrypt(new TextEncoder().encode(plaintext));
  const ephemeralPublic = new Uint8Array(ephemeralKeyPair.getPublic(false, 'array'));

  const result = new Uint8Array(65 + 12 + encrypted.length);
  result.set(ephemeralPublic, 0);
  result.set(nonce, 65);
  result.set(encrypted, 77);

  const encryptedContent = Buffer.from(result).toString('hex');
  const messages = [{ role: 'user', content: encryptedContent }];

  // Step 4: Send E2EE request
  console.log('📤 Sending encrypted request...');
  const response = await fetch(`${BASE_URL}/chat/completions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      'X-Venice-TEE-Client-Pub-Key': clientPublicKeyHex,
      'X-Venice-TEE-Model-Pub-Key': modelPublicKey,
      'X-Venice-TEE-Signing-Algo': 'ecdsa',
    },
    body: JSON.stringify({ model: MODEL, messages, stream: true }),
  });

  // Step 5: Decrypt response
  console.log('📥 Decrypting response...\n');
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    for (const line of text.split('\n')) {
      if (!line.startsWith('data: ') || line.includes('[DONE]')) continue;

      try {
        const chunk = JSON.parse(line.slice(6));
        const content = chunk.choices?.[0]?.delta?.content;
        if (!content) continue;

        if (/^[0-9a-fA-F]+$/.test(content) && content.length >= 186) {
          // Decrypt
          const raw = hexToBytes(content);
          const serverEphemeralPub = raw.slice(0, 65);
          const nonce = raw.slice(65, 77);
          const ciphertext = raw.slice(77);

          const serverKey = ec.keyFromPublic(Buffer.from(serverEphemeralPub));
          const sharedSecret = keyPair.derive(serverKey.getPublic());
          const aesKey = hkdf(sha256, new Uint8Array(sharedSecret.toArray('be', 32)), undefined, HKDF_INFO, 32);
          const cipher = gcm(aesKey, nonce);
          const plaintext = new TextDecoder().decode(cipher.decrypt(ciphertext));
          process.stdout.write(plaintext);
        } else {
          process.stdout.write(content);
        }
      } catch {}
    }
  }

  console.log('\n\n🔐 Response decrypted end-to-end');
}

main().catch(console.error);

E2EE Limitations

E2EE has some constraints due to the encryption requirements:
FeatureStatus
StreamingRequired (non-streaming not supported)
Web searchDisabled (would leak content)
File uploadsNot supported
Function callingNot supported
Venice system promptDisabled (must be encrypted client-side)

Security Best Practices

  1. Generate new key pairs per session - Don’t reuse ephemeral keys
  2. Zero-fill private keys - Clear private key bytes from memory when done
  3. Verify attestation - Always check verified: true and nonce match
  4. Check for debug mode - Reject attestations from debug enclaves
  5. Use streaming - E2EE requires streaming for proper encryption chunking
  6. Handle errors gracefully - Don’t expose decryption errors to users
  7. Use 32-byte nonces - TEE providers require exactly 32 bytes

Best Practices

Don’t just trust the verified: true response. Parse the Intel TDX quote client-side and verify the measurements match expected values. For NVIDIA GPUs, check the attestation via NVIDIA’s verification service.
Always generate a new random nonce for each attestation request. This prevents replay attacks where an attacker could serve a stale attestation.
The signing key should be bound to the TDX REPORTDATA field. This proves the key was generated inside the enclave.
Verify the TDX attestation doesn’t have debug flags set. A debug enclave can be inspected and should not be trusted for production.
E2EE requires careful cryptographic implementation. Use our official SDKs rather than implementing the protocol yourself.

Checking Model Capabilities

You can check if a model supports TEE or E2EE via the models endpoint:
curl https://api.venice.ai/api/v1/models \
  -H "Authorization: Bearer $API_KEY_VENICE" | jq '.data[] | select(.model_spec.capabilities.supportsTeeAttestation == true or .model_spec.capabilities.supportsE2EE == true) | {id, tee: .model_spec.capabilities.supportsTeeAttestation, e2ee: .model_spec.capabilities.supportsE2EE}'

Error Handling

ErrorCauseSolution
TEE attestation verification failedAttestation didn’t pass validationRetry or contact support
Attestation nonce mismatchPossible replay attackGenerate a fresh nonce
TDX debug mode detectedEnclave is in debug modeDon’t use for production
Failed to decrypt fieldE2EE decryption failed server-sideCheck your encryption implementation
E2EE requires streamingNon-streaming request to E2EE modelSet stream: true
Encrypted field is not valid hexPlaintext sent with E2EE headersEncrypt all user/system messages
Invalid public keyWrong key formatUse 130 hex chars starting with 04

Troubleshooting

The nonce length is incorrect. TEE providers require exactly 32 bytes (64 hex characters).
  • Use crypto.randomBytes(32).toString('hex') (JS) or secrets.token_hex(32) (Python)
  • Common mistake: secrets.token_hex(16) produces 32 hex chars (16 bytes), not 32 bytes
  • Check that the model supports E2EE (supportsE2EE: true)
  • Verify your API key is valid and has access to the requested model
  • Verify network connectivity to Venice API
  • Ensure you’re using the same private key that generated the public key sent in headers
  • Check that the response content is actually hex-encoded (E2EE active)
  • Verify the model public key matches what was used for encryption
  • All user and system role messages must be encrypted when E2EE headers are present
  • Verify your encrypted content passes the isValidEncrypted() validation (minimum 186 hex characters)
  • Check that encryption output is lowercase hex without any prefixes
  • Client public key must be exactly 130 hex characters starting with 04
  • Use the validateClientPubkey() helper to verify format before sending
  • Ensure you’re using uncompressed public key format (65 bytes = 130 hex chars)
  • Verify the model ID is correct and the model supports E2EE
  • Use the /models endpoint to verify available E2EE models

Resources