Skip to main content

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_url includes the version prefix. Requests go to {base_url}/responses.
  • api_key is sent as a Bearer token. Leave it out for local servers.
  • Every request field of the spec is a keyword argument, for example tools or reasoning.
  • Pass session= to reuse your own aiohttp.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 type get "type": "message" added.
  • Files use {"type": "input_file", "filename": "a.pdf", "file_data": "data:..."}.
  • All item shapes are typed as TypedDicts in openresponses_client.params.
  • Models in input are checked when sent: a non-string in a str field raises TypeError.

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() raises ResponseStreamError if no final response arrives.
  • ResponseAccumulator rebuilds 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_id instead.
  • tool_choice takes "auto", "required", "none", a function, allowed_tools or 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, like client.stream().
  • One turn runs at a time. Concurrent calls are queued. Open more connections for parallel turns.
  • stream, stream_options and background are 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)
  • APIStatusError has status, code, type, param, message, body and headers.
  • APITimeoutError and WebSocketClosedError are subclasses of APIConnectionError.
  • 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, UnknownTool or UnknownEvent.
  • 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 error events and response.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() and compact() also take timeout, extra_headers and extra_body.
  • Retries respect the Retry-After header.

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)

Source distribution for openresponses-client 0.0.2
File Size Uploaded Provenance
openresponses_client-0.0.2.tar.gz 82.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for openresponses-client 0.0.2
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page