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

# OpenAI SDK

> Route Python and Node.js OpenAI SDK calls through Xenovia.

## Setup

```bash theme={null}
export XENOVIA_API_KEY=xe_...
export XENOVIA_PROXY_ID=your-proxy-id
```

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install openai
    ```

    ```python theme={null}
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.environ["XENOVIA_API_KEY"],
        base_url=f"https://runtime.xenovia.io/a/{os.environ['XENOVIA_PROXY_ID']}/openai/v1"
    )

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello"}]
    )
    print(response.choices[0].message.content)
    ```
  </Tab>

  <Tab title="Node.js">
    ```bash theme={null}
    npm install openai
    ```

    ```typescript theme={null}
    import OpenAI from "openai"

    const client = new OpenAI({
      apiKey: process.env.XENOVIA_API_KEY,
      baseURL: `https://runtime.xenovia.io/a/${process.env.XENOVIA_PROXY_ID}/openai/v1`
    })

    const response = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: "Hello" }]
    })
    console.log(response.choices[0].message.content)
    ```
  </Tab>
</Tabs>

## Streaming

Streaming works without any additional configuration. The runtime stamps `X-Xenovia-Session-Id` and `X-Xenovia-Trace-Id` into the response headers before the first byte.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Count to 5"}],
        stream=True
    )
    for chunk in stream:
        print(chunk.choices[0].delta.content or "", end="")
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    const stream = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: "Count to 5" }],
      stream: true
    })
    for await (const chunk of stream) {
      process.stdout.write(chunk.choices[0]?.delta?.content ?? "")
    }
    ```
  </Tab>
</Tabs>

## Tool calling

Tool definitions pass through unchanged. Xenovia records tool call names and arguments in the trace and evaluates them against your request-stage Rego policy before forwarding.

```python theme={null}
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string"}
                },
                "required": ["location"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "What's the weather in London?"}],
    tools=tools
)
```

If a tool name matches a block rule in your Rego policy, the request returns `403` before reaching the upstream LLM.

## Sessions

Pass `X-Xenovia-Session-Id` (a valid UUID) to group related calls into a session. All turns under that session ID appear together in Traces with a per-session turn counter.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import uuid

    client = OpenAI(
        api_key=os.environ["XENOVIA_API_KEY"],
        base_url=f"https://runtime.xenovia.io/a/{os.environ['XENOVIA_PROXY_ID']}/openai/v1",
        default_headers={"X-Xenovia-Session-Id": str(uuid.uuid4())}
    )
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    import { randomUUID } from "crypto"

    const client = new OpenAI({
      apiKey: process.env.XENOVIA_API_KEY,
      baseURL: `https://runtime.xenovia.io/a/${process.env.XENOVIA_PROXY_ID}/openai/v1`,
      defaultHeaders: { "X-Xenovia-Session-Id": randomUUID() }
    })
    ```
  </Tab>
</Tabs>

If you omit the header, Xenovia resolves a session automatically using message fingerprinting or the `user` field. See [Runtime Architecture](/platform/runtime-architecture) for the full five-strategy chain.

## Custom trace properties

Attach up to 20 `X-Xenovia-Property-*` headers to tag traces with application context. Keys must be ≤ 64 chars; values ≤ 512 chars; the `policy_` prefix is reserved.

```python theme={null}
client = OpenAI(
    api_key=os.environ["XENOVIA_API_KEY"],
    base_url=f"https://runtime.xenovia.io/a/{os.environ['XENOVIA_PROXY_ID']}/openai/v1",
    default_headers={
        "X-Xenovia-Session-Id": str(uuid.uuid4()),
        "X-Xenovia-Property-user-tier": "enterprise",
        "X-Xenovia-Property-feature": "document-summary",
        "X-Xenovia-Session-Path": "workflows/summarise"
    }
)
```

## Policy blocks

When a request is blocked by policy, the runtime returns `403 Forbidden`. The OpenAI SDK raises this as `PermissionDeniedError`. The `X-Xenovia-Trace-Id` response header identifies the blocking trace.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from openai import PermissionDeniedError

    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": "Delete everything"}],
            tools=tools
        )
    except PermissionDeniedError as e:
        print(f"Blocked by policy: {e.message}")
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    import { PermissionDeniedError } from "openai"

    try {
      const response = await client.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [{ role: "user", content: "Delete everything" }],
        tools
      })
    } catch (e) {
      if (e instanceof PermissionDeniedError) {
        console.log("Blocked by policy:", e.message)
      }
    }
    ```
  </Tab>
</Tabs>

## Reading trace headers

The response always includes trace headers you can log for correlation:

```python theme={null}
import httpx
from openai import OpenAI

# Use httpx to access response headers
with httpx.Client() as http:
    resp = http.post(
        f"https://runtime.xenovia.io/a/{os.environ['XENOVIA_PROXY_ID']}/openai/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {os.environ['XENOVIA_API_KEY']}",
            "Content-Type": "application/json"
        },
        json={
            "model": "gpt-4o-mini",
            "messages": [{"role": "user", "content": "Hello"}]
        }
    )
    trace_id = resp.headers.get("X-Xenovia-Trace-Id")
    session_id = resp.headers.get("X-Xenovia-Session-Id")
    print(f"Trace: {trace_id}, Session: {session_id}")
```
