openresponses-client
Async Python client for Open Responses, the open, multi-provider API spec based on the Responses API. Built on aiohttp.
- Works with any server that implements the spec
- Typed dataclasses (mashumaro) for all items and 24 streaming events
- HTTP streaming, WebSocket mode and compaction
- Keeps provider extensions instead of failing on them
- Python 3.14+, targets spec release
2026-04-24
Quick start | Streaming | Tool calling | WebSocket | Errors | Configuration
Install
pip install openresponses-client
Quick start
import asyncio
from openresponses_client import OpenResponsesClient
async def main() -> None:
client = OpenResponsesClient("http://localhost:8080/v1", api_key="sk-...")
async with client:
response = await client.create(model="gpt-oss:20b", input="Hi!")
print(response.output_text)
asyncio.run(main())
base_urlincludes the version prefix. Requests go to{base_url}/responses.api_keyis sent as a Bearer token. Leave it out for local servers.- Every request field of the spec is a keyword argument, for example
toolsorreasoning. - Pass
session=to reuse your ownaiohttp.ClientSession.
Input
Input is a string or a list of items. Items are plain dicts or output items of a response.
response = await client.create(
model="gpt-oss:20b",
input=[
{"role": "developer", "content": "You are a pirate."},
{
"role": "user",
"content": [
{"type": "input_text", "text": "What do you see?"},
{"type": "input_image", "image_url": "data:image/png;base64,..."},
],
},
],
)
- Messages without
typeget"type": "message"added. - Files use
{"type": "input_file", "filename": "a.pdf", "file_data": "data:..."}. - All item shapes are typed as
TypedDicts inopenresponses_client.params. - Models in
inputare checked when sent: a non-string in astrfield raisesTypeError.
Streaming
from openresponses_client.models import ResponseOutputTextDeltaEvent
async with client.stream(model="gpt-oss:20b", input="Write a haiku.") as stream:
async for event in stream:
match event:
case ResponseOutputTextDeltaEvent(delta=delta):
print(delta, end="", flush=True)
response = await stream.get_final_response()
| Member | Gives you |
|---|---|
await get_final_response() |
The final response, after consuming the stream |
text_deltas() |
An async iterator over the text deltas |
snapshot |
The response rebuilt from the events so far |
final_response |
The final response, once a terminal event arrived |
error_event |
The last error event, if any |
- Failed and incomplete responses are returned, not raised. Check
response.status. get_final_response()raisesResponseStreamErrorif no final response arrives.ResponseAccumulatorrebuilds a response from events you received elsewhere.
Tool calling
Run the function calls of a response and send the results back:
import json
tools = [
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
]
history = [{"role": "user", "content": "What's the weather in Paris?"}]
while True:
response = await client.create(
model="gpt-oss:20b", input=history, tools=tools, store=False
)
history.extend(response.output)
if not response.function_calls:
break
for call in response.function_calls:
result = get_weather(**call.parse_arguments())
history.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
}
)
print(response.output_text)
- Output items go back as input unchanged, including reasoning and provider items.
- Servers that store responses can continue with
previous_response_idinstead. tool_choicetakes"auto","required","none", a function,allowed_toolsor a hosted tool such as{"type": "image_generation"}.
WebSocket mode
One connection for many turns, with the same events as HTTP streaming:
async with client.websocket() as ws:
first = await ws.create(model="gpt-oss:20b", store=False, input="Remember: cobalt.")
second = await ws.create(
model="gpt-oss:20b",
store=False,
previous_response_id=first.id,
input="What was the code word?",
)
ws.stream()streams a turn, likeclient.stream().- One turn runs at a time. Concurrent calls are queued. Open more connections for parallel turns.
stream,stream_optionsandbackgroundare not accepted.- A closed connection reopens for the next turn. A turn the server never started is resent once.
- Options:
heartbeat,receive_timeout,max_msg_size,extra_headers,auto_reconnect.
from openresponses_client import PreviousResponseNotFoundError
try:
response = await ws.create(
model="gpt-oss:20b",
store=False,
previous_response_id=previous.id,
input=[message],
)
except PreviousResponseNotFoundError:
response = await ws.create(
model="gpt-oss:20b", store=False, input=[*history, message]
)
Compaction
Shrink a long conversation, then start a new chain from the result:
compacted = await client.compact(model="gpt-oss:20b", input=history)
response = await client.create(
model="gpt-oss:20b",
input=[*compacted.output, {"role": "user", "content": "Continue."}],
)
Errors
All exceptions derive from OpenResponsesError.
| Exception | Raised when |
|---|---|
APIStatusError |
The server returned an error (base class) |
BadRequestError |
Status 400 |
AuthenticationError |
Status 401 |
PermissionDeniedError |
Status 403 |
NotFoundError |
Status 404 |
ConflictError |
Status 409 |
UnprocessableEntityError |
Status 422 |
RateLimitError |
Status 429 |
InternalServerError |
Status 5xx |
PreviousResponseNotFoundError |
Code previous_response_not_found |
WebSocketConnectionLimitReachedError |
Code websocket_connection_limit_reached |
APIConnectionError |
The server is unreachable or disconnected |
APITimeoutError |
A request or stream timed out |
WebSocketClosedError |
The WebSocket closed during a turn |
APIResponseValidationError |
The server sent invalid data |
ResponseStreamError |
A stream ended without a final response |
from openresponses_client import APIStatusError, RateLimitError
try:
response = await client.create(model="gpt-oss:20b", input="Hi")
except RateLimitError:
...
except APIStatusError as err:
print(err.status, err.code, err.message)
APIStatusErrorhasstatus,code,type,param,message,bodyandheaders.APITimeoutErrorandWebSocketClosedErrorare subclasses ofAPIConnectionError.- Error messages strip query strings and credentials from URLs, so they are safe to log.
Extensions
Providers can add their own items, tools, events and fields. Nothing is lost:
- Unknown types become
UnknownItem,UnknownContent,UnknownToolorUnknownEvent. - Their fields are attributes, also for type checkers, for example
item.code. - Extra fields of known types are in
extra. to_dict()returns the data unchanged, ready to send back.- OpenAI style
errorevents andresponse.reasoning_text.*events are mapped to the spec.
Send your own request fields and headers with extra_body and extra_headers:
response = await client.create(
model="my-model",
input="Find documents about climate change.",
tools=[{"type": "acme:document_search", "index": "papers"}],
extra_body={"acme_priority": "high"},
)
Configuration
| Argument | Default | Description |
|---|---|---|
base_url |
required | API URL including the version prefix, such as .../v1 |
api_key |
None |
Sent as Authorization: Bearer <api_key> |
session |
None |
Your own aiohttp.ClientSession |
headers |
None |
Headers for every request |
timeout |
600 |
Idle timeout in seconds, an aiohttp.ClientTimeout or None |
max_retries |
2 |
Retries for connection errors and status 408, 409, 429, 5xx |
websocket_url |
derived | ws:// or wss:// version of {base_url}/responses |
create(),stream()andcompact()also taketimeout,extra_headersandextra_body.- Retries respect the
Retry-Afterheader.
Examples
Runnable scripts in examples: basic request, streaming, tool loop and WebSocket.
Set OPENRESPONSES_BASE_URL, OPENRESPONSES_API_KEY and OPENRESPONSES_MODEL to run them.
Development
uv sync
uv run ruff check . && uv run ruff format --check .
uv run mypy
uv run pytest --cov
Tests use an in-process server and validate every request against the official OpenAPI
document in tests/fixtures.
Metadata
Release files for openresponses-client 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| openresponses_client-0.0.2.tar.gz | 82.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openresponses_client-0.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 122.5 kB
Release files / openresponses_client-0.0.2.tar.gz
| Download URL | openresponses_client-0.0.2.tar.gz |
|---|---|
| Size | 82.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b1f0ce0b9c686867855a85d2f3d51651a26f4bce89f32c2a73515a3e89ed914
|
|
BLAKE2b-256 checksum How to use checksums |
fc958b1c664f7ad2436cb638c5e7059c9aeda98f83cd53a86691ba5951b6c794
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / openresponses_client-0.0.2-py3-none-any.whl
| Download URL | openresponses_client-0.0.2-py3-none-any.whl |
|---|---|
| Size | 40.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d9858a2b3a86caa59c95b166c8e879a5647952ebf92077531f9192a5311fdd6c
|
|
BLAKE2b-256 checksum How to use checksums |
c8e1f2de52c7cc0fd44d133fab28e964a0cfafd45566aaefb4dacd3d41d53646
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logSecurity for openresponses-client 0.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for openresponses_client-0.0.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.