diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Jens Thomas
+
+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 Jens Thomas nor the names of other
+      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
+OWNER 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/Network/Syncthing.hs b/Network/Syncthing.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing.hs
@@ -0,0 +1,296 @@
+
+{-# OPTIONS_HADDOCK show-extensions #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Network.Syncthing
+-- Copyright   : (c) 2014 Jens Thomas
+--
+-- License     : BSD-style
+-- Maintainer  : jetho@gmx.de
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Haskell bindings for the Syncthing REST API.
+--
+-- The library is based on the "Network.Wreq" package and uses some of wreq\'s
+-- functionalities for client configuration. For example, to use authentication,
+-- you need to import the "Network.Wreq" module.
+--
+-- __/Example Usage:/__
+--
+-- @
+-- \{\-\# LANGUAGE OverloadedStrings \#\-\}
+--
+-- import qualified "Network.Wreq" as Wreq
+-- import "Control.Monad" ('Control.Monad.liftM2')
+-- import "Control.Lens" (('Control.Lens.&'), ('Control.Lens..~'), ('Control.Lens.?~'))
+-- import "Network.Syncthing"
+-- import qualified "Network.Syncthing.Get" as Get 
+--
+-- \-\- A single Syncthing request.
+-- single = 'syncthing' 'defaultConfig' Get.'Network.Syncthing.Get.ping'
+--
+-- \-\- Connection sharing for multiple Syncthing requests.
+-- multiple1 = 'withManager' $ \\cfg ->
+--     'syncthing' cfg $ do
+--         p <- Get.'Network.Syncthing.Get.ping'
+--         v <- Get.'Network.Syncthing.Get.version'
+--         return (p, v)
+--
+-- \-\- Multiple Syncthing requests with connection sharing and customized configuration.
+-- multiple2 = 'withManager' $ \\cfg -> do
+--     let cfg\' = cfg 'Control.Lens.&' 'pServer' 'Control.Lens..~' \"192.168.0.10:8080\"
+--                    'Control.Lens.&' 'pHttps'  'Control.Lens..~' True
+--                    'Control.Lens.&' 'pAuth'   'Control.Lens.?~' Wreq.'Network.Wreq.basicAuth' \"user\" \"pass\"
+--     'syncthing' cfg\' $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+
+module Network.Syncthing
+    (
+    -- * Types
+      Server
+    , Device
+    , FolderName
+    , Path
+    , Host
+    , Port
+    , Addr
+
+    -- * The Syncthing Monad
+    , SyncResult
+    , SyncM
+    , syncthing
+
+    -- * Connection sharing
+    , withManager
+    , withManagerNoVerify
+    , withManager'
+
+    -- * Configuration
+    , SyncConfig
+    , pServer
+    , pApiKey
+    , pAuth
+    , pHttps
+    , pManager
+
+    -- * Defaults
+    , defaultConfig
+    , defaultFolder
+
+    -- * Manager Settings
+    , defaultManagerSettings
+    , noSSLVerifyManagerSettings
+    , setResponseTimeout 
+
+    -- * Error Handling
+    , DeviceError(..)
+    , SyncError(..)
+
+    -- * Data Types
+    , CacheEntry(..)
+    , Config(..)
+    , AddressType(..)
+    , FolderConfig(..)
+    , DeviceConfig(..)
+    , VersioningConfig(..)
+    , GuiConfig(..)
+    , OptionsConfig(..)
+    , Connection(..)
+    , DirTree(..)
+    , Error(..)
+    , Ignore(..)
+    , Model(..)
+    , ModelState(..)
+    , Need(..)
+    , Progress(..)
+    , System(..)
+    , SystemMsg(..)
+    , Upgrade(..)
+    , Version(..)
+
+    -- * Utility functions
+    , parseAddr 
+    , encodeAddr
+    , toUTC
+    , fromUTC
+    ) where
+
+import           Control.Applicative              ((<$>))
+import           Control.Exception                (catch, throwIO)
+import           Control.Lens                     (Lens', (&), (.~), (^.))
+import           Control.Monad                    ((<=<))
+import           Control.Monad.Trans.Either       (runEitherT)
+import           Control.Monad.Trans.Reader       (runReaderT)
+import           Data.ByteString.Lazy             (fromStrict)
+import           Data.Text                        (Text)
+import           Network.Connection               (TLSSettings (..))
+import qualified Network.HTTP.Client              as HTTP
+import           Network.HTTP.Client.TLS          (mkManagerSettings, tlsManagerSettings)
+import qualified Network.Wreq                     as W
+
+import           Network.Syncthing.Internal.Config
+import           Network.Syncthing.Internal.Error
+import qualified Network.Syncthing.Internal.Lens  as PL
+import           Network.Syncthing.Internal.Monad
+import           Network.Syncthing.Internal.Utils
+import           Network.Syncthing.Types
+
+
+-- | Use Wreq's getWith and postWith functions when running in IO
+instance MonadSync IO where
+    getMethod  o s   = (^. W.responseBody) <$> W.getWith  o s
+    postMethod o s p = (^. W.responseBody) <$> W.postWith o s p
+
+-- | Create a default configuration with a new manager for connection
+-- sharing. The manager is released after running the Syncthing actions(s).
+-- This is equivalent to:
+--
+-- @
+-- 'withManager'' 'defaultManagerSettings'
+-- @
+--
+-- /Examples:/
+--
+-- @
+-- 'withManager' $ \\cfg ->
+--     'syncthing' cfg $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+-- @
+-- 'withManager' $ \\cfg -> do
+--     let cfg\' = cfg 'Control.Lens.&' 'pServer' 'Control.Lens..~' \"192.168.0.10:8080\"
+--     'syncthing' cfg\' $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+withManager :: (SyncConfig -> IO a) -> IO a
+withManager = withManager' defaultManagerSettings
+
+-- | Create a manager with disabled SSL certificate verification. 
+-- This is equivalent to:
+--
+-- @
+-- 'withManager'' 'noSSLVerifyManagerSettings'
+-- @
+--
+-- /Example:/
+--
+-- @
+-- 'withManagerNoVerify' $ \\cfg -> do
+--     let cfg\' = cfg 'Control.Lens.&' 'pHttps' 'Control.Lens..~' True
+--     'syncthing' cfg\' $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+withManagerNoVerify :: (SyncConfig -> IO a) -> IO a
+withManagerNoVerify = withManager' noSSLVerifyManagerSettings
+
+-- | Create a manager by using the provided manager settings.
+--
+-- /Example:/
+--
+-- @
+-- 'withManager'' 'noSSLVerifyManagerSettings' $ \\cfg -> do
+--     let cfg\' = cfg 'Control.Lens.&' 'pHttps' 'Control.Lens..~' True
+--     'syncthing' cfg\' $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+withManager' :: HTTP.ManagerSettings -> (SyncConfig -> IO a) -> IO a
+withManager' settings act =
+    HTTP.withManager settings $ \mgr ->
+        act $ defaultConfig & pManager .~ Right mgr
+
+-- | Run Syncthing requests.
+syncthing :: SyncConfig -> SyncM IO a -> IO (SyncResult a)
+syncthing config action =
+    runReaderT (runEitherT $ runSyncthing action) config `catch` handler
+  where
+    handler e@(HTTP.StatusCodeException _ headers _) =
+        maybe (throwIO e) (return . Left) $ maybeSyncError headers
+    handler unhandledErr = throwIO unhandledErr
+    maybeSyncError = decodeError . fromStrict <=< lookup "X-Response-Body-Start"
+
+-- | The default Syncthing configuration. Customize it to your needs by using
+-- the SyncConfig lenses.
+--
+-- /Example:/
+--
+-- >>> defaultConfig
+-- SyncConfig { pServer = "127.0.0.1:8080", pApiKey = Nothing, pAuth = Nothing, pHttps = False, pManager = Left _ }
+-- >>> defaultConfig & pServer .~ "192.168.0.10:8080" & pApiKey ?~ "XXXX"
+-- SyncConfig { pServer = "192.168.0.10:8080", pApiKey = Just "XXXX", pAuth = Nothing, pHttps = False, pManager = Left _ }
+defaultConfig :: SyncConfig
+defaultConfig = SyncConfig {
+      _pServer   = "127.0.0.1:8080"
+    , _pApiKey   = Nothing
+    , _pAuth     = Nothing
+    , _pHttps    = False
+    , _pManager  = Left defaultManagerSettings
+    }
+
+defaultResponseTimeout :: Int
+defaultResponseTimeout = 300000000 
+
+-- | Set the response timeout (in microseconds). Default is 300 seconds.
+setResponseTimeout :: HTTP.ManagerSettings -> Int -> HTTP.ManagerSettings
+setResponseTimeout ms t = ms { HTTP.managerResponseTimeout = Just t }
+
+-- | The default manager settings used by 'defaultConfig'.
+defaultManagerSettings :: HTTP.ManagerSettings
+defaultManagerSettings = 
+    tlsManagerSettings `setResponseTimeout` defaultResponseTimeout 
+
+-- | Alternative manager settings with disabled SSL certificate verification.
+noSSLVerifyManagerSettings :: HTTP.ManagerSettings
+noSSLVerifyManagerSettings = ms `setResponseTimeout` defaultResponseTimeout 
+  where ms = mkManagerSettings (TLSSettingsSimple True False False) Nothing
+
+-- | A lens for configuring the server address. Use the ADDRESS:PORT format.
+--
+-- /Example:/
+--
+-- @
+-- let cfg = 'defaultConfig' 'Control.Lens.&' 'pApiKey' 'Control.Lens..~' \"192.168.0.10:8080\"
+-- 'syncthing' cfg Get.'Network.Syncthing.Get.ping'
+-- @
+pServer :: Lens' SyncConfig Server
+pServer  = PL.pServer
+
+-- | A lens for specifying the Syncthing API Key.
+--
+-- /Example:/
+--
+-- @
+-- let cfg = 'defaultConfig' 'Control.Lens.&' 'pApiKey' 'Control.Lens.?~' \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"
+-- 'syncthing' cfg Get.'Network.Syncthing.Get.ping'
+-- @
+pApiKey :: Lens' SyncConfig (Maybe Text)
+pApiKey  = PL.pApiKey
+
+-- | A lens for configuring request authentication provided by the 
+-- 'Network.Wreq' package (see 'Network.Wreq.Auth').
+--
+-- /Example:/
+--
+-- @
+-- import qualified "Network.Wreq" as Wreq
+--
+-- let cfg = 'defaultConfig' 'Control.Lens.&' 'pHttps' 'Control.Lens..~' True
+--                         'Control.Lens.&' 'pAuth'  'Control.Lens.?~' Wreq.'Network.Wreq.basicAuth' \"user\" \"pass\"
+-- 'syncthing' cfg Get.'Network.Syncthing.Get.ping'
+-- @
+pAuth :: Lens' SyncConfig (Maybe W.Auth)
+pAuth    = PL.pAuth
+
+-- | A lens for enabling HTTPS usage.
+--
+-- /Example:/
+--
+-- @
+-- let cfg = 'defaultConfig' 'Control.Lens.&' 'pHttps' 'Control.Lens..~' True
+-- 'syncthing' cfg Get.'Network.Syncthing.Get.ping'
+-- @
+pHttps :: Lens' SyncConfig Bool
+pHttps = PL.pHttps
+
+-- | A lens for specifying your own ManagerSettings/Manager. For more
+-- information, please refer to the "Network.HTTP.Client" package.
+pManager :: Lens' SyncConfig (Either HTTP.ManagerSettings HTTP.Manager)
+pManager = PL.pManager
+
diff --git a/Network/Syncthing/Get.hs b/Network/Syncthing/Get.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Get.hs
@@ -0,0 +1,149 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+{-# OPTIONS_HADDOCK show-extensions #-}
+
+-- |
+-- Module      : Network.Syncthing.Get
+-- Copyright   : (c) 2014 Jens Thomas
+--
+-- License     : BSD-style
+-- Maintainer  : jetho@gmx.de
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Syncthing GET requests.
+
+module Network.Syncthing.Get
+    (
+    -- * Request functions
+      ping
+    , apiKey
+    , config
+    , completion
+    , connections
+    , deviceId
+    , discovery
+    , errors
+    , ignores
+    , model
+    , need
+    , sync
+    , system
+    , tree
+    , upgrade
+    , version
+    ) where
+
+import           Control.Applicative                ((<$>))
+import           Control.Monad                      ((>=>))
+import qualified Data.Map                           as M
+import           Data.Maybe                         (catMaybes)
+import           Data.Text                          (Text, pack)
+
+import           Network.Syncthing.Internal.Error
+import           Network.Syncthing.Internal.Monad
+import           Network.Syncthing.Internal.Request
+import           Network.Syncthing.Types
+
+
+-- | Ping the Syncthing server. Returns the string \"pong\".
+ping :: MonadSync m => SyncM m Text
+ping = getPing <$> ping'
+  where
+    ping' = query $ getRequest { path = "/rest/ping" }
+
+-- | Return the current configuration.
+config :: MonadSync m => SyncM m Config
+config = query $ getRequest { path = "/rest/config" }
+
+-- | Get the API Key if available.
+apiKey :: MonadSync m => SyncM m (Maybe Text)
+apiKey = getApiKey . getGuiConfig <$> config
+
+-- | Return the completion percentage (0 to 100) for a given device and
+-- folder.
+completion :: MonadSync m => Device -> FolderName -> SyncM m Int
+completion device folder = getCompletion <$> completion'
+  where
+    completion' = query $ getRequest { path   = "/rest/completion"
+                                     , params = [ ("device", device)
+                                                , ("folder", folder) ]
+                                     }
+
+-- | Get the list of current connections and some metadata associated
+-- with the connection/peer.
+connections :: MonadSync m => SyncM m (M.Map Device Connection)
+connections = query $ getRequest { path = "/rest/connections" }
+
+-- | Verifiy and format a device ID. Return either a valid device ID in
+-- modern format, or an error.
+deviceId :: MonadSync m => Device -> SyncM m Device
+deviceId = deviceId' >=> either (liftLeft . InvalidDeviceId) liftRight
+  where
+    deviceId' :: MonadSync m => Device -> SyncM m (Either DeviceError Device)
+    deviceId' device = query $ getRequest { path   = "/rest/deviceid"
+                                          , params = [("id", device)]
+                                          }
+
+-- | Fetch the contents of the local discovery cache.
+discovery :: MonadSync m => SyncM m (M.Map Device [CacheEntry])
+discovery = query $ getRequest { path = "/rest/discovery" }
+
+-- | Get the list of recent errors.
+errors :: MonadSync m => SyncM m [Error]
+errors = getErrors <$> errors'
+  where
+    errors' = query $ getRequest { path = "/rest/errors" }
+
+-- | Fetch the ignores list.
+ignores :: MonadSync m => FolderName -> SyncM m Ignore
+ignores folder = query $ getRequest { path   = "/rest/ignores"
+                                    , params = [ ("folder", folder) ]
+                                    }
+
+-- | Get information about the current status of a folder.
+model :: MonadSync m => FolderName -> SyncM m Model
+model folder = query $ getRequest { path   = "/rest/model"
+                                  , params = [("folder", folder)]
+                                  }
+
+-- | Get lists of files which are needed by this device in order for it
+-- to become in sync.
+need :: MonadSync m => FolderName -> SyncM m Need
+need folder = query $ getRequest { path   = "/rest/need"
+                                 , params = [ ("folder", folder) ]
+                                 }
+
+-- | Determine whether the config is in sync.
+sync :: MonadSync m => SyncM m Bool
+sync = getSync <$> sync'
+  where
+    sync' = query $ getRequest { path = "/rest/config/sync" }
+
+-- | Returns information about current system status and resource usage.
+system :: MonadSync m => SyncM m System
+system = query $ getRequest { path = "/rest/system" }
+
+-- | Get the directory tree of the global model.
+tree :: MonadSync m 
+    => FolderName -- ^ root folder
+    -> Maybe Path -- ^ defines a prefix within the tree where to start building the structure
+    -> Maybe Int  -- ^ defines how deep within the tree we want to dwell down (0 based, defaults to unlimited depth)  
+    -> SyncM m (Maybe DirTree)
+tree folder prefix levels  = 
+    queryMaybe $ getRequest { path   = "/rest/tree"
+                            , params = [("folder", folder)] ++ optionals
+                            }
+  where 
+    optionals  = catMaybes [("prefix",) <$> prefix, ("levels",) <$> levelsText]
+    levelsText = pack . show <$> levels
+
+-- | Check for a possible upgrade.
+upgrade :: MonadSync m => SyncM m Upgrade
+upgrade = query $ getRequest { path   = "/rest/upgrade" }
+
+-- | Get the current syncthing version information.
+version :: MonadSync m => SyncM m Version
+version = query $ getRequest { path = "/rest/version" }
+
diff --git a/Network/Syncthing/Internal.hs b/Network/Syncthing/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal.hs
@@ -0,0 +1,23 @@
+
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Internal constructors and helper functions. You should NOT use this
+-- module under normal circumstances! 
+module Network.Syncthing.Internal (
+    -- * Configuration
+      module Network.Syncthing.Internal.Config
+    -- * The Syncthing Monad
+    , module Network.Syncthing.Internal.Monad
+    -- * Requests
+    , module Network.Syncthing.Internal.Request
+    -- * Error Handling
+    , module Network.Syncthing.Internal.Error
+    ) where
+
+
+import           Network.Syncthing.Internal.Config
+import           Network.Syncthing.Internal.Error
+import           Network.Syncthing.Internal.Monad   hiding (liftEither,
+                                                     liftInner, liftReader)
+import           Network.Syncthing.Internal.Request
+
diff --git a/Network/Syncthing/Internal/Config.hs b/Network/Syncthing/Internal/Config.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal/Config.hs
@@ -0,0 +1,38 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+module Network.Syncthing.Internal.Config
+    ( SyncConfig(..)
+    ) where
+
+import qualified Data.Text                      as T
+import           Network.HTTP.Client            (Manager, ManagerSettings)
+import qualified Network.Wreq                   as W
+
+import           Network.Syncthing.Types.Common (Server)
+
+
+-- | The Syncthing configuration for specifying the Syncthing server,
+-- authentication, the API Key etc.
+data SyncConfig = SyncConfig {
+      _pServer  :: Server
+    , _pApiKey  :: Maybe T.Text
+    , _pAuth    :: Maybe W.Auth
+    , _pHttps   :: Bool
+    , _pManager :: Either ManagerSettings Manager
+    }
+
+instance Show SyncConfig where
+    show SyncConfig{..} =
+        concat [ "SyncConfig { "
+               , "pServer = ", show _pServer
+               , ", pApiKey = ", show _pApiKey
+               , ", pAuth = ", show _pAuth
+               , ", pHttps = ", show _pHttps
+               , ", pManager = ", case _pManager of
+                      Left _  -> "Left _"
+                      Right _ -> "Right _"
+               , " }"
+               ]
+
diff --git a/Network/Syncthing/Internal/Error.hs b/Network/Syncthing/Internal/Error.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal/Error.hs
@@ -0,0 +1,68 @@
+
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE OverloadedStrings  #-}
+
+module Network.Syncthing.Internal.Error
+    ( DeviceError(..)
+    , SyncError(..)
+    , decodeDeviceError
+    , decodeError
+    ) where
+
+import           Control.Exception          (Exception)
+import qualified Data.ByteString.Lazy.Char8 as BS
+import           Data.List                  (find)
+import           Data.Maybe                 (fromMaybe)
+import qualified Data.Text                  as T
+import           Data.Typeable              (Typeable)
+import           Text.Regex.Posix           ((=~))
+
+
+data DeviceError =
+      IncorrectLength
+    | IncorrectCheckDigit
+    | OtherDeviceError T.Text
+    deriving (Eq, Show)
+
+data SyncError =
+      ParseError String
+    | NotAuthorized
+    | CSRFError
+    | NotFound
+    | InvalidDeviceId DeviceError
+    | NoSuchFolder
+    deriving (Typeable, Eq, Show)
+
+instance Exception SyncError
+
+
+deviceIdLength, deviceIdCheckDigit :: String
+deviceIdLength     = "device ID invalid: incorrect length"
+deviceIdCheckDigit = "check digit incorrect"
+
+decodeError :: BS.ByteString -> Maybe SyncError
+decodeError =
+      fmap snd
+    . flip find errorPatterns
+    . (\msg patTup -> msg =~ fst patTup)
+    . BS.unpack
+  where
+    errorPatterns :: [(String, SyncError)]
+    errorPatterns =
+        [ ("CSRF Error", CSRFError)
+        , ("Not Authorized", NotAuthorized)
+        , ("404 page not found", NotFound)
+        , (deviceIdLength, InvalidDeviceId IncorrectLength)
+        , (deviceIdCheckDigit, InvalidDeviceId IncorrectCheckDigit)
+        , ("no such folder", NoSuchFolder )
+        , ("Folder .*? does not exist", NoSuchFolder )
+        ]
+
+decodeDeviceError :: T.Text -> DeviceError
+decodeDeviceError msg =
+    fromMaybe (OtherDeviceError msg) $
+        lookup (T.unpack msg)
+               [ (deviceIdLength, IncorrectLength)
+               , (deviceIdCheckDigit, IncorrectCheckDigit)
+               ]
+
diff --git a/Network/Syncthing/Internal/Lens.hs b/Network/Syncthing/Internal/Lens.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal/Lens.hs
@@ -0,0 +1,16 @@
+
+{-# LANGUAGE TemplateHaskell #-}
+
+module Network.Syncthing.Internal.Lens
+    ( pServer
+    , pApiKey
+    , pAuth
+    , pHttps
+    , pManager
+    ) where
+
+import           Control.Lens                      (makeLenses)
+import           Network.Syncthing.Internal.Config (SyncConfig)
+
+$(makeLenses ''SyncConfig)
+
diff --git a/Network/Syncthing/Internal/Monad.hs b/Network/Syncthing/Internal/Monad.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal/Monad.hs
@@ -0,0 +1,54 @@
+
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings          #-}
+
+module Network.Syncthing.Internal.Monad
+    ( SyncResult 
+    , SyncM(..)
+    , MonadSync(..)
+    , liftEither
+    , liftReader
+    , liftInner
+    , liftLeft
+    , liftRight
+    ) where
+
+import           Control.Applicative        (Applicative)
+import           Control.Monad.Trans.Class  (lift)
+import           Control.Monad.Trans.Either (EitherT, left, right)
+import           Control.Monad.Trans.Reader (ReaderT)
+import           Data.Aeson                 (Value)
+import           Data.ByteString.Lazy       (ByteString)
+import qualified Network.Wreq               as W
+
+import Network.Syncthing.Internal.Config
+import Network.Syncthing.Internal.Error
+
+
+-- | The result type of Syncthing requests.
+type SyncResult a = Either SyncError a
+
+-- | The SyncM Monad represents one or multiple Syncthing requests.
+newtype SyncM m a = SyncM {
+      runSyncthing :: EitherT SyncError (ReaderT SyncConfig m) a
+    } deriving (Functor , Applicative , Monad)
+
+class Monad m => MonadSync m where
+    getMethod  :: W.Options -> String -> m ByteString
+    postMethod :: W.Options -> String -> Value -> m ByteString
+
+liftEither :: Monad m => EitherT SyncError (ReaderT SyncConfig m) a -> SyncM m a
+liftEither = SyncM
+
+liftReader :: Monad m => (ReaderT SyncConfig m) a -> SyncM m a
+liftReader = liftEither . lift
+
+liftInner :: Monad m => m a -> SyncM m a
+liftInner = liftEither . lift . lift
+
+liftLeft :: Monad m => SyncError -> SyncM m a
+liftLeft = liftEither . left
+
+liftRight :: Monad m => a -> SyncM m a
+liftRight = liftEither . right
+
diff --git a/Network/Syncthing/Internal/Request.hs b/Network/Syncthing/Internal/Request.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal/Request.hs
@@ -0,0 +1,103 @@
+
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Internal.Request
+    ( Param
+    , HttpMethod(..)
+    , SyncRequest(..)
+    , query
+    , queryMaybe
+    , send
+    , get
+    , post
+    , getRequest
+    , postRequest
+    ) where
+
+import           Control.Lens                      ((&), (.~), (^.))
+import           Control.Monad                     ((<=<), (>=>))
+import           Control.Monad.Trans.Reader        (ask)
+import           Data.Aeson                        
+import           Data.ByteString.Lazy              (ByteString)
+import qualified Data.Text                         as T
+import           Data.Text.Encoding                (encodeUtf8)
+import qualified Network.Wreq                      as W
+
+import           Network.Syncthing.Internal.Config
+import           Network.Syncthing.Internal.Error
+import           Network.Syncthing.Internal.Lens
+import           Network.Syncthing.Internal.Monad
+
+
+type Param = (T.Text, T.Text)
+
+data HttpMethod =
+      Get
+    | Post Value
+    deriving (Eq, Show)
+
+data SyncRequest = SyncRequest {
+      path   :: String
+    , method :: HttpMethod
+    , params :: [Param]
+    } deriving (Eq, Show)
+
+query :: (MonadSync m, FromJSON a) => SyncRequest -> SyncM m a
+query = either (liftLeft . ParseError) liftRight . eitherDecode <=< request
+
+queryMaybe :: (MonadSync m, FromJSON a) => SyncRequest -> SyncM m (Maybe a)
+queryMaybe = request >=> \case
+    "" -> liftRight Nothing
+    bs -> liftRight $ decode bs
+
+send :: MonadSync m => SyncRequest -> SyncM m ()
+send = const (liftRight ()) <=< request
+
+request :: MonadSync m => SyncRequest -> SyncM m ByteString
+request req = do
+    config     <- liftReader ask
+    let opts    = prepareOptions config (params req) W.defaults
+    let server  = T.unpack $ config ^. pServer
+    let proto   = if (config ^. pHttps) then "https://" else "http://"
+    let url     = concat [proto, server, path req]
+    liftInner $
+        case method req of
+            Get          -> getMethod opts url
+            Post payload -> postMethod opts url payload
+
+prepareOptions :: SyncConfig -> [Param] -> W.Options -> W.Options
+prepareOptions cfg params' =
+      setManager (cfg ^. pManager)
+    . setApiKey  (cfg ^. pApiKey)
+    . setAuth    (cfg ^. pAuth)
+    . setParams
+    . setJsonHeader
+  where
+    setManager mgr          = (& W.manager .~ mgr)
+    setAuth authInfo        = (& W.auth .~ authInfo)
+    setJsonHeader           = (& W.header "Accept" .~ ["application/json"])
+    setParams               = (& W.params .~ params')
+    setApiKey (Just apiKey) = (& W.header "X-API-Key" .~ [encodeUtf8 apiKey])
+    setApiKey Nothing       = id
+
+get :: HttpMethod
+get = Get
+
+post :: ToJSON a => a -> HttpMethod
+post = Post . toJSON
+
+getRequest :: SyncRequest
+getRequest = SyncRequest {
+      path   = "/rest/ping"
+    , method = get
+    , params = []
+    }
+
+postRequest :: SyncRequest
+postRequest = SyncRequest {
+      path   = "/rest/ping"
+    , method = post ()
+    , params = []
+    }
+
diff --git a/Network/Syncthing/Internal/Utils.hs b/Network/Syncthing/Internal/Utils.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Internal/Utils.hs
@@ -0,0 +1,51 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Internal.Utils
+    ( parseAddr
+    , encodeAddr
+    , toUTC
+    , fromUTC
+    ) where
+
+import           Control.Applicative                     ((<$>))
+import           Control.Arrow                           (second, (***))
+import           Data.Maybe                              (fromMaybe)
+import qualified Data.Text                               as T
+import           Data.Time.Clock                         (UTCTime)
+import           Data.Time.Format                        (formatTime, parseTime)
+import           System.Locale                           (defaultTimeLocale)
+import           Text.Regex.Posix                        ((=~))
+
+import           Network.Syncthing.Types.Common
+
+
+-- | Parse server string (SERVER:PORT) into an address type.
+parseAddr :: Server -> Addr
+parseAddr s =
+    if serverString =~ ("^[^:]+:[0-9]+$" :: String)
+        then mapAddr . split (== ':') $ serverString
+        else (T.pack serverString, Nothing)
+  where
+    serverString = T.unpack s
+    mapAddr :: (String, String) -> Addr
+    mapAddr = T.pack *** (Just . read)
+
+split :: (Char -> Bool) -> String -> (String, String)
+split p = (second $ drop 1) . break p
+
+-- | Generate server string.
+encodeAddr :: Addr -> Server
+encodeAddr (host, maybePort) = host `T.append` portSuffix
+  where
+    portSuffix = fromMaybe "" portPart
+    portPart   = T.pack . (:) ':' . show <$> maybePort
+
+-- | Convert time string to UTCTime type.
+toUTC :: String -> Maybe UTCTime
+toUTC = parseTime defaultTimeLocale "%FT%X%Q%z"
+
+-- | Generate time string from UTC.
+fromUTC :: UTCTime -> String
+fromUTC = formatTime defaultTimeLocale "%FT%X%Q%z"
+
diff --git a/Network/Syncthing/Post.hs b/Network/Syncthing/Post.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Post.hs
@@ -0,0 +1,123 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections     #-}
+{-# OPTIONS_HADDOCK show-extensions #-}
+
+-- |
+-- Module      : Network.Syncthing.Post
+-- Copyright   : (c) 2014 Jens Thomas
+--
+-- License     : BSD-style
+-- Maintainer  : jetho@gmx.de
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Syncthing POST requests.
+
+module Network.Syncthing.Post
+    (
+    -- * Request functions
+      ping
+    , bump
+    , hint
+    , sendConfig
+    , sendError
+    , clearErrors
+    , sendIgnores
+    , scanFolder
+    , reset
+    , restart
+    , shutdown
+    , upgrade
+    ) where
+
+import           Control.Applicative                ((<$>))
+import           Control.Monad                      (join, (>=>))
+import qualified Data.Map                           as Map
+import           Data.Maybe                         (maybeToList)
+import           Data.Text                          (Text)
+
+import           Network.Syncthing.Internal.Monad
+import           Network.Syncthing.Internal.Request
+import           Network.Syncthing.Types
+
+
+maybeSystemMsg :: MonadSync m => SyncRequest -> SyncM m (Maybe SystemMsg)
+maybeSystemMsg = queryMaybe >=> return . join
+
+-- | Ping the Syncthing server. Returns the string \"pong\".
+ping :: MonadSync m => SyncM m Text
+ping = getPing <$> ping'
+  where
+    ping' = query $ postRequest { path = "/rest/ping" }
+
+-- | Move the given file to the top of the download queue.
+bump :: MonadSync m => FolderName -> Path -> SyncM m Need
+bump folder filePath =
+    query $ postRequest { path   = "/rest/bump"
+                        , params = [ ("folder", folder) , ("file", filePath) ]
+                        }
+
+-- | Add an entry to the discovery cache.
+hint:: MonadSync m => Device -> Server -> SyncM m ()
+hint device server=
+    send $ postRequest { path   = "/rest/discovery/hint"
+                       , params = [("device", device), ("addr", server)]
+                       }
+
+-- | Update the server configuration. The configuration will be saved to
+-- disk and the configInSync flag set to false. 
+-- 'Network.Syncthing.Post.restart' Syncthing to activate.
+sendConfig :: MonadSync m => Config -> SyncM m ()
+sendConfig cfg = send $ postRequest { path   = "/rest/config"
+                                    , method = post cfg
+                                    }
+
+-- | Register a new error message.
+sendError :: MonadSync m => Text -> SyncM m ()
+sendError msg = send $ postRequest { path   = "/rest/error"
+                                   , method = post msg
+                                   }
+
+-- | Remove all recent errors.
+clearErrors :: MonadSync m => SyncM m ()
+clearErrors = send $ postRequest { path = "/rest/error/clear" }
+
+-- | Update the ignores list and echo it back as response.
+sendIgnores :: MonadSync m => FolderName -> [Text] -> SyncM m (Maybe [Text])
+sendIgnores folder ignores =
+    getIgnores <$> query postRequest { path   = "/rest/ignores"
+                                     , method = post ignoresMap
+                                     , params = [("folder", folder)]
+                                     }
+  where
+    ignoresMap :: Map.Map Text [Text]
+    ignoresMap = Map.singleton "ignore" ignores
+
+-- | Request rescan of a folder. Restrict the scan to a relative subpath
+-- within the folder by specifying the optional path parameter.
+scanFolder:: MonadSync m => FolderName -> Maybe Path -> SyncM m ()
+scanFolder folder subPath =
+    send $ postRequest { path   = "/rest/scan"
+                       , params = [("folder", folder)]
+                                  ++ maybeToList (("sub",) <$> subPath)
+                       }
+
+-- | Restart Syncthing.
+restart :: MonadSync m => SyncM m SystemMsg
+restart = query postRequest { path = "/rest/restart" }
+
+-- | Shutdown Syncthing.
+shutdown :: MonadSync m => SyncM m SystemMsg
+shutdown = query postRequest { path = "/rest/shutdown" }
+
+-- | Reset Syncthing by renaming all folder directories to temporary,
+-- unique names, wiping all indexes and restarting.
+reset :: MonadSync m => SyncM m SystemMsg
+reset = query postRequest { path = "/rest/reset" }
+
+-- | Perform an upgrade to the newest release and restart. Does nothing if
+-- there is no newer version.
+upgrade :: MonadSync m => SyncM m (Maybe SystemMsg)
+upgrade = maybeSystemMsg $ postRequest { path = "/rest/upgrade" }
+
diff --git a/Network/Syncthing/Session.hs b/Network/Syncthing/Session.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Session.hs
@@ -0,0 +1,113 @@
+
+{-# OPTIONS_HADDOCK show-extensions #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module      : Network.Syncthing.Session
+-- Copyright   : (c) 2014 Jens Thomas
+--
+-- License     : BSD-style
+-- Maintainer  : jetho@gmx.de
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- This module provides functions for manual session handling.
+--
+-- __/Example Usage:/__
+--
+-- @
+-- \{\-\# LANGUAGE OverloadedStrings \#\-\}
+--
+-- import "Control.Lens" (('Control.Lens.&'), ('Control.Lens..~'))
+-- import "Network.Syncthing"
+-- import "Network.Syncthing.Session"
+-- import qualified "Network.Syncthing.Get" as Get
+--
+-- \-\- Customized configuration.
+-- settings1 = 'defaultConfig' 'Control.Lens.&' 'Network.Syncthing.pServer' 'Control.Lens..~' \"192.168.0.10:8080\"
+--
+-- session1 = do
+--     session <- 'newSyncSession' settings1
+--     p       <- 'runSyncSession' session Get.'Network.Syncthing.Get.ping'
+--     v       <- 'runSyncSession' session Get.'Network.Syncthing.Get.version'
+--     'closeSyncSession' session
+--     return (p, v)
+--
+-- \-\- Customized configuration with disabled SSL certificate verification.
+-- settings2 = 'defaultConfig' 'Control.Lens.&' 'Network.Syncthing.pHttps'   'Control.Lens..~' True
+--                           'Control.Lens.&' 'Network.Syncthing.pManager' 'Control.Lens..~' Left 'Network.Syncthing.noSSLVerifyManagerSettings'
+--
+-- session2 = do
+--     session <- 'newSyncSession' settings2
+--     p       <- 'runSyncSession' session Get.'Network.Syncthing.Get.ping'
+--     v       <- 'runSyncSession' session Get.'Network.Syncthing.Get.version'
+--     'closeSyncSession' session
+--     return (p, v)
+-- @
+
+module Network.Syncthing.Session
+    (
+    -- * Types
+      SyncSession
+
+    -- * Session Management
+    , newSyncSession
+    , closeSyncSession
+    , withSyncSession
+
+    -- * Running requests
+    , runSyncSession
+    ) where
+
+import           Control.Exception                (bracket)
+import           Control.Lens                     ((&), (.~), (^.))
+import           Network.HTTP.Client              (closeManager, newManager)
+
+import           Network.Syncthing
+
+
+-- | Holds the session configuration and the connection manager.
+newtype SyncSession = SyncSession { getConfig :: SyncConfig }
+
+-- | Create a new Syncthing session for with provided configuration. You should
+-- reuse the session whenever possible because of connection sharing.
+newSyncSession :: SyncConfig -> IO SyncSession
+newSyncSession config = do
+    mgr <- createManager $ config ^. pManager
+    return . SyncSession $ config & pManager .~ Right mgr
+  where
+    createManager (Left settings)   = newManager settings
+    createManager (Right mgr)       = return mgr
+
+-- | Close a Syncthing session.
+closeSyncSession :: SyncSession -> IO ()
+closeSyncSession session = either doNothing closeManager mgr
+  where
+    doNothing   = const $ return ()
+    mgr         = getConfig session ^. pManager
+
+-- | Run a Syncthing request using connection sharing within a session.
+runSyncSession :: SyncSession -> SyncM IO a -> IO (SyncResult a)
+runSyncSession = syncthing . getConfig
+
+-- | Create a new session using the provided configuration, run the
+-- action and close the session.
+--
+-- /Examples:/
+--
+-- @
+-- 'withSyncSession' 'defaultConfig' $ \\session ->
+--     'runSyncSession' session $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+-- @
+-- import qualified "Network.Wreq" as Wreq
+--
+-- let cfg = 'defaultConfig' 'Control.Lens.&' 'pHttps'  'Control.Lens..~' True
+--                         'Control.Lens.&' 'pAuth'   'Control.Lens.?~' Wreq.'Network.Wreq.basicAuth' \"user\" \"pass\"
+--                         'Control.Lens.&' 'pApiKey' 'Control.Lens.?~' \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"
+-- 'withSyncSession' cfg $ \\session ->
+--     'runSyncSession' session $ 'Control.Monad.liftM2' (,) Get.'Network.Syncthing.Get.ping' Get.'Network.Syncthing.Get.version'
+-- @
+withSyncSession :: SyncConfig -> (SyncSession -> IO a) -> IO a
+withSyncSession config = bracket (newSyncSession config) closeSyncSession
+
diff --git a/Network/Syncthing/Types.hs b/Network/Syncthing/Types.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types.hs
@@ -0,0 +1,24 @@
+
+module Network.Syncthing.Types
+    ( module Ty
+    ) where
+
+
+import           Network.Syncthing.Types.CacheEntry as Ty
+import           Network.Syncthing.Types.Common     as Ty
+import           Network.Syncthing.Types.Completion as Ty
+import           Network.Syncthing.Types.Config     as Ty
+import           Network.Syncthing.Types.Connection as Ty
+import           Network.Syncthing.Types.DeviceId   as Ty
+import           Network.Syncthing.Types.DirTree    as Ty
+import           Network.Syncthing.Types.Error      as Ty
+import           Network.Syncthing.Types.Ignore     as Ty
+import           Network.Syncthing.Types.Model      as Ty
+import           Network.Syncthing.Types.Need       as Ty
+import           Network.Syncthing.Types.Ping       as Ty
+import           Network.Syncthing.Types.Sync       as Ty
+import           Network.Syncthing.Types.System     as Ty
+import           Network.Syncthing.Types.SystemMsg  as Ty
+import           Network.Syncthing.Types.Upgrade    as Ty
+import           Network.Syncthing.Types.Version    as Ty
+
diff --git a/Network/Syncthing/Types/CacheEntry.hs b/Network/Syncthing/Types/CacheEntry.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/CacheEntry.hs
@@ -0,0 +1,29 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.CacheEntry
+    ( CacheEntry(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text                        ()
+import           Data.Time.Clock                  (UTCTime)
+
+import           Network.Syncthing.Internal.Utils (parseAddr, toUTC)
+import           Network.Syncthing.Types.Common   (Addr)
+
+
+-- | Represents an entry in the discovery cache.
+data CacheEntry = CacheEntry {
+      getAddr :: Addr
+    , getSeen :: Maybe UTCTime
+    } deriving (Eq, Show)
+
+instance FromJSON CacheEntry where
+    parseJSON (Object v) =
+        CacheEntry <$> (parseAddr <$> (v .:  "Address"))
+                   <*> (toUTC <$> (v .:  "Seen"))
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Common.hs b/Network/Syncthing/Types/Common.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Common.hs
@@ -0,0 +1,37 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Common
+    (
+      Server
+    , Device
+    , FolderName
+    , Path
+    , Host
+    , Port
+    , Addr
+    , defaultFolder
+    ) where
+
+import           Data.Text (Text)
+
+
+-- | Use the SERVER:PORT format for specifying servers.
+type Server     = Text
+
+type Device     = Text
+
+type FolderName = Text
+
+type Path       = Text
+
+type Host       = Text
+
+type Port       = Int
+
+type Addr       = (Host, Maybe Port)
+
+-- | The default folder name.
+defaultFolder :: FolderName
+defaultFolder = "default"
+
diff --git a/Network/Syncthing/Types/Completion.hs b/Network/Syncthing/Types/Completion.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Completion.hs
@@ -0,0 +1,19 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Completion
+    ( Completion(..)
+    ) where
+
+import           Control.Applicative              ((<$>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+
+
+newtype Completion = Completion { getCompletion :: Int } 
+                     deriving (Eq, Show)
+
+instance FromJSON Completion where
+    parseJSON (Object v) = Completion <$> (v .: "completion")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Config.hs b/Network/Syncthing/Types/Config.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Config.hs
@@ -0,0 +1,322 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+module Network.Syncthing.Types.Config
+    ( Config(..)
+    , FolderConfig(..)
+    , DeviceConfig(..)
+    , VersioningConfig(..)
+    , OptionsConfig(..)
+    , GuiConfig(..)
+    , AddressType(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson
+import qualified Data.Map                         as M
+import           Data.Maybe                       (fromMaybe)
+import           Data.Text                        (Text, cons, uncons)
+
+import           Network.Syncthing.Types.Common
+import           Network.Syncthing.Internal.Utils
+
+
+
+-------------------------------------------------------------------------------
+-- CONFIG RECORD -----
+-------------------------------------------------------------------------------
+
+-- | The current configuration data structure.
+data Config = Config {
+      getConfigVersion :: Int
+    , getFolderConfigs :: [FolderConfig]
+    , getDeviceConfigs :: [DeviceConfig]
+    , getGuiConfig     :: GuiConfig
+    , getOptionsConfig :: OptionsConfig
+    } deriving (Eq, Show)
+
+instance FromJSON Config where
+    parseJSON (Object v) =
+        Config <$> (v .: "Version")
+               <*> (v .: "Folders")
+               <*> (v .: "Devices")
+               <*> (v .: "GUI")
+               <*> (v .: "Options")
+    parseJSON _          = mzero
+
+instance ToJSON Config where
+    toJSON Config{..} =
+        object [ "Version"  .= getConfigVersion
+               , "Folders"  .= getFolderConfigs
+               , "Devices"  .= getDeviceConfigs
+               , "GUI"      .= getGuiConfig
+               , "Options"  .= getOptionsConfig
+               ]
+
+
+-------------------------------------------------------------------------------
+-- ADDRESS TYPE -----
+-------------------------------------------------------------------------------
+
+-- | An address can be dynamic or static.
+data AddressType =
+      Dynamic
+    | Address Addr
+    deriving (Eq, Show)
+
+decodeAddressType :: Text -> AddressType
+decodeAddressType "dynamic" = Dynamic
+decodeAddressType addr      = Address $ parseAddr addr
+
+encodeAddressType :: AddressType -> Text
+encodeAddressType Dynamic        = "dynamic"
+encodeAddressType (Address addr) = encodeAddr addr
+
+
+-------------------------------------------------------------------------------
+-- FOLDER CONFIG -----
+-------------------------------------------------------------------------------
+
+-- | The folder specific configuration.
+data FolderConfig = FolderConfig {
+      getId              :: FolderName
+    , getPath            :: Path
+    , getFolderDevices   :: [Device]
+    , getReadOnly        :: Bool
+    , getRescanIntervalS :: Int
+    , getIgnorePerms     :: Bool
+    , getVersioning      :: VersioningConfig
+    , getLenientMtimes   :: Bool
+    , getCopiers         :: Int
+    , getPullers         :: Int
+    , getFolderInvalid   :: Text
+    } deriving (Eq, Show)
+
+instance FromJSON FolderConfig where
+    parseJSON (Object v) =
+        FolderConfig <$> (v .: "ID")
+                     <*> (v .: "Path")
+                     <*> (map getFolderDevice <$> (v .: "Devices"))
+                     <*> (v .: "ReadOnly")
+                     <*> (v .: "RescanIntervalS")
+                     <*> (v .: "IgnorePerms")
+                     <*> (v .: "Versioning")
+                     <*> (v .: "LenientMtimes")
+                     <*> (v .: "Copiers")
+                     <*> (v .: "Pullers")
+                     <*> (v .: "Invalid")
+    parseJSON _          = mzero
+
+instance ToJSON FolderConfig where
+    toJSON FolderConfig{..} =
+        object [ "ID"              .= getId
+               , "Path"            .= getPath
+               , "Devices"         .= map FolderDeviceConfig getFolderDevices
+               , "ReadOnly"        .= getReadOnly
+               , "RescanIntervalS" .= getRescanIntervalS
+               , "IgnorePerms"     .= getIgnorePerms
+               , "Versioning"      .= getVersioning
+               , "LenientMtimes"   .= getLenientMtimes
+               , "Copiers"         .= getCopiers
+               , "Pullers"         .= getPullers
+               , "Invalid"         .= getFolderInvalid
+               ]
+
+
+-------------------------------------------------------------------------------
+-- VERSIONING CONFIG -----
+-------------------------------------------------------------------------------
+
+-- | Information about versioning.
+data VersioningConfig = VersioningConfig {
+      getType   :: Text
+    , getParams :: M.Map Text Text
+    } deriving (Eq, Show)
+
+instance FromJSON VersioningConfig where
+    parseJSON (Object v) =
+        VersioningConfig <$> (v .: "Type")
+                         <*> (v .: "Params")
+    parseJSON _          = mzero
+
+instance ToJSON VersioningConfig where
+    toJSON VersioningConfig{..} =
+        object [ "Type"   .= getType
+               , "Params" .= getParams
+               ]
+
+
+-------------------------------------------------------------------------------
+-- DEVICE CONFIG -----
+-------------------------------------------------------------------------------
+
+-- | Device specific configuration information.
+data DeviceConfig = DeviceConfig {
+      getDevice      :: Device
+    , getDeviceName  :: Text
+    , getAddresses   :: [AddressType]
+    , getCompression :: Bool
+    , getCertName    :: Text
+    , getIntroducer  :: Bool
+    } deriving (Eq, Show)
+
+instance FromJSON DeviceConfig where
+    parseJSON (Object v) =
+        DeviceConfig <$> (v .: "DeviceID")
+                     <*> (v .: "Name")
+                     <*> (map decodeAddressType <$> (v .: "Addresses"))
+                     <*> (v .: "Compression")
+                     <*> (v .: "CertName")
+                     <*> (v .: "Introducer")
+    parseJSON _          = mzero
+
+instance ToJSON DeviceConfig where
+    toJSON DeviceConfig{..} =
+        object [ "DeviceID"     .= getDevice
+               , "Name"         .= getDeviceName
+               , "Addresses"    .= map encodeAddressType getAddresses
+               , "Compression"  .= getCompression
+               , "CertName"     .= getCertName
+               , "Introducer"   .= getIntroducer
+               ]
+
+
+-------------------------------------------------------------------------------
+-- FOLDER-DEVICE CONFIG -----
+-------------------------------------------------------------------------------
+
+data FolderDeviceConfig = FolderDeviceConfig {
+      getFolderDevice :: Device
+    } deriving (Eq, Show)
+
+instance FromJSON FolderDeviceConfig where
+    parseJSON (Object v) = FolderDeviceConfig <$> (v .: "DeviceID")
+    parseJSON _          = mzero
+
+instance ToJSON FolderDeviceConfig where
+    toJSON (FolderDeviceConfig device) =
+        object [ "DeviceID" .= device ]
+
+
+-------------------------------------------------------------------------------
+-- GUI CONFIG -----
+-------------------------------------------------------------------------------
+
+-- | Gui settings.
+data GuiConfig = GuiConfig {
+      getEnabled    :: Bool
+    , getApiKey     :: Maybe Text
+    , getGuiAddress :: Addr
+    , getUser       :: Text
+    , getPassword   :: Text
+    , getUseTLS     :: Bool
+    } deriving (Eq, Show)
+
+instance FromJSON GuiConfig where
+    parseJSON (Object v) =
+        GuiConfig <$> (v .: "Enabled")
+                  <*> (decodeApiKey <$> (v .: "APIKey"))
+                  <*> (parseAddr <$> (v .: "Address"))
+                  <*> (v .: "User")
+                  <*> (v .: "Password")
+                  <*> (v .: "UseTLS")
+    parseJSON _          = mzero
+
+instance ToJSON GuiConfig where
+    toJSON GuiConfig{..} =
+        object [ "Enabled"  .= getEnabled
+               , "APIKey"   .= encodeApiKey getApiKey
+               , "Address"  .= encodeAddr getGuiAddress
+               , "User"     .= getUser
+               , "Password" .= getPassword
+               , "UseTLS"   .= getUseTLS
+               ]
+
+decodeApiKey :: Text -> Maybe Text
+decodeApiKey = (uncurry cons `fmap`) . uncons
+
+encodeApiKey :: Maybe Text -> Text
+encodeApiKey = fromMaybe ""
+
+
+-------------------------------------------------------------------------------
+-- OPTIONS CONFIG -----
+-------------------------------------------------------------------------------
+
+-- | Various config settings.
+data OptionsConfig = OptionsConfig {
+      getListenAddress           :: [Addr]
+    , getGlobalAnnServers        :: [Text]
+    , getGlobalAnnEnabled        :: Bool
+    , getLocalAnnEnabled         :: Bool
+    , getLocalAnnPort            :: Int
+    , getLocalAnnMCAddr          :: Text
+    , getMaxSendKbps             :: Int
+    , getMaxRecvKbps             :: Int
+    , getReconnectIntervalS      :: Int
+    , getStartBrowser            :: Bool
+    , getUPnPEnabled             :: Bool
+    , getUPnPLease               :: Int
+    , getUPnPRenewal             :: Int
+    , getURAccepted              :: Int
+    , getURUniqueID              :: Text
+    , getRestartOnWakeup         :: Bool
+    , getAutoUpgradeIntervalH    :: Int
+    , getKeepTemporariesH        :: Int
+    , getCacheIgnoredFiles       :: Bool
+    , getProgressUpdateIntervalS :: Int
+    , getSymlinksEnabled         :: Bool
+} deriving (Eq, Show)
+
+instance FromJSON OptionsConfig where
+    parseJSON (Object v) =
+        OptionsConfig <$> (map parseAddr <$> (v .: "ListenAddress"))
+                      <*> (v .: "GlobalAnnServers")
+                      <*> (v .: "GlobalAnnEnabled")
+                      <*> (v .: "LocalAnnEnabled")
+                      <*> (v .: "LocalAnnPort")
+                      <*> (v .: "LocalAnnMCAddr")
+                      <*> (v .: "MaxSendKbps")
+                      <*> (v .: "MaxRecvKbps")
+                      <*> (v .: "ReconnectIntervalS")
+                      <*> (v .: "StartBrowser")
+                      <*> (v .: "UPnPEnabled")
+                      <*> (v .: "UPnPLease")
+                      <*> (v .: "UPnPRenewal")
+                      <*> (v .: "URAccepted")
+                      <*> (v .: "URUniqueID")
+                      <*> (v .: "RestartOnWakeup")
+                      <*> (v .: "AutoUpgradeIntervalH")
+                      <*> (v .: "KeepTemporariesH")
+                      <*> (v .: "CacheIgnoredFiles")
+                      <*> (v .: "ProgressUpdateIntervalS")
+                      <*> (v .: "SymlinksEnabled")
+    parseJSON _          = mzero
+
+instance ToJSON OptionsConfig where
+    toJSON OptionsConfig{..} =
+        object [ "ListenAddress"           .= map encodeAddr getListenAddress
+               , "GlobalAnnServers"        .= getGlobalAnnServers
+               , "GlobalAnnEnabled"        .= getGlobalAnnEnabled
+               , "LocalAnnEnabled"         .= getLocalAnnEnabled
+               , "LocalAnnPort"            .= getLocalAnnPort
+               , "LocalAnnMCAddr"          .= getLocalAnnMCAddr
+               , "MaxSendKbps"             .= getMaxSendKbps
+               , "MaxRecvKbps"             .= getMaxRecvKbps
+               , "ReconnectIntervalS"      .= getReconnectIntervalS
+               , "StartBrowser"            .= getStartBrowser
+               , "UPnPEnabled"             .= getUPnPEnabled
+               , "UPnPLease"               .= getUPnPLease
+               , "UPnPRenewal"             .= getUPnPRenewal
+               , "URAccepted"              .= getURAccepted
+               , "URUniqueID"              .= getURUniqueID
+               , "RestartOnWakeup"         .= getRestartOnWakeup
+               , "AutoUpgradeIntervalH"    .= getAutoUpgradeIntervalH
+               , "KeepTemporariesH"        .= getKeepTemporariesH
+               , "CacheIgnoredFiles"       .= getCacheIgnoredFiles
+               , "ProgressUpdateIntervalS" .= getProgressUpdateIntervalS
+               , "SymlinksEnabled"         .= getSymlinksEnabled
+               ]
+
diff --git a/Network/Syncthing/Types/Connection.hs b/Network/Syncthing/Types/Connection.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Connection.hs
@@ -0,0 +1,35 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Connection
+    ( Connection(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text                        (Text)
+import           Data.Time.Clock                  (UTCTime)
+
+import           Network.Syncthing.Types.Common   (Addr)
+import           Network.Syncthing.Internal.Utils (parseAddr, toUTC)
+
+
+-- | Connection information and some associated metadata.
+data Connection = Connection {
+      getAt            :: Maybe UTCTime
+    , getInBytesTotal  :: Integer
+    , getOutBytesTotal :: Integer
+    , getAddress       :: Addr
+    , getClientVersion :: Text
+    } deriving (Eq, Show)
+
+instance FromJSON Connection where
+    parseJSON (Object v) =
+        Connection <$> (toUTC <$> (v .:  "At"))
+                   <*> (v .:  "InBytesTotal")
+                   <*> (v .:  "OutBytesTotal")
+                   <*> (parseAddr <$> (v .:  "Address"))
+                   <*> (v .:  "ClientVersion")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/DeviceId.hs b/Network/Syncthing/Types/DeviceId.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/DeviceId.hs
@@ -0,0 +1,26 @@
+
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.DeviceId where
+
+import           Control.Applicative              ((<$>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.HashMap.Lazy                (member)
+import           Data.Text                        ()
+
+import           Network.Syncthing.Internal.Error
+import           Network.Syncthing.Types.Common
+
+
+instance FromJSON (Either DeviceError Device) where
+    parseJSON (Object v) = result
+      where hasId        = member "id" v
+            result       = parseIdResult hasId v
+    parseJSON _          = mzero
+
+parseIdResult hasId v
+    | hasId     = Right <$> v .: "id"
+    | otherwise = Left  <$> (decodeDeviceError <$> v .: "error")
+
diff --git a/Network/Syncthing/Types/DirTree.hs b/Network/Syncthing/Types/DirTree.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/DirTree.hs
@@ -0,0 +1,35 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.DirTree
+    ( DirTree(..)
+    ) where
+
+import           Control.Applicative ((<$>))
+import           Control.Monad       (MonadPlus (mzero))
+import           Data.Aeson          (FromJSON, Value (..), parseJSON)
+import qualified Data.Map            as M
+import           Data.Text           (Text)
+
+
+-- | A directory tree contains files or subdirectories.
+data DirTree 
+    = Dir {
+        getDirContents :: M.Map Text DirTree
+      }
+    | File {
+        getModTime  :: Integer    -- ^ file modification time
+      , getFileSize :: Integer    -- ^ file size
+      } 
+    deriving (Eq, Show)
+
+instance FromJSON DirTree where
+    parseJSON obj@(Object _) = Dir <$> parseJSON obj
+    parseJSON arr@(Array _)  = decodeFile <$> parseJSON arr
+    parseJSON _              = mzero
+
+
+decodeFile :: [Integer] -> DirTree
+decodeFile [modTime, fileSize] = File modTime fileSize
+decodeFile _                   = File 0 0
+
diff --git a/Network/Syncthing/Types/Error.hs b/Network/Syncthing/Types/Error.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Error.hs
@@ -0,0 +1,36 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Error
+    ( Error(..)
+    , Errors(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text                        (Text)
+import           Data.Time.Clock                  (UTCTime)
+
+import           Network.Syncthing.Internal.Utils (toUTC)
+
+
+-- | An error message and its timestamp.
+data Error = Error {
+      getTime :: Maybe UTCTime
+    , getMsg  :: Text
+    } deriving (Eq, Show)
+
+newtype Errors = Errors { getErrors :: [Error] }
+                 deriving (Eq, Show)
+
+instance FromJSON Error where
+    parseJSON (Object v) =
+        Error <$> (toUTC <$> (v .:  "Time"))
+              <*> (v .:  "Error")
+    parseJSON _          = mzero
+
+instance FromJSON Errors where
+    parseJSON (Object v) = Errors <$> (v .: "errors")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Ignore.hs b/Network/Syncthing/Types/Ignore.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Ignore.hs
@@ -0,0 +1,25 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Ignore
+    ( Ignore(..)
+    ) where
+
+import           Control.Applicative ((<$>), (<*>))
+import           Control.Monad       (MonadPlus (mzero))
+import           Data.Aeson          (FromJSON, Value (..), parseJSON, (.:?))
+import           Data.Text           (Text)
+
+
+-- | Contains the ignores list and a list of all compiled ignore patterns.
+data Ignore = Ignore {
+      getIgnores  :: Maybe [Text]
+    , getPatterns :: Maybe [Text]
+    } deriving (Eq, Show)
+
+instance FromJSON Ignore where
+    parseJSON (Object v) =
+        Ignore <$> (v .:? "ignore")
+               <*> (v .:? "patterns")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Model.hs b/Network/Syncthing/Types/Model.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Model.hs
@@ -0,0 +1,73 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Model
+    ( Model(..)
+    , ModelState(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text                        (Text)
+import           Data.Time.Clock                  (UTCTime)
+
+import           Network.Syncthing.Internal.Utils (toUTC)
+
+
+-- | The current state of activity of a folder.
+data ModelState
+    = Idle
+    | Scanning
+    | Cleaning
+    | Syncing
+    deriving (Eq, Show)
+
+-- | Information about the current status of a folder.
+data Model = Model {
+      getGlobalBytes   :: Integer
+    , getGlobalDeleted :: Integer
+    , getGlobalFiles   :: Integer
+    , getInSyncBytes   :: Integer
+    , getInSyncFiles   :: Integer
+    , getLocalBytes    :: Integer
+    , getLocalDeleted  :: Integer
+    , getLocalFiles    :: Integer
+    , getNeedBytes     :: Integer
+    , getNeedFiles     :: Integer
+    , getState         :: Maybe ModelState
+    , getStateChanged  :: Maybe UTCTime
+    , getInvalid       :: Maybe Text
+    , getModelVersion  :: Int
+    } deriving (Eq, Show)
+
+instance FromJSON Model where
+    parseJSON (Object v) =
+        Model <$> (v .: "globalBytes")
+              <*> (v .: "globalDeleted")
+              <*> (v .: "globalFiles")
+              <*> (v .: "inSyncBytes")
+              <*> (v .: "inSyncFiles")
+              <*> (v .: "localBytes")
+              <*> (v .: "localDeleted")
+              <*> (v .: "localFiles")
+              <*> (v .: "needBytes")
+              <*> (v .: "needFiles")
+              <*> (decodeModelState <$> (v .: "state"))
+              <*> (toUTC <$> (v .: "stateChanged"))
+              <*> (decodeInvalid <$> (v .: "invalid"))
+              <*> (v .: "version")
+    parseJSON _          = mzero
+
+decodeModelState :: Text -> Maybe ModelState
+decodeModelState = flip lookup
+    [ ("idle", Idle)
+    , ("scanning", Scanning)
+    , ("cleaning", Cleaning)
+    , ("syncing", Syncing)
+    ]
+
+decodeInvalid :: Text -> Maybe Text
+decodeInvalid "" = Nothing
+decodeInvalid s  = Just s
+
diff --git a/Network/Syncthing/Types/Need.hs b/Network/Syncthing/Types/Need.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Need.hs
@@ -0,0 +1,51 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Need
+    ( Need(..)
+    , Progress(..)
+    ) where
+
+import           Control.Applicative ((<$>), (<*>))
+import           Control.Monad       (MonadPlus (mzero))
+import           Data.Aeson          (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text           (Text)
+
+
+-- | Contains lists of files which are needed by a device for becoming in
+-- sync.
+data Need = Need {
+      getProgress :: [Progress]
+    , getQueued   :: [Text]
+    , getRest     :: [Text]
+    } deriving (Eq, Show)
+
+-- | A file that is currently downloading. 
+data Progress = Progress { 
+      getName            :: Text
+    , getFlags           :: Int
+    , getModified        :: Integer
+    , getProgressVersion :: Int
+    , getLocalVersion    :: Int
+    , getNumBlocks       :: Int
+    , getSize            :: Integer
+    } deriving (Eq, Show)
+
+instance FromJSON Progress where
+    parseJSON (Object v) =
+        Progress <$> (v .: "Name")
+                 <*> (v .: "Flags")
+                 <*> (v .: "Modified")
+                 <*> (v .: "Version")
+                 <*> (v .: "LocalVersion")
+                 <*> (v .: "NumBlocks")
+                 <*> (v .: "Size")
+    parseJSON _          = mzero
+
+instance FromJSON Need where
+    parseJSON (Object v) =
+        Need <$> (v .: "progress")
+             <*> (v .: "queued")
+             <*> (v .: "rest")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Ping.hs b/Network/Syncthing/Types/Ping.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Ping.hs
@@ -0,0 +1,19 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Ping
+    ( Ping(..)
+    ) where
+
+import           Control.Applicative ((<$>))
+import           Control.Monad       (MonadPlus (mzero))
+import           Data.Aeson          (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text           (Text)
+
+newtype Ping = Ping { getPing :: Text } 
+               deriving (Eq, Show)
+
+instance FromJSON Ping where
+    parseJSON (Object v) = Ping <$> (v .: "ping")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Sync.hs b/Network/Syncthing/Types/Sync.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Sync.hs
@@ -0,0 +1,19 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Sync
+    ( Sync(..)
+    ) where
+
+import           Control.Applicative              ((<$>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+
+
+newtype Sync = Sync { getSync :: Bool } 
+               deriving (Eq, Show)
+
+instance FromJSON Sync where
+    parseJSON (Object v) = Sync <$> (v .: "configInSync")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/System.hs b/Network/Syncthing/Types/System.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/System.hs
@@ -0,0 +1,36 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.System
+    ( System(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:), (.:?))
+import qualified Data.Map                         as M
+import           Data.Text                        (Text)
+
+import           Network.Syncthing.Types.Common   (Server)
+
+
+-- | Information about the system status and resource usage.
+data System = System {
+      getAlloc         :: Integer
+    , getCpuPercent    :: Double
+    , getExtAnnounceOK :: Maybe (M.Map Server Bool)
+    , getGoRoutines    :: Int
+    , getMyId          :: Text
+    , getSys           :: Integer
+    } deriving (Eq, Show)
+
+instance FromJSON System where
+    parseJSON (Object v) =
+        System <$> (v .:  "alloc")
+               <*> (v .:  "cpuPercent")
+               <*> (v .:? "extAnnounceOK")
+               <*> (v .:  "goroutines")
+               <*> (v .:  "myID")
+               <*> (v .:  "sys")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/SystemMsg.hs b/Network/Syncthing/Types/SystemMsg.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/SystemMsg.hs
@@ -0,0 +1,35 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.SystemMsg
+    ( SystemMsg(..)
+    ) where
+
+import           Control.Applicative ((<$>))
+import           Control.Monad       (MonadPlus (mzero))
+import           Data.Aeson          (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Maybe          (fromMaybe)
+import           Data.Text           (Text)
+
+
+-- | System messages.
+data SystemMsg
+    = Restarting
+    | ShuttingDown
+    | ResettingFolders
+    | OtherSystemMsg Text
+    deriving (Eq, Show)
+
+instance FromJSON SystemMsg where
+    parseJSON (Object v) = decodeSystemMsg <$> (v .: "ok")
+    parseJSON _          = mzero
+
+decodeSystemMsg :: Text -> SystemMsg
+decodeSystemMsg msg = fromMaybe (OtherSystemMsg msg) maybeMsg
+  where 
+    maybeMsg = lookup msg
+        [ ("restarting",        Restarting)
+        , ("shutting down",     ShuttingDown)
+        , ("resetting folders", ResettingFolders)
+        ]
+
diff --git a/Network/Syncthing/Types/Upgrade.hs b/Network/Syncthing/Types/Upgrade.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Upgrade.hs
@@ -0,0 +1,27 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Upgrade
+    ( Upgrade(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text                        (Text)
+
+
+-- | Information about the current software version and upgrade possibilities.
+data Upgrade = Upgrade {
+      getLatest  :: Text
+    , getNewer   :: Bool
+    , getRunning :: Text
+    } deriving (Eq, Show)
+
+instance FromJSON Upgrade where
+    parseJSON (Object v) =
+        Upgrade <$> (v .: "latest")
+                <*> (v .: "newer")
+                <*> (v .: "running")
+    parseJSON _          = mzero
+
diff --git a/Network/Syncthing/Types/Version.hs b/Network/Syncthing/Types/Version.hs
new file mode 100644
--- /dev/null
+++ b/Network/Syncthing/Types/Version.hs
@@ -0,0 +1,29 @@
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Syncthing.Types.Version
+    ( Version(..)
+    ) where
+
+import           Control.Applicative              ((<$>), (<*>))
+import           Control.Monad                    (MonadPlus (mzero))
+import           Data.Aeson                       (FromJSON, Value (..), parseJSON, (.:))
+import           Data.Text                        (Text)
+
+
+-- | Current Syncthing version information.
+data Version = Version {
+      getArch        :: Text
+    , getLongVersion :: Text
+    , getOs          :: Text
+    , getVersion     :: Text
+    } deriving (Eq, Show)
+
+instance FromJSON Version where
+    parseJSON (Object v) =
+        Version <$> (v .: "arch")
+                <*> (v .: "longVersion")
+                <*> (v .: "os")
+                <*> (v .: "version")
+    parseJSON _          = mzero
+
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+
+syncthing-hs 
+============
+
+[![Build Status](https://travis-ci.org/jetho/syncthing-hs.svg?branch=master)](https://travis-ci.org/jetho/syncthing-hs)
+
+Haskell bindings for the [Syncthing REST API](https://github.com/syncthing/syncthing/wiki/REST-Interface).
+
+Tutorial
+--------
+
+A short tutorial is available at: 
+[http://jetho.org/posts/2015-03-07-syncthing-hs-tutorial.html](http://jetho.org/posts/2015-03-07-syncthing-hs-tutorial.html)
+
+
+Installation
+------------
+
+    cabal update
+    cabal install syncthing-hs
+
+Usage Example
+-------------
+
+``` haskell
+{-# LANGUAGE OverloadedStrings #-}
+
+import qualified Network.Wreq as Wreq
+import Control.Monad (liftM2)
+import Control.Lens ((&), (.~), (?~))
+import Network.Syncthing
+import qualified Network.Syncthing.Get as Get
+
+-- A single Syncthing request.
+single = syncthing defaultConfig Get.ping
+
+-- Connection sharing for multiple Syncthing requests.
+multiple1 = withManager $ \cfg ->
+    syncthing cfg $ do
+        p <- Get.ping
+        v <- Get.version
+        return (p, v)
+
+-- Multiple Syncthing requests with connection sharing and customized configuration.
+multiple2 = withManager $ \cfg -> do
+    let cfg' = cfg & pServer .~ "192.168.0.10:8080"
+                   & pHttps  .~ True
+                   & pAuth   ?~ Wreq.basicAuth "user" "pass"
+    syncthing cfg' $ liftM2 (,) Get.ping Get.version
+```
+
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/syncthing-hs.cabal b/syncthing-hs.cabal
new file mode 100644
--- /dev/null
+++ b/syncthing-hs.cabal
@@ -0,0 +1,103 @@
+name:                syncthing-hs
+version:             0.1.0.0
+synopsis:            Haskell bindings for the Syncthing REST API
+description:         
+    .
+    see: <https://github.com/syncthing/syncthing/wiki/REST-Interface>
+    .
+    A short tutorial is available at: 
+    <http://jetho.org/posts/2015-03-07-syncthing-hs-tutorial.html>
+homepage:            https://github.com/jetho/syncthing-hs
+bug-reports:         https://github.com/jetho/syncthing-hs/issues
+license:             BSD3
+license-file:        LICENSE
+author:              Jens Thomas
+maintainer:          jetho@gmx.de
+-- copyright:           
+category:            Network
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  ghc-options:       -Wall -fno-warn-orphans
+  exposed-modules:   Network.Syncthing    
+                     Network.Syncthing.Session
+                     Network.Syncthing.Get
+                     Network.Syncthing.Post
+                     Network.Syncthing.Internal
+  other-modules:     Network.Syncthing.Internal.Config
+                     Network.Syncthing.Internal.Error
+                     Network.Syncthing.Internal.Monad
+                     Network.Syncthing.Internal.Request
+                     Network.Syncthing.Internal.Lens
+                     Network.Syncthing.Internal.Utils
+                     Network.Syncthing.Types
+                     Network.Syncthing.Types.CacheEntry
+                     Network.Syncthing.Types.Common
+                     Network.Syncthing.Types.Completion
+                     Network.Syncthing.Types.Config
+                     Network.Syncthing.Types.Connection
+                     Network.Syncthing.Types.DeviceId
+                     Network.Syncthing.Types.DirTree
+                     Network.Syncthing.Types.Error
+                     Network.Syncthing.Types.Ignore
+                     Network.Syncthing.Types.Model
+                     Network.Syncthing.Types.Need
+                     Network.Syncthing.Types.Ping
+                     Network.Syncthing.Types.Sync
+                     Network.Syncthing.Types.System
+                     Network.Syncthing.Types.SystemMsg
+                     Network.Syncthing.Types.Upgrade
+                     Network.Syncthing.Types.Version
+  -- other-extensions:    
+  build-depends:       aeson >=0.8.0.1
+                     , base >=4.5 && <5
+                     , bytestring >=0.9
+                     , connection >= 0.2.3
+                     , containers >= 0.5.5.1
+                     , either >=4.3.1
+                     , http-client >=0.3.1.1
+                     , http-client-tls >=0.2
+                     , lens >=4.5 
+                     , old-locale >= 1.0.0.6
+                     , regex-posix >= 0.95.2
+                     , text >=1.2.0.0
+                     , time >= 1.4.2
+                     , transformers >=0.3.0.0
+                     , unordered-containers >= 0.2.5.1
+                     , wreq >=0.3.0.0
+  -- hs-source-dirs:    
+  default-language:  Haskell2010
+
+test-Suite tests
+  type:              exitcode-stdio-1.0
+  main-is:           Test.hs
+  build-depends:       aeson >=0.8.0.1
+                     , base >=4.5 && <5
+                     , bytestring >=0.9
+                     , connection >= 0.2.3
+                     , containers >= 0.5.5.1
+                     , either >=4.3.1
+                     , http-client >=0.3.1.1
+                     , http-client-tls >=0.2
+                     , lens >=4.5 
+                     , old-locale >= 1.0.0.6
+                     , regex-posix >= 0.95.2
+                     , text >=1.2.0.0
+                     , time >= 1.4.2
+                     , transformers >=0.3.0.0
+                     , unordered-containers >= 0.2.5.1
+                     , wreq >=0.3.0.0
+  build-depends:       derive
+                     , quickcheck-instances
+                     , tasty
+                     , tasty-hunit
+                     , tasty-quickcheck
+  hs-source-dirs:    tests, .
+  default-language:  Haskell2010
+
+source-repository head
+  type: git
+  location: https://github.com/jetho/syncthing-hs
+
diff --git a/tests/Test.hs b/tests/Test.hs
new file mode 100644
--- /dev/null
+++ b/tests/Test.hs
@@ -0,0 +1,23 @@
+
+module Main where
+
+import           Test.Tasty
+
+import           Properties.ErrorProperties
+import           Properties.JsonProperties
+import           UnitTests.Errors
+import           UnitTests.Requests
+
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: TestTree
+tests = testGroup "Tests" [properties, unitTests]
+
+properties :: TestTree
+properties = testGroup "Properties" [jsonProps, errorProps]
+
+unitTests :: TestTree
+unitTests = testGroup "Unit Tests" [errorUnits, requestUnits]
+
