diff --git a/antiope-s3.cabal b/antiope-s3.cabal
--- a/antiope-s3.cabal
+++ b/antiope-s3.cabal
@@ -2,10 +2,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: b7cf7304b5bae80126c4ada18b9c5026d495b4687dbf94483446682c5dd9ea72
+-- hash: cb9253395f0d7044cd5476617e13b349681f71983576c96f9c54dc1fd444ecfc
 
 name:           antiope-s3
-version:        6.0.5
+version:        6.1.0
 description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>
 category:       Services
 homepage:       https://github.com/arbor/antiope#readme
@@ -28,7 +28,10 @@
 library
   exposed-modules:
       Antiope.S3
+      Antiope.S3.GetObject
       Antiope.S3.Internal
+      Antiope.S3.Lazy
+      Antiope.S3.Strict
       Antiope.S3.Types
   other-modules:
       Paths_antiope_s3
@@ -40,6 +43,7 @@
       amazonka >=1.6
     , amazonka-core >=1.6
     , amazonka-s3 >=1.6
+    , antiope-core
     , base >=4.7 && <5
     , bytestring
     , conduit
diff --git a/src/Antiope/S3.hs b/src/Antiope/S3.hs
--- a/src/Antiope/S3.hs
+++ b/src/Antiope/S3.hs
@@ -2,10 +2,7 @@
 {-# LANGUAGE TupleSections       #-}
 
 module Antiope.S3
-( downloadLBS
-, downloadLBS'
-, downloadS3Uri
-, s3ObjectSource
+( s3ObjectSource
 , putFile, putContent , putContent'
 , copySingle
 , fromS3Uri
@@ -23,21 +20,17 @@
 import Antiope.S3.Types             (S3Uri (S3Uri))
 import Control.Lens
 import Control.Monad
-import Control.Monad.Catch          (catch)
 import Control.Monad.Trans.AWS      hiding (send)
 import Control.Monad.Trans.Resource
-import Data.ByteString.Lazy         (ByteString, empty)
 import Data.Conduit
 import Data.Conduit.Combinators     as CC (concatMap)
-import Data.Conduit.Lazy            (lazyConsume)
 import Data.Conduit.List            (unfoldM)
 import Data.Monoid                  ((<>))
 import Data.Text                    as T (Text, pack, unpack)
-import Network.AWS                  (Error (..), MonadAWS, ServiceError (..))
+import Network.AWS                  (MonadAWS)
 import Network.AWS.Data
 import Network.AWS.Data.Body        (_streamBody)
 import Network.AWS.S3
-import Network.HTTP.Types.Status    (Status (..))
 import Network.URI                  (URI (..), URIAuth (..), parseURI, unEscapeString)
 
 import qualified Data.ByteString      as BS
@@ -55,31 +48,6 @@
   let k = pack $ unEscapeString $ drop 1 $ puri & uriPath
   pure $ S3Uri (BucketName b) (ObjectKey k)
 
-downloadLBS :: (MonadAWS m, MonadResource m)
-  => BucketName
-  -> ObjectKey
-  -> m ByteString
-downloadLBS bucketName objectKey = do
-  resp <- AWS.send $ getObject bucketName objectKey
-  liftResourceT $ LBS.fromChunks <$> lazyConsume (_streamBody $ resp ^. gorsBody)
-
-downloadLBS' :: (MonadAWS m, MonadResource m)
-  => BucketName
-  -> ObjectKey
-  -> m (Maybe ByteString)
-downloadLBS' bucketName objectKey = do
-  ebs <- (Right <$> downloadLBS bucketName objectKey) `catch` \(err :: Error) -> case err of
-    (ServiceError (ServiceError' _ (Status 404 _) _ _ _ _)) -> return (Left empty)
-    _                                                       -> throwM err
-  case ebs of
-    Right bs -> return (Just bs)
-    Left _   -> return Nothing
-
-downloadS3Uri :: (MonadAWS m, MonadResource m)
-  => S3Uri
-  -> m (Maybe ByteString)
-downloadS3Uri (S3Uri b k) = downloadLBS' b k
-
 s3ObjectSource :: (MonadAWS m, MonadResource m)
   => BucketName
   -> ObjectKey
@@ -101,13 +69,13 @@
 putContent :: MonadAWS m
   => BucketName
   -> ObjectKey
-  -> ByteString
+  -> LBS.ByteString
   -> m (Maybe ETag)
 putContent b k c = view porsETag <$> AWS.send (putObject b k (toBody c))
 
 putContent' :: MonadAWS m
   => S3Uri
-  -> ByteString
+  -> LBS.ByteString
   -> m (Maybe ETag)
 putContent' (S3Uri b k) = putContent b k
 
diff --git a/src/Antiope/S3/GetObject.hs b/src/Antiope/S3/GetObject.hs
new file mode 100644
--- /dev/null
+++ b/src/Antiope/S3/GetObject.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Antiope.S3.GetObject
+  ( lazyByteString
+  , getObject
+  , getObjectForS3Uri
+  ) where
+
+import Antiope.S3.Types             (S3Uri (S3Uri))
+import Control.Monad.Trans.AWS      hiding (send)
+import Control.Monad.Trans.Resource
+import Data.Conduit.Lazy            (lazyConsume)
+import Network.AWS                  (MonadAWS)
+import Network.AWS.Data.Body        (_streamBody)
+
+import qualified Data.ByteString.Lazy as LBS
+import qualified Network.AWS          as AWS
+import qualified Network.AWS.S3       as AWS
+
+-- | Access the response body as a lazy bytestring
+lazyByteString :: MonadResource m => RsBody -> m LBS.ByteString
+lazyByteString rsBody = liftResourceT $ LBS.fromChunks <$> lazyConsume (_streamBody rsBody)
+
+getObject :: MonadAWS m
+  => AWS.BucketName
+  -> AWS.ObjectKey
+  -> m (AWS.Rs AWS.GetObject)
+getObject bucketName objectKey = AWS.send $ AWS.getObject bucketName objectKey
+
+getObjectForS3Uri :: MonadAWS m
+  => S3Uri
+  -> m AWS.GetObjectResponse
+getObjectForS3Uri (S3Uri bucketName objectKey) = getObject bucketName objectKey
diff --git a/src/Antiope/S3/Lazy.hs b/src/Antiope/S3/Lazy.hs
new file mode 100644
--- /dev/null
+++ b/src/Antiope/S3/Lazy.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Antiope.S3.Lazy
+  ( unsafeDownload
+  , download
+  , downloadFromS3Uri
+  ) where
+
+import Antiope.S3.GetObject
+import Antiope.S3.Types             (S3Uri (S3Uri))
+import Control.Lens
+import Control.Monad.Catch          (catch)
+import Control.Monad.Trans.Resource
+import Network.AWS                  (Error (..), MonadAWS, ServiceError (..))
+import Network.HTTP.Types.Status    (Status (..))
+
+import qualified Data.ByteString.Lazy as LBS
+import qualified Network.AWS.S3       as AWS
+
+unsafeDownload :: (MonadAWS m, MonadResource m)
+  => AWS.BucketName
+  -> AWS.ObjectKey
+  -> m LBS.ByteString
+unsafeDownload bucketName objectKey = do
+  resp <- getObject bucketName objectKey
+  lazyByteString (resp ^. AWS.gorsBody)
+
+download :: (MonadAWS m, MonadResource m)
+  => AWS.BucketName
+  -> AWS.ObjectKey
+  -> m (Maybe LBS.ByteString)
+download bucketName objectKey = do
+  ebs <- (Right <$> unsafeDownload bucketName objectKey) `catch` \(err :: Error) -> case err of
+    (ServiceError (ServiceError' _ (Status 404 _) _ _ _ _)) -> return (Left LBS.empty)
+    _                                                       -> throwM err
+  case ebs of
+    Right bs -> return (Just bs)
+    Left _   -> return Nothing
+
+downloadFromS3Uri :: (MonadAWS m, MonadResource m)
+  => S3Uri
+  -> m (Maybe LBS.ByteString)
+downloadFromS3Uri (S3Uri b k) = download b k
diff --git a/src/Antiope/S3/Strict.hs b/src/Antiope/S3/Strict.hs
new file mode 100644
--- /dev/null
+++ b/src/Antiope/S3/Strict.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Antiope.S3.Strict
+  ( unsafeDownload
+  , download
+  , downloadFromS3Uri
+  ) where
+
+import Antiope.Core                 ()
+import Antiope.S3.Types             (S3Uri)
+import Control.Monad.Trans.AWS      hiding (send)
+import Control.Monad.Trans.Resource
+import Network.AWS                  (MonadAWS)
+
+import qualified Antiope.S3.Lazy      as LBS
+import qualified Data.ByteString      as BS
+import qualified Data.ByteString.Lazy as LBS
+import qualified Network.AWS.S3       as AWS
+
+unsafeDownload :: (MonadAWS m, MonadUnliftIO m)
+  => AWS.BucketName
+  -> AWS.ObjectKey
+  -> m BS.ByteString
+unsafeDownload bucketName objectKey = runResourceT $ LBS.toStrict <$> LBS.unsafeDownload bucketName objectKey
+
+download :: (MonadAWS m, MonadUnliftIO m)
+  => AWS.BucketName
+  -> AWS.ObjectKey
+  -> m (Maybe BS.ByteString)
+download bucketName objectKey = runResourceT $ fmap LBS.toStrict <$> LBS.download bucketName objectKey
+
+downloadFromS3Uri :: (MonadAWS m, MonadUnliftIO m)
+  => S3Uri
+  -> m (Maybe BS.ByteString)
+downloadFromS3Uri s3Uri = runResourceT $ fmap LBS.toStrict <$> LBS.downloadFromS3Uri s3Uri
diff --git a/test/Antiope/S3Spec.hs b/test/Antiope/S3Spec.hs
--- a/test/Antiope/S3Spec.hs
+++ b/test/Antiope/S3Spec.hs
@@ -2,7 +2,6 @@
 
 import Antiope.Core
 import Antiope.Env
-import Antiope.S3
 import Control.Monad.IO.Class
 import Data.Maybe                  (fromJust)
 import HaskellWorks.Hspec.Hedgehog
@@ -10,6 +9,7 @@
 import System.Environment          (lookupEnv)
 import Test.Hspec
 
+import qualified Antiope.S3.Lazy            as LBS
 import qualified Data.ByteString.Lazy.Char8 as LBS
 
 {-# ANN module ("HLint: Ignore Redundant do" :: String) #-}
@@ -22,7 +22,7 @@
       Just remoteS3Uri -> do
         env <- liftIO $ mkEnv Oregon (const LBS.putStrLn)
         liftIO $ runResourceT $ do
-          mlbs <- runAws env $ downloadS3Uri (read remoteS3Uri)
+          mlbs <- runAws env $ LBS.downloadFromS3Uri (read remoteS3Uri)
           let lbs = fromJust mlbs
           let !_ = LBS.take 10 lbs
           return ()
