diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,30 @@
 # Revision history for wai-handler-hal
 
+## 0.4.0.0 -- 2024-01-17
+
+- New function: `Wai.Handler.Hal.runWithOptions :: Options ->
+  Application -> ProxyRequest NoAuthorizer -> ProxyResponse`. This
+  provides a convenient way to pass custom `Options` without all the
+  bells and whistles of `runWithContext`.
+
+- Instead of guessing whether a given response `Content-Type` should
+  be sent as text or base64-encoded binary, `Options` now contains a
+  `binaryMediaTypes :: [MediaType]`, which lists the media types that
+  should be base64-encoded. This should match the `binaryMediaTypes`
+  setting you have configured on the API Gateway that integrates with
+  your Lambda Function.
+
+  _See:_ [Content type conversion in API
+    Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html)
+    in the [Amazon API Gateway Developer
+    Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/).
+
 ## 0.3.0.0 -- 2023-12-17
 
-- Breaking change: add `Options` record parameter to `runWithOptions`,
+- Accidental breaking change: more elaborate `Content-Type` headers
+  like `Content-Type: application/json; charset=utf-8` are now encoded
+  as if they were binary payloads. This release has been deprecated.
+- Breaking change: add `Options` record parameter to `runWithContext`,
   `toWaiRequest` and `fromWaiResponse`.
 - Provide a `defaultOptions`.
 - Make whether or not to run base64-encoding on the response body customizable
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2021 Bellroy Pty Ltd
+Copyright (C) 2021, 2024 Bellroy Pty Ltd
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
diff --git a/src/Network/Wai/Handler/Hal.hs b/src/Network/Wai/Handler/Hal.hs
--- a/src/Network/Wai/Handler/Hal.hs
+++ b/src/Network/Wai/Handler/Hal.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TupleSections #-}
@@ -8,14 +9,14 @@
 -- |
 --
 -- Module      : Network.Wai.Handler.Hal
--- Copyright   : (C) 2021 Bellroy Pty Ltd
+-- Copyright   : (C) 2021, 2024 Bellroy Pty Ltd
 -- License     : BSD-3-Clause
 -- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>
 -- Stability   : experimental
 --
 -- Lifts an 'Wai.Application' so that it can be run using
 -- 'AWS.Lambda.Runtime.mRuntime' or
--- 'AWS.Lambda.Runtime.mRuntimeWithContext''. The glue code will look
+-- 'AWS.Lambda.Runtime.mRuntimeWithContext'. The glue code will look
 -- something like this:
 --
 -- @
@@ -31,6 +32,7 @@
 -- @
 module Network.Wai.Handler.Hal
   ( run,
+    runWithOptions,
     runWithContext,
     Options (..),
     defaultOptions,
@@ -61,11 +63,13 @@
 import qualified Data.HashMap.Lazy as H
 import qualified Data.IORef as IORef
 import Data.List (foldl', sort)
+import Data.Maybe (fromMaybe)
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import Data.Vault.Lazy (Key, Vault)
 import qualified Data.Vault.Lazy as Vault
+import Network.HTTP.Media (MediaType, matches, parseAccept, renderHeader)
 import Network.HTTP.Types.Header
   ( HeaderName,
     ResponseHeaders,
@@ -105,13 +109,7 @@
   Wai.Application ->
   HalRequest.ProxyRequest HalRequest.NoAuthorizer ->
   m HalResponse.ProxyResponse
-run app req = liftIO $ do
-  waiReq <- toWaiRequest defaultOptions req
-  responseRef <- IORef.newIORef Nothing
-  Wai.ResponseReceived <- app waiReq $ \waiResp ->
-    Wai.ResponseReceived <$ IORef.writeIORef responseRef (Just waiResp)
-  Just waiResp <- IORef.readIORef responseRef
-  fromWaiResponse defaultOptions waiResp
+run = runWithOptions defaultOptions
 
 -- | Options that can be used to customize the behaviour of 'runWithContext'.
 -- 'defaultOptions' provides sensible defaults.
@@ -124,17 +122,20 @@
     -- have to tell it yourself. This is almost always going to be 443
     -- (HTTPS).
     portNumber :: PortNumber,
-    -- | Binary responses need to be encoded as base64. This option lets you
-    -- customize which mime types are considered binary data.
+    -- | To return binary data, API Gateway requires you to configure
+    -- the @binaryMediaTypes@ setting on your API, and then
+    -- base64-encode your binary responses.
     --
-    -- The following mime types are __not__ considered binary by default:
+    -- If the @Content-Type@ header in the @wai@ 'Wai.Response'
+    -- matches any of the media types in this field, @wai-handler-hal@
+    -- will base64-encode its response to the API Gateway.
     --
-    -- * @application/json@
-    -- * @application/xml@
-    -- * anything starting with @text/@
-    -- * anything ending with @+json@
-    -- * anything ending with @+xml@
-    binaryMimeType :: Text -> Bool
+    -- If you set @binaryMediaTypes@ in your API, you should override
+    -- the default (empty) list to match.
+    --
+    -- /See:/ [Content type conversion in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html)
+    -- in the [Amazon API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/).
+    binaryMediaTypes :: [MediaType]
   }
 
 -- | Default options for running 'Wai.Application's on Lambda.
@@ -143,17 +144,31 @@
   Options
     { vault = Vault.empty,
       portNumber = 443,
-      binaryMimeType = \mime -> case mime of
-        "application/json" -> False
-        "application/xml" -> False
-        _ | "text/" `T.isPrefixOf` mime -> False
-        _ | "+json" `T.isSuffixOf` mime -> False
-        _ | "+xml" `T.isSuffixOf` mime -> False
-        _ -> True
+      binaryMediaTypes = []
     }
 
+-- | A variant of 'run' with configurable 'Options'. Useful if you
+-- just want to override the 'binaryMediaTypes' setting but don't need
+-- the rest of 'runWithContext''s features.
+--
+-- @since 0.4.0.0
+runWithOptions ::
+  (MonadIO m) =>
+  -- | Configuration options. 'defaultOptions' provides sensible defaults.
+  Options ->
+  Wai.Application ->
+  HalRequest.ProxyRequest HalRequest.NoAuthorizer ->
+  m HalResponse.ProxyResponse
+runWithOptions opts app req = liftIO $ do
+    waiReq <- toWaiRequest opts req
+    responseRef <- IORef.newIORef Nothing
+    Wai.ResponseReceived <- app waiReq $ \waiResp ->
+      Wai.ResponseReceived <$ IORef.writeIORef responseRef (Just waiResp)
+    Just waiResp <- IORef.readIORef responseRef
+    fromWaiResponse opts waiResp
+
 -- | Convert a WAI 'Wai.Application' into a function that can
--- be run by hal's 'AWS.Lambda.Runtime.mRuntimeWithContext''. This
+-- be run by hal's 'AWS.Lambda.Runtime.mRuntimeWithContext'. This
 -- function exposes all the configurable knobs.
 runWithContext ::
   (MonadIO m) =>
@@ -330,12 +345,18 @@
       hSeek h AbsoluteSeek offset
       B.hGet h $ fromIntegral count
 
-createProxyBody :: Options -> Text -> ByteString -> HalResponse.ProxyBody
-createProxyBody opts contentType body
-  | binaryMimeType opts contentType =
-      HalResponse.ProxyBody contentType (T.decodeUtf8 $ B64.encode body) True
-  | otherwise =
-      HalResponse.ProxyBody contentType (T.decodeUtf8 body) False
+createProxyBody :: Options -> MediaType -> ByteString -> HalResponse.ProxyBody
+createProxyBody opts contentType body =
+  HalResponse.ProxyBody
+    { HalResponse.contentType = T.decodeUtf8 $ renderHeader contentType,
+      HalResponse.serialized =
+        if isBase64Encoded
+          then T.decodeUtf8 $ B64.encode body
+          else T.decodeUtf8 body,
+      HalResponse.isBase64Encoded
+    }
+  where
+    isBase64Encoded = any (contentType `matches`) $ binaryMediaTypes opts
 
 addHeaders ::
   ResponseHeaders -> HalResponse.ProxyResponse -> HalResponse.ProxyResponse
@@ -349,6 +370,7 @@
 
 -- | Try to find the content-type of a response, given the response
 -- headers. If we can't, return @"application/octet-stream"@.
-getContentType :: ResponseHeaders -> Text
-getContentType =
-  maybe "application/octet-stream" T.decodeUtf8 . lookup hContentType
+getContentType :: ResponseHeaders -> MediaType
+getContentType headers =
+  fromMaybe "application/octet-stream" $
+    lookup hContentType headers >>= parseAccept
diff --git a/test/Network/Wai/Handler/HalTest.hs b/test/Network/Wai/Handler/HalTest.hs
--- a/test/Network/Wai/Handler/HalTest.hs
+++ b/test/Network/Wai/Handler/HalTest.hs
@@ -1,17 +1,26 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
 module Network.Wai.Handler.HalTest where
 
-import AWS.Lambda.Events.ApiGateway.ProxyRequest
+import AWS.Lambda.Events.ApiGateway.ProxyRequest (ProxyRequest)
+import AWS.Lambda.Events.ApiGateway.ProxyResponse
+  ( ProxyBody (..),
+    ProxyResponse (..),
+  )
 import Data.Aeson (eitherDecodeFileStrict')
-import qualified Data.Text as T
+import qualified Data.ByteString.Base64 as B64
+import qualified Data.Text.Encoding as T
 import qualified Data.Text.Lazy.Encoding as TL
 import Data.Void (Void)
+import Network.HTTP.Types (hContentType, ok200)
+import Network.Wai (Response, responseLBS)
 import Network.Wai.Handler.Hal
-import Test.Tasty
-import Test.Tasty.Golden
-import Test.Tasty.HUnit (assertEqual, testCase)
-import Text.Pretty.Simple
+import Test.Tasty (TestTree)
+import Test.Tasty.Golden (goldenVsString)
+import Test.Tasty.HUnit (Assertion, assertEqual)
+import Text.Pretty.Simple (pShowNoColor)
 
 test_ConvertProxyRequest :: TestTree
 test_ConvertProxyRequest =
@@ -22,22 +31,25 @@
     waiRequest <- toWaiRequest defaultOptions proxyRequest
     pure . TL.encodeUtf8 $ pShowNoColor waiRequest
 
-test_DefaultBinaryMimeTypes :: TestTree
-test_DefaultBinaryMimeTypes = testCase "default binary MIME types" $ do
-  assertBinary False "text/plain"
-  assertBinary False "text/html"
-  assertBinary False "application/json"
-  assertBinary False "application/xml"
-  assertBinary False "application/vnd.api+json"
-  assertBinary False "application/vnd.api+xml"
-  assertBinary False "image/svg+xml"
+unit_BinaryResponse :: Assertion
+unit_BinaryResponse = do
+  let options = defaultOptions {binaryMediaTypes = ["*/*"]}
+  ProxyResponse {body = ProxyBody {..}} <-
+    fromWaiResponse options helloWorld
 
-  assertBinary True "application/octet-stream"
-  assertBinary True "audio/vorbis"
-  assertBinary True "image/png"
-  where
-    assertBinary expected mime =
-      assertEqual
-        mime
-        (binaryMimeType defaultOptions (T.pack mime))
-        expected
+  assertEqual "response is binary" True isBase64Encoded
+  assertEqual
+    "response is base64-encoded"
+    (Right "Hello, World!")
+    (B64.decode (T.encodeUtf8 serialized))
+
+unit_TextResponse :: Assertion
+unit_TextResponse = do
+  ProxyResponse {body = ProxyBody {..}} <-
+    fromWaiResponse defaultOptions helloWorld
+
+  assertEqual "response is not binary" False isBase64Encoded
+  assertEqual "response is unmangled" "Hello, World!" serialized
+
+helloWorld :: Response
+helloWorld = responseLBS ok200 [(hContentType, "text/plain")] "Hello, World!"
diff --git a/wai-handler-hal.cabal b/wai-handler-hal.cabal
--- a/wai-handler-hal.cabal
+++ b/wai-handler-hal.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               wai-handler-hal
-version:            0.3.0.0
+version:            0.4.0.0
 synopsis:           Wrap WAI applications to run on AWS Lambda
 description:
   This library provides a function 'Network.Wai.Handler.Hal.run' to
@@ -18,11 +18,12 @@
 license-file:       LICENSE
 author:             Bellroy Tech Team <haskell@bellroy.com>
 maintainer:         Bellroy Tech Team <haskell@bellroy.com>
-copyright:          Copyright (C) 2021 Bellroy Pty Ltd
+copyright:          Copyright (C) 2021, 2024 Bellroy Pty Ltd
 category:           AWS, Cloud
 build-type:         Simple
-extra-source-files:
+extra-doc-files:
   CHANGELOG.md
+extra-source-files:
   README.md
   test/data/ProxyRequest.json
   test/golden/WaiRequest.txt
@@ -51,7 +52,8 @@
     , base64-bytestring     >=1.0.0.0   && <1.3
     , bytestring            >=0.10.8    && <0.12.1
     , case-insensitive      ^>=1.2.0.0
-    , hal                   >=0.4.7     && <0.4.11 || >=1.0.0 && <1.1
+    , hal                   >=0.4.7     && <0.4.11 || >=1.0.0 && <1.2
+    , http-media            ^>=0.8.1.1
     , http-types            ^>=0.12.3
     , network               >=2.8.0.0   && <3.2
     , text                  ^>=1.2.3    || >=2.0   && <2.1.1
