diff --git a/baikai-openai.cabal b/baikai-openai.cabal
--- a/baikai-openai.cabal
+++ b/baikai-openai.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.4
 name:          baikai-openai
-version:       0.1.0.0
+version:       0.1.1.0
 synopsis:      OpenAI providers for the baikai abstraction
 description:
   Wraps the openai Haskell package as a Baikai Provider for OpenAI's Chat Completions API.
@@ -37,7 +37,7 @@
 
   build-depends:
     , aeson
-    , baikai             ^>=0.1.0
+    , baikai             ^>=0.1.1
     , base               >=4.20   && <5
     , base64-bytestring
     , bytestring
@@ -61,12 +61,14 @@
   hs-source-dirs: test
   main-is:        Main.hs
   build-depends:
-    , baikai         ^>=0.1.0
+    , aeson
+    , baikai         ^>=0.1.1
     , baikai-openai
     , base           >=4.20   && <5
     , bytestring
     , generic-lens
     , lens           ^>=5.3
+    , openai
     , streamly-core  >=0.3    && <0.5
     , tasty
     , tasty-hunit
diff --git a/src/Baikai/Provider/OpenAI/Api.hs b/src/Baikai/Provider/OpenAI/Api.hs
--- a/src/Baikai/Provider/OpenAI/Api.hs
+++ b/src/Baikai/Provider/OpenAI/Api.hs
@@ -30,6 +30,7 @@
   ( register,
     registerWithRegistry,
     openaiChatStream,
+    mapRequest,
   )
 where
 
@@ -52,6 +53,7 @@
     globalProviderRegistry,
     registerApiProviderWith,
   )
+import Baikai.ResponseFormat (ResponseFormat (..))
 import Baikai.StopReason qualified as Stop
 import Baikai.Stream (streamingComplete)
 import Baikai.Stream.Event
@@ -95,6 +97,7 @@
 import OpenAI.V1 qualified as OpenAI
 import OpenAI.V1.Chat.Completions qualified as Chat
 import OpenAI.V1.Models qualified as OpenAIModels
+import OpenAI.V1.ResponseFormat qualified as RF
 import OpenAI.V1.Tool qualified as OpenAITool
 import OpenAI.V1.ToolCall qualified as ToolCall
 import Streamly.Data.Stream (Stream)
@@ -726,6 +729,8 @@
       toolChoiceField = fmap mkOpenAIToolChoice (opts ^. #toolChoice)
       reasoningEffortField =
         applyThinkingFormat compat (opts ^. #thinking)
+      responseFormatField =
+        fmap mkOpenAIResponseFormat (opts ^. #responseFormat)
   pure
     Chat._CreateChatCompletion
       { Chat.messages = Vector.fromList (prefix <> body),
@@ -734,7 +739,26 @@
         Chat.temperature = opts ^. #temperature,
         Chat.tools = toolsField,
         Chat.tool_choice = toolChoiceField,
-        Chat.reasoning_effort = reasoningEffortField
+        Chat.reasoning_effort = reasoningEffortField,
+        Chat.response_format = responseFormatField
+      }
+
+-- | Map a baikai 'ResponseFormat' onto the upstream OpenAI
+-- 'RF.ResponseFormat'. 'JsonObject' becomes plain-JSON mode;
+-- 'JsonSchema' becomes a named, optionally-strict schema. The
+-- schema 'Value' is forwarded verbatim.
+mkOpenAIResponseFormat :: ResponseFormat -> RF.ResponseFormat
+mkOpenAIResponseFormat = \case
+  JsonObject -> RF.JSON_Object
+  JsonSchema {name = n, schema = s, strict = st} ->
+    RF.JSON_Schema
+      { RF.json_schema =
+          RF.JSONSchema
+            { RF.description = Nothing,
+              RF.name = n,
+              RF.schema = Just s,
+              RF.strict = Just st
+            }
       }
 
 -- | Map a 'Baikai.ThinkingLevel.ThinkingLevel' onto the OpenAI SDK's
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -4,24 +4,66 @@
 import Baikai.Provider.OpenAI.Api
 import Baikai.Provider.OpenAI.Interactive
 import Control.Lens ((&), (.~), (^.))
+import Data.Aeson qualified as Aeson
 import Data.ByteString.Char8 qualified as BS8
 import Data.Generics.Labels ()
 import Data.Text qualified as Text
 import Data.Vector qualified as Vector
+import OpenAI.V1.Chat.Completions qualified as Chat
+import OpenAI.V1.ResponseFormat qualified as RF
 import Streamly.Data.Stream qualified as Stream
 import Test.Tasty (TestTree, defaultMain, testGroup)
-import Test.Tasty.HUnit (assertBool, testCase, (@?=))
+import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))
 
 main :: IO ()
 main =
   defaultMain $
     testGroup
-      "Baikai.Provider.OpenAI.Interactive"
+      "Baikai.Provider.OpenAI"
       [ commandRenderingTest,
         promptRenderingTest,
         compatDetectionTest,
-        rejectsImageToolResultsTest
+        rejectsImageToolResultsTest,
+        responseFormatMappingTest
       ]
+
+-- | A 'JsonSchema' on 'Options.responseFormat' maps onto the
+-- upstream OpenAI @response_format@ as a named, strict JSON schema,
+-- forwarding the schema 'Value' verbatim. Pure: 'mapRequest' is
+-- 'Either Text Chat.CreateChatCompletion'.
+responseFormatMappingTest :: TestTree
+responseFormatMappingTest =
+  testCase "responseFormat JsonSchema maps onto OpenAI response_format" $ do
+    let model =
+          _Model
+            & #modelId .~ "gpt-4o-mini"
+            & #api .~ OpenAIChatCompletions
+            & #provider .~ "openai"
+        personSchema =
+          Aeson.object
+            [ "type" Aeson..= ("object" :: Text.Text),
+              "properties"
+                Aeson..= Aeson.object
+                  [ "name" Aeson..= Aeson.object ["type" Aeson..= ("string" :: Text.Text)],
+                    "age" Aeson..= Aeson.object ["type" Aeson..= ("integer" :: Text.Text)]
+                  ],
+              "required" Aeson..= (["name", "age"] :: [Text.Text]),
+              "additionalProperties" Aeson..= False
+            ]
+        ctx = _Context
+        opts =
+          _Options
+            & #responseFormat
+              .~ Just (JsonSchema {name = "person", schema = personSchema, strict = True})
+    case mapRequest model ctx opts of
+      Left e -> assertFailure ("mapRequest failed: " <> Text.unpack e)
+      Right req -> case Chat.response_format req of
+        Just (RF.JSON_Schema {RF.json_schema = js}) -> do
+          RF.name js @?= "person"
+          RF.schema js @?= Just personSchema
+          RF.strict js @?= Just True
+          RF.description js @?= Nothing
+        other -> assertFailure ("expected JSON_Schema, got: " <> show other)
 
 commandRenderingTest :: TestTree
 commandRenderingTest =
