# Session affinity (/en/docs/developers/model/guides/session-affinity)



Use the same `x-session-id` for requests in one conversation. A new conversation gets a new ID. Session affinity helps eligible requests reuse a provider, but does not guarantee a fixed provider or a cache hit.

The ID does not store Chat Completions history for you. Send the messages needed for each turn; use [Responses](/en/docs/developers/model/guides/responses-api) if you want to continue by response ID.

## 1. Prepare your terminal [#1-prepare-your-terminal]

Use Python 3.10+ and [uv](https://docs.astral.sh/uv/getting-started/installation/). Put your [Praxis cloud API Key](/en/docs/developers/account/api-keys) in `PRAXIS_API_KEY`; do not put it in source code or logs.

```bash
test -n "$PRAXIS_API_KEY"
```

Expected: exit code 0. Exit code 1 means the variable is missing. Examples default to `https://api.deepraxis.com`. To switch environments, set `PRAXIS_API_BASE` to the API origin without a trailing slash or API path.

## 2. Reuse an ID across two turns [#2-reuse-an-id-across-two-turns]

This example generates a UUID and sends it on both requests. It also includes the first exchange in the second request.

Save as `session-affinity.py` and run `uv run -q --no-project --with openai --with anthropic --with google-genai python session-affinity.py`.

```python
import os
import uuid
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
session_id = str(uuid.uuid4())
messages = [{'role': 'user', 'content': 'Remember this code: LIME-742. Reply OK.'}]
for turn in range(2):
    response = client.chat.completions.create(model='deepseek-flash',
        messages=messages, reasoning_effort='none', max_tokens=256,
        extra_headers={'x-session-id': session_id})
    print('X-Request-Id:', response._request_id)
    assert response.choices[0].finish_reason == 'stop'
    answer = response.choices[0].message.content or ''
    assert answer
    print('turn', turn + 1, answer)
    if turn == 0:
        messages.append({'role': 'assistant', 'content': answer})
        messages.append({'role': 'user', 'content': 'What was the code? Reply with only the code.'})
    else:
        assert 'LIME-742' in answer, answer
```

Expected: two completed responses, two request IDs, and `LIME-742` in the second answer. Exit code 0. This verifies the requests and conversation history; it does not prove a particular provider or cache hit. Verify cache usage separately with the [prompt cache guide](/en/docs/developers/model/guides/prompt-cache).

## Observed failure and recovery [#observed-failure-and-recovery]

An empty body `session_id` returned HTTP 400: `session_id must not be empty`. Use a nonempty conversation ID. When the body also contains `session_id`, it takes precedence over the header; avoid keeping conflicting values in two places. Do not reuse one global ID for unrelated conversations.

Field details: [Chat Completions](/en/docs/api/compatible/openai/createChatCompletion).

[API reference →](/en/docs/api/compatible/openai) · [Cookbook →](/en/docs/cookbook)
