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: f00bb2bf467fa6bce7f0b14ea41b95633520a912a4753bf0116ab7e2f2576b34
+-- hash: f9e791b7b53dd965cce315e225098b21908c0e5a7fbe783cb870fb8656dee283
 
 name:           antiope-s3
-version:        6.0.2
+version:        6.0.3
 description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>
 category:       Services
 homepage:       https://github.com/arbor/antiope#readme
@@ -26,12 +26,6 @@
   location: https://github.com/arbor/antiope
 
 library
-  exposed-modules:
-      Antiope.S3
-      Antiope.S3.Internal
-      Antiope.S3.Types
-  other-modules:
-      Paths_antiope_s3
   hs-source-dirs:
       src
   default-extensions: BangPatterns GeneralizedNewtypeDeriving OverloadedStrings TupleSections
@@ -53,13 +47,17 @@
     , resourcet
     , text
     , unliftio-core
+  exposed-modules:
+      Antiope.S3
+      Antiope.S3.Internal
+      Antiope.S3.Types
+  other-modules:
+      Paths_antiope_s3
   default-language: Haskell2010
 
 test-suite antiope-s3-test
   type: exitcode-stdio-1.0
   main-is: Spec.hs
-  other-modules:
-      Paths_antiope_s3
   hs-source-dirs:
       test
   default-extensions: BangPatterns GeneralizedNewtypeDeriving OverloadedStrings TupleSections
@@ -82,4 +80,6 @@
     , resourcet
     , text
     , unliftio-core
+  other-modules:
+      Paths_antiope_s3
   default-language: Haskell2010
diff --git a/src/Antiope/S3.hs b/src/Antiope/S3.hs
--- a/src/Antiope/S3.hs
+++ b/src/Antiope/S3.hs
@@ -32,13 +32,13 @@
 import Data.Conduit.Combinators     as CC (concatMap)
 import Data.Conduit.List            (unfoldM)
 import Data.Monoid                  ((<>))
-import Data.Text                    (Text, pack, unpack)
+import Data.Text                    as T (Text, null, pack, unpack)
 import Network.AWS                  (Error (..), MonadAWS, ServiceError (..))
 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)
+import Network.URI                  (URI (..), URIAuth (..), parseURI, unEscapeString)
 
 import qualified Data.ByteString as BS
 import qualified Network.AWS     as AWS
@@ -50,9 +50,14 @@
 fromS3Uri uri = do
   puri <- parseURI (unpack uri)
   auth <- puri & uriAuthority
-  let b = pack $ auth & uriRegName       -- URI lib is pretty weird
-  let k = pack $ drop 1 $ puri & uriPath
-  pure $ S3Uri (BucketName b) (ObjectKey k)
+  let b = pack $ unEscapeString $ auth & uriRegName       -- URI lib is pretty weird
+  let k = pack $ unEscapeString $ drop 1 $ puri & uriPath
+  S3Uri <$> (BucketName <$> checkEmpty b)
+        <*> (ObjectKey <$> checkEmpty k)
+  where
+    checkEmpty t
+      | T.null t  = Nothing
+      | otherwise = Just t
 
 downloadLBS :: MonadAWS m
   => BucketName
diff --git a/src/Antiope/S3/Types.hs b/src/Antiope/S3/Types.hs
--- a/src/Antiope/S3/Types.hs
+++ b/src/Antiope/S3/Types.hs
@@ -22,6 +22,7 @@
 import GHC.Generics
 import Network.AWS.Data
 import Network.AWS.S3            (BucketName (..), ObjectKey (..))
+import Network.URI               (unEscapeString)
 
 import qualified Data.Text                       as T
 import qualified Network.AWS.S3.Types            as X
@@ -66,5 +67,5 @@
   readsPrec = RP.readPrec_to_S $ do
     _  <- readString "s3://"
     bn <- readBucketName
-    ok <- ObjectKey . T.pack <$> readWhile (/= ' ')
+    ok <- ObjectKey . T.pack . unEscapeString . drop 1 <$> readWhile (/= ' ')
     return (S3Uri bn ok)
