From 7f7925b4074ecbf879714698000e10fa0519d51a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 2 Oct 2025 20:13:26 +0000
Subject: [PATCH 1/2] feat(api): add support for realtime calls
---
.stats.yml | 8 +-
api.md | 10 +
src/openai/lib/_realtime.py | 92 +++
src/openai/resources/realtime/__init__.py | 14 +
src/openai/resources/realtime/calls.py | 734 ++++++++++++++++++
src/openai/resources/realtime/realtime.py | 70 +-
src/openai/types/realtime/__init__.py | 4 +
.../types/realtime/call_accept_params.py | 107 +++
.../types/realtime/call_create_params.py | 17 +
.../types/realtime/call_refer_params.py | 15 +
.../types/realtime/call_reject_params.py | 15 +
.../types/realtime/realtime_connect_params.py | 6 +-
tests/api_resources/realtime/test_calls.py | 692 +++++++++++++++++
13 files changed, 1770 insertions(+), 14 deletions(-)
create mode 100644 src/openai/lib/_realtime.py
create mode 100644 src/openai/resources/realtime/calls.py
create mode 100644 src/openai/types/realtime/call_accept_params.py
create mode 100644 src/openai/types/realtime/call_create_params.py
create mode 100644 src/openai/types/realtime/call_refer_params.py
create mode 100644 src/openai/types/realtime/call_reject_params.py
create mode 100644 tests/api_resources/realtime/test_calls.py
diff --git a/.stats.yml b/.stats.yml
index 27f2ffc6db..d974760a99 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 118
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-e205b1f2da6a1f2caa229efa9ede63f2d3d2fedeeb2dd6ed3d880bafdcb0ab88.yml
-openapi_spec_hash: c8aee2469a749f6a838b40c57e4b7b06
-config_hash: 45dcba51451ba532959c020a0ddbf23c
+configured_endpoints: 123
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-fadefdc7c7e30df47c09df323669b242ff90ee08e51f304175ace5274e0aab49.yml
+openapi_spec_hash: 6d20f639d9ff8a097a34962da6218231
+config_hash: 902654e60f5d659f2bfcfd903e17c46d
diff --git a/api.md b/api.md
index 1c80bd6e5f..d5331803ce 100644
--- a/api.md
+++ b/api.md
@@ -989,6 +989,16 @@ Methods:
- client.realtime.client_secrets.create(\*\*params) -> ClientSecretCreateResponse
+## Calls
+
+Methods:
+
+- client.realtime.calls.create(\*\*params) -> HttpxBinaryResponseContent
+- client.realtime.calls.accept(call_id, \*\*params) -> None
+- client.realtime.calls.hangup(call_id) -> None
+- client.realtime.calls.refer(call_id, \*\*params) -> None
+- client.realtime.calls.reject(call_id, \*\*params) -> None
+
# Conversations
Types:
diff --git a/src/openai/lib/_realtime.py b/src/openai/lib/_realtime.py
new file mode 100644
index 0000000000..999d1e4463
--- /dev/null
+++ b/src/openai/lib/_realtime.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+import json
+from typing_extensions import override
+
+import httpx
+
+from openai import _legacy_response
+from openai._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from openai._utils import maybe_transform, async_maybe_transform
+from openai._base_client import make_request_options
+from openai.resources.realtime.calls import Calls, AsyncCalls
+from openai.types.realtime.realtime_session_create_request_param import RealtimeSessionCreateRequestParam
+
+__all__ = ["_Calls", "_AsyncCalls"]
+
+
+# Custom code to override the `create` method to have correct behavior with
+# application/sdp and multipart/form-data.
+# Ideally we can cutover to the generated code this overrides eventually and remove this.
+class _Calls(Calls):
+ @override
+ def create(
+ self,
+ *,
+ sdp: str,
+ session: RealtimeSessionCreateRequestParam | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> _legacy_response.HttpxBinaryResponseContent:
+ if session is omit:
+ extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})}
+ return self._post(
+ "/realtime/calls",
+ body=sdp.encode("utf-8"),
+ options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, timeout=timeout),
+ cast_to=_legacy_response.HttpxBinaryResponseContent,
+ )
+
+ extra_headers = {"Accept": "application/sdp", "Content-Type": "multipart/form-data", **(extra_headers or {})}
+ session_payload = maybe_transform(session, RealtimeSessionCreateRequestParam)
+ files = [
+ ("sdp", (None, sdp.encode("utf-8"), "application/sdp")),
+ ("session", (None, json.dumps(session_payload).encode("utf-8"), "application/json")),
+ ]
+ return self._post(
+ "/realtime/calls",
+ files=files,
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=_legacy_response.HttpxBinaryResponseContent,
+ )
+
+
+class _AsyncCalls(AsyncCalls):
+ @override
+ async def create(
+ self,
+ *,
+ sdp: str,
+ session: RealtimeSessionCreateRequestParam | Omit = omit,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> _legacy_response.HttpxBinaryResponseContent:
+ if session is omit:
+ extra_headers = {"Accept": "application/sdp", "Content-Type": "application/sdp", **(extra_headers or {})}
+ return await self._post(
+ "/realtime/calls",
+ body=sdp.encode("utf-8"),
+ options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, timeout=timeout),
+ cast_to=_legacy_response.HttpxBinaryResponseContent,
+ )
+
+ extra_headers = {"Accept": "application/sdp", "Content-Type": "multipart/form-data", **(extra_headers or {})}
+ session_payload = await async_maybe_transform(session, RealtimeSessionCreateRequestParam)
+ files = [
+ ("sdp", (None, sdp.encode("utf-8"), "application/sdp")),
+ ("session", (None, json.dumps(session_payload).encode("utf-8"), "application/json")),
+ ]
+ return await self._post(
+ "/realtime/calls",
+ files=files,
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=_legacy_response.HttpxBinaryResponseContent,
+ )
diff --git a/src/openai/resources/realtime/__init__.py b/src/openai/resources/realtime/__init__.py
index 7a41de8648..c11841017f 100644
--- a/src/openai/resources/realtime/__init__.py
+++ b/src/openai/resources/realtime/__init__.py
@@ -1,5 +1,13 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from .calls import (
+ Calls,
+ AsyncCalls,
+ CallsWithRawResponse,
+ AsyncCallsWithRawResponse,
+ CallsWithStreamingResponse,
+ AsyncCallsWithStreamingResponse,
+)
from .realtime import (
Realtime,
AsyncRealtime,
@@ -24,6 +32,12 @@
"AsyncClientSecretsWithRawResponse",
"ClientSecretsWithStreamingResponse",
"AsyncClientSecretsWithStreamingResponse",
+ "Calls",
+ "AsyncCalls",
+ "CallsWithRawResponse",
+ "AsyncCallsWithRawResponse",
+ "CallsWithStreamingResponse",
+ "AsyncCallsWithStreamingResponse",
"Realtime",
"AsyncRealtime",
"RealtimeWithRawResponse",
diff --git a/src/openai/resources/realtime/calls.py b/src/openai/resources/realtime/calls.py
new file mode 100644
index 0000000000..7dcea6b5cf
--- /dev/null
+++ b/src/openai/resources/realtime/calls.py
@@ -0,0 +1,734 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ... import _legacy_response
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ StreamedBinaryAPIResponse,
+ AsyncStreamedBinaryAPIResponse,
+ to_streamed_response_wrapper,
+ async_to_streamed_response_wrapper,
+ to_custom_streamed_response_wrapper,
+ async_to_custom_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.realtime import (
+ call_refer_params,
+ call_accept_params,
+ call_create_params,
+ call_reject_params,
+)
+from ...types.responses.response_prompt_param import ResponsePromptParam
+from ...types.realtime.realtime_truncation_param import RealtimeTruncationParam
+from ...types.realtime.realtime_audio_config_param import RealtimeAudioConfigParam
+from ...types.realtime.realtime_tools_config_param import RealtimeToolsConfigParam
+from ...types.realtime.realtime_tracing_config_param import RealtimeTracingConfigParam
+from ...types.realtime.realtime_tool_choice_config_param import RealtimeToolChoiceConfigParam
+from ...types.realtime.realtime_session_create_request_param import RealtimeSessionCreateRequestParam
+
+__all__ = ["Calls", "AsyncCalls"]
+
+
+class Calls(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> CallsWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
+ """
+ return CallsWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CallsWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/openai/openai-python#with_streaming_response
+ """
+ return CallsWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ sdp: str,
+ session: RealtimeSessionCreateRequestParam | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> _legacy_response.HttpxBinaryResponseContent:
+ """
+ Create a new Realtime API call over WebRTC and receive the SDP answer needed to
+ complete the peer connection.
+
+ Args:
+ sdp: WebRTC Session Description Protocol (SDP) offer generated by the caller.
+
+ session: Realtime session object configuration.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "application/sdp", **(extra_headers or {})}
+ return self._post(
+ "/realtime/calls",
+ body=maybe_transform(
+ {
+ "sdp": sdp,
+ "session": session,
+ },
+ call_create_params.CallCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=_legacy_response.HttpxBinaryResponseContent,
+ )
+
+ def accept(
+ self,
+ call_id: str,
+ *,
+ type: Literal["realtime"],
+ audio: RealtimeAudioConfigParam | Omit = omit,
+ include: List[Literal["item.input_audio_transcription.logprobs"]] | Omit = omit,
+ instructions: str | Omit = omit,
+ max_output_tokens: Union[int, Literal["inf"]] | Omit = omit,
+ model: Union[
+ str,
+ Literal[
+ "gpt-realtime",
+ "gpt-realtime-2025-08-28",
+ "gpt-4o-realtime-preview",
+ "gpt-4o-realtime-preview-2024-10-01",
+ "gpt-4o-realtime-preview-2024-12-17",
+ "gpt-4o-realtime-preview-2025-06-03",
+ "gpt-4o-mini-realtime-preview",
+ "gpt-4o-mini-realtime-preview-2024-12-17",
+ ],
+ ]
+ | Omit = omit,
+ output_modalities: List[Literal["text", "audio"]] | Omit = omit,
+ prompt: Optional[ResponsePromptParam] | Omit = omit,
+ tool_choice: RealtimeToolChoiceConfigParam | Omit = omit,
+ tools: RealtimeToolsConfigParam | Omit = omit,
+ tracing: Optional[RealtimeTracingConfigParam] | Omit = omit,
+ truncation: RealtimeTruncationParam | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Accept an incoming SIP call and configure the realtime session that will handle
+ it.
+
+ Args:
+ type: The type of session to create. Always `realtime` for the Realtime API.
+
+ audio: Configuration for input and output audio.
+
+ include: Additional fields to include in server outputs.
+
+ `item.input_audio_transcription.logprobs`: Include logprobs for input audio
+ transcription.
+
+ instructions: The default system instructions (i.e. system message) prepended to model calls.
+ This field allows the client to guide the model on desired responses. The model
+ can be instructed on response content and format, (e.g. "be extremely succinct",
+ "act friendly", "here are examples of good responses") and on audio behavior
+ (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The
+ instructions are not guaranteed to be followed by the model, but they provide
+ guidance to the model on the desired behavior.
+
+ Note that the server sets default instructions which will be used if this field
+ is not set and are visible in the `session.created` event at the start of the
+ session.
+
+ max_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of
+ tool calls. Provide an integer between 1 and 4096 to limit output tokens, or
+ `inf` for the maximum available tokens for a given model. Defaults to `inf`.
+
+ model: The Realtime model used for this session.
+
+ output_modalities: The set of modalities the model can respond with. It defaults to `["audio"]`,
+ indicating that the model will respond with audio plus a transcript. `["text"]`
+ can be used to make the model respond with text only. It is not possible to
+ request both `text` and `audio` at the same time.
+
+ prompt: Reference to a prompt template and its variables.
+ [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts).
+
+ tool_choice: How the model chooses tools. Provide one of the string modes or force a specific
+ function/MCP tool.
+
+ tools: Tools available to the model.
+
+ tracing: Realtime API can write session traces to the
+ [Traces Dashboard](/logs?api=traces). Set to null to disable tracing. Once
+ tracing is enabled for a session, the configuration cannot be modified.
+
+ `auto` will create a trace for the session with default values for the workflow
+ name, group id, and metadata.
+
+ truncation: Controls how the realtime conversation is truncated prior to model inference.
+ The default is `auto`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ f"/realtime/calls/{call_id}/accept",
+ body=maybe_transform(
+ {
+ "type": type,
+ "audio": audio,
+ "include": include,
+ "instructions": instructions,
+ "max_output_tokens": max_output_tokens,
+ "model": model,
+ "output_modalities": output_modalities,
+ "prompt": prompt,
+ "tool_choice": tool_choice,
+ "tools": tools,
+ "tracing": tracing,
+ "truncation": truncation,
+ },
+ call_accept_params.CallAcceptParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def hangup(
+ self,
+ call_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ End an active Realtime API call, whether it was initiated over SIP or WebRTC.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ f"/realtime/calls/{call_id}/hangup",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def refer(
+ self,
+ call_id: str,
+ *,
+ target_uri: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Transfer an active SIP call to a new destination using the SIP REFER verb.
+
+ Args:
+ target_uri: URI that should appear in the SIP Refer-To header. Supports values like
+ `tel:+14155550123` or `sip:agent@example.com`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ f"/realtime/calls/{call_id}/refer",
+ body=maybe_transform({"target_uri": target_uri}, call_refer_params.CallReferParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def reject(
+ self,
+ call_id: str,
+ *,
+ status_code: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Decline an incoming SIP call by returning a SIP status code to the caller.
+
+ Args:
+ status_code: SIP response code to send back to the caller. Defaults to `603` (Decline) when
+ omitted.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ f"/realtime/calls/{call_id}/reject",
+ body=maybe_transform({"status_code": status_code}, call_reject_params.CallRejectParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncCalls(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncCallsWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncCallsWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCallsWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/openai/openai-python#with_streaming_response
+ """
+ return AsyncCallsWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ sdp: str,
+ session: RealtimeSessionCreateRequestParam | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> _legacy_response.HttpxBinaryResponseContent:
+ """
+ Create a new Realtime API call over WebRTC and receive the SDP answer needed to
+ complete the peer connection.
+
+ Args:
+ sdp: WebRTC Session Description Protocol (SDP) offer generated by the caller.
+
+ session: Realtime session object configuration.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "application/sdp", **(extra_headers or {})}
+ return await self._post(
+ "/realtime/calls",
+ body=await async_maybe_transform(
+ {
+ "sdp": sdp,
+ "session": session,
+ },
+ call_create_params.CallCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=_legacy_response.HttpxBinaryResponseContent,
+ )
+
+ async def accept(
+ self,
+ call_id: str,
+ *,
+ type: Literal["realtime"],
+ audio: RealtimeAudioConfigParam | Omit = omit,
+ include: List[Literal["item.input_audio_transcription.logprobs"]] | Omit = omit,
+ instructions: str | Omit = omit,
+ max_output_tokens: Union[int, Literal["inf"]] | Omit = omit,
+ model: Union[
+ str,
+ Literal[
+ "gpt-realtime",
+ "gpt-realtime-2025-08-28",
+ "gpt-4o-realtime-preview",
+ "gpt-4o-realtime-preview-2024-10-01",
+ "gpt-4o-realtime-preview-2024-12-17",
+ "gpt-4o-realtime-preview-2025-06-03",
+ "gpt-4o-mini-realtime-preview",
+ "gpt-4o-mini-realtime-preview-2024-12-17",
+ ],
+ ]
+ | Omit = omit,
+ output_modalities: List[Literal["text", "audio"]] | Omit = omit,
+ prompt: Optional[ResponsePromptParam] | Omit = omit,
+ tool_choice: RealtimeToolChoiceConfigParam | Omit = omit,
+ tools: RealtimeToolsConfigParam | Omit = omit,
+ tracing: Optional[RealtimeTracingConfigParam] | Omit = omit,
+ truncation: RealtimeTruncationParam | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Accept an incoming SIP call and configure the realtime session that will handle
+ it.
+
+ Args:
+ type: The type of session to create. Always `realtime` for the Realtime API.
+
+ audio: Configuration for input and output audio.
+
+ include: Additional fields to include in server outputs.
+
+ `item.input_audio_transcription.logprobs`: Include logprobs for input audio
+ transcription.
+
+ instructions: The default system instructions (i.e. system message) prepended to model calls.
+ This field allows the client to guide the model on desired responses. The model
+ can be instructed on response content and format, (e.g. "be extremely succinct",
+ "act friendly", "here are examples of good responses") and on audio behavior
+ (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The
+ instructions are not guaranteed to be followed by the model, but they provide
+ guidance to the model on the desired behavior.
+
+ Note that the server sets default instructions which will be used if this field
+ is not set and are visible in the `session.created` event at the start of the
+ session.
+
+ max_output_tokens: Maximum number of output tokens for a single assistant response, inclusive of
+ tool calls. Provide an integer between 1 and 4096 to limit output tokens, or
+ `inf` for the maximum available tokens for a given model. Defaults to `inf`.
+
+ model: The Realtime model used for this session.
+
+ output_modalities: The set of modalities the model can respond with. It defaults to `["audio"]`,
+ indicating that the model will respond with audio plus a transcript. `["text"]`
+ can be used to make the model respond with text only. It is not possible to
+ request both `text` and `audio` at the same time.
+
+ prompt: Reference to a prompt template and its variables.
+ [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts).
+
+ tool_choice: How the model chooses tools. Provide one of the string modes or force a specific
+ function/MCP tool.
+
+ tools: Tools available to the model.
+
+ tracing: Realtime API can write session traces to the
+ [Traces Dashboard](/logs?api=traces). Set to null to disable tracing. Once
+ tracing is enabled for a session, the configuration cannot be modified.
+
+ `auto` will create a trace for the session with default values for the workflow
+ name, group id, and metadata.
+
+ truncation: Controls how the realtime conversation is truncated prior to model inference.
+ The default is `auto`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ f"/realtime/calls/{call_id}/accept",
+ body=await async_maybe_transform(
+ {
+ "type": type,
+ "audio": audio,
+ "include": include,
+ "instructions": instructions,
+ "max_output_tokens": max_output_tokens,
+ "model": model,
+ "output_modalities": output_modalities,
+ "prompt": prompt,
+ "tool_choice": tool_choice,
+ "tools": tools,
+ "tracing": tracing,
+ "truncation": truncation,
+ },
+ call_accept_params.CallAcceptParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def hangup(
+ self,
+ call_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ End an active Realtime API call, whether it was initiated over SIP or WebRTC.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ f"/realtime/calls/{call_id}/hangup",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def refer(
+ self,
+ call_id: str,
+ *,
+ target_uri: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Transfer an active SIP call to a new destination using the SIP REFER verb.
+
+ Args:
+ target_uri: URI that should appear in the SIP Refer-To header. Supports values like
+ `tel:+14155550123` or `sip:agent@example.com`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ f"/realtime/calls/{call_id}/refer",
+ body=await async_maybe_transform({"target_uri": target_uri}, call_refer_params.CallReferParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def reject(
+ self,
+ call_id: str,
+ *,
+ status_code: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Decline an incoming SIP call by returning a SIP status code to the caller.
+
+ Args:
+ status_code: SIP response code to send back to the caller. Defaults to `603` (Decline) when
+ omitted.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not call_id:
+ raise ValueError(f"Expected a non-empty value for `call_id` but received {call_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ f"/realtime/calls/{call_id}/reject",
+ body=await async_maybe_transform({"status_code": status_code}, call_reject_params.CallRejectParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class CallsWithRawResponse:
+ def __init__(self, calls: Calls) -> None:
+ self._calls = calls
+
+ self.create = _legacy_response.to_raw_response_wrapper(
+ calls.create,
+ )
+ self.accept = _legacy_response.to_raw_response_wrapper(
+ calls.accept,
+ )
+ self.hangup = _legacy_response.to_raw_response_wrapper(
+ calls.hangup,
+ )
+ self.refer = _legacy_response.to_raw_response_wrapper(
+ calls.refer,
+ )
+ self.reject = _legacy_response.to_raw_response_wrapper(
+ calls.reject,
+ )
+
+
+class AsyncCallsWithRawResponse:
+ def __init__(self, calls: AsyncCalls) -> None:
+ self._calls = calls
+
+ self.create = _legacy_response.async_to_raw_response_wrapper(
+ calls.create,
+ )
+ self.accept = _legacy_response.async_to_raw_response_wrapper(
+ calls.accept,
+ )
+ self.hangup = _legacy_response.async_to_raw_response_wrapper(
+ calls.hangup,
+ )
+ self.refer = _legacy_response.async_to_raw_response_wrapper(
+ calls.refer,
+ )
+ self.reject = _legacy_response.async_to_raw_response_wrapper(
+ calls.reject,
+ )
+
+
+class CallsWithStreamingResponse:
+ def __init__(self, calls: Calls) -> None:
+ self._calls = calls
+
+ self.create = to_custom_streamed_response_wrapper(
+ calls.create,
+ StreamedBinaryAPIResponse,
+ )
+ self.accept = to_streamed_response_wrapper(
+ calls.accept,
+ )
+ self.hangup = to_streamed_response_wrapper(
+ calls.hangup,
+ )
+ self.refer = to_streamed_response_wrapper(
+ calls.refer,
+ )
+ self.reject = to_streamed_response_wrapper(
+ calls.reject,
+ )
+
+
+class AsyncCallsWithStreamingResponse:
+ def __init__(self, calls: AsyncCalls) -> None:
+ self._calls = calls
+
+ self.create = async_to_custom_streamed_response_wrapper(
+ calls.create,
+ AsyncStreamedBinaryAPIResponse,
+ )
+ self.accept = async_to_streamed_response_wrapper(
+ calls.accept,
+ )
+ self.hangup = async_to_streamed_response_wrapper(
+ calls.hangup,
+ )
+ self.refer = async_to_streamed_response_wrapper(
+ calls.refer,
+ )
+ self.reject = async_to_streamed_response_wrapper(
+ calls.reject,
+ )
diff --git a/src/openai/resources/realtime/realtime.py b/src/openai/resources/realtime/realtime.py
index 9d61fa25e0..7b4486d502 100644
--- a/src/openai/resources/realtime/realtime.py
+++ b/src/openai/resources/realtime/realtime.py
@@ -11,6 +11,14 @@
import httpx
from pydantic import BaseModel
+from .calls import (
+ Calls,
+ AsyncCalls,
+ CallsWithRawResponse,
+ AsyncCallsWithRawResponse,
+ CallsWithStreamingResponse,
+ AsyncCallsWithStreamingResponse,
+)
from ..._types import Omit, Query, Headers, omit
from ..._utils import (
is_azure_client,
@@ -56,6 +64,11 @@ class Realtime(SyncAPIResource):
def client_secrets(self) -> ClientSecrets:
return ClientSecrets(self._client)
+ @cached_property
+ def calls(self) -> Calls:
+ from ...lib._realtime import _Calls
+ return _Calls(self._client)
+
@cached_property
def with_raw_response(self) -> RealtimeWithRawResponse:
"""
@@ -78,7 +91,8 @@ def with_streaming_response(self) -> RealtimeWithStreamingResponse:
def connect(
self,
*,
- model: str,
+ call_id: str | Omit = omit,
+ model: str | Omit = omit,
extra_query: Query = {},
extra_headers: Headers = {},
websocket_connection_options: WebsocketConnectionOptions = {},
@@ -99,6 +113,7 @@ def connect(
extra_query=extra_query,
extra_headers=extra_headers,
websocket_connection_options=websocket_connection_options,
+ call_id=call_id,
model=model,
)
@@ -108,6 +123,11 @@ class AsyncRealtime(AsyncAPIResource):
def client_secrets(self) -> AsyncClientSecrets:
return AsyncClientSecrets(self._client)
+ @cached_property
+ def calls(self) -> AsyncCalls:
+ from ...lib._realtime import _AsyncCalls
+ return _AsyncCalls(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncRealtimeWithRawResponse:
"""
@@ -130,7 +150,8 @@ def with_streaming_response(self) -> AsyncRealtimeWithStreamingResponse:
def connect(
self,
*,
- model: str,
+ call_id: str | Omit = omit,
+ model: str | Omit = omit,
extra_query: Query = {},
extra_headers: Headers = {},
websocket_connection_options: WebsocketConnectionOptions = {},
@@ -151,6 +172,7 @@ def connect(
extra_query=extra_query,
extra_headers=extra_headers,
websocket_connection_options=websocket_connection_options,
+ call_id=call_id,
model=model,
)
@@ -163,6 +185,10 @@ def __init__(self, realtime: Realtime) -> None:
def client_secrets(self) -> ClientSecretsWithRawResponse:
return ClientSecretsWithRawResponse(self._realtime.client_secrets)
+ @cached_property
+ def calls(self) -> CallsWithRawResponse:
+ return CallsWithRawResponse(self._realtime.calls)
+
class AsyncRealtimeWithRawResponse:
def __init__(self, realtime: AsyncRealtime) -> None:
@@ -172,6 +198,10 @@ def __init__(self, realtime: AsyncRealtime) -> None:
def client_secrets(self) -> AsyncClientSecretsWithRawResponse:
return AsyncClientSecretsWithRawResponse(self._realtime.client_secrets)
+ @cached_property
+ def calls(self) -> AsyncCallsWithRawResponse:
+ return AsyncCallsWithRawResponse(self._realtime.calls)
+
class RealtimeWithStreamingResponse:
def __init__(self, realtime: Realtime) -> None:
@@ -181,6 +211,10 @@ def __init__(self, realtime: Realtime) -> None:
def client_secrets(self) -> ClientSecretsWithStreamingResponse:
return ClientSecretsWithStreamingResponse(self._realtime.client_secrets)
+ @cached_property
+ def calls(self) -> CallsWithStreamingResponse:
+ return CallsWithStreamingResponse(self._realtime.calls)
+
class AsyncRealtimeWithStreamingResponse:
def __init__(self, realtime: AsyncRealtime) -> None:
@@ -190,6 +224,10 @@ def __init__(self, realtime: AsyncRealtime) -> None:
def client_secrets(self) -> AsyncClientSecretsWithStreamingResponse:
return AsyncClientSecretsWithStreamingResponse(self._realtime.client_secrets)
+ @cached_property
+ def calls(self) -> AsyncCallsWithStreamingResponse:
+ return AsyncCallsWithStreamingResponse(self._realtime.calls)
+
class AsyncRealtimeConnection:
"""Represents a live websocket connection to the Realtime API"""
@@ -290,12 +328,14 @@ def __init__(
self,
*,
client: AsyncOpenAI,
- model: str,
+ call_id: str | Omit = omit,
+ model: str | Omit = omit,
extra_query: Query,
extra_headers: Headers,
websocket_connection_options: WebsocketConnectionOptions,
) -> None:
self.__client = client
+ self.__call_id = call_id
self.__model = model
self.__connection: AsyncRealtimeConnection | None = None
self.__extra_query = extra_query
@@ -323,13 +363,19 @@ async def __aenter__(self) -> AsyncRealtimeConnection:
extra_query = self.__extra_query
await self.__client._refresh_api_key()
auth_headers = self.__client.auth_headers
+ if self.__call_id is not omit:
+ extra_query = {**extra_query, "call_id": self.__call_id}
if is_async_azure_client(self.__client):
- url, auth_headers = await self.__client._configure_realtime(self.__model, extra_query)
+ model = self.__model
+ if not model:
+ raise OpenAIError("`model` is required for Azure Realtime API")
+ else:
+ url, auth_headers = await self.__client._configure_realtime(model, extra_query)
else:
url = self._prepare_url().copy_with(
params={
**self.__client.base_url.params,
- "model": self.__model,
+ **({"model": self.__model} if self.__model is not omit else {}),
**extra_query,
},
)
@@ -470,12 +516,14 @@ def __init__(
self,
*,
client: OpenAI,
- model: str,
+ call_id: str | Omit = omit,
+ model: str | Omit = omit,
extra_query: Query,
extra_headers: Headers,
websocket_connection_options: WebsocketConnectionOptions,
) -> None:
self.__client = client
+ self.__call_id = call_id
self.__model = model
self.__connection: RealtimeConnection | None = None
self.__extra_query = extra_query
@@ -503,13 +551,19 @@ def __enter__(self) -> RealtimeConnection:
extra_query = self.__extra_query
self.__client._refresh_api_key()
auth_headers = self.__client.auth_headers
+ if self.__call_id is not omit:
+ extra_query = {**extra_query, "call_id": self.__call_id}
if is_azure_client(self.__client):
- url, auth_headers = self.__client._configure_realtime(self.__model, extra_query)
+ model = self.__model
+ if not model:
+ raise OpenAIError("`model` is required for Azure Realtime API")
+ else:
+ url, auth_headers = self.__client._configure_realtime(model, extra_query)
else:
url = self._prepare_url().copy_with(
params={
**self.__client.base_url.params,
- "model": self.__model,
+ **({"model": self.__model} if self.__model is not omit else {}),
**extra_query,
},
)
diff --git a/src/openai/types/realtime/__init__.py b/src/openai/types/realtime/__init__.py
index 2d947c8a2f..83e81a034a 100644
--- a/src/openai/types/realtime/__init__.py
+++ b/src/openai/types/realtime/__init__.py
@@ -3,8 +3,12 @@
from __future__ import annotations
from .realtime_error import RealtimeError as RealtimeError
+from .call_refer_params import CallReferParams as CallReferParams
from .conversation_item import ConversationItem as ConversationItem
from .realtime_response import RealtimeResponse as RealtimeResponse
+from .call_accept_params import CallAcceptParams as CallAcceptParams
+from .call_create_params import CallCreateParams as CallCreateParams
+from .call_reject_params import CallRejectParams as CallRejectParams
from .audio_transcription import AudioTranscription as AudioTranscription
from .log_prob_properties import LogProbProperties as LogProbProperties
from .realtime_truncation import RealtimeTruncation as RealtimeTruncation
diff --git a/src/openai/types/realtime/call_accept_params.py b/src/openai/types/realtime/call_accept_params.py
new file mode 100644
index 0000000000..1780572e89
--- /dev/null
+++ b/src/openai/types/realtime/call_accept_params.py
@@ -0,0 +1,107 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List, Union, Optional
+from typing_extensions import Literal, Required, TypedDict
+
+from .realtime_truncation_param import RealtimeTruncationParam
+from .realtime_audio_config_param import RealtimeAudioConfigParam
+from .realtime_tools_config_param import RealtimeToolsConfigParam
+from .realtime_tracing_config_param import RealtimeTracingConfigParam
+from ..responses.response_prompt_param import ResponsePromptParam
+from .realtime_tool_choice_config_param import RealtimeToolChoiceConfigParam
+
+__all__ = ["CallAcceptParams"]
+
+
+class CallAcceptParams(TypedDict, total=False):
+ type: Required[Literal["realtime"]]
+ """The type of session to create. Always `realtime` for the Realtime API."""
+
+ audio: RealtimeAudioConfigParam
+ """Configuration for input and output audio."""
+
+ include: List[Literal["item.input_audio_transcription.logprobs"]]
+ """Additional fields to include in server outputs.
+
+ `item.input_audio_transcription.logprobs`: Include logprobs for input audio
+ transcription.
+ """
+
+ instructions: str
+ """The default system instructions (i.e.
+
+ system message) prepended to model calls. This field allows the client to guide
+ the model on desired responses. The model can be instructed on response content
+ and format, (e.g. "be extremely succinct", "act friendly", "here are examples of
+ good responses") and on audio behavior (e.g. "talk quickly", "inject emotion
+ into your voice", "laugh frequently"). The instructions are not guaranteed to be
+ followed by the model, but they provide guidance to the model on the desired
+ behavior.
+
+ Note that the server sets default instructions which will be used if this field
+ is not set and are visible in the `session.created` event at the start of the
+ session.
+ """
+
+ max_output_tokens: Union[int, Literal["inf"]]
+ """
+ Maximum number of output tokens for a single assistant response, inclusive of
+ tool calls. Provide an integer between 1 and 4096 to limit output tokens, or
+ `inf` for the maximum available tokens for a given model. Defaults to `inf`.
+ """
+
+ model: Union[
+ str,
+ Literal[
+ "gpt-realtime",
+ "gpt-realtime-2025-08-28",
+ "gpt-4o-realtime-preview",
+ "gpt-4o-realtime-preview-2024-10-01",
+ "gpt-4o-realtime-preview-2024-12-17",
+ "gpt-4o-realtime-preview-2025-06-03",
+ "gpt-4o-mini-realtime-preview",
+ "gpt-4o-mini-realtime-preview-2024-12-17",
+ ],
+ ]
+ """The Realtime model used for this session."""
+
+ output_modalities: List[Literal["text", "audio"]]
+ """The set of modalities the model can respond with.
+
+ It defaults to `["audio"]`, indicating that the model will respond with audio
+ plus a transcript. `["text"]` can be used to make the model respond with text
+ only. It is not possible to request both `text` and `audio` at the same time.
+ """
+
+ prompt: Optional[ResponsePromptParam]
+ """
+ Reference to a prompt template and its variables.
+ [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts).
+ """
+
+ tool_choice: RealtimeToolChoiceConfigParam
+ """How the model chooses tools.
+
+ Provide one of the string modes or force a specific function/MCP tool.
+ """
+
+ tools: RealtimeToolsConfigParam
+ """Tools available to the model."""
+
+ tracing: Optional[RealtimeTracingConfigParam]
+ """
+ Realtime API can write session traces to the
+ [Traces Dashboard](/logs?api=traces). Set to null to disable tracing. Once
+ tracing is enabled for a session, the configuration cannot be modified.
+
+ `auto` will create a trace for the session with default values for the workflow
+ name, group id, and metadata.
+ """
+
+ truncation: RealtimeTruncationParam
+ """
+ Controls how the realtime conversation is truncated prior to model inference.
+ The default is `auto`.
+ """
diff --git a/src/openai/types/realtime/call_create_params.py b/src/openai/types/realtime/call_create_params.py
new file mode 100644
index 0000000000..a378092a66
--- /dev/null
+++ b/src/openai/types/realtime/call_create_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .realtime_session_create_request_param import RealtimeSessionCreateRequestParam
+
+__all__ = ["CallCreateParams"]
+
+
+class CallCreateParams(TypedDict, total=False):
+ sdp: Required[str]
+ """WebRTC Session Description Protocol (SDP) offer generated by the caller."""
+
+ session: RealtimeSessionCreateRequestParam
+ """Realtime session object configuration."""
diff --git a/src/openai/types/realtime/call_refer_params.py b/src/openai/types/realtime/call_refer_params.py
new file mode 100644
index 0000000000..3d8623855b
--- /dev/null
+++ b/src/openai/types/realtime/call_refer_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["CallReferParams"]
+
+
+class CallReferParams(TypedDict, total=False):
+ target_uri: Required[str]
+ """URI that should appear in the SIP Refer-To header.
+
+ Supports values like `tel:+14155550123` or `sip:agent@example.com`.
+ """
diff --git a/src/openai/types/realtime/call_reject_params.py b/src/openai/types/realtime/call_reject_params.py
new file mode 100644
index 0000000000..f12222cded
--- /dev/null
+++ b/src/openai/types/realtime/call_reject_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["CallRejectParams"]
+
+
+class CallRejectParams(TypedDict, total=False):
+ status_code: int
+ """SIP response code to send back to the caller.
+
+ Defaults to `603` (Decline) when omitted.
+ """
diff --git a/src/openai/types/realtime/realtime_connect_params.py b/src/openai/types/realtime/realtime_connect_params.py
index 76474f3de4..950f36212f 100644
--- a/src/openai/types/realtime/realtime_connect_params.py
+++ b/src/openai/types/realtime/realtime_connect_params.py
@@ -2,10 +2,12 @@
from __future__ import annotations
-from typing_extensions import Required, TypedDict
+from typing_extensions import TypedDict
__all__ = ["RealtimeConnectParams"]
class RealtimeConnectParams(TypedDict, total=False):
- model: Required[str]
+ call_id: str
+
+ model: str
diff --git a/tests/api_resources/realtime/test_calls.py b/tests/api_resources/realtime/test_calls.py
new file mode 100644
index 0000000000..5495a58a4e
--- /dev/null
+++ b/tests/api_resources/realtime/test_calls.py
@@ -0,0 +1,692 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import httpx
+import pytest
+from respx import MockRouter
+
+import openai._legacy_response as _legacy_response
+from openai import OpenAI, AsyncOpenAI
+from tests.utils import assert_matches_type
+
+# pyright: reportDeprecated=false
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCalls:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ call = client.realtime.calls.create(
+ sdp="sdp",
+ )
+ assert isinstance(call, _legacy_response.HttpxBinaryResponseContent)
+ assert call.json() == {"foo": "bar"}
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ call = client.realtime.calls.create(
+ sdp="sdp",
+ session={
+ "type": "realtime",
+ "audio": {
+ "input": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "noise_reduction": {"type": "near_field"},
+ "transcription": {
+ "language": "language",
+ "model": "whisper-1",
+ "prompt": "prompt",
+ },
+ "turn_detection": {
+ "type": "server_vad",
+ "create_response": True,
+ "idle_timeout_ms": 5000,
+ "interrupt_response": True,
+ "prefix_padding_ms": 0,
+ "silence_duration_ms": 0,
+ "threshold": 0,
+ },
+ },
+ "output": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "speed": 0.25,
+ "voice": "ash",
+ },
+ },
+ "include": ["item.input_audio_transcription.logprobs"],
+ "instructions": "instructions",
+ "max_output_tokens": 0,
+ "model": "string",
+ "output_modalities": ["text"],
+ "prompt": {
+ "id": "id",
+ "variables": {"foo": "string"},
+ "version": "version",
+ },
+ "tool_choice": "none",
+ "tools": [
+ {
+ "description": "description",
+ "name": "name",
+ "parameters": {},
+ "type": "function",
+ }
+ ],
+ "tracing": "auto",
+ "truncation": "auto",
+ },
+ )
+ assert isinstance(call, _legacy_response.HttpxBinaryResponseContent)
+ assert call.json() == {"foo": "bar"}
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = client.realtime.calls.with_raw_response.create(
+ sdp="sdp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert_matches_type(_legacy_response.HttpxBinaryResponseContent, call, path=["response"])
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ with client.realtime.calls.with_streaming_response.create(
+ sdp="sdp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = response.parse()
+ assert_matches_type(bytes, call, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_accept(self, client: OpenAI) -> None:
+ call = client.realtime.calls.accept(
+ call_id="call_id",
+ type="realtime",
+ )
+ assert call is None
+
+ @parametrize
+ def test_method_accept_with_all_params(self, client: OpenAI) -> None:
+ call = client.realtime.calls.accept(
+ call_id="call_id",
+ type="realtime",
+ audio={
+ "input": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "noise_reduction": {"type": "near_field"},
+ "transcription": {
+ "language": "language",
+ "model": "whisper-1",
+ "prompt": "prompt",
+ },
+ "turn_detection": {
+ "type": "server_vad",
+ "create_response": True,
+ "idle_timeout_ms": 5000,
+ "interrupt_response": True,
+ "prefix_padding_ms": 0,
+ "silence_duration_ms": 0,
+ "threshold": 0,
+ },
+ },
+ "output": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "speed": 0.25,
+ "voice": "ash",
+ },
+ },
+ include=["item.input_audio_transcription.logprobs"],
+ instructions="instructions",
+ max_output_tokens=0,
+ model="string",
+ output_modalities=["text"],
+ prompt={
+ "id": "id",
+ "variables": {"foo": "string"},
+ "version": "version",
+ },
+ tool_choice="none",
+ tools=[
+ {
+ "description": "description",
+ "name": "name",
+ "parameters": {},
+ "type": "function",
+ }
+ ],
+ tracing="auto",
+ truncation="auto",
+ )
+ assert call is None
+
+ @parametrize
+ def test_raw_response_accept(self, client: OpenAI) -> None:
+ response = client.realtime.calls.with_raw_response.accept(
+ call_id="call_id",
+ type="realtime",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ def test_streaming_response_accept(self, client: OpenAI) -> None:
+ with client.realtime.calls.with_streaming_response.accept(
+ call_id="call_id",
+ type="realtime",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_accept(self, client: OpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ client.realtime.calls.with_raw_response.accept(
+ call_id="",
+ type="realtime",
+ )
+
+ @parametrize
+ def test_method_hangup(self, client: OpenAI) -> None:
+ call = client.realtime.calls.hangup(
+ "call_id",
+ )
+ assert call is None
+
+ @parametrize
+ def test_raw_response_hangup(self, client: OpenAI) -> None:
+ response = client.realtime.calls.with_raw_response.hangup(
+ "call_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ def test_streaming_response_hangup(self, client: OpenAI) -> None:
+ with client.realtime.calls.with_streaming_response.hangup(
+ "call_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_hangup(self, client: OpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ client.realtime.calls.with_raw_response.hangup(
+ "",
+ )
+
+ @parametrize
+ def test_method_refer(self, client: OpenAI) -> None:
+ call = client.realtime.calls.refer(
+ call_id="call_id",
+ target_uri="tel:+14155550123",
+ )
+ assert call is None
+
+ @parametrize
+ def test_raw_response_refer(self, client: OpenAI) -> None:
+ response = client.realtime.calls.with_raw_response.refer(
+ call_id="call_id",
+ target_uri="tel:+14155550123",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ def test_streaming_response_refer(self, client: OpenAI) -> None:
+ with client.realtime.calls.with_streaming_response.refer(
+ call_id="call_id",
+ target_uri="tel:+14155550123",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_refer(self, client: OpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ client.realtime.calls.with_raw_response.refer(
+ call_id="",
+ target_uri="tel:+14155550123",
+ )
+
+ @parametrize
+ def test_method_reject(self, client: OpenAI) -> None:
+ call = client.realtime.calls.reject(
+ call_id="call_id",
+ )
+ assert call is None
+
+ @parametrize
+ def test_method_reject_with_all_params(self, client: OpenAI) -> None:
+ call = client.realtime.calls.reject(
+ call_id="call_id",
+ status_code=486,
+ )
+ assert call is None
+
+ @parametrize
+ def test_raw_response_reject(self, client: OpenAI) -> None:
+ response = client.realtime.calls.with_raw_response.reject(
+ call_id="call_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ def test_streaming_response_reject(self, client: OpenAI) -> None:
+ with client.realtime.calls.with_streaming_response.reject(
+ call_id="call_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_reject(self, client: OpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ client.realtime.calls.with_raw_response.reject(
+ call_id="",
+ )
+
+
+class TestAsyncCalls:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_method_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ call = await async_client.realtime.calls.create(
+ sdp="sdp",
+ )
+ assert isinstance(call, _legacy_response.HttpxBinaryResponseContent)
+ assert call.json() == {"foo": "bar"}
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_method_create_with_all_params(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ call = await async_client.realtime.calls.create(
+ sdp="sdp",
+ session={
+ "type": "realtime",
+ "audio": {
+ "input": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "noise_reduction": {"type": "near_field"},
+ "transcription": {
+ "language": "language",
+ "model": "whisper-1",
+ "prompt": "prompt",
+ },
+ "turn_detection": {
+ "type": "server_vad",
+ "create_response": True,
+ "idle_timeout_ms": 5000,
+ "interrupt_response": True,
+ "prefix_padding_ms": 0,
+ "silence_duration_ms": 0,
+ "threshold": 0,
+ },
+ },
+ "output": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "speed": 0.25,
+ "voice": "ash",
+ },
+ },
+ "include": ["item.input_audio_transcription.logprobs"],
+ "instructions": "instructions",
+ "max_output_tokens": 0,
+ "model": "string",
+ "output_modalities": ["text"],
+ "prompt": {
+ "id": "id",
+ "variables": {"foo": "string"},
+ "version": "version",
+ },
+ "tool_choice": "none",
+ "tools": [
+ {
+ "description": "description",
+ "name": "name",
+ "parameters": {},
+ "type": "function",
+ }
+ ],
+ "tracing": "auto",
+ "truncation": "auto",
+ },
+ )
+ assert isinstance(call, _legacy_response.HttpxBinaryResponseContent)
+ assert call.json() == {"foo": "bar"}
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_raw_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = await async_client.realtime.calls.with_raw_response.create(
+ sdp="sdp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert_matches_type(_legacy_response.HttpxBinaryResponseContent, call, path=["response"])
+
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_streaming_response_create(self, async_client: AsyncOpenAI, respx_mock: MockRouter) -> None:
+ respx_mock.post("/realtime/calls").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ async with async_client.realtime.calls.with_streaming_response.create(
+ sdp="sdp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = await response.parse()
+ assert_matches_type(bytes, call, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_accept(self, async_client: AsyncOpenAI) -> None:
+ call = await async_client.realtime.calls.accept(
+ call_id="call_id",
+ type="realtime",
+ )
+ assert call is None
+
+ @parametrize
+ async def test_method_accept_with_all_params(self, async_client: AsyncOpenAI) -> None:
+ call = await async_client.realtime.calls.accept(
+ call_id="call_id",
+ type="realtime",
+ audio={
+ "input": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "noise_reduction": {"type": "near_field"},
+ "transcription": {
+ "language": "language",
+ "model": "whisper-1",
+ "prompt": "prompt",
+ },
+ "turn_detection": {
+ "type": "server_vad",
+ "create_response": True,
+ "idle_timeout_ms": 5000,
+ "interrupt_response": True,
+ "prefix_padding_ms": 0,
+ "silence_duration_ms": 0,
+ "threshold": 0,
+ },
+ },
+ "output": {
+ "format": {
+ "rate": 24000,
+ "type": "audio/pcm",
+ },
+ "speed": 0.25,
+ "voice": "ash",
+ },
+ },
+ include=["item.input_audio_transcription.logprobs"],
+ instructions="instructions",
+ max_output_tokens=0,
+ model="string",
+ output_modalities=["text"],
+ prompt={
+ "id": "id",
+ "variables": {"foo": "string"},
+ "version": "version",
+ },
+ tool_choice="none",
+ tools=[
+ {
+ "description": "description",
+ "name": "name",
+ "parameters": {},
+ "type": "function",
+ }
+ ],
+ tracing="auto",
+ truncation="auto",
+ )
+ assert call is None
+
+ @parametrize
+ async def test_raw_response_accept(self, async_client: AsyncOpenAI) -> None:
+ response = await async_client.realtime.calls.with_raw_response.accept(
+ call_id="call_id",
+ type="realtime",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ async def test_streaming_response_accept(self, async_client: AsyncOpenAI) -> None:
+ async with async_client.realtime.calls.with_streaming_response.accept(
+ call_id="call_id",
+ type="realtime",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = await response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_accept(self, async_client: AsyncOpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ await async_client.realtime.calls.with_raw_response.accept(
+ call_id="",
+ type="realtime",
+ )
+
+ @parametrize
+ async def test_method_hangup(self, async_client: AsyncOpenAI) -> None:
+ call = await async_client.realtime.calls.hangup(
+ "call_id",
+ )
+ assert call is None
+
+ @parametrize
+ async def test_raw_response_hangup(self, async_client: AsyncOpenAI) -> None:
+ response = await async_client.realtime.calls.with_raw_response.hangup(
+ "call_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ async def test_streaming_response_hangup(self, async_client: AsyncOpenAI) -> None:
+ async with async_client.realtime.calls.with_streaming_response.hangup(
+ "call_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = await response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_hangup(self, async_client: AsyncOpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ await async_client.realtime.calls.with_raw_response.hangup(
+ "",
+ )
+
+ @parametrize
+ async def test_method_refer(self, async_client: AsyncOpenAI) -> None:
+ call = await async_client.realtime.calls.refer(
+ call_id="call_id",
+ target_uri="tel:+14155550123",
+ )
+ assert call is None
+
+ @parametrize
+ async def test_raw_response_refer(self, async_client: AsyncOpenAI) -> None:
+ response = await async_client.realtime.calls.with_raw_response.refer(
+ call_id="call_id",
+ target_uri="tel:+14155550123",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ async def test_streaming_response_refer(self, async_client: AsyncOpenAI) -> None:
+ async with async_client.realtime.calls.with_streaming_response.refer(
+ call_id="call_id",
+ target_uri="tel:+14155550123",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = await response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_refer(self, async_client: AsyncOpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ await async_client.realtime.calls.with_raw_response.refer(
+ call_id="",
+ target_uri="tel:+14155550123",
+ )
+
+ @parametrize
+ async def test_method_reject(self, async_client: AsyncOpenAI) -> None:
+ call = await async_client.realtime.calls.reject(
+ call_id="call_id",
+ )
+ assert call is None
+
+ @parametrize
+ async def test_method_reject_with_all_params(self, async_client: AsyncOpenAI) -> None:
+ call = await async_client.realtime.calls.reject(
+ call_id="call_id",
+ status_code=486,
+ )
+ assert call is None
+
+ @parametrize
+ async def test_raw_response_reject(self, async_client: AsyncOpenAI) -> None:
+ response = await async_client.realtime.calls.with_raw_response.reject(
+ call_id="call_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ call = response.parse()
+ assert call is None
+
+ @parametrize
+ async def test_streaming_response_reject(self, async_client: AsyncOpenAI) -> None:
+ async with async_client.realtime.calls.with_streaming_response.reject(
+ call_id="call_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ call = await response.parse()
+ assert call is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_reject(self, async_client: AsyncOpenAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `call_id` but received ''"):
+ await async_client.realtime.calls.with_raw_response.reject(
+ call_id="",
+ )
From 8a864874cfcb006f7c67384f8a0a3622faf96cb7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 2 Oct 2025 20:13:57 +0000
Subject: [PATCH 2/2] release: 2.1.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 8 ++++++++
pyproject.toml | 2 +-
src/openai/_version.py | 2 +-
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index cf7239890c..656a2ef17d 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.0.1"
+ ".": "2.1.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcb9926c29..76d700f05e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 2.1.0 (2025-10-02)
+
+Full Changelog: [v2.0.1...v2.1.0](https://github.com/openai/openai-python/compare/v2.0.1...v2.1.0)
+
+### Features
+
+* **api:** add support for realtime calls ([7f7925b](https://github.com/openai/openai-python/commit/7f7925b4074ecbf879714698000e10fa0519d51a))
+
## 2.0.1 (2025-10-01)
Full Changelog: [v2.0.0...v2.0.1](https://github.com/openai/openai-python/compare/v2.0.0...v2.0.1)
diff --git a/pyproject.toml b/pyproject.toml
index a83bca2dcd..d8deac4e61 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "openai"
-version = "2.0.1"
+version = "2.1.0"
description = "The official Python library for the openai API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/openai/_version.py b/src/openai/_version.py
index f20794ac19..2860526ced 100644
--- a/src/openai/_version.py
+++ b/src/openai/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "openai"
-__version__ = "2.0.1" # x-release-please-version
+__version__ = "2.1.0" # x-release-please-version