# OpenAI

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/http-api/openai/
> Summary: The openai endpoints of the Tracon HTTP API — 6 operations.

6 operations. `{prefix}` is the route prefix passed to
`MapTracon`; the template uses `/tracon`.

The OpenAPI snapshot cannot express the polymorphic request bodies of the Responses
and Chat Completions adapters. Use the [OpenAI-compatible API guide]
(/guides/openai-api/) for copyable requests, streaming events, state,
and the exact compatibility boundary.

## POST {prefix}/v1/chat/completions

**Operation ID:** `TraconOpenAIChatCompletions`

**Run endpoint compatible with the OpenAI Chat Completions API.**

Stateless: the client carries history. The agent is selected from the 'model' field; if not found, 'metadata.entity_id' is tried. If a registered IRunAuthorizationHandler denies the caller, the response is 403 on BOTH the streaming and the non-streaming path. A tool that needs approval ends the request: this endpoint carries no session, so the approval can never be answered. The run closes as Failed and the answer is 409 with the error type 'approval_required', or, on a stream, an error frame of that type and no [DONE].

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `RunsWrite` API-key scope.

**Request body** (required):

- `application/json` → [`JsonElement`](/http-api/schemas/jsonelement/)

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`ChatCompletion`](/http-api/schemas/chatcompletion/)<br>`text/event-stream` → [`ChatCompletion`](/http-api/schemas/chatcompletion/) | — |
| **400** Bad Request | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **403** Forbidden | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **404** Not Found | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **409** Conflict | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **502** Bad Gateway | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **503** Service Unavailable | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |

## POST {prefix}/v1/conversations

**Operation ID:** `TraconOpenAICreateConversation`

**Generates a new conversation identifier.**

An identifier reservation: the session is born on the first /v1/responses call. The returned identifier is used directly in the 'conversation' field.

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `RunsWrite` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`ConversationResource`](/http-api/schemas/conversationresource/) | — |
| **400** Bad Request | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |

## GET {prefix}/v1/conversations/{conversationId}

**Operation ID:** `TraconOpenAIGetConversation`

**Returns a conversation's metadata.**

A conversation identifier is a reservation, so an id that has never carried a call is still valid and answers 200 with the current time as its creation time. 404 therefore means 'not yours', not 'never used': an identifier owned by another tenant is reported as missing rather than forbidden, so the API does not confirm that it exists.

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `RunsRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `conversationId` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`ConversationResource`](/http-api/schemas/conversationresource/) | — |
| **404** Not Found | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |

## DELETE {prefix}/v1/conversations/{conversationId}

**Operation ID:** `TraconOpenAIDeleteConversation`

**Deletes a conversation and the session underneath it.**

Following the OpenAI shape, the response is 200 with a 'deleted' flag rather than 204: the flag is false when the identifier was valid but no session had been created for it yet, so a client can tell a real deletion from a no-op. An identifier owned by another tenant returns 404.

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `RunsWrite` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `conversationId` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`DeletedResource`](/http-api/schemas/deletedresource/) | — |
| **404** Not Found | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |

## GET {prefix}/v1/conversations/{conversationId}/items

**Operation ID:** `TraconOpenAIListConversationItems`

**Lists a conversation's messages in the OpenAI item format.**

Items come back oldest first, and one stored message can expand into several items — a reply plus its tool calls, for example. '?limit=' trims the list from the end and sets 'has_more' to true, which is computed from the real total before trimming, so a truncated list never looks complete. There is no cursor paging: 'first_id' and 'last_id' describe the returned window only. A conversation with no history returns an empty list, and an identifier owned by another tenant returns 404.

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `RunsRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `conversationId` | path | yes | `string` | — |
| `limit` | query | no | `integer (int32)` | pattern `^-?(?:0\\|[1-9]\d*)$` |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`ItemListResource`](/http-api/schemas/itemlistresource/) | — |
| **404** Not Found | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |

## POST {prefix}/v1/responses

**Operation ID:** `TraconOpenAIResponses`

**Run endpoint compatible with the OpenAI Responses API.**

The agent is selected from the 'model' field; if not found, 'metadata.entity_id' is tried. If 'conversation' is given the session is stored under that identifier; if not, under the generated response identifier, so chaining with 'previous_response_id' works. If a registered IRunAuthorizationHandler denies the caller, the response is 403. When session ownership is turned on, a 'conversation' or 'previous_response_id' that belongs to another user is refused the same way, and so is opening a NEW conversation when no authenticated identity can be resolved to own it.

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `RunsWrite` API-key scope.

**Request body** (required):

- `application/json` → [`JsonElement`](/http-api/schemas/jsonelement/)

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`JsonElement`](/http-api/schemas/jsonelement/)<br>`text/event-stream` → [`JsonElement`](/http-api/schemas/jsonelement/) | — |
| **400** Bad Request | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **403** Forbidden | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **404** Not Found | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **502** Bad Gateway | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
| **503** Service Unavailable | `application/json` → [`OpenAIErrorEnvelope`](/http-api/schemas/openaierrorenvelope/) | — |
