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: a6dc2ca01c78d1727a20927280fd9f26d58e5db89a27aac402c952b9a6a66267
+-- hash: 50906432a72f329fac6e445be19a211a8f7455549afcacae337ea11c97c1489d
 
 name:           antiope-s3
-version:        6.0.0
+version:        6.0.1
 description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>
 category:       Services
 homepage:       https://github.com/arbor/antiope#readme
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
@@ -7,19 +7,25 @@
   , X.ObjectKey(..)
   , X.ETag(..)
   , S3Uri(..)
+  , readBucketName
+  , readWhile
   ) where
 
 import Antiope.S3.Internal
 import Control.Lens
+import Control.Monad
 import Control.Monad.Logger      (ToLogStr (..))
+import Data.Char
 import Data.Generics.Product.Any
+import Data.List
 import Data.String               (fromString)
 import GHC.Generics
 import Network.AWS.Data
 import Network.AWS.S3            (BucketName (..), ObjectKey (..))
 
-import qualified Data.Text            as T
-import qualified Network.AWS.S3.Types as X
+import qualified Data.Text                       as T
+import qualified Network.AWS.S3.Types            as X
+import qualified Text.ParserCombinators.ReadPrec as RP
 
 data S3Uri = S3Uri
   { bucket    :: BucketName
@@ -31,3 +37,34 @@
 
 instance ToLogStr S3Uri where
   toLogStr s = fromString $ T.unpack $ toText s
+
+readString :: String -> RP.ReadPrec String
+readString s = do
+  remainder <- RP.look
+  if s `isPrefixOf` remainder
+    then do
+      replicateM_ (length s) RP.get
+      return s
+    else RP.pfail
+
+readWhile :: (Char -> Bool) -> RP.ReadPrec String
+readWhile f = do
+  remainder <- RP.look
+  let taken = takeWhile f remainder
+  replicateM_ (length taken) RP.get
+  return taken
+
+-- As per: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
+readBucketName :: RP.ReadPrec BucketName
+readBucketName = do
+  bucketName <- readWhile bucketNameChar
+  when (length bucketName < 3 || length bucketName > 63) RP.pfail
+  return (BucketName (T.pack bucketName))
+  where bucketNameChar c = isLower c || isDigit c || c == '.' || c == '-'
+
+instance Read S3Uri where
+  readsPrec = RP.readPrec_to_S $ do
+    _  <- readString "s3://"
+    bn <- readBucketName
+    ok <- ObjectKey . T.pack <$> readWhile (/= ' ')
+    return (S3Uri bn ok)
