diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,63 @@
+## Req 3.13.4
+
+* Fixed empty ciphersuite list when compiling against `tls < 2.0.6`
+  (see [PR 175](https://github.com/mrkkrp/req/pull/175)). As a side effect, now
+  compatible with older versions of `crypton-connection` (>= 0.3).
+
+## Req 3.13.3
+
+* Works with `crypton-connection-0.4` and newer.
+
+## Req 3.13.2
+
+* Disable the problematic `httpbin-tests` test suite by default. Only enable
+  it when the `dev` flag is enabled. In that case it is expected that an
+  httpbin server is run locally at `localhost:1234`.
+
+## Req 3.13.1
+
+* Switched the non-pure test suite to use https://httpbun.org instead of
+  https://httpbin.org since the latter proved to be highly unreliable
+  lately.
+
+* Switched from `connection` to `crypton-connection`.
+
+* Builds with GHC 9.6.1.
+
+## Req 3.13.0
+
+* Add `headerRedacted` function to add header fields, which will be with
+  redacted values on print.
+
+## Req 3.12.0
+
+* Add `isStatusCodeException` function.
+* Add `instance HttpResponse (Network.HTTP.Client.Response ())`.
+
+## Req 3.11.0
+
+* Add the `queryParamToList` method to the `QueryParam` type class.
+* Add the `formToQuery` function. [Issue 126](https://github.com/mrkkrp/req/issues/126).
+* Add `FromForm` instances (in the `Web.FormUrlEncoded` module) to the
+  `Option` and `FormUrlEncodedParam` types.
+
+## Req 3.10.0
+
+* Add `MonadHttp` instances for `transformers` types.
+
+## Req 3.9.2
+
+* The test suite works with `aeson-2.x.x.x`.
+
+## Req 3.9.1
+
+* Builds with GHC 9.0.
+
+## Req 3.9.0
+
+* The `useHttpURI` and `useHttpsURI` functions now preserve trailing
+  slashes.
+
 ## Req 3.8.0
 
 * Adjusted the value of the `httpConfigRetryJudgeException` field of
diff --git a/Network/HTTP/Req.hs b/Network/HTTP/Req.hs
--- a/Network/HTTP/Req.hs
+++ b/Network/HTTP/Req.hs
@@ -1,20 +1,9 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DeriveLift #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE RoleAnnotations #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE TemplateHaskellQuotes #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
@@ -30,61 +19,40 @@
 --
 -- The documentation below is structured in such a way that the most
 -- important information is presented first: you learn how to do HTTP
--- requests, how to embed them in any monad you have, and then it gives you
+-- requests, how to embed them in the monad you have, and then it gives you
 -- details about less-common things you may want to know about. The
 -- documentation is written with sufficient coverage of details and
 -- examples, and it's designed to be a complete tutorial on its own.
 --
--- /(A modest intro goes here, click on 'req' to start making requests.)/
---
 -- === About the library
 --
--- Req is an easy-to-use, type-safe, expandable, high-level HTTP client
--- library that just works without any fooling around.
---
--- What does the phrase “easy-to-use” mean? It means that the library is
--- designed to be beginner-friendly so it's simple to add to your monad
--- stack, intuitive to work with, well-documented, and does not get in your
--- way. Doing HTTP requests is a common task and Haskell library for this
--- should be very approachable and clear to beginners, thus certain
--- compromises were made. For example, one cannot currently modify
--- 'L.ManagerSettings' of the default manager because the library always
--- uses the same implicit global manager for simplicity and maximal
--- connection sharing. There is a way to use your own manager with different
--- settings, but it requires a bit more typing.
+-- Req is an HTTP client library that attempts to be easy-to-use, type-safe,
+-- and expandable.
 --
--- “Type-safe” means that the library is protective and eliminates certain
--- classes of errors. For example, we have correct-by-construction 'Url's,
--- it's guaranteed that the user does not send the request body when using
--- methods like 'GET' or 'OPTIONS', and the amount of implicit assumptions
--- is minimized by making the user specify his\/her intentions in an
--- explicit form (for example, it's not possible to avoid specifying the
--- body or method of request). Authentication methods that assume HTTPS
--- force the user to use HTTPS at the type level. The library also carefully
--- hides underlying types from the lower-level @http-client@ package because
--- those types are not safe enough (for example 'L.Request' is an instance
--- of 'Data.String.IsString' and, if it's malformed, it will blow up at
--- run-time).
+-- “Easy-to-use” means that the library is designed to be beginner-friendly
+-- so it's simple to add to your monad stack, intuitive to work with,
+-- well-documented, and does not get in your way. Doing HTTP requests is a
+-- common task and a Haskell library for this should be approachable and
+-- clear to beginners, thus certain compromises were made. For example, one
+-- cannot currently modify 'L.ManagerSettings' of the default manager
+-- because the library always uses the same implicit global manager for
+-- simplicity and maximal connection sharing. There is a way to use your own
+-- manager with different settings, but it requires more typing.
 --
--- “Expandable” refers to the ability of the library to be expanded without
--- having to resort to ugly hacking. For example, it's possible to define
--- your own HTTP methods, create new ways to construct the body of a
--- request, create new authorization options, perform a request in a
--- different way, and create your own methods to parse and represent a
--- response. As the user extends the library to satisfy his\/her special
--- needs, the new solutions will work just like the built-ins. However, all
--- of the common cases are also covered by the library out-of-the-box.
+-- “Type-safe” means that the library tries to eliminate certain classes of
+-- errors. For example, we have correct-by-construction URLs; it is
+-- guaranteed that the user does not send the request body when using
+-- methods like GET or OPTIONS, and the amount of implicit assumptions is
+-- minimized by making the user specify their intentions in an explicit
+-- form. For example, it's not possible to avoid specifying the body or the
+-- method of a request. Authentication methods that assume HTTPS force the
+-- user to use HTTPS at the type level.
 --
--- “High-level” means that there are less details to worry about. The
--- library is a result of my experiences as a Haskell consultant. Working
--- for several clients, who had very different projects, showed me that the
--- library should adapt easily to any particular style of writing Haskell
--- applications. For example, some people prefer throwing exceptions, while
--- others are concerned with purity. Just define 'handleHttpException'
--- accordingly when making your monad instance of 'MonadHttp' and it will
--- play together seamlessly. Finally, the library cuts down boilerplate
--- considerably, and helps you write concise, easy to read, and maintainable
--- code.
+-- “Expandable” refers to the ability to create new components without
+-- having to resort to hacking. For example, it's possible to define your
+-- own HTTP methods, create new ways to construct the body of a request,
+-- create new authorization options, perform a request in a different way,
+-- and create your own methods to parse a response.
 --
 -- === Using with other libraries
 --
@@ -176,11 +144,13 @@
     -- $query-parameters
     (=:),
     queryFlag,
+    formToQuery,
     QueryParam (..),
 
     -- *** Headers
     header,
     attachHeader,
+    headerRedacted,
 
     -- *** Cookies
     -- $cookies
@@ -227,58 +197,78 @@
 
     -- * Other
     HttpException (..),
+    isStatusCodeException,
     CanHaveBody (..),
     Scheme (..),
   )
 where
 
-import qualified Blaze.ByteString.Builder as BB
+import Blaze.ByteString.Builder qualified as BB
 import Control.Applicative
 import Control.Arrow (first, second)
 import Control.Exception hiding (Handler (..), TypeError)
+import Control.Monad (guard, void, (>=>))
 import Control.Monad.Base
 import Control.Monad.Catch (Handler (..), MonadCatch, MonadMask, MonadThrow)
 import Control.Monad.IO.Class
 import Control.Monad.IO.Unlift
-import Control.Monad.Reader
+import Control.Monad.Reader (ReaderT (ReaderT), ask, lift, runReaderT)
+import Control.Monad.Trans.Accum (AccumT)
+import Control.Monad.Trans.Cont (ContT)
 import Control.Monad.Trans.Control
+import Control.Monad.Trans.Except (ExceptT)
+import Control.Monad.Trans.Identity (IdentityT)
+import Control.Monad.Trans.Maybe (MaybeT)
+import Control.Monad.Trans.RWS.CPS qualified as RWS.CPS
+import Control.Monad.Trans.RWS.Lazy qualified as RWS.Lazy
+import Control.Monad.Trans.RWS.Strict qualified as RWS.Strict
+import Control.Monad.Trans.Select (SelectT)
+import Control.Monad.Trans.State.Lazy qualified as State.Lazy
+import Control.Monad.Trans.State.Strict qualified as State.Strict
+import Control.Monad.Trans.Writer.CPS qualified as Writer.CPS
+import Control.Monad.Trans.Writer.Lazy qualified as Writer.Lazy
+import Control.Monad.Trans.Writer.Strict qualified as Writer.Strict
 import Control.Retry
 import Data.Aeson (FromJSON (..), ToJSON (..))
-import qualified Data.Aeson as A
+import Data.Aeson qualified as A
 import Data.ByteString (ByteString)
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as BL
-import qualified Data.CaseInsensitive as CI
+import Data.ByteString qualified as B
+import Data.ByteString.Lazy qualified as BL
+import Data.CaseInsensitive qualified as CI
 import Data.Data (Data)
+import Data.Default.Class (def)
 import Data.Function (on)
 import Data.IORef
 import Data.Kind (Constraint, Type)
 import Data.List (foldl', nubBy)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe (fromMaybe)
 import Data.Proxy
-import Data.Semigroup hiding (Option, option)
+import Data.Semigroup (Endo (..))
+import Data.Set qualified as S
 import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
 import Data.Typeable (Typeable, cast)
 import GHC.Generics
 import GHC.TypeLits
-import qualified Language.Haskell.TH as TH
-import qualified Language.Haskell.TH.Quote as TH
-import qualified Language.Haskell.TH.Syntax as TH
-import qualified Network.Connection as NC
-import qualified Network.HTTP.Client as L
-import qualified Network.HTTP.Client.Internal as LI
-import qualified Network.HTTP.Client.MultipartFormData as LM
-import qualified Network.HTTP.Client.TLS as L
-import qualified Network.HTTP.Types as Y
+import Language.Haskell.TH qualified as TH
+import Language.Haskell.TH.Quote qualified as TH
+import Language.Haskell.TH.Syntax qualified as TH
+import Network.Connection qualified as NC
+import Network.HTTP.Client qualified as L
+import Network.HTTP.Client.Internal qualified as LI
+import Network.HTTP.Client.MultipartFormData qualified as LM
+import Network.HTTP.Client.TLS qualified as L
+import Network.HTTP.Types qualified as Y
 import System.IO.Unsafe (unsafePerformIO)
 import Text.URI (URI)
-import qualified Text.URI as URI
-import qualified Text.URI.QQ as QQ
-import qualified Web.Authenticate.OAuth as OAuth
+import Text.URI qualified as URI
+import Text.URI.QQ qualified as QQ
+import Web.Authenticate.OAuth qualified as OAuth
+import Web.FormUrlEncoded (FromForm (..), ToForm (..))
+import Web.FormUrlEncoded qualified as Form
 import Web.HttpApiData (ToHttpApiData (..))
 
 ----------------------------------------------------------------------------
@@ -303,7 +293,7 @@
 --
 -- @body@ is a body option such as 'NoReqBody' or 'ReqBodyJson'. The
 -- tutorial has a section about HTTP bodies, but usage is very
--- straightforward and should be clear from the examples below.
+-- straightforward and should be clear from the examples.
 --
 -- @response@ is a type hint how to make and interpret response of an HTTP
 -- request. Out-of-the-box it can be the following:
@@ -499,7 +489,7 @@
 --
 -- @since 3.7.0
 reqHandler ::
-  MonadHttp m =>
+  (MonadHttp m) =>
   -- | How to get final result from a 'L.Response'
   (L.Response L.BodyReader -> IO b) ->
   -- | 'L.Request' to perform
@@ -588,22 +578,22 @@
           nubHeaders
             <> getRequestMod options
             <> getRequestMod config
-            <> getRequestMod (Womb body :: Womb "body" body)
+            <> getRequestMod (Tagged body :: Tagged "body" body)
             <> getRequestMod url
-            <> getRequestMod (Womb method :: Womb "method" method)
+            <> getRequestMod (Tagged method :: Tagged "method" method)
   request <- finalizeRequest options request'
   withReqManager (m request)
 
 -- | Perform an action using the global implicit 'L.Manager' that the rest
 -- of the library uses. This allows to reuse connections that the
 -- 'L.Manager' controls.
-withReqManager :: MonadIO m => (L.Manager -> m a) -> m a
+withReqManager :: (MonadIO m) => (L.Manager -> m a) -> m a
 withReqManager m = liftIO (readIORef globalManager) >>= m
 
--- | Global 'L.Manager' that 'req' uses. Here we just go with the default
--- settings, so users don't need to deal with this manager stuff at all, but
--- when we create a request, instance 'HttpConfig' can affect the default
--- settings via 'getHttpConfig'.
+-- | The global 'L.Manager' that 'req' uses. Here we just go with the
+-- default settings, so users don't need to deal with this manager stuff at
+-- all, but when we create a request, instance 'HttpConfig' can affect the
+-- default settings via 'getHttpConfig'.
 --
 -- A note about safety, in case 'unsafePerformIO' looks suspicious to you.
 -- The value of 'globalManager' is named and lives on top level. This means
@@ -617,7 +607,7 @@
   let settings =
         L.mkManagerSettingsContext
           (Just context)
-          (NC.TLSSettingsSimple False False False)
+          def
           Nothing
   manager <- L.newManager settings
   newIORef manager
@@ -634,13 +624,13 @@
 -- When writing a library, keep your API polymorphic in terms of
 -- 'MonadHttp', only define instance of 'MonadHttp' in final application.
 -- Another option is to use a @newtype@-wrapped monad stack and define
--- 'MonadHttp' for it. As of version /0.4.0/, the 'Req' monad that follows
--- this strategy is provided out-of-the-box (see below).
+-- 'MonadHttp' for it. As of the version /0.4.0/, the 'Req' monad that
+-- follows this strategy is provided out-of-the-box (see below).
 
 -- | A type class for monads that support performing HTTP requests.
 -- Typically, you only need to define the 'handleHttpException' method
 -- unless you want to tweak 'HttpConfig'.
-class MonadIO m => MonadHttp m where
+class (MonadIO m) => MonadHttp m where
   -- | This method describes how to deal with 'HttpException' that was
   -- caught by the library. One option is to re-throw it if you are OK with
   -- exceptions, but if you prefer working with something like
@@ -648,17 +638,16 @@
   -- 'Control.Monad.Except.throwError'.
   handleHttpException :: HttpException -> m a
 
-  -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
-  -- implementation returns its 'def' value, which is described in the
-  -- documentation for the type. Common usage pattern with manually defined
-  -- 'getHttpConfig' is to return some hard-coded value, or a value
+  -- | Return the 'HttpConfig' to be used when performing HTTP requests.
+  -- Default implementation returns its 'def' value, which is described in
+  -- the documentation for the type. Common usage pattern with manually
+  -- defined 'getHttpConfig' is to return some hard-coded value, or a value
   -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
   -- approach to configuration is desirable.
   getHttpConfig :: m HttpConfig
   getHttpConfig = return defaultHttpConfig
 
--- | 'HttpConfig' contains general and default settings to be used when
--- making HTTP requests.
+-- | 'HttpConfig' contains settings to be used when making HTTP requests.
 data HttpConfig = HttpConfig
   { -- | Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
     -- environment variables are respected, this setting overwrites them.
@@ -702,8 +691,8 @@
     -- | The retry policy to use for request retrying. By default 'def' is
     -- used (see 'RetryPolicyM').
     --
-    -- __Note__: signature of this function was changed in the version
-    -- /1.0.0/.
+    -- __Note__: signature of this function was changed to disallow 'IO' in
+    -- version /1.0.0/ and then changed back to its current form in /3.1.0/.
     --
     -- @since 0.3.0
     httpConfigRetryPolicy :: RetryPolicyM IO,
@@ -725,11 +714,11 @@
     -- | Max length of preview fragment of response body.
     --
     -- @since 3.6.0
-    httpConfigBodyPreviewLength :: forall a. Num a => a
+    httpConfigBodyPreviewLength :: forall a. (Num a) => a
   }
   deriving (Typeable)
 
--- | Default value of 'HttpConfig'.
+-- | The default value of 'HttpConfig'.
 --
 -- @since 2.0.0
 defaultHttpConfig :: HttpConfig
@@ -773,7 +762,7 @@
         LI.requestManagerOverride = httpConfigAltManager
       }
 
--- | A monad that allows to run 'req' in any 'IO'-enabled monad without
+-- | A monad that allows us to run 'req' in any 'IO'-enabled monad without
 -- having to define new instances.
 --
 -- @since 0.4.0
@@ -809,12 +798,87 @@
   handleHttpException = Req . lift . throwIO
   getHttpConfig = Req ask
 
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (AccumT w m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (ContT r m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (ExceptT e m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (IdentityT m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (MaybeT m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (ReaderT r m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (RWS.CPS.RWST r w s m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (RWS.Lazy.RWST r w s m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (RWS.Strict.RWST r w s m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (SelectT r m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (State.Lazy.StateT s m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m) => MonadHttp (State.Strict.StateT s m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (Writer.CPS.WriterT w m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (Writer.Lazy.WriterT w m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
+-- | @since 3.10.0
+instance (MonadHttp m, Monoid w) => MonadHttp (Writer.Strict.WriterT w m) where
+  handleHttpException = lift . handleHttpException
+  getHttpConfig = lift getHttpConfig
+
 -- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
--- case of exceptional situation an 'HttpException' will be thrown.
+-- the case of an exceptional situation an 'HttpException' will be thrown.
 --
 -- @since 0.4.0
 runReq ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | 'HttpConfig' to use
   HttpConfig ->
   -- | Computation to run
@@ -919,7 +983,7 @@
   -- | Return name of the method as a 'ByteString'.
   httpMethodName :: Proxy a -> ByteString
 
-instance HttpMethod method => RequestComponent (Womb "method" method) where
+instance (HttpMethod method) => RequestComponent (Tagged "method" method) where
   getRequestMod _ = Endo $ \x ->
     x {L.method = httpMethodName (Proxy :: Proxy method)}
 
@@ -966,18 +1030,15 @@
 type role Url nominal
 
 -- With template-haskell >=2.15 and text >=1.2.4 Lift can be derived, however
--- the derived lift forgets the type of scheme.
-instance Typeable scheme => TH.Lift (Url scheme) where
+-- the derived lift forgets the type of the scheme.
+instance (Typeable scheme) => TH.Lift (Url scheme) where
   lift url =
     TH.dataToExpQ (fmap liftText . cast) url `TH.sigE` case url of
       Url Http _ -> [t|Url 'Http|]
       Url Https _ -> [t|Url 'Https|]
     where
       liftText t = TH.AppE (TH.VarE 'T.pack) <$> TH.lift (T.unpack t)
-
-#if MIN_VERSION_template_haskell(2,16,0)
-  liftTyped url = TH.TExp <$> TH.lift url
-#endif
+  liftTyped = TH.Code . TH.unsafeTExpCoerce . TH.lift
 
 -- | Given host name, produce a 'Url' which has “http” as its scheme and
 -- empty path. This also sets port to @80@.
@@ -989,14 +1050,14 @@
 https :: Text -> Url 'Https
 https = Url Https . pure
 
--- | Grow given 'Url' appending a single path segment to it. Note that the
+-- | Grow a given 'Url' appending a single path segment to it. Note that the
 -- path segment can be of any type that is an instance of 'ToHttpApiData'.
 infixl 5 /~
 
-(/~) :: ToHttpApiData a => Url scheme -> a -> Url scheme
+(/~) :: (ToHttpApiData a) => Url scheme -> a -> Url scheme
 Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
 
--- | Type-constrained version of @('/~')@ to remove ambiguity in the cases
+-- | A type-constrained version of @('/~')@ to remove ambiguity in the cases
 -- when next URL piece is a 'Data.Text.Text' literal.
 infixl 5 /:
 
@@ -1029,8 +1090,7 @@
   urlHead <- http <$> uriHost uri
   let url = case URI.uriPath uri of
         Nothing -> urlHead
-        Just (_, xs) ->
-          foldl' (/:) urlHead (URI.unRText <$> NE.toList xs)
+        Just uriPath -> uriPathToUrl uriPath urlHead
   return (url, uriOptions uri)
 
 -- | Just like 'useHttpURI', but expects the “https” scheme.
@@ -1042,10 +1102,21 @@
   urlHead <- https <$> uriHost uri
   let url = case URI.uriPath uri of
         Nothing -> urlHead
-        Just (_, xs) ->
-          foldl' (/:) urlHead (URI.unRText <$> NE.toList xs)
+        Just uriPath -> uriPathToUrl uriPath urlHead
   return (url, uriOptions uri)
 
+-- | Convert URI path to a 'Url'. Internal.
+--
+-- @since 3.9.0
+uriPathToUrl ::
+  (Bool, NonEmpty (URI.RText 'URI.PathPiece)) ->
+  Url scheme ->
+  Url scheme
+uriPathToUrl (trailingSlash, xs) urlHead =
+  if trailingSlash then path /: T.empty else path
+  where
+    path = foldl' (/:) urlHead (URI.unRText <$> NE.toList xs)
+
 -- | A combination of 'useHttpURI' and 'useHttpsURI' for cases when scheme
 -- is not known in advance.
 --
@@ -1162,21 +1233,21 @@
 instance HttpBody NoReqBody where
   getRequestBody NoReqBody = L.RequestBodyBS B.empty
 
--- | This body option allows to use a JSON object as request body—probably
--- the most popular format right now. Just wrap a data type that is an
--- instance of 'ToJSON' type class and you are done: it will be converted to
--- JSON and inserted as request body.
+-- | This body option allows us to use a JSON object as the request
+-- body—probably the most popular format right now. Just wrap a data type
+-- that is an instance of 'ToJSON' type class and you are done: it will be
+-- converted to JSON and inserted as request body.
 --
 -- This body option sets the @Content-Type@ header to @\"application/json;
 -- charset=utf-8\"@ value.
 newtype ReqBodyJson a = ReqBodyJson a
 
-instance ToJSON a => HttpBody (ReqBodyJson a) where
+instance (ToJSON a) => HttpBody (ReqBodyJson a) where
   getRequestBody (ReqBodyJson a) = L.RequestBodyLBS (A.encode a)
   getRequestContentType _ = pure "application/json; charset=utf-8"
 
 -- | This body option streams request body from a file. It is expected that
--- the file size does not change during the streaming.
+-- the file size does not change during streaming.
 --
 -- Using of this body option does not set the @Content-Type@ header.
 newtype ReqBodyFile = ReqBodyFile FilePath
@@ -1201,12 +1272,11 @@
 instance HttpBody ReqBodyLbs where
   getRequestBody (ReqBodyLbs bs) = L.RequestBodyLBS bs
 
--- | Form URL-encoded body. This can hold a collection of parameters which
--- are encoded similarly to query parameters at the end of query string,
--- with the only difference that they are stored in request body. The
--- similarity is reflected in the API as well, as you can use the same
--- combinators you would use to add query parameters: @('=:')@ and
--- 'queryFlag'.
+-- | URL-encoded body. This can hold a collection of parameters which are
+-- encoded similarly to query parameters at the end of query string, with
+-- the only difference that they are stored in request body. The similarity
+-- is reflected in the API as well, as you can use the same combinators you
+-- would use to add query parameters: @('=:')@ and 'queryFlag'.
 --
 -- This body option sets the @Content-Type@ header to
 -- @\"application/x-www-form-urlencoded\"@ value.
@@ -1225,7 +1295,14 @@
 instance QueryParam FormUrlEncodedParam where
   queryParam name mvalue =
     FormUrlEncodedParam [(name, toQueryParam <$> mvalue)]
+  queryParamToList (FormUrlEncodedParam p) = p
 
+-- | Use 'formToQuery'.
+--
+-- @since 3.11.0
+instance FromForm FormUrlEncodedParam where
+  fromForm = Right . formToQuery
+
 -- | Multipart form data. Please consult the
 -- "Network.HTTP.Client.MultipartFormData" module for how to construct
 -- parts, then use 'reqBodyMultipart' to create actual request body from the
@@ -1264,7 +1341,7 @@
 -- | Create 'ReqBodyMultipart' request body from a collection of 'LM.Part's.
 --
 -- @since 0.2.0
-reqBodyMultipart :: MonadIO m => [LM.Part] -> m ReqBodyMultipart
+reqBodyMultipart :: (MonadIO m) => [LM.Part] -> m ReqBodyMultipart
 reqBodyMultipart parts = liftIO $ do
   boundary <- LM.webkitBoundary
   body <- LM.renderParts boundary parts
@@ -1292,9 +1369,6 @@
 -- | This type function allows any HTTP body if method says it
 -- 'CanHaveBody'. When the method says it should have 'NoBody', the only
 -- body option to use is 'NoReqBody'.
---
--- __Note__: users of GHC 8.0.1 and later will see a slightly more friendly
--- error message when method does not allow a body and body is provided.
 type family
   HttpBodyAllowed
     (allowsBody :: CanHaveBody)
@@ -1307,8 +1381,8 @@
     TypeError
       ('Text "This HTTP method does not allow attaching a request body.")
 
-instance HttpBody body => RequestComponent (Womb "body" body) where
-  getRequestMod (Womb body) = Endo $ \x ->
+instance (HttpBody body) => RequestComponent (Tagged "body" body) where
+  getRequestMod (Tagged body) = Endo $ \x ->
     x
       { L.requestBody = getRequestBody body,
         L.requestHeaders =
@@ -1337,8 +1411,8 @@
   = Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
 
 -- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
--- Request to avoid appending to existing query string in request every
--- time new parameter is added. Additional Maybe (L.Request -> IO
+-- Request to avoid appending to an existing query string in request every
+-- time new parameter is added. The additional Maybe (L.Request -> IO
 -- L.Request) is a finalizer that will be applied after all other
 -- transformations. This is for authentication methods that sign requests
 -- based on data in Request.
@@ -1353,6 +1427,12 @@
   mempty = Option mempty Nothing
   mappend = (<>)
 
+-- | Use 'formToQuery'.
+--
+-- @since 3.11.0
+instance FromForm (Option scheme) where
+  fromForm = Right . formToQuery
+
 -- | A helper to create an 'Option' that modifies only collection of query
 -- parameters. This helper is not a part of the public API.
 withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
@@ -1371,7 +1451,7 @@
 
 -- | Finalize given 'L.Request' by applying a finalizer from the given
 -- 'Option' (if it has any).
-finalizeRequest :: MonadIO m => Option scheme -> L.Request -> m L.Request
+finalizeRequest :: (MonadIO m) => Option scheme -> L.Request -> m L.Request
 finalizeRequest (Option _ mfinalizer) = liftIO . fromMaybe pure mfinalizer
 
 ----------------------------------------------------------------------------
@@ -1395,7 +1475,7 @@
 (=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
 name =: value = queryParam name (pure value)
 
--- | Construct a flag, that is, valueless query parameter. For example, in
+-- | Construct a flag, that is, a valueless query parameter. For example, in
 -- the following URL @\"a\"@ is a flag, while @\"b\"@ is a query parameter
 -- with a value:
 --
@@ -1404,9 +1484,27 @@
 -- This operator is defined in terms of 'queryParam':
 --
 -- > queryFlag name = queryParam name (Nothing :: Maybe ())
-queryFlag :: QueryParam param => Text -> param
+queryFlag :: (QueryParam param) => Text -> param
 queryFlag name = queryParam name (Nothing :: Maybe ())
 
+-- | Construct query parameters from a 'ToForm' instance. This function
+-- produces the same query params as 'Form.urlEncodeAsFormStable'.
+--
+-- Note that 'Form.Form' doesn't have the concept of parameters with the
+-- empty value (i.e. what you can get by @key =: ""@). If the value is
+-- empty, it will be encoded as a valueless parameter (i.e. what you can get
+-- by @queryFlag key@).
+--
+-- @since 3.11.0
+formToQuery :: (QueryParam param, Monoid param, ToForm f) => f -> param
+formToQuery f = mconcat . fmap toParam . Form.toListStable $ toForm f
+  where
+    toParam (key, val) =
+      queryParam key $
+        if val == ""
+          then Nothing
+          else Just val
+
 -- | A type class for query-parameter-like things. The reason to have an
 -- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
 -- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
@@ -1416,11 +1514,17 @@
   -- 'Nothing', it won't be included at all (i.e. you create a flag this
   -- way). It's recommended to use @('=:')@ and 'queryFlag' instead of this
   -- method, because they are easier to read.
-  queryParam :: ToHttpApiData a => Text -> Maybe a -> param
+  queryParam :: (ToHttpApiData a) => Text -> Maybe a -> param
 
+  -- | Get the query parameter names and values set by 'queryParam'.
+  --
+  -- @since 3.11.0
+  queryParamToList :: param -> [(Text, Maybe Text)]
+
 instance QueryParam (Option scheme) where
   queryParam name mvalue =
     withQueryParams ((:) (name, toQueryParam <$> mvalue))
+  queryParamToList (Option f _) = fst $ appEndo f ([], L.defaultRequest)
 
 ----------------------------------------------------------------------------
 -- Request—Optional parameters—Headers
@@ -1444,6 +1548,14 @@
 attachHeader name value x =
   x {L.requestHeaders = (CI.mk name, value) : L.requestHeaders x}
 
+-- | Same as 'header', but with redacted values on print.
+--
+-- @since 3.13.0
+headerRedacted :: ByteString -> ByteString -> Option scheme
+headerRedacted name value = withRequest $ \x ->
+  let y = attachHeader name value x
+   in y {L.redactHeaders = CI.mk name `S.insert` L.redactHeaders y}
+
 ----------------------------------------------------------------------------
 -- Request—Optional parameters—Cookies
 
@@ -1585,7 +1697,7 @@
 
 -- | Specify the port to connect to explicitly. Normally, 'Url' you use
 -- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
--- 'Option' allows to choose an arbitrary port overwriting the defaults.
+-- 'Option' allows us to choose an arbitrary port overwriting the defaults.
 port :: Int -> Option scheme
 port n = withRequest $ \x ->
   x {L.port = n}
@@ -1600,8 +1712,8 @@
 --
 -- > decompress (const True)
 decompress ::
-  -- | Predicate that is given MIME type, it
-  -- returns 'True' when content should be decompressed on the fly.
+  -- | Predicate that is given MIME type, it returns 'True' when content
+  -- should be decompressed on the fly.
   (ByteString -> Bool) ->
   Option scheme
 decompress f = withRequest $ \x ->
@@ -1651,7 +1763,7 @@
 newtype JsonResponse a = JsonResponse (L.Response a)
   deriving (Show)
 
-instance FromJSON a => HttpResponse (JsonResponse a) where
+instance (FromJSON a) => HttpResponse (JsonResponse a) where
   type HttpResponseBody (JsonResponse a) = a
   toVanillaResponse (JsonResponse r) = r
   getHttpResponse r = do
@@ -1703,7 +1815,7 @@
 ----------------------------------------------------------------------------
 -- Helpers for response interpretations
 
--- | Fetch beginning of response and return it together with new
+-- | Fetch beginning of the response and return it together with a new
 -- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
 -- such.
 grabPreview ::
@@ -1767,14 +1879,14 @@
 
 -- | Get the response body.
 responseBody ::
-  HttpResponse response =>
+  (HttpResponse response) =>
   response ->
   HttpResponseBody response
 responseBody = L.responseBody . toVanillaResponse
 
 -- | Get the response status code.
 responseStatusCode ::
-  HttpResponse response =>
+  (HttpResponse response) =>
   response ->
   Int
 responseStatusCode =
@@ -1782,7 +1894,7 @@
 
 -- | Get the response status message.
 responseStatusMessage ::
-  HttpResponse response =>
+  (HttpResponse response) =>
   response ->
   ByteString
 responseStatusMessage =
@@ -1790,7 +1902,7 @@
 
 -- | Lookup a particular header from a response.
 responseHeader ::
-  HttpResponse response =>
+  (HttpResponse response) =>
   -- | Response interpretation
   response ->
   -- | Header to lookup
@@ -1802,7 +1914,7 @@
 
 -- | Get the response 'L.CookieJar'.
 responseCookieJar ::
-  HttpResponse response =>
+  (HttpResponse response) =>
   response ->
   L.CookieJar
 responseCookieJar = L.responseCookieJar . toVanillaResponse
@@ -1815,9 +1927,9 @@
 -- To create a new response interpretation you just need to make your data
 -- type an instance of the 'HttpResponse' type class.
 
--- | A type class for response interpretations. It allows to describe how to
--- consume response from a @'L.Response' 'L.BodyReader'@ and produce the
--- final result that is to be returned to the user.
+-- | A type class for response interpretations. It allows us to describe how
+-- to consume the response from a @'L.Response' 'L.BodyReader'@ and produce
+-- the final result that is to be returned to the user.
 class HttpResponse response where
   -- | The associated type is the type of body that can be extracted from an
   -- instance of 'HttpResponse'.
@@ -1856,6 +1968,16 @@
   acceptHeader :: Proxy response -> Maybe ByteString
   acceptHeader Proxy = Nothing
 
+-- | This instance has been added to make it easier to inspect 'L.Response'
+-- using Req's functions like 'responseStatusCode', 'responseStatusMessage',
+-- etc.
+--
+-- @since 3.12.0
+instance HttpResponse (L.Response ()) where
+  type HttpResponseBody (L.Response ()) = ()
+  toVanillaResponse = id
+  getHttpResponse = return . void
+
 ----------------------------------------------------------------------------
 -- Other
 
@@ -1881,7 +2003,7 @@
 -- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
 -- which instance to use (i.e. the constraints are taken into account later,
 -- when instance is already chosen).
-newtype Womb (tag :: Symbol) a = Womb a
+newtype Tagged (tag :: Symbol) a = Tagged a
 
 -- | Exceptions that this library throws.
 data HttpException
@@ -1893,6 +2015,20 @@
   deriving (Show, Typeable, Generic)
 
 instance Exception HttpException
+
+-- | Return 'Just' if the given 'HttpException' is wrapping a http-client's
+-- 'L.StatusCodeException'. Otherwise, return 'Nothing'.
+--
+-- @since 3.12.0
+isStatusCodeException :: HttpException -> Maybe (L.Response ())
+isStatusCodeException
+  ( VanillaHttpException
+      ( L.HttpExceptionRequest
+          _
+          (L.StatusCodeException r _)
+        )
+    ) = Just r
+isStatusCodeException _ = Nothing
 
 -- | A simple type isomorphic to 'Bool' that we only have for better error
 -- messages. We use it as a kind and its data constructors as type-level
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -6,8 +6,6 @@
 [![Stackage LTS](http://stackage.org/package/req/badge/lts)](http://stackage.org/lts/package/req)
 ![CI](https://github.com/mrkkrp/req/workflows/CI/badge.svg?branch=master)
 
-* [Motivation and Req vs other libraries](#motivation-and-req-vs-other-libraries)
-* [Unsolved problems](#unsolved-problems)
 * [Related packages](#related-packages)
 * [Blog posts](#blog-posts)
 * [Contribution](#contribution)
@@ -43,49 +41,33 @@
   liftIO $ print (responseBody r :: Value)
 ```
 
-Req is an easy-to-use, type-safe, expandable, high-level HTTP client library
-that just works without any fooling around.
+Req is an HTTP client library that attempts to be easy-to-use, type-safe,
+and expandable.
 
-What does the phrase “easy-to-use” mean? It means that the library is
-designed to be beginner-friendly so it's simple to add to your monad stack,
-intuitive to work with, well-documented, and does not get in your way. Doing
-HTTP requests is a common task and Haskell library for this should be very
-approachable and clear to beginners, thus certain compromises were made. For
-example, one cannot currently modify `ManagerSettings` of the default
-manager because the library always uses the same implicit global manager for
-simplicity and maximal connection sharing. There is a way to use your own
-manager with different settings, but it requires a bit more typing.
+“Easy-to-use” means that the library is designed to be beginner-friendly so
+it's simple to add to your monad stack, intuitive to work with,
+well-documented, and does not get in your way. Doing HTTP requests is a
+common task and a Haskell library for this should be approachable and clear
+to beginners, thus certain compromises were made. For example, one cannot
+currently modify `ManagerSettings` of the default manager because the
+library always uses the same implicit global manager for simplicity and
+maximal connection sharing. There is a way to use your own manager with
+different settings, but it requires more typing.
 
-“Type-safe” means that the library is protective and eliminates certain
-classes of errors. For example, we have correct-by-construction URLs, it's
-guaranteed that the user does not send the request body when using methods
-like GET or OPTIONS, and the amount of implicit assumptions is minimized by
-making the user specify his/her intentions in an explicit form (for example,
-it's not possible to avoid specifying the body or method of request).
+“Type-safe” means that the library tries to eliminate certain classes of
+errors. For example, we have correct-by-construction URLs; it is guaranteed
+that the user does not send the request body when using methods like GET or
+OPTIONS, and the amount of implicit assumptions is minimized by making the
+user specify their intentions in an explicit form. For example, it's not
+possible to avoid specifying the body or the method of a request.
 Authentication methods that assume HTTPS force the user to use HTTPS at the
-type level. The library also carefully hides underlying types from the
-lower-level `http-client` package because those types are not safe enough
-(for example `Request` is an instance of `IsString` and, if it's malformed,
-it will blow up at run-time).
-
-“Expandable” refers to the ability to create new components for dealing with
-HTTP without having to resort to ugly hacking. For example, it's possible to
-define your own HTTP methods, create new ways to construct the body of a
-request, create new authorization options, perform a request in a different
-way, and create your own methods to parse and represent a response. As the
-user extends the library to satisfy his/her special needs, the new solutions
-will work just like the built-ins. However, all of the common cases are also
-covered by the library out-of-the-box.
+type level.
 
-“High-level” means that there are less details to worry about. The library
-is a result of my experiences as a Haskell consultant. Working for several
-clients, who had very different projects, showed me that the library should
-adapt easily to any particular style of writing Haskell applications. For
-example, some people prefer throwing exceptions, while others are concerned
-with purity. Just define `handleHttpException` accordingly when making your
-monad instance of `MonadHttp` and it will play together seamlessly. Finally,
-the library cuts down boilerplate considerably, and helps you write concise,
-easy to read, and maintainable code.
+“Expandable” refers to the ability to create new components without having
+to resort to hacking. For example, it's possible to define your own HTTP
+methods, create new ways to construct the body of a request, create new
+authorization options, perform a request in a different way, and create your
+own methods to parse a response.
 
 The library uses the following mature packages under the hood to guarantee
 you the best experience:
@@ -95,88 +77,10 @@
 * [`http-client-tls`](https://hackage.haskell.org/package/http-client-tls)—TLS
   (HTTPS) support for `http-client`.
 
-It's important to note that since we leverage well-known libraries that the
+It is important to note that since we leverage well-known libraries that the
 whole Haskell ecosystem uses, there is no risk in using Req. The machinery
 for performing requests is the same as with `http-conduit` and Wreq. The
 only difference is the API.
-
-## Motivation and Req vs other libraries
-
-*This section is my opinion and it contains criticisms of other well-known
-libraries. If you're a user/fan of one of these libraries, please remember
-not to react aggressively and respect the fact that I may have different
-views on API design from yours.*
-
-I have spent time to write the library because sending HTTP requests is a
-common need, but there is no high-level library for that in Haskell that I
-could use with pleasure. I'll explain why.
-
-First of all, there is `http-client` and `http-client-tls`. They just work.
-I have no issues with the libraries except that they are too low-level for
-my taste. Indeed, even the docs say that they are low-level and “intended as
-a base layer for more user-friendly packages”. This is exactly how I use
-them in Req, as base level. Req is nothing but a different API to
-`http-client`, so it only works because of the hard work put into
-`http-client`.
-
-`http-conduit` definitely has its place. For one thing it allows you to
-stream request and response bodies in constant memory, what other library
-allows you to do that? On the other hand if you take a look at
-`Network.HTTP.Simple`, then although it's said that it's a “higher level
-API”, it's mostly the same as vanilla `http-client` in spirit/approach and
-just adds `conduit`-powered functions to perform requests and allows to use
-global implicit `Manager` (Req does the same). If I tried to frame what
-exactly I don't like about `http-conduit` in words, then it would be “the
-way requests are constructed”. You *set* parameters instead of *being
-forced* to declare necessary bits and *being allowed* to declare optional
-bits in a way that their combination is valid. Also, with `http-conduit` you
-parse request from a string without the protection of TH that otherwise
-saves the day as in Yesod.
-
-Then there is Wreq. `wreq` [doesn't see much development
-lately](https://github.com/bos/wreq/issues/93). `wreq` is by itself a weird
-library, IMO. You have functions per method—not very good, as there may be
-new methods, like PATCH which is not new but still missing (well, you have
-`customMethod`, but what is the point of having per-method functions if you
-have a more general way to use any method? you should be able to just insert
-methods in the “argument slot” of `customMethod` and end up with a more
-general solution). Now, every method function has a companion that takes
-`Options` (like you have `get` and `getWith`). Why the duplication? Where is
-generality and flexibility? This is not all though, because you cannot
-really use `get` you see in the main module, because you want to have
-connection sharing. Wreq's author does not take the gift of automatic
-connection re-use `Manager` from `http-client` provides, he invents the
-whole new thing of “sessions”. Only inside a session your connections will
-be shared and re-used. However with the session stuff you have yet another
-set of per-method functions like `get` and `getWith`—these are different
-ones, to be used with sessions! Now if you have a multi-threaded app, here
-is a surprise for you: you can't share connections between threads as
-connections are shared only inside of the `withSession` friend and “session
-will no longer be valid after that function returns”. There are valid uses
-for sessions, but the point is that they are just too inconvenient for
-common tasks.
-
-I used `servant-client` a couple of times but the amount of boilerplate it
-requires is frightening. If you have several query parameters, and you use
-just one of them, you'll have to pass lots of `Nothing`s.
-
-## Unsolved problems
-
-AWS request signing is problematic because request body can be in the form
-of an action to execute (and all that “popper” stuff for streaming), not
-just a `ByteString` and so getting its digest (hash) is not trivial without
-running the action and consuming body in its entirety before the request in
-made. In Wreq the author chose to just use `error` when body is not a
-(strict or lazy) `ByteString`. Maybe it's OK for Wreq, but I don't consider
-this a proper solution for Req as we support full variety of body options.
-For example, what if I want to upload 1 Gb file to S3? I want to stream it
-in constant memory but at the same time I need to calculate its hash before
-I start streaming. One solution to the problem seems to be in taking the
-hash explicitly (as an argument of the hypothetical `awsAuth`) and making it
-a responsibility of the user to calculate the hash correctly. I don't like
-this because it's not user-friendly. So the question stays open, for now
-there is no AWS signing functionality provided out-of-the-box. The best
-solution for talking to AWS is the `amazonka` package so far.
 
 ## Related packages
 
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Main (main) where
-
-import Distribution.Simple
-
-main :: IO ()
-main = defaultMain
diff --git a/httpbin-tests/Network/HTTP/ReqSpec.hs b/httpbin-tests/Network/HTTP/ReqSpec.hs
--- a/httpbin-tests/Network/HTTP/ReqSpec.hs
+++ b/httpbin-tests/Network/HTTP/ReqSpec.hs
@@ -1,6 +1,6 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -8,29 +8,28 @@
 module Network.HTTP.ReqSpec (spec) where
 
 import Control.Exception
-import Control.Monad.Reader
+import Control.Monad (forM_)
 import Control.Monad.Trans.Control
 import Data.Aeson (ToJSON (..), Value (..), object, (.=))
-import qualified Data.Aeson as A
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as BL
-import qualified Data.HashMap.Strict as HM
+import Data.Aeson qualified as A
+import Data.Aeson.KeyMap qualified as Aeson.KeyMap
+import Data.ByteString qualified as B
+import Data.ByteString.Lazy qualified as BL
+import Data.Functor.Identity (runIdentity)
+import Data.Maybe (fromJust)
 import Data.Proxy
 import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import qualified Data.Text.IO as TIO
-import qualified Network.HTTP.Client as L
-import qualified Network.HTTP.Client.MultipartFormData as LM
-import Network.HTTP.Req
-import qualified Network.HTTP.Types as Y
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Text.IO qualified as TIO
+import Network.HTTP.Client qualified as L
+import Network.HTTP.Client.MultipartFormData qualified as LM
+import Network.HTTP.Req hiding (req)
+import Network.HTTP.Req qualified as Req
+import Network.HTTP.Types qualified as Y
 import Test.Hspec
 import Test.QuickCheck
 
-#if !MIN_VERSION_base(4,13,0)
-import Data.Semigroup ((<>))
-#endif
-
 spec :: Spec
 spec = do
   describe "exception throwing on non-2xx status codes" $
@@ -51,6 +50,11 @@
         blindlyThrowing (req GET httpbin NoReqBody ignoreResponse mempty)
           `shouldThrow` anyException
 
+  describe "isStatusCodeException" $
+    it "extracts non-2xx response" $
+      req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty
+        `shouldThrow` selector404ByStatusCodeException
+
   describe "receiving user-agent header back" $
     it "works" $ do
       r <-
@@ -81,8 +85,7 @@
               .= object
                 [ "Accept-Encoding" .= ("gzip" :: Text),
                   "Foo" .= ("bar" :: Text),
-                  "Baz" .= ("quux" :: Text),
-                  "Host" .= ("httpbin.org" :: Text)
+                  "Baz" .= ("quux" :: Text)
                 ]
           ]
       responseStatusCode r `shouldBe` 200
@@ -94,11 +97,10 @@
       (stripFunnyHeaders . stripOrigin) (responseBody r)
         `shouldBe` object
           [ "args" .= emptyObject,
-            "url" .= ("https://httpbin.org/get" :: Text),
+            "url" .= ("http://localhost:1234/get" :: Text),
             "headers"
               .= object
-                [ "Accept-Encoding" .= ("gzip" :: Text),
-                  "Host" .= ("httpbin.org" :: Text)
+                [ "Accept-Encoding" .= ("gzip" :: Text)
                 ]
           ]
       responseHeader r "Content-Type" `shouldBe` return "application/json"
@@ -115,12 +117,11 @@
           [ "args" .= emptyObject,
             "json" .= text,
             "data" .= reflected,
-            "url" .= ("https://httpbin.org/post" :: Text),
+            "url" .= ("http://localhost:1234/post" :: Text),
             "headers"
               .= object
                 [ "Content-Type" .= ("application/json; charset=utf-8" :: Text),
                   "Accept-Encoding" .= ("gzip" :: Text),
-                  "Host" .= ("httpbin.org" :: Text),
                   "Content-Length" .= show (T.length reflected)
                 ],
             "files" .= emptyObject,
@@ -138,18 +139,17 @@
             LM.partBS "bar" "bar data!"
           ]
       r <- req POST (httpbin /: "post") body jsonResponse mempty
-      let Just contentType = getRequestContentType body
+      let contentType = fromJust (getRequestContentType body)
       (stripFunnyHeaders . stripOrigin) (responseBody r)
         `shouldBe` object
           [ "args" .= emptyObject,
             "json" .= Null,
             "data" .= ("" :: Text),
-            "url" .= ("https://httpbin.org/post" :: Text),
+            "url" .= ("http://localhost:1234/post" :: Text),
             "headers"
               .= object
                 [ "Content-Type" .= T.decodeUtf8 contentType,
                   "Accept-Encoding" .= ("gzip" :: Text),
-                  "Host" .= ("httpbin.org" :: Text),
                   "Content-Length" .= ("242" :: Text)
                 ],
             "files" .= emptyObject,
@@ -174,11 +174,10 @@
           [ "args" .= emptyObject,
             "json" .= Null,
             "data" .= contents,
-            "url" .= ("https://httpbin.org/patch" :: Text),
+            "url" .= ("http://localhost:1234/patch" :: Text),
             "headers"
               .= object
                 [ "Accept-Encoding" .= ("gzip" :: Text),
-                  "Host" .= ("httpbin.org" :: Text),
                   "Content-Length" .= show (T.length contents)
                 ],
             "files" .= emptyObject,
@@ -200,12 +199,11 @@
           [ "args" .= emptyObject,
             "json" .= Null,
             "data" .= ("" :: Text),
-            "url" .= ("https://httpbin.org/put" :: Text),
+            "url" .= ("http://localhost:1234/put" :: Text),
             "headers"
               .= object
                 [ "Content-Type" .= ("application/x-www-form-urlencoded" :: Text),
                   "Accept-Encoding" .= ("gzip" :: Text),
-                  "Host" .= ("httpbin.org" :: Text),
                   "Content-Length" .= ("18" :: Text)
                 ],
             "files" .= emptyObject,
@@ -263,21 +261,17 @@
   -- TODO /response-headers
   -- TODO /redirect
 
-  -- FIXME Redirects test is temporarily disabled due to
-  --
-  -- https://github.com/postmanlabs/httpbin/issues/617
-
-  -- describe "redirects" $
-  --   it "follows redirects" $ do
-  --     r <-
-  --       req
-  --         GET
-  --         (httpbin /: "redirect-to")
-  --         NoReqBody
-  --         ignoreResponse
-  --         ("url" =: ("https://httpbin.org" :: Text))
-  --     responseStatusCode r `shouldBe` 200
-  --     responseStatusMessage r `shouldBe` "OK"
+  describe "redirects" $
+    it "follows redirects" $ do
+      r <-
+        req
+          GET
+          (httpbin /: "redirect-to")
+          NoReqBody
+          ignoreResponse
+          ("url" =: ("https://httpbin.org" :: Text))
+      responseStatusCode r `shouldBe` 200
+      responseStatusMessage r `shouldBe` "OK"
 
   -- TODO /relative-redicet
   -- TODO /absolute-redirect
@@ -307,7 +301,7 @@
             (httpbin /: "basic-auth" /~ user /~ password)
             NoReqBody
             ignoreResponse
-            (basicAuth (T.encodeUtf8 user) (T.encodeUtf8 password))
+            (basicAuthUnsafe (T.encodeUtf8 user) (T.encodeUtf8 password))
         responseStatusCode r `shouldBe` 200
         responseStatusMessage r `shouldBe` "OK"
 
@@ -394,45 +388,81 @@
 ----------------------------------------------------------------------------
 -- Helpers
 
--- | Run request with such settings that it does not signal error on adverse
--- response status codes.
+-- | Run a request with such settings that it does not signal errors.
 prepareForShit :: Req a -> IO a
 prepareForShit = runReq defaultHttpConfig {httpConfigCheckResponse = noNoise}
   where
     noNoise _ _ _ = Nothing
 
--- | Run request with such settings that it throws on any response.
+-- | Run a request with such settings that it throws on any response.
 blindlyThrowing :: Req a -> IO a
 blindlyThrowing = runReq defaultHttpConfig {httpConfigCheckResponse = doit}
   where
     doit _ _ = error "Oops!"
 
 -- | 'Url' representing <https://httpbin.org>.
-httpbin :: Url 'Https
-httpbin = https "httpbin.org"
+httpbin :: Url 'Http
+httpbin = http "localhost"
 
+req ::
+  ( MonadHttp m,
+    HttpMethod method,
+    HttpBody body,
+    HttpResponse response,
+    HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
+  ) =>
+  method ->
+  Url scheme ->
+  body ->
+  Proxy response ->
+  Option scheme ->
+  m response
+req method url body responseProxy options =
+  Req.req method url body responseProxy (options <> defaultOptions)
+
+-- | Options to apply by default.
+defaultOptions :: Option scheme
+defaultOptions = port 1234
+
 -- | Remove “origin” field from JSON value. Origin may change, we don't want
 -- to depend on that.
 stripOrigin :: Value -> Value
-stripOrigin (Object m) = Object (HM.delete "origin" m)
+stripOrigin (Object m) = Object (Aeson.KeyMap.delete "origin" m)
 stripOrigin value = value
 
 -- | Remove funny headers that might break the tests.
 stripFunnyHeaders :: Value -> Value
-stripFunnyHeaders (Object m) =
-  let f (Object p) = Object $ HM.filterWithKey (\k _ -> k `elem` hs) p
-      f value = value
-      hs =
-        [ "Content-Type",
-          "Accept-Encoding",
-          "Host",
-          "Content-Length",
-          "Foo",
-          "Baz"
-        ]
-   in Object (HM.adjust f "headers" m)
-stripFunnyHeaders value = value
+stripFunnyHeaders = \case
+  Object m ->
+    Object
+      ( runIdentity
+          ( Aeson.KeyMap.alterF
+              (pure . fmap stripFunnyHeaders')
+              "headers"
+              m
+          )
+      )
+  value -> value
 
+-- | Similar to 'stripFunnyHeaders', but acts directly on the argument
+-- without trying to access its "headers" field.
+stripFunnyHeaders' :: Value -> Value
+stripFunnyHeaders' = \case
+  Object p ->
+    Object $
+      Aeson.KeyMap.filterWithKey
+        (\k _ -> k `elem` whitelistedHeaders)
+        p
+  value -> value
+  where
+    whitelistedHeaders =
+      [ "Content-Type",
+        "Accept-Encoding",
+        "Content-Length",
+        "Foo",
+        "Baz"
+      ]
+
 -- | This is a complete test case that makes use of <https://httpbin.org> to
 -- get various response status codes.
 checkStatusCode :: Int -> SpecWith ()
@@ -461,12 +491,19 @@
     L.responseStatus response == Y.status404 && not (B.null chunk)
 selector404 _ = False
 
--- | Empty JSON 'Object'.
+-- | Same as 'selector404' except that it uses 'isStatusCodeException'.
+selector404ByStatusCodeException :: HttpException -> Bool
+selector404ByStatusCodeException e =
+  case isStatusCodeException e of
+    Nothing -> False
+    Just r -> responseStatusCode r == 404
+
+-- | The empty JSON 'Object'.
 emptyObject :: Value
-emptyObject = Object HM.empty
+emptyObject = Object Aeson.KeyMap.empty
 
--- | Get rendered JSON value as 'Text'.
-reflectJSON :: ToJSON a => a -> Text
+-- | Get a rendered JSON value as 'Text'.
+reflectJSON :: (ToJSON a) => a -> Text
 reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
 
 -- | Clarify to the type checker that the inner computation is in the 'IO'
diff --git a/pure-tests/Network/HTTP/ReqSpec.hs b/pure-tests/Network/HTTP/ReqSpec.hs
--- a/pure-tests/Network/HTTP/ReqSpec.hs
+++ b/pure-tests/Network/HTTP/ReqSpec.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE FlexibleInstances #-}
@@ -14,43 +13,41 @@
 
 module Network.HTTP.ReqSpec (spec) where
 
-import qualified Blaze.ByteString.Builder as BB
+import Blaze.ByteString.Builder qualified as BB
 import Control.Exception (throwIO)
 import Control.Monad
 import Control.Retry
 import Data.Aeson (ToJSON (..))
-import qualified Data.Aeson as A
+import Data.Aeson qualified as A
 import Data.ByteString (ByteString)
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Char8 as B8
-import qualified Data.ByteString.Lazy as BL
-import qualified Data.CaseInsensitive as CI
+import Data.ByteString qualified as B
+import Data.ByteString.Char8 qualified as B8
+import Data.ByteString.Lazy qualified as BL
+import Data.CaseInsensitive qualified as CI
 import Data.Either (isRight)
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
 import Data.Proxy
 import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
 import Data.Time
 import Data.Typeable (Typeable, eqT)
+import GHC.Exts (IsList (..))
 import GHC.Generics
-import qualified Language.Haskell.TH as TH
-import qualified Language.Haskell.TH.Quote as TH
-import qualified Network.HTTP.Client as L
+import Language.Haskell.TH qualified as TH
+import Language.Haskell.TH.Quote qualified as TH
+import Network.HTTP.Client qualified as L
 import Network.HTTP.Req
-import qualified Network.HTTP.Types as Y
-import qualified Network.HTTP.Types.Header as Y
+import Network.HTTP.Types qualified as Y
+import Network.HTTP.Types.Header qualified as Y
 import Test.Hspec
 import Test.Hspec.Core.Spec (SpecM)
 import Test.QuickCheck
 import Text.URI (URI)
-import qualified Text.URI as URI
-import qualified Text.URI.QQ as QQ
-
-#if !MIN_VERSION_base(4,13,0)
-import Data.Semigroup ((<>))
-#endif
+import Text.URI qualified as URI
+import Text.URI.QQ qualified as QQ
+import Web.FormUrlEncoded qualified as F
 
 spec :: Spec
 spec = do
@@ -164,11 +161,13 @@
           let uriHttp = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
               uriHttps = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
           requestHttp <-
-            let Left (url', options) = fromJust (useURI uriHttp)
-             in req_ GET url' NoReqBody options
+            case fromJust (useURI uriHttp) of
+              Left (url', options) -> req_ GET url' NoReqBody options
+              _ -> error "(useURI uriHttp) should have returned Left"
           requestHttps <-
-            let Right (url', options) = fromJust (useURI uriHttps)
-             in req_ GET url' NoReqBody options
+            case fromJust (useURI uriHttps) of
+              Right (url', options) -> req_ GET url' NoReqBody options
+              _ -> error "(useURI uriHttps) should have returned Right"
           L.host requestHttp `shouldBe` uriHost uriHttp
           L.host requestHttps `shouldBe` uriHost uriHttps
           L.port requestHttp `shouldBe` uriPort 80 uriHttp
@@ -238,6 +237,19 @@
             L.RequestBodyLBS x -> x `shouldBe` renderQuery params
             _ -> expectationFailure "Wrong request body constructor."
 
+  describe "query params" $ do
+    describe "FormUrlEncodedParam" $ do
+      describe "formToQuery" $ do
+        it "should produce the same parameters as F.urlEncodeFormStable" $
+          property $ \form -> do
+            request <- req_ POST url (ReqBodyUrlEnc $ formToQuery form) mempty
+            case L.requestBody request of
+              L.RequestBodyLBS x -> x `shouldBe` F.urlEncodeFormStable form
+              _ -> expectationFailure "Wrong request body constructor"
+      specParamToList (Proxy :: Proxy FormUrlEncodedParam)
+    describe "Option" $ do
+      specParamToList (Proxy :: Proxy (Option 'Http))
+
   describe "optional parameters" $ do
     describe "header" $ do
       it "sets specified header value" $
@@ -266,11 +278,7 @@
       it "cookie jar is set without modifications" $
         property $ \cjar -> do
           request <- req_ GET url NoReqBody (cookieJar cjar)
-#if MIN_VERSION_http_client(0,7,0)
           L.cookieJar request `shouldSatisfy` (maybe False (L.equalCookieJar cjar))
-#else
-          L.cookieJar request `shouldBe` Just cjar
-#endif
     describe "basicAuth" $ do
       it "sets Authorization header to correct value" $
         property $ \username password -> do
@@ -405,7 +413,7 @@
         -- 'Https, Option _) type checks, so we can catch if the type of scheme
         -- is unspecified.
         let testTypeOfQuoterResult ::
-              forall a s. Typeable a => (a, Option s) -> Bool
+              forall a s. (Typeable a) => (a, Option s) -> Bool
             testTypeOfQuoterResult _ = isJust $ eqT @a @(Url 'Https)
          in property $ testTypeOfQuoterResult [urlQ|https://example.org/|]
       it "doesn't work for invalid urls" $
@@ -495,6 +503,9 @@
 instance Arbitrary DiffTime where
   arbitrary = secondsToDiffTime <$> arbitrary
 
+instance Arbitrary F.Form where
+  arbitrary = (F.Form . fromList) <$> arbitrary
+
 ----------------------------------------------------------------------------
 -- Helper types
 
@@ -562,13 +573,14 @@
   maybe def (fromIntegral) $
     either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
 
--- | Get path from 'URI'.
+-- | Get the path from a 'URI'.
 uriPath :: URI -> ByteString
 uriPath uri = fromMaybe "" $ do
-  (_, xs) <- URI.uriPath uri
-  (return . encodePathPieces . fmap URI.unRText . NE.toList) xs
+  (trailingSlash, xs) <- URI.uriPath uri
+  let pref = (encodePathPieces . fmap URI.unRText . NE.toList) xs
+  return $ if trailingSlash then pref <> "/" else pref
 
--- | Get query string from 'URI'.
+-- | Get the query string from a 'URI'.
 uriQuery :: URI -> ByteString
 uriQuery uri = do
   let liftQueryParam = \case
@@ -606,3 +618,15 @@
 basicProxyAuthHeader username password =
   fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
     L.applyBasicProxyAuth username password L.defaultRequest
+
+-- | Spec about 'paramToList' for the type @p@.
+specParamToList :: (QueryParam p, Monoid p) => Proxy p -> Spec
+specParamToList typeProxy = do
+  describe "paramToList" $ do
+    it "should reproduce the parameters given by queryParam" $
+      property $ \(QueryParams params) -> do
+        let queryParam0 =
+              (mconcat $ fmap (uncurry queryParam) params)
+                `asProxyTypeOf` typeProxy
+            got = queryParamToList queryParam0
+        got `shouldBe` params
diff --git a/req.cabal b/req.cabal
--- a/req.cabal
+++ b/req.cabal
@@ -1,19 +1,15 @@
-cabal-version:   1.18
+cabal-version:   2.4
 name:            req
-version:         3.8.0
-license:         BSD3
+version:         3.13.4
+license:         BSD-3-Clause
 license-file:    LICENSE.md
 maintainer:      Mark Karpov <markkarpov92@gmail.com>
 author:          Mark Karpov <markkarpov92@gmail.com>
-tested-with:     ghc ==8.6.5 ghc ==8.8.4 ghc ==8.10.2
+tested-with:     ghc ==9.6.3 ghc ==9.8.2 ghc ==9.10.1
 homepage:        https://github.com/mrkkrp/req
 bug-reports:     https://github.com/mrkkrp/req/issues
-synopsis:
-    Easy-to-use, type-safe, expandable, high-level HTTP client library
-
-description:
-    Easy-to-use, type-safe, expandable, high-level HTTP client library.
-
+synopsis:        HTTP client library
+description:     HTTP client library.
 category:        Network, Web
 build-type:      Simple
 data-files:
@@ -35,97 +31,97 @@
 
 library
     exposed-modules:  Network.HTTP.Req
-    default-language: Haskell2010
+    default-language: GHC2021
     build-depends:
-        aeson >=0.9 && <1.6,
-        authenticate-oauth >=1.5 && <1.7,
-        base >=4.12 && <5.0,
+        aeson >=0.9 && <3,
+        authenticate-oauth >=1.5 && <1.8,
+        base >=4.15 && <5,
         blaze-builder >=0.3 && <0.5,
-        bytestring >=0.10.8 && <0.11,
+        bytestring >=0.10.8 && <0.13,
         case-insensitive >=0.2 && <1.3,
-        connection >=0.2.2 && <0.4,
+        containers >=0.5 && <0.7,
+        crypton-connection >=0.3 && <0.5,
+        data-default-class,
         exceptions >=0.6 && <0.11,
-        http-api-data >=0.2 && <0.5,
-        http-client >=0.5 && <0.8,
+        http-api-data >=0.2 && <0.7,
+        http-client >=0.7.13.1 && <0.8,
         http-client-tls >=0.3.2 && <0.4,
-        http-types >=0.8 && <10.0,
+        http-types >=0.8 && <10,
         modern-uri >=0.3 && <0.4,
         monad-control >=1.0 && <1.1,
         mtl >=2.0 && <3.0,
-        retry >=0.8 && <0.9,
-        template-haskell >=2.14 && <2.17,
-        text >=0.2 && <1.3,
-        time >=1.2 && <1.10,
-        transformers >=0.4 && <0.6,
-        transformers-base -any,
+        retry >=0.8 && <0.10,
+        template-haskell >=2.19 && <2.23,
+        text >=0.2 && <2.2,
+        transformers >=0.5.3.0 && <0.7,
+        transformers-base,
         unliftio-core >=0.1.1 && <0.3
 
     if flag(dev)
-        ghc-options: -O0 -Wall -Werror
+        ghc-options:
+            -Wall -Werror -Wpartial-fields -Wunused-packages
+            -Wno-unused-imports
 
     else
         ghc-options: -O2 -Wall
 
-    if flag(dev)
-        ghc-options:
-            -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns
-            -Wnoncanonical-monad-instances
-
 test-suite pure-tests
-    type:             exitcode-stdio-1.0
-    main-is:          Spec.hs
-    build-tools:      hspec-discover >=2.0 && <3.0
-    hs-source-dirs:   pure-tests
-    other-modules:    Network.HTTP.ReqSpec
-    default-language: Haskell2010
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    build-tool-depends: hspec-discover:hspec-discover >=2 && <3
+    hs-source-dirs:     pure-tests
+    other-modules:      Network.HTTP.ReqSpec
+    default-language:   GHC2021
     build-depends:
-        QuickCheck >=2.7 && <3.0,
-        aeson >=0.9 && <1.6,
-        base >=4.12 && <5.0,
+        QuickCheck >=2.7 && <3,
+        aeson >=0.9 && <3,
+        base >=4.15 && <5.0,
         blaze-builder >=0.3 && <0.5,
-        bytestring >=0.10.8 && <0.11,
+        bytestring >=0.10.8 && <0.13,
         case-insensitive >=0.2 && <1.3,
-        hspec >=2.0 && <3.0,
-        hspec-core >=2.0 && <3.0,
-        http-client >=0.5 && <0.8,
-        http-types >=0.8 && <10.0,
+        hspec >=2.0 && <3,
+        hspec-core >=2.0 && <3,
+        http-api-data >=0.2 && <0.7,
+        http-client >=0.7 && <0.8,
+        http-types >=0.8 && <10,
         modern-uri >=0.3 && <0.4,
-        mtl >=2.0 && <3.0,
-        req -any,
-        retry >=0.8 && <0.9,
-        template-haskell >=2.14 && <2.17,
-        text >=0.2 && <1.3,
-        time >=1.2 && <1.10
+        req,
+        retry >=0.8 && <0.10,
+        template-haskell >=2.19 && <2.23,
+        text >=0.2 && <2.2,
+        time >=1.2 && <1.13
 
     if flag(dev)
-        ghc-options: -O0 -Wall -Werror
+        ghc-options:
+            -Wall -Werror -Wredundant-constraints -Wpartial-fields
+            -Wunused-packages
 
     else
         ghc-options: -O2 -Wall
 
 test-suite httpbin-tests
-    type:             exitcode-stdio-1.0
-    main-is:          Spec.hs
-    build-tools:      hspec-discover >=2.0 && <3.0
-    hs-source-dirs:   httpbin-tests
-    other-modules:    Network.HTTP.ReqSpec
-    default-language: Haskell2010
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    build-tool-depends: hspec-discover:hspec-discover >=2 && <3
+    hs-source-dirs:     httpbin-tests
+    other-modules:      Network.HTTP.ReqSpec
+    default-language:   GHC2021
     build-depends:
-        QuickCheck >=2.7 && <3.0,
-        aeson >=0.9 && <1.6,
-        base >=4.12 && <5.0,
-        bytestring >=0.10.8 && <0.11,
+        QuickCheck >=2.7 && <3,
+        aeson >=2 && <3,
+        base >=4.15 && <5,
+        bytestring >=0.10.8 && <0.13,
         hspec >=2.0 && <3.0,
-        http-client >=0.5 && <0.8,
-        http-types >=0.8 && <10.0,
+        http-client >=0.7 && <0.8,
+        http-types >=0.8 && <10,
         monad-control >=1.0 && <1.1,
-        mtl >=2.0 && <3.0,
-        req -any,
-        text >=0.2 && <1.3,
-        unordered-containers >=0.2.5 && <0.3
+        req,
+        text >=0.2 && <2.2
 
     if flag(dev)
-        ghc-options: -O0 -Wall -Werror
+        ghc-options:
+            -Wall -Werror -Wredundant-constraints -Wpartial-fields
+            -Wunused-packages
 
     else
-        ghc-options: -O2 -Wall
+        buildable: False
