---
title: "OpenAI-Compatible API"
group: "Reference"
groupOrder: 5
order: 2
---
The STAIR OpenAI-compatible API lets any OpenAI client call a STAIR agent by name over HTTPS, with streaming and multi-turn sessions.

## How the OpenAI-compatible API works in STAIR

STAIR exposes its agents through the same request and response format as the OpenAI Chat Completions API. An existing OpenAI SDK or tool works after you change two things: the base URL and the API key.

| Method | Path | What it returns |
|---|---|---|
| `POST` | `/ee/v1/chat/completions` | A chat completion from the agent, as one JSON object or as a Server-Sent Events (SSE) stream. |
| `GET` | `/ee/v1/models` | The agents you can call, in the OpenAI model list format. |
| `GET` | `/ee/v1/models/{id}` | One agent, where `{id}` is the agent name. |

The base URL for an OpenAI SDK is `https://<your STAIR host>/ee/v1`. The examples on this page use `https://stair.example.com`.

Key rules:

- **`model` is the agent name.** The value is the **Agent Name** exactly as it appears in **Agents**, for example `Demo · Support Triage`. It is not the name of an LLM: the agent's own model, instructions, toolsets, skills and sub-agents are used.
- **Use the plain agent name.** STAIR Chat shows agents with a connection prefix, such as `DEV.Demo · Support Triage`. The prefix belongs to STAIR Chat only; do not send it in `model`.
- **Access follows the ACL.** `GET /ee/v1/models` lists only the agents your key or SSO session may use, and a request to any other agent fails with `403` or `404`.
- **Only chat completions and models.** There are no `/responses` or `/files` endpoints. Files and images travel inline in the message as content parts.

## How to authenticate API requests

Every request needs one of these headers:

| Header | Value |
|---|---|
| `Authorization` | `Bearer aip_sk_…` (a personal API key or a service account key), or `Bearer <SSO access token>` |
| `STAIR-API-KEY` | `aip_sk_…` |

A personal API key acts with the roles and permissions of the user who created it. A service account key acts as the service account, which needs **Execute Access** on the agent. See [API Keys and Service Accounts](/docs/api-keys-and-service-accounts/llm.md) for how to create both.

## How to send a chat completion request

Send a `POST` request with the agent name in `model` and the conversation in `messages`:

```bash
curl https://stair.example.com/ee/v1/chat/completions \
  -H "Authorization: Bearer $STAIR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Session-Id: helpdesk-ticket-4711" \
  -d '{
    "model": "Demo · Support Triage",
    "messages": [
      {"role": "user", "content": "I was charged twice for my Brightpath Cloud subscription this month."}
    ]
  }'
```

The response is a standard `chat.completion` object. The answer is in `choices[0].message.content`, and token counts are in `usage`.

With the OpenAI Python SDK, set `base_url` and `api_key`:

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://stair.example.com/ee/v1",
    api_key="aip_sk_...",  # sent as Authorization: Bearer
)

response = client.chat.completions.create(
    model="Demo · Support Triage",
    messages=[{"role": "user", "content": "How long is a password reset link valid?"}],
    extra_headers={"X-Session-Id": "helpdesk-ticket-4711"},
)
print(response.choices[0].message.content)
```

To list the agents you can call:

```bash
curl https://stair.example.com/ee/v1/models \
  -H "Authorization: Bearer $STAIR_API_KEY"
```

Each entry has `id` (the agent name, to use as `model`), `description` and, when the agent's model has a context window set, `context_length`.

## How to stream a response

Set `"stream": true` to receive the answer as Server-Sent Events. Each event is a `chat.completion.chunk` in the OpenAI format, and the stream ends with `data: [DONE]`. Add `"stream_options": {"include_usage": true}` to receive one more chunk with the total token usage before `[DONE]`.

```bash
curl -N https://stair.example.com/ee/v1/chat/completions \
  -H "STAIR-API-KEY: $STAIR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Demo · Finance Analyst",
    "messages": [{"role": "user", "content": "What is 5% annual interest on 12,000 EUR over 3 years?"}],
    "stream": true,
    "stream_options": {"include_usage": true}
  }'
```

```python
stream = client.chat.completions.create(
    model="Demo · Finance Analyst",
    messages=[{"role": "user", "content": "What is 5% annual interest on 12,000 EUR over 3 years?"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print("\nTokens:", chunk.usage.total_tokens)
```

## How to continue a conversation with X-Session-Id

Send the same `X-Session-Id` header value with every request of one conversation. Without the header, each request opens a new session. The response carries the session id in its `X-Session-Id` header, so a client that sent no header can read the generated id and reuse it.

As with any OpenAI-compatible API, send the previous turns in `messages` on every request. A session holds one request at a time: a second request with the same `X-Session-Id` while the first is still running is rejected.

Use one session id per conversation, for example a ticket number or a UUID. Do not share one id between unrelated users or conversations.

## How to send files and images

Files and images go inline in `messages`, as content parts of a user message:

```json
{
  "model": "Demo · Support Triage",
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "The customer attached this screenshot of the error."},
      {"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KGgo..."}}
    ]
  }]
}
```

- **Images:** a `image_url` part with a `data:` URI (Base64) or an `https` URL.
- **Documents:** a `file` part with `file_data` (Base64) and `filename`. Whether the agent can read the document depends on the agent's model.

There is no upload endpoint and no file id to reference later. Send the file again in a later request if the agent needs it.

## Chat completions request reference

| Field | Required | Description |
|---|---|---|
| `model` | Yes | The agent name, exactly as in **Agents**. |
| `messages` | Yes | The conversation, at least one message. Roles `system`, `user`, `assistant`; `content` is a string or a list of content parts (`text`, `image_url`, `file`). |
| `stream` | No | `true` returns an SSE stream. Default `false`. |
| `stream_options.include_usage` | No | With `stream: true`, adds a final chunk with token usage before `[DONE]`. |
| `temperature` | No | Accepted for OpenAI compatibility (between 0 and 2). Generation is controlled by the agent's model settings in STAIR. |
| `max_tokens` | No | Accepted for OpenAI compatibility (greater than 0). Generation is controlled by the agent's model settings in STAIR. |
| `top_p` | No | Accepted for OpenAI compatibility (between 0 and 1). Generation is controlled by the agent's model settings in STAIR. |

| Header | Required | Description |
|---|---|---|
| `Authorization` or `STAIR-API-KEY` | Yes | The API key or SSO token, see [How to authenticate API requests](#how-to-authenticate-api-requests). |
| `X-Session-Id` | No | The conversation's session id. Without it, every request opens a new session. |
| `Content-Type` | Yes, for `POST` | `application/json`. |

## Troubleshooting the OpenAI-compatible API

### 401 "Invalid access token"

The key is missing, mistyped, revoked or expired, or the `Authorization` header is not in the `Bearer <key>` format. Create a new key in **Connections → Tokens** or in **Service Accounts** and send it in `Authorization: Bearer` or `STAIR-API-KEY`.

### 403 or 404 "Model '…' not found or access denied"

The `model` value does not match an agent you may use, or the key's holder has no access to it. Check the spelling, including spaces and the `·` character, and remove a STAIR Chat prefix such as `DEV.`. Call `GET /ee/v1/models` to see the exact names. For a service account, ask an administrator to grant **Execute Access** on the agent.

### Request rejected because the session is busy

A request with the same `X-Session-Id` is still running. Wait for the first answer, or use a different session id for parallel requests.

### Every answer forgets the previous turn

The previous turns were not sent. Include the earlier user and assistant messages in `messages`, and send the same `X-Session-Id` on each request.

## FAQ

### What do I put in the model field?

Put the agent name exactly as it appears in **Agents**, for example `Demo · Support Triage`. STAIR runs that agent with its own model, instructions and tools.

### Can I use the official OpenAI SDKs?

Yes. Set the base URL to `https://<your STAIR host>/ee/v1` and the API key to an `aip_sk_` key. Use the chat completions methods; the Responses and Files APIs are not available.

### Does the API support streaming?

Yes. Set `stream: true` to receive Server-Sent Events in the OpenAI chunk format, and `stream_options.include_usage: true` to get token usage at the end of the stream.

### How do I keep a multi-turn conversation together?

Send the same `X-Session-Id` header with every request and include the previous turns in `messages`. Without the header, each request starts a new session.

### How do I send a file to an agent?

Put it inline in the user message as a content part: `image_url` with a Base64 `data:` URI for images, or `file` with Base64 `file_data` for documents. There is no separate upload endpoint.

## Related

- [API Keys and Service Accounts](/docs/api-keys-and-service-accounts/llm.md)
- [AI Agents](/docs/ai-agents/llm.md)
- [MCP Gateways](/docs/mcp-gateways/llm.md)
- [Security and Access Control](/docs/security-and-access-control/llm.md)
- [STAIR Chat](/docs/stair-chat/llm.md)
