diff --git a/Database/Vault/KVv2/Client.hs b/Database/Vault/KVv2/Client.hs
new file mode 100644
--- /dev/null
+++ b/Database/Vault/KVv2/Client.hs
@@ -0,0 +1,233 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+--------------------------------------------------------------------------------------------------
+-- | See https://www.vaultproject.io/api/secret/kv/kv-v2.html for HashiCorp Vault KVv2 API details
+--------------------------------------------------------------------------------------------------
+
+module Database.Vault.KVv2.Client (
+
+    VaultConnection,
+
+    -- * Connect & configure Vault KVv2 Engine
+    vaultConnect,
+    kvEngineConfig,
+    secretConfig,
+  
+    -- * Basic operations
+
+    putSecret,
+    getSecret,
+  
+    -- * Soft secret deletion
+    deleteSecret,
+    deleteSecretVersions,
+    unDeleteSecretVersions,
+  
+    -- * Permanent secret deletion
+    destroySecret,
+    destroySecretVersions,
+  
+    -- * Get informations
+
+    currentSecretVersion,
+    readSecretMetadata,
+    secretsList,
+
+    -- * Utils
+    toSecretData,
+    fromSecretData,
+    toSecretVersions,
+
+  ) where
+
+import qualified Data.Aeson                          as A
+import qualified Data.ByteString                     as B
+import qualified Data.ByteString.Char8               as C
+import           Data.HashMap.Strict
+import qualified Data.Maybe                          as M
+import           Data.Text                           hiding (concat)
+import           Network.Connection 
+import           Network.HTTP.Client.TLS
+import           System.Environment                  (lookupEnv)
+import           System.Posix.Files                  (fileExist)
+
+import           Database.Vault.KVv2.Client.Types
+import           Database.Vault.KVv2.Client.Lens
+import           Database.Vault.KVv2.Client.Requests
+
+-- | Get a 'VaultConnection', or an error message.
+--
+-- >λ: vaultConnect (Just "https://vault.local.lan:8200/") "/secret" Nothing False
+--
+vaultConnect
+  :: Maybe String                       -- ^ Use 'Just' this string as Vault address or get it from variable environment VAULT_ADDR
+  -> String                             -- ^ KV engine path
+  -> Maybe VaultToken                   -- ^ Use 'Just' this 'VaultToken' or get it from $HOME/.vaut-token
+  -> Bool                               -- ^ Disable certificate validation
+  -> IO (Either String VaultConnection)
+vaultConnect mva kvep mvt dcv = do
+  nm <- newTlsManagerWith $
+          mkManagerSettings
+            TLSSettingsSimple
+              { settingDisableCertificateValidation = dcv
+              , settingDisableSession               = False
+              , settingUseServerName                = True
+              }
+            Nothing
+  va <- case mva of
+          Just va -> return (Just va)
+          Nothing -> lookupEnv "VAULT_ADDR"
+  evt <- case mvt of
+           Just t  -> return (Right $ C.pack t)
+           Nothing -> do
+             hm <- lookupEnv "HOME"
+             if M.isJust hm
+               then do
+                 let fp = M.fromJust hm ++ "/.vault-token"
+                 if M.isJust va
+                   then do
+                     fe <- fileExist fp
+                     if fe 
+                       then Right <$> B.readFile fp
+                       else return (Left $ "No Vault token file found at " ++ fp)
+                   else return (Left "Variable environment VAULT_ADDR not set")
+               else return (Left "Variable environment HOME not set")
+  pure $
+    (\vt ->
+      VaultConnection
+        { vaultAddr    = M.fromJust va
+        , vaultToken   = vt
+        , kvEnginePath = kvep
+        , manager      = nm
+        }
+    ) <$> evt
+
+-- | Set default secret settings for the KVv2 engine.
+kvEngineConfig
+  :: VaultConnection
+  -> Int                        -- ^ Max versions
+  -> Bool                       -- ^ CAS required
+  -> IO (Either String A.Value)
+kvEngineConfig vc@VaultConnection{..} =
+  configR ["POST ", show vc, "/config"] vc
+
+-- | Override default secret settings for the given secret.
+secretConfig
+  :: VaultConnection
+  -> SecretPath
+  -> Int                        -- ^ Max versions
+  -> Bool                       -- ^ CAS required
+  -> IO (Either String A.Value)
+secretConfig vc@VaultConnection{..} SecretPath{..} =
+  configR ["POST ", show vc, "/metadata/", path] vc
+
+-- | Get a secret from Vault. Give 'Just' the 'SecretVersion'
+-- to retrieve or 'Nothing' to get the current one.
+--
+-- >λ>getSecret conn (SecretPath "MySecret") Nothing
+-- >Right (SecretData (fromList [("my","password")]))
+--
+getSecret
+  :: VaultConnection
+  -> SecretPath
+  -> Maybe SecretVersion
+  -> IO (Either String SecretData)
+getSecret vc sp msv =
+  (>>= secret) <$> getSecretR vc sp msv
+
+-- | Put 'SecretData' into Vault at the given location.
+putSecret
+  :: VaultConnection
+  -> CheckAndSet                      -- ^ 'WriteAllowed', 'CreateOnly' or 'CurrentVersion'
+  -> SecretPath
+  -> SecretData                       -- ^ Data to put at 'SecretPath' location
+  -> IO (Either String SecretVersion)
+putSecret vc cas sp sd =
+  (>>= version) <$> putSecretR vc cas sp sd
+
+deleteSecret
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Maybe Error)
+deleteSecret vc sp =
+  maybeError <$> deleteSecretR vc sp
+
+deleteSecretVersions
+  :: VaultConnection
+  -> SecretPath
+  -> SecretVersions
+  -> IO (Maybe Error)
+deleteSecretVersions vc@VaultConnection{..} SecretPath{..} svs =
+  maybeError <$> secretVersionsR ["POST ", show vc, "/delete/", path] vc svs
+
+unDeleteSecretVersions
+  :: VaultConnection
+  -> SecretPath
+  -> SecretVersions
+  -> IO (Maybe Error)
+unDeleteSecretVersions vc@VaultConnection{..} SecretPath{..} svs =
+  maybeError <$> secretVersionsR ["POST ", show vc, "/undelete/", path] vc svs
+
+-- | Permanently delete a secret, i.e. all its versions and metadata.
+destroySecret
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Maybe Error)
+destroySecret vc sp =
+  maybeError <$> destroySecretR vc sp
+
+destroySecretVersions
+  :: VaultConnection
+  -> SecretPath
+  -> SecretVersions
+  -> IO (Either String A.Value)
+destroySecretVersions vc@VaultConnection{..} SecretPath{..} =
+  secretVersionsR ["POST ", show vc, "/destroy/", path] vc
+
+-- | Get list of secrets and folders at the given location.
+secretsList
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String [VaultKey])
+secretsList vc sp =
+  (>>= list) <$> secretsListR vc sp
+
+-- | Retrieve versions history of the given secret.
+--
+-- >λ: readSecretMetadata conn (SecretPath "MySecret") 
+-- >Right (SecretMetadata (fromList [(SecretVersion 1,Metadata {destroyed = True, deletion_time = "", created_time = "2019-05-30T13:22:58.416399224Z"}),(SecretVersion 2,Metadata {destroyed = True, deletion_time = "2019-06-29T15:28:46.145302138Z"})]))
+--
+readSecretMetadata
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String SecretMetadata)
+readSecretMetadata vc sp =
+  (>>= metadata) <$> readSecretMetadataR vc sp
+
+-- | Get version number of the current given secret.
+currentSecretVersion
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String SecretVersion)
+currentSecretVersion vc sp =
+  (>>= current) <$> readSecretMetadataR vc sp
+
+-- Utils
+
+toSecretData
+  :: [(Text,Text)]
+  -> SecretData
+toSecretData = SecretData . fromList
+
+fromSecretData
+  :: SecretData
+  -> [(Text,Text)]
+fromSecretData (SecretData sd) = toList sd
+
+toSecretVersions
+  :: [Int]
+  -> SecretVersions
+toSecretVersions is =
+  SecretVersions (SecretVersion <$> is)
+
diff --git a/Database/Vault/KVv2/Client/Internal.hs b/Database/Vault/KVv2/Client/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Database/Vault/KVv2/Client/Internal.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Database.Vault.KVv2.Client.Internal where
+
+import           Control.Lens
+import           Control.Monad.Catch
+import qualified Data.ByteString           as B
+import qualified Data.Aeson                as A
+import           Data.Aeson.Lens
+import qualified Data.Maybe                as M
+import           Data.Scientific
+import           Data.List                 as L
+import           Data.Text                 as T
+import           Network.HTTP.Client
+import           Network.HTTP.Types.Header
+import qualified Data.Vector               as V
+
+runRequest
+  :: Manager
+  -> Request
+  -> IO (Either String A.Value)
+runRequest m r =
+  esv <$> try (httpLbs r m)
+  where
+  esv t =
+    case t of
+      Right b ->
+        pure (M.fromMaybe A.Null $ A.decode $ responseBody b)
+      Left  e -> Left $ show (e::SomeException)
+
+fromVaultResponse
+  :: T.Text
+  -> (A.Value -> Either String a)
+  -> A.Value
+  -> Either String a
+fromVaultResponse k f v =
+  case v ^? key "data" . key k of
+    Just o@(A.Object _) -> f o
+    Just n@(A.Number _) -> f n
+    Just a@(A.Array  _) -> f a
+    Just _              -> Left "Unexpected JSON type"
+    Nothing             -> Left (jsonErrors v)
+
+vaultHeaders
+  :: B.ByteString -- ^ Vault token
+  -> [(HeaderName, B.ByteString)]
+vaultHeaders vt =
+  [ ("Content-Type", "application/json; charset=utf-8")
+  , ("X-Vault-Token", vt)
+  ]
+
+toJSONName :: String -> String
+toJSONName "secret_data"     = "data"
+toJSONName "secret_metadata" = "metadata"
+toJSONName "response_data"   = "data"
+toJSONName s                 = s
+
+jsonErrors :: A.Value -> String
+jsonErrors v =
+  case v ^? key "errors" of
+    Just ja ->
+      case ja of
+        A.Array a ->
+          if a == mempty
+            then "Undetermined error"
+            else
+              L.intercalate
+                ", "
+                (toString <$> V.toList a) ++ "."
+        _         -> "Unexpected JSON type"
+    Nothing -> expectedJSONField "errors"
+
+toString :: A.Value -> String
+toString (A.String s) = T.unpack s
+toString _            = fail "Expecting JSON type String only"
+
+expectedJSONField :: String -> String
+expectedJSONField f = "Expected JSON field not found: " ++ f
+
+unexpectedJSONType :: Either String b
+unexpectedJSONType = Left "Unexpected JSON type"
+
+toInt :: Scientific -> Int
+toInt = M.fromJust . toBoundedInteger
+
+hasTrailingSlash :: String -> Bool
+hasTrailingSlash s = s /= mempty && L.last s == '/'
+
+removeTrailingSlash :: String -> String
+removeTrailingSlash s =
+  if hasTrailingSlash s
+    then L.init s
+    else s
+
+hasLeadingSlash :: String -> Bool
+hasLeadingSlash s = s /= mempty && L.head s == '/'
+
+removeLeadingSlash :: String -> String
+removeLeadingSlash s =
+  if hasLeadingSlash s
+    then L.tail s
+    else s
diff --git a/Database/Vault/KVv2/Client/Lens.hs b/Database/Vault/KVv2/Client/Lens.hs
new file mode 100644
--- /dev/null
+++ b/Database/Vault/KVv2/Client/Lens.hs
@@ -0,0 +1,94 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- {-# LANGUAGE ExplicitForAll    #-}
+-- {-# LANGUAGE RankNTypes #-}
+
+module Database.Vault.KVv2.Client.Lens (
+
+    current,
+    list,
+    metadata,
+    maybeError,
+    secret,
+    version
+
+  ) where
+
+import           Control.Lens
+import qualified Data.Aeson                          as A
+import           Data.Aeson.Lens
+import qualified Data.Text                           as T
+import qualified Data.Vector                         as V
+
+import           Database.Vault.KVv2.Client.Internal
+import           Database.Vault.KVv2.Client.Types
+
+secret
+  :: A.Value
+  -> Either String SecretData
+secret =
+  fromVaultResponse "data" toSecretData
+  where
+  toSecretData o@(A.Object _) =
+    case A.fromJSON o of
+      A.Success sd -> Right sd
+      A.Error e    -> Left e
+  toSecretData A.Null         = Left "No current secret version"
+  toSecretData _              = Left "Unexpected JSON type"
+
+version
+  :: A.Value
+  -> Either String SecretVersion
+version =
+  fromVaultResponse "version" toSecretVersion
+  where
+  toSecretVersion (A.Number n) = Right (SecretVersion $ toInt n)
+  toSecretVersion _            = undefined
+
+current
+  :: A.Value
+  -> Either String SecretVersion
+current =
+  fromVaultResponse "current_version" toSecretVersion
+  where
+  toSecretVersion (A.Number n) = Right (SecretVersion $ toInt n)
+  toSecretVersion _            = undefined
+
+metadata
+  :: A.Value
+  -> Either String SecretMetadata
+metadata =
+  fromVaultResponse "versions" toSecretMetadata
+  where
+  toSecretMetadata o@(A.Object _) =
+    case A.fromJSON o of
+      A.Success vs -> Right vs
+      A.Error e    -> Left e
+  toSecretMetadata _            = undefined
+
+list
+  :: A.Value
+  -> Either String [VaultKey]
+list =
+  fromVaultResponse "keys" toListKeys
+  where
+    toListKeys (A.Array a) =
+      Right (V.foldl lks mempty a)
+      where
+        lks ks (A.String t) =
+          let s = T.unpack t in
+          (if hasTrailingSlash s
+             then VaultFolder s
+             else VaultKey s) : ks
+        lks p       _       = p
+    toListKeys _            = undefined
+ 
+maybeError
+  :: Either String A.Value
+  -> Maybe Error
+maybeError (Left s)  = Just s
+maybeError (Right v) =
+  case v ^? key "data" . key "version" of
+    Just A.Null -> Nothing
+    Just _      -> Just "Unexpected JSON type"
+    Nothing     -> Just (jsonErrors v)
+  
diff --git a/Database/Vault/KVv2/Client/Requests.hs b/Database/Vault/KVv2/Client/Requests.hs
new file mode 100644
--- /dev/null
+++ b/Database/Vault/KVv2/Client/Requests.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+module Database.Vault.KVv2.Client.Requests (
+
+    configR,
+    getSecretR,
+    putSecretR,
+    deleteSecretR,
+    destroySecretR,
+    secretVersionsR,
+    readSecretMetadataR,
+    secretsListR
+
+  ) where
+
+import qualified Data.Aeson                          as A
+import           Network.HTTP.Client
+import           Network.HTTP.Simple                 ( setRequestBody
+                                                     , setRequestHeaders
+                                                     , setRequestBodyJSON
+                                                     )
+
+import           Database.Vault.KVv2.Client.Internal
+import           Database.Vault.KVv2.Client.Types
+
+configR
+  :: [String]                   -- ^ Endpoint
+  ->VaultConnection
+  -> Int                        -- ^ Max versions
+  -> Bool                       -- ^ CAS required
+  -> IO (Either String A.Value)
+configR ss VaultConnection{..} mvs casr =
+  parseRequest (concat ss)
+  >>= runRequest manager
+    . setRequestHeaders (vaultHeaders vaultToken)
+    . setRequestBodyJSON
+        SecretSettings
+          { max_versions = mvs
+          , cas_required = casr
+          }
+
+getSecretR
+  :: VaultConnection
+  -> SecretPath
+  -> Maybe SecretVersion
+  -> IO (Either String A.Value)
+getSecretR vc@VaultConnection{..} SecretPath{..} msv =
+  parseRequest
+    (concat [show vc, "/data/", path, queryString msv])
+  >>= runRequest manager . setRequestHeaders (vaultHeaders vaultToken)
+  where
+  queryString = maybe "" (\(SecretVersion v) -> "?version=" ++ show v) 
+  
+putSecretR
+  :: VaultConnection
+  -> CheckAndSet
+  -> SecretPath
+  -> SecretData
+  -> IO (Either String A.Value)
+putSecretR vc@VaultConnection{..} cas SecretPath{..} sd =
+  parseRequest
+    (concat ["POST ", show vc, "/data/", path])
+  >>= runRequest manager
+    . setRequestHeaders (vaultHeaders vaultToken)
+    . setRequestBodyJSON
+        PutSecretRequestBody
+          { options  = PutSecretOptions { cas = cas }
+          , put_data = sd
+          }
+
+deleteSecretR
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String A.Value)
+deleteSecretR vc@VaultConnection{..} SecretPath{..} =
+  parseRequest
+    (concat ["DELETE ", show vc, "/data/", path])
+  >>= runRequest manager . setRequestHeaders (vaultHeaders vaultToken)
+
+secretVersionsR
+  :: [String]                    -- ^ Endpoint
+  -> VaultConnection
+  -> SecretVersions
+  -> IO (Either String A.Value)
+secretVersionsR ss VaultConnection{..} vs =
+  parseRequest (concat ss) >>=
+    runRequest manager
+      . setRequestHeaders (vaultHeaders vaultToken)
+      . setRequestBody (RequestBodyLBS $ A.encode vs)
+
+destroySecretR
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String A.Value)
+destroySecretR vc@VaultConnection{..} SecretPath{..} =
+  parseRequest
+    (concat ["DELETE ", show vc, "/metadata/", path])
+  >>= runRequest manager . setRequestHeaders (vaultHeaders vaultToken)
+
+secretsListR
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String A.Value)
+secretsListR vc@VaultConnection{..} SecretPath{..} =
+  if hasTrailingSlash path
+    then
+      parseRequest
+        (concat ["LIST ", show vc, "/metadata/", path])
+      >>= runRequest manager . setRequestHeaders (vaultHeaders vaultToken)
+    else pure (Left "SecretPath must be a folder/")
+
+readSecretMetadataR
+  :: VaultConnection
+  -> SecretPath
+  -> IO (Either String A.Value)
+readSecretMetadataR vc@VaultConnection{..} SecretPath{..} =
+  parseRequest
+    (concat ["GET ", show vc, "/metadata/", path])
+  >>= runRequest manager . setRequestHeaders (vaultHeaders vaultToken)
+
diff --git a/Database/Vault/KVv2/Client/Types.hs b/Database/Vault/KVv2/Client/Types.hs
new file mode 100644
--- /dev/null
+++ b/Database/Vault/KVv2/Client/Types.hs
@@ -0,0 +1,127 @@
+{-# LANGUAGE DeriveAnyClass     #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE OverloadedStrings  #-}
+
+module Database.Vault.KVv2.Client.Types where
+
+import           Control.Monad        (mzero)
+import           Data.Aeson
+-- import           Data.Binary -- TODO
+import qualified Data.ByteString      as B
+import           Data.HashMap.Strict
+import           Data.Hashable
+import           Data.Scientific
+import qualified Data.Text            as T
+import           Data.Text.Read       (decimal)
+import           GHC.Generics
+import           Network.HTTP.Client  (Manager)
+
+import           Database.Vault.KVv2.Client.Internal
+
+type VaultToken = String
+
+type Error = String
+
+data VaultConnection =
+  VaultConnection
+    { vaultAddr    :: !String
+    , kvEnginePath :: !String
+    , vaultToken   :: !B.ByteString
+    , manager      :: !Manager
+    }
+
+instance Show VaultConnection where
+  show (VaultConnection a p _ _) =
+    removeTrailingSlash a ++ "/v1/"
+    ++ removeTrailingSlash (removeLeadingSlash p)
+
+newtype SecretVersions =
+  SecretVersions [SecretVersion]
+  deriving (Show, Eq)
+
+instance ToJSON SecretVersions where
+  toJSON (SecretVersions svs) =
+    object
+      [ "versions" .= ((\(SecretVersion i) -> i) <$> svs) ]
+
+newtype SecretVersion
+  = SecretVersion Int
+  deriving (Show, Eq, Generic, Hashable)
+
+newtype SecretMetadata =
+  SecretMetadata (HashMap SecretVersion Metadata)
+  deriving (Show, Eq)
+
+instance FromJSON SecretMetadata where
+  parseJSON (Object o) =
+    pure (SecretMetadata . fromList $ trans <$> toList o)
+    where
+    trans p =
+      case p of
+        (t,j@(Object _)) -> do
+          let Right (i,_) = decimal t
+          let Success sv  = fromJSON j
+          (SecretVersion i,sv)
+        _                -> undefined
+  parseJSON _          = mzero
+
+data Metadata =
+  Metadata 
+    { destroyed     :: !Bool
+    , deletion_time :: !T.Text
+    , created_time  :: !T.Text
+    } deriving (Show, Eq, Generic, ToJSON, FromJSON)
+
+newtype SecretData =
+  SecretData
+    (HashMap T.Text T.Text)
+    deriving (Show, Generic, ToJSON, FromJSON)
+
+-- instance Binary SecretData -- TODO
+
+data SecretSettings =
+  SecretSettings
+    { max_versions :: Int
+    , cas_required :: Bool
+    } deriving (Show, Generic, ToJSON, FromJSON)
+    
+newtype SecretPath =
+  SecretPath
+    { path :: String }
+    deriving (Show, Generic, ToJSON)
+
+data CheckAndSet
+  = WriteAllowed
+  | CreateOnly
+  | CurrentVersion !Int
+  deriving (Show, Generic, ToJSON)
+
+newtype PutSecretOptions =
+  PutSecretOptions
+    { cas :: CheckAndSet }
+    deriving (Show)
+
+instance ToJSON PutSecretOptions where
+  toJSON PutSecretOptions { cas = WriteAllowed } = object []
+  toJSON PutSecretOptions { cas = CreateOnly }   = object [ "cas" .= Number 0.0 ]
+  toJSON PutSecretOptions { cas = CurrentVersion v } =
+    object [ "cas" .= Number (read (show v) :: Scientific) ]
+
+data PutSecretRequestBody =
+  PutSecretRequestBody
+    { options  :: PutSecretOptions
+    , put_data :: SecretData
+    }
+
+instance ToJSON PutSecretRequestBody where
+  toJSON (PutSecretRequestBody os sd) =
+    object
+      [ "options" .= os
+      , "data"    .= sd
+      ]
+
+data VaultKey
+  = VaultKey    !String
+  | VaultFolder !String
+  deriving (Show) 
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+gothic - Copyright (c) 2019, Michel Boucey
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of gothic nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/ReadMe.md b/ReadMe.md
new file mode 100644
--- /dev/null
+++ b/ReadMe.md
@@ -0,0 +1,8 @@
+
+> "Historically, strongrooms were built in the basement of a bank where the ceilings were vaulted, hence the name."
+
+<div style="text-align: right">Art. "Bank vault", Wikipedia.</div>
+
+### Gothic, a Haskell client for Vault KV Engine v2 [![Build Status](https://travis-ci.org/MichelBoucey/gothic.svg?branch=master)](https://travis-ci.org/MichelBoucey/gothic)
+
+This library implements the [HashiCorp Vault KVv2 Engine API](https://www.vaultproject.io/api/secret/kv/kv-v2.html).
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/gothic.cabal b/gothic.cabal
new file mode 100644
--- /dev/null
+++ b/gothic.cabal
@@ -0,0 +1,52 @@
+name:                gothic
+version:             0.1.0
+synopsis:            A Haskell Vault KVv2 secret engine client
+description:         A Haskell HashiCorp Vault KVv2 secret engine client library
+homepage:            https://github.com/MichelBoucey/gothic
+license:             BSD3
+license-file:        LICENSE
+author:              Michel Boucey
+maintainer:          michel.boucey@gmail.com
+copyright:           (c) 2019 - Michel Boucey
+category:            DevOps, Security, Database
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  ReadMe.md
+
+Tested-With: GHC ==8.4.3 || ==8.6.5
+
+Source-Repository head
+  Type: git
+  Location: https://github.com/MichelBoucey/gothic
+
+library
+  exposed-modules:    Database.Vault.KVv2.Client
+                    , Database.Vault.KVv2.Client.Internal
+                    , Database.Vault.KVv2.Client.Lens
+                    , Database.Vault.KVv2.Client.Requests
+                    , Database.Vault.KVv2.Client.Types
+
+  other-extensions:   OverloadedStrings
+
+  build-depends:      aeson                >= 0.8.0.2 && < 1.5
+                    , base                 >= 4.8.1.0 && < 5
+                    , binary               >= 0.8.5.1 && < 0.9
+                    , connection           >= 0.2.8 && < 0.3
+                    , exceptions           >= 0.10.0 && < 0.11
+                    , hashable             >= 1.2.7.0 && < 1.3
+                    , http-conduit         >= 2.3.2 && < 2.4
+                    , http-client          >= 0.5.13.1 && < 0.6
+                    , http-client-tls      >= 0.3.5.3 && < 0.4
+                    , http-types           >= 0.12.2 && < 0.13
+                    , bytestring           >= 0.10.8.2 && < 0.11
+                    , http-conduit         >= 2.3.2 && < 2.4
+                    , text                 >= 1.2.3.0 && < 1.3
+                    , unordered-containers >= 0.2.9.0 && < 0.3
+                    , lens                 >= 4.16.1 && < 4.18
+                    , lens-aeson           >= 1.0.2 && < 1.1
+                    , scientific           >= 0.3.6.2 && < 0.4
+                    , unix                 >= 2.7.2.2 && < 2.8
+                    , vector               >= 0.12.0.1 && < 0.13
+
+  default-language:   Haskell2010
+  GHC-Options:        -Wall
