Commit 86aaa1de

stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
2025-10-03 04:13:26
feat(api): add support for realtime calls
1 parent 75a3aa4
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,
+        )
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 @@ __all__ = [
     "AsyncClientSecretsWithRawResponse",
     "ClientSecretsWithStreamingResponse",
     "AsyncClientSecretsWithStreamingResponse",
+    "Calls",
+    "AsyncCalls",
+    "CallsWithRawResponse",
+    "AsyncCallsWithRawResponse",
+    "CallsWithStreamingResponse",
+    "AsyncCallsWithStreamingResponse",
     "Realtime",
     "AsyncRealtime",
     "RealtimeWithRawResponse",
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,
+        )
src/openai/resources/realtime/realtime.py
@@ -11,6 +11,14 @@ from typing_extensions import AsyncIterator
 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 @@ class Realtime(SyncAPIResource):
     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 @@ class Realtime(SyncAPIResource):
             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 @@ class AsyncRealtime(AsyncAPIResource):
     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 @@ class AsyncRealtime(AsyncAPIResource):
             extra_query=extra_query,
             extra_headers=extra_headers,
             websocket_connection_options=websocket_connection_options,
+            call_id=call_id,
             model=model,
         )
 
@@ -163,6 +185,10 @@ class RealtimeWithRawResponse:
     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 @@ class AsyncRealtimeWithRawResponse:
     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 @@ class RealtimeWithStreamingResponse:
     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 @@ class AsyncRealtimeWithStreamingResponse:
     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 @@ class AsyncRealtimeConnectionManager:
         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 @@ class AsyncRealtimeConnectionManager:
         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 @@ class RealtimeConnectionManager:
         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 @@ class RealtimeConnectionManager:
         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,
                 },
             )
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
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`.
+    """
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."""
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`.
+    """
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.
+    """
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
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="",
+            )
.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
api.md
@@ -989,6 +989,16 @@ Methods:
 
 - <code title="post /realtime/client_secrets">client.realtime.client_secrets.<a href="./src/openai/resources/realtime/client_secrets.py">create</a>(\*\*<a href="src/openai/types/realtime/client_secret_create_params.py">params</a>) -> <a href="./src/openai/types/realtime/client_secret_create_response.py">ClientSecretCreateResponse</a></code>
 
+## Calls
+
+Methods:
+
+- <code title="post /realtime/calls">client.realtime.calls.<a href="./src/openai/resources/realtime/calls.py">create</a>(\*\*<a href="src/openai/types/realtime/call_create_params.py">params</a>) -> HttpxBinaryResponseContent</code>
+- <code title="post /realtime/calls/{call_id}/accept">client.realtime.calls.<a href="./src/openai/resources/realtime/calls.py">accept</a>(call_id, \*\*<a href="src/openai/types/realtime/call_accept_params.py">params</a>) -> None</code>
+- <code title="post /realtime/calls/{call_id}/hangup">client.realtime.calls.<a href="./src/openai/resources/realtime/calls.py">hangup</a>(call_id) -> None</code>
+- <code title="post /realtime/calls/{call_id}/refer">client.realtime.calls.<a href="./src/openai/resources/realtime/calls.py">refer</a>(call_id, \*\*<a href="src/openai/types/realtime/call_refer_params.py">params</a>) -> None</code>
+- <code title="post /realtime/calls/{call_id}/reject">client.realtime.calls.<a href="./src/openai/resources/realtime/calls.py">reject</a>(call_id, \*\*<a href="src/openai/types/realtime/call_reject_params.py">params</a>) -> None</code>
+
 # Conversations
 
 Types: