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: ffe49f5ed92045faacaef34f9862c4d951852c4549f7f680a2771e2b2a77400e
+-- hash: b136f26caee3f70a03b45b3a3210ac5a8432285cc62b59f1ceb669e17839bded
 
 name:           antiope-s3
-version:        6.1.2
+version:        6.1.3
 description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>
 category:       Services
 homepage:       https://github.com/arbor/antiope#readme
@@ -44,6 +44,7 @@
     , amazonka-core >=1.6
     , amazonka-s3 >=1.6
     , antiope-core
+    , attoparsec
     , base >=4.7 && <5
     , bytestring
     , conduit
@@ -64,6 +65,7 @@
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
+      Antiope.S3.TypesSpec
       Antiope.S3Spec
       Paths_antiope_s3
   hs-source-dirs:
@@ -76,6 +78,7 @@
     , amazonka-s3 >=1.6
     , antiope-core
     , antiope-s3
+    , attoparsec
     , base >=4.7 && <5
     , bytestring
     , conduit
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
@@ -12,6 +12,7 @@
   ) where
 
 import Antiope.S3.Internal
+import Control.Applicative
 import Control.Lens
 import Control.Monad
 import Control.Monad.Logger      (ToLogStr (..))
@@ -24,6 +25,8 @@
 import Network.AWS.S3            (BucketName (..), ObjectKey (..))
 import Network.URI               (unEscapeString)
 
+import qualified Data.Attoparsec.Combinator      as DAC
+import qualified Data.Attoparsec.Text            as DAT
 import qualified Data.Text                       as T
 import qualified Network.AWS.S3.Types            as X
 import qualified Text.ParserCombinators.ReadPrec as RP
@@ -32,6 +35,15 @@
   { bucket    :: BucketName
   , objectKey :: ObjectKey
   } deriving (Show, Eq, Generic)
+
+instance FromText S3Uri where
+  parser = do
+    _  <- DAT.string "s3://"
+    bn <- BucketName . T.pack <$> DAC.many1 (DAT.satisfy (\c -> c /= '/' && c /= ' '))
+    _  <- optional (DAT.char '/')
+    ok <- ObjectKey . T.pack <$> many DAT.anyChar
+    DAT.endOfInput
+    return (S3Uri bn ok)
 
 instance ToText S3Uri where
   toText loc = toS3Uri (loc ^. the @"bucket") (loc ^. the @"objectKey")
diff --git a/test/Antiope/S3/TypesSpec.hs b/test/Antiope/S3/TypesSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Antiope/S3/TypesSpec.hs
@@ -0,0 +1,22 @@
+module Antiope.S3.TypesSpec (spec) where
+
+import Antiope.Core
+import Antiope.S3.Types
+import HaskellWorks.Hspec.Hedgehog
+import Hedgehog
+import Test.Hspec
+
+{-# ANN module ("HLint: Ignore Redundant do" :: String) #-}
+
+spec :: Spec
+spec = describe "Antiope.TypesSpec" $ do
+  it "Can parse S3Uri" $ requireTest $ do
+    fromText ""                   === (Left "not enough input"  :: Either String S3Uri)
+    fromText "boo"                === (Left "string"            :: Either String S3Uri)
+    fromText "s3://hello"         === Right (S3Uri (BucketName "hello") (ObjectKey ""))
+    fromText "s3://hello/"        === Right (S3Uri (BucketName "hello") (ObjectKey ""))
+    fromText "s3://hello/a"       === Right (S3Uri (BucketName "hello") (ObjectKey "a"))
+    fromText "s3://hello/ab"      === Right (S3Uri (BucketName "hello") (ObjectKey "ab"))
+    fromText "s3://hello/ab "     === Right (S3Uri (BucketName "hello") (ObjectKey "ab "))
+    fromText "s3://hello/ab c"    === Right (S3Uri (BucketName "hello") (ObjectKey "ab c"))
+    fromText "s3://hello/ab%3dc"  === Right (S3Uri (BucketName "hello") (ObjectKey "ab%3dc"))
