Skip to navigation

Long Responses

Run text responses past the 15 minute HTTP limit over a WebSocket connection

An HTTP request to the API can run for up to 15 minutes. For a response that takes longer, connect to /v1/responses over a WebSocket and send the same request. This works with every text model, and it uses the WebSocket mode of the OpenAI Responses API, so the OpenAI SDKs connect to it directly.

Nothing is stored. The response reaches you as it is written, like a stream, so stay connected until it ends.

Endpoint

WEBSOCKET /v1/responses

wss://api.empiriolabs.ai/v1/responses

Authenticate with your API key in the Authorization header of the WebSocket handshake:

Authorization: Bearer $EMPIRIOLABS_API_KEY

If the connection cannot start, the handshake is refused with the same HTTP status and JSON error body as the REST API.

Send a request

Each request is a response.create message: the POST /v1/responses body, with type added and without stream. Events come back as one JSON message each, the same events a streaming POST /v1/responses sends.

With the OpenAI Python SDK (pip install "openai[realtime]"):

import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["EMPIRIOLABS_API_KEY"],
base_url="https://api.empiriolabs.ai/v1",
)
DONE = ("response.completed", "response.incomplete", "response.failed", "error")
with client.responses.connect() as connection:
connection.response.create(
model="kimi-k3-1-pro",
input="Write a full specification for a task scheduler.",
max_output_tokens=100000,
)
for event in connection:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type in DONE:
break

Without an SDK:

import asyncio
import json
import os
import websockets
URL = "wss://api.empiriolabs.ai/v1/responses"
DONE = ("response.completed", "response.incomplete", "response.failed", "error")
async def main():
headers = {"Authorization": f"Bearer {os.environ['EMPIRIOLABS_API_KEY']}"}
async with websockets.connect(URL, additional_headers=headers) as ws:
await ws.send(json.dumps({
"type": "response.create",
"model": "kimi-k3-1-pro",
"input": "Write a full specification for a task scheduler.",
"max_output_tokens": 100000,
}))
async for message in ws:
event = json.loads(message)
if event["type"] == "response.output_text.delta":
print(event["delta"], end="", flush=True)
elif event["type"] in DONE:
break
asyncio.run(main())

A request that cannot run is answered with an error event, and the connection stays open for your next request:

{
"type": "error",
"status": 404,
"code": "model_not_found",
"message": "Model 'no-such-model' is not available. Call GET /v1/models for the current catalog.",
"param": "model",
"sequence_number": 0,
"error": {
"type": "invalid_request_error",
"code": "model_not_found",
"message": "Model 'no-such-model' is not available. Call GET /v1/models for the current catalog.",
"param": "model"
}
}

Limits and behavior

ItemLimit
One responseRuns for up to 60 minutes.
One connectionAccepts new requests for 60 minutes. A response that is running finishes, then open a new connection.
At once16 responses on a connection.
Named lanes32 stream_id values on a connection. Requests on one lane run in order, different lanes run together, and every event of a named lane carries its stream_id.
Prompts and outputThe same limits as POST /v1/responses for that model. Use a high max_output_tokens for a long response.

Requests are the same as POST /v1/responses, with these differences:

  • Send the full conversation in input with every request. Responses are not stored, so previous_response_id returns previous_response_not_found, and generate: false returns 400.
  • background: true is not available, here or over HTTP.
  • If the connection drops, the rest of the response does not reach you, and the request bills under the cancellation policy. Send the request again to get it.

Billing

Each response is billed as its own request, at the same rates as POST /v1/responses, and appears on its own line in your usage. Opening a connection costs nothing.

At the 15 minute HTTP limit

A request over HTTP that reaches 15 minutes ends with a timeout error that names this page’s endpoint, on every text endpoint:

  • A stream sends what was written so far, then the error event of its protocol.
  • A request without stream returns the error in its JSON body.

The request bills under the cancellation policy. Send it over the WebSocket to run it to the end.