diff --git a/pivotal-tracker.cabal b/pivotal-tracker.cabal
--- a/pivotal-tracker.cabal
+++ b/pivotal-tracker.cabal
@@ -2,7 +2,7 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                pivotal-tracker
-version:             0.1.0.1
+version:             0.2.0.0
 synopsis:            A library and a CLI tool for accessing Pivotal Tracker API
 -- description:         
 license:             BSD3
@@ -21,25 +21,40 @@
 
 library
   exposed-modules:   Web.Tracker
-  other-modules:     Web.Tracker.Types
-  build-depends:       base >=4.8 && <4.9
+                   , Web.Tracker.Story
+  build-depends:       base >=4.6 && <4.11
                      , servant
                      , servant-client
                      , aeson
+                     , http-client
+                     , http-client-tls
+                     , mtl
                      , text
                      , transformers
                      , time
                      , aeson-casing
   hs-source-dirs:      src
   default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
 
 executable tracker
   main-is: src/Main.hs
-  build-depends:      base >=4.8 && <4.9
+  build-depends:      base >=4.6 && <4.11
                     , pivotal-tracker
                     , servant
                     , text
                     , either
                     , transformers
   default-language: Haskell2010
+  default-extensions:  OverloadedStrings
   
+test-suite tracker-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:       base
+                     , hspec
+                     , pivotal-tracker
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -1,13 +1,12 @@
 {-# LANGUAGE LambdaCase        #-}
 {-# LANGUAGE NamedFieldPuns    #-}
-{-# LANGUAGE OverloadedStrings #-}
 
 module Main where
 
 --------------------------------------------------------------------------------
+import           Control.Applicative        ((<$>))
 import           Control.Monad
 import           Control.Monad.IO.Class
-import           Control.Monad.Trans.Either
 import           Data.List
 import           Data.Monoid
 import           Data.Text                  (Text)
@@ -18,36 +17,37 @@
 import           Text.Read                  (readMaybe)
 --------------------------------------------------------------------------------
 import           Web.Tracker
+import           Web.Tracker.Story
 --------------------------------------------------------------------------------
 
 main :: IO ()
 main = do
-  token <- fmap T.pack <$> lookupEnv "TRACKER_API_TOKEN"
+  token <- T.pack <$> lookupFail "TRACKER_API_TOKEN"
+  manager <- mkManager
 
   query <- getArgs >>= return . \case
-    "unstart"        :sid:[] -> doSetState token Unstarted <$> readMaybe sid
-    "start"          :sid:[] -> doSetState token Started   <$> readMaybe sid
-    "finish"         :sid:[] -> doSetState token Finished  <$> readMaybe sid
-    "deliver"        :sid:[] -> doSetState token Delivered <$> readMaybe sid
-    "accept"         :sid:[] -> doSetState token Accepted  <$> readMaybe sid
-    "reject"         :sid:[] -> doSetState token Rejected  <$> readMaybe sid
-    "stateOf"        :sid:[] -> doStateOf  token           <$> readMaybe sid
-    "list-unstarted" :pid:[] -> doList     token Unstarted <$> readMaybe pid
-    "list-started"   :pid:[] -> doList     token Started   <$> readMaybe pid
-    "list-finished"  :pid:[] -> doList     token Finished  <$> readMaybe pid
-    "list-delivered" :pid:[] -> doList     token Delivered <$> readMaybe pid
+    "unstart"        :sid:[] -> doSetState Unstarted <$> readMaybe sid
+    "start"          :sid:[] -> doSetState Started   <$> readMaybe sid
+    "finish"         :sid:[] -> doSetState Finished  <$> readMaybe sid
+    "deliver"        :sid:[] -> doSetState Delivered <$> readMaybe sid
+    "accept"         :sid:[] -> doSetState Accepted  <$> readMaybe sid
+    "reject"         :sid:[] -> doSetState Rejected  <$> readMaybe sid
+    "stateOf"        :sid:[] -> doStateOf            <$> readMaybe sid
+    "list-unstarted" :pid:[] -> doList     Unstarted <$> readMaybe pid
+    "list-started"   :pid:[] -> doList     Started   <$> readMaybe pid
+    "list-finished"  :pid:[] -> doList     Finished  <$> readMaybe pid
+    "list-delivered" :pid:[] -> doList     Delivered <$> readMaybe pid
     _                        -> Nothing
 
-  query <- maybe invalidSyntax return query
-
-  runEitherT query >>= \case
+  maybe invalidSyntax (runTrackerAPI token manager) query >>= \case
     Left err -> print err >> exitWith (ExitFailure 1)
     Right () -> return ()
 
-  return ()
+lookupFail :: String -> IO String
+lookupFail var = maybe (fail $ "Expected environment variable " ++ var) return =<< lookupEnv var
 
-doSetState :: Maybe Text -> StoryState -> StoryId -> EitherT ServantError IO ()
-doSetState t s i = void $ updateStory t (SetStoryState s) i
+doSetState :: StoryState -> StoryId -> TrackerAPI ()
+doSetState s i = void $ updateStory (SetStoryState s) i
 
 printStory :: MonadIO m => Story ->  m ()
 printStory Story{sId=StoryId id, sName, sStoryType, sCurrentState}
@@ -56,11 +56,11 @@
                      <> "\t" <> show sCurrentState
                      <> "\t" <> T.unpack sName
 
-doList :: Maybe Text -> StoryState -> ProjectId -> EitherT ServantError IO ()
-doList t s i = stories t i (Just s) >>= mapM_ printStory . take 30
+doList :: StoryState -> ProjectId -> TrackerAPI ()
+doList s i = stories i (Just s) >>= mapM_ printStory . take 30
 
-doStateOf :: Maybe Text -> StoryId -> EitherT ServantError IO ()
-doStateOf t i = story t i >>= liftIO . print . sCurrentState
+doStateOf :: StoryId -> TrackerAPI ()
+doStateOf i = story i >>= liftIO . print . sCurrentState
 
 invalidSyntax :: IO a
 invalidSyntax = getProgName >>= putStrLn . msg >> exitWith (ExitFailure 1)
diff --git a/src/Web/Tracker.hs b/src/Web/Tracker.hs
--- a/src/Web/Tracker.hs
+++ b/src/Web/Tracker.hs
@@ -1,44 +1,76 @@
 {-# LANGUAGE DataKinds         #-}
 {-# LANGUAGE LambdaCase        #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeOperators     #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 module Web.Tracker
-  ( module Web.Tracker.Types
-  , story
+  ( story
   , updateStory
   , stories
 
   -- Re-exports
   , ServantError
+  , HTTP.Manager
+  , mkManager
+  , TrackerAPI
+  , runTrackerAPI
   ) where
 
 --------------------------------------------------------------------------------
+import           Control.Monad.IO.Class
+import           Control.Monad.Trans.Except
+import           Control.Monad.Trans.Reader
 import           Data.Proxy
 import           Data.Text         (Text)
+import qualified Network.HTTP.Client as HTTP
+import qualified Network.HTTP.Client.TLS as HTTP
 import           Servant.API
 import           Servant.Client
 --------------------------------------------------------------------------------
-import           Web.Tracker.Types
+import           Web.Tracker.Story
 --------------------------------------------------------------------------------
 
 type API = "services" :> "v5" :> "stories"
-             :> Header "X-TrackerToken" Text
              :> Capture ":storyId" StoryId
+             :> Header "X-TrackerToken" Text
              :> Get '[JSON] Story
       :<|> "services" :> "v5" :> "stories"
-             :> Header "X-TrackerToken" Text
              :> ReqBody '[JSON] UpdateStory
              :> Capture ":storyId" StoryId
+             :> Header "X-TrackerToken" Text
              :> Put '[JSON] Story
       :<|> "services" :> "v5" :> "projects"
-             :> Header "X-TrackerToken" Text
              :> Capture ":projectId" ProjectId
              :> "stories"
              :> QueryParam "with_state" StoryState
+             :> Header "X-TrackerToken" Text
              :> Get '[JSON] [Story]
 
 api :: Proxy API
 api = Proxy
 
-story :<|> updateStory :<|> stories = client api (BaseUrl Https "www.pivotaltracker.com" 443)
+story' :<|> updateStory' :<|> stories' = client api --
+
+trackerUrl = (BaseUrl Https "www.pivotaltracker.com" 443 "")
+
+type TrackerAPI a = ReaderT (Text, HTTP.Manager) (ExceptT ServantError IO ) a
+
+type TrackerClientM a = Maybe Text -> ClientM a
+
+runTrackerAPI :: Text -> HTTP.Manager -> TrackerAPI a -> IO (Either ServantError a)
+runTrackerAPI tok man tApi = runExceptT $ runReaderT tApi (tok,man)
+
+toTrackerAPI :: TrackerClientM a -> TrackerAPI a
+toTrackerAPI cli = ReaderT $ \(tok,man) -> ExceptT $ runClientM (cli $ Just tok) (ClientEnv man trackerUrl)
+
+mkManager :: IO HTTP.Manager
+mkManager = HTTP.newManager HTTP.tlsManagerSettings
+
+story :: StoryId -> TrackerAPI Story
+story = toTrackerAPI . story'
+
+updateStory :: UpdateStory -> StoryId -> TrackerAPI Story
+updateStory us sid = toTrackerAPI $ updateStory' us sid
+
+stories :: ProjectId -> Maybe StoryState -> TrackerAPI [Story]
+stories pid mbss = toTrackerAPI $ stories' pid mbss
diff --git a/src/Web/Tracker/Story.hs b/src/Web/Tracker/Story.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tracker/Story.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Web.Tracker.Story where
+
+--------------------------------------------------------------------------------
+import           Data.Aeson
+import           Data.Aeson.Casing
+import           Data.String
+import           Data.Text         (Text)
+import qualified Data.Text         as T
+import           Data.Time.Clock
+import           GHC.Generics
+import           Servant.API
+import           Text.Read
+--------------------------------------------------------------------------------
+data StoryId
+  = StoryId Integer
+  deriving (Show, Eq, Ord)
+
+instance FromJSON StoryId where
+  parseJSON = fmap StoryId . parseJSON
+
+instance Read StoryId where
+  readPrec = fmap StoryId readPrec
+
+instance ToJSON StoryId where
+  toJSON (StoryId t) = toJSON t
+
+instance ToHttpApiData StoryId where
+  toUrlPiece (StoryId t) = toUrlPiece t
+
+data ProjectId
+  = ProjectId Int
+  deriving (Show, Eq, Ord)
+
+instance FromJSON ProjectId where
+  parseJSON = fmap ProjectId . parseJSON
+
+instance Read ProjectId where
+  readPrec = fmap ProjectId readPrec
+
+instance ToHttpApiData ProjectId where
+  toUrlPiece (ProjectId t) = toUrlPiece t
+
+data UserId
+  = UserId Int
+  deriving (Show, Eq, Ord)
+
+instance FromJSON UserId where
+  parseJSON = fmap UserId . parseJSON
+
+data StoryState
+  = Accepted | Delivered | Finished | Started
+  | Rejected | Planned | Unstarted | Unscheduled
+  deriving (Show, Eq, Ord, Enum, Bounded)
+
+instance FromJSON StoryState where
+  parseJSON = withText "StoryState" $ \case
+   "accepted"    -> return Accepted
+   "delivered"   -> return Delivered
+   "finished"    -> return Finished
+   "started"     -> return Started
+   "rejected"    -> return Rejected
+   "planned"     -> return Planned
+   "unstarted"   -> return Unstarted
+   "unscheduled" -> return Unscheduled
+   other -> fail $ "Invalid story state: " ++ T.unpack other
+
+instance ToJSON StoryState where
+  toJSON = toJSON . toUrlPiece
+
+instance ToHttpApiData StoryState where
+  toUrlPiece = \case
+    Accepted    -> "accepted"
+    Delivered   -> "delivered"
+    Finished    -> "finished"
+    Started     -> "started"
+    Rejected    -> "rejected"
+    Planned     -> "planned"
+    Unstarted   -> "unstarted"
+    Unscheduled -> "unscheduled"
+
+data StoryType
+  = Feature | Bug | Chore | Release
+  deriving (Show, Eq, Ord)
+
+instance FromJSON StoryType where
+  parseJSON = withText "StoryType" $ \case
+   "feature" -> return Feature
+   "bug"     -> return Bug
+   "chore"   -> return Chore
+   "release" -> return Release
+   other -> fail $ "Invalid StoryType: " ++ T.unpack other
+
+data UpdateStory
+  = SetStoryState StoryState
+
+instance ToJSON UpdateStory where
+  toJSON (SetStoryState state)
+    = object [ "current_state" .= state ]
+
+data Story = Story
+  { sId            :: StoryId
+  , sProjectId     :: ProjectId
+  , sName          :: Text
+  , sDescription   :: Maybe Text
+  , sStoryType     :: StoryType
+  , sCurrentState  :: StoryState
+  , sEstimate      :: Maybe Double
+  , sAcceptedAt    :: Maybe UTCTime
+  -- , sDeadline            :: UTCTime
+  -- , sProjectedCompletion :: UTCTime
+  , sRequestedById :: UserId
+  , sOwnerIds      :: [UserId]
+  -- , sLabels        :: [Label]
+  -- , sFollowerIds   :: [UserId]
+  , sCreatedAt     :: UTCTime
+  , sUpdatedAt     :: UTCTime
+  -- , sBeforeId :: UTCTime
+  -- , sAfterId :: UTCtime
+  -- , sIntegrationId
+  -- , sExternalId
+  , sUrl           :: Text
+  } deriving (Show, Generic)
+
+instance FromJSON Story where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
diff --git a/src/Web/Tracker/Types.hs b/src/Web/Tracker/Types.hs
deleted file mode 100644
--- a/src/Web/Tracker/Types.hs
+++ /dev/null
@@ -1,123 +0,0 @@
-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-module Web.Tracker.Types where
-
---------------------------------------------------------------------------------
-import           Data.Aeson
-import           Data.Aeson.Casing
-import           Data.String
-import           Data.Text         (Text)
-import qualified Data.Text         as T
-import           Data.Time.Clock
-import           GHC.Generics
-import           Servant.API
-import           Text.Read
---------------------------------------------------------------------------------
-data StoryId
-  = StoryId Integer
-  deriving (Show, Eq, Ord)
-
-instance FromJSON StoryId where
-  parseJSON = fmap StoryId . parseJSON
-
-instance Read StoryId where
-  readPrec = fmap StoryId readPrec
-
-instance ToJSON StoryId where
-  toJSON (StoryId t) = toJSON t
-
-instance ToText StoryId where
-  toText (StoryId t) = toText t
-
-data ProjectId
-  = ProjectId Int
-  deriving (Show, Eq, Ord)
-
-instance FromJSON ProjectId where
-  parseJSON = fmap ProjectId . parseJSON
-
-instance Read ProjectId where
-  readPrec = fmap ProjectId readPrec
-
-instance ToText ProjectId where
-  toText (ProjectId t) = toText t
-
-data UserId
-  = UserId Int
-  deriving (Show, Eq, Ord)
-
-instance FromJSON UserId where
-  parseJSON = fmap UserId . parseJSON
-
-data StoryState
-  = Accepted | Delivered | Finished | Started
-  | Rejected | Planned | Unstarted | Unscheduled
-  deriving (Show, Eq, Ord, Enum, Bounded)
-
-instance FromJSON StoryState where
-  parseJSON "accepted"    = return Accepted
-  parseJSON "delivered"   = return Delivered
-  parseJSON "finished"    = return Finished
-  parseJSON "started"     = return Started
-  parseJSON "rejected"    = return Rejected
-  parseJSON "planned"     = return Planned
-  parseJSON "unstarted"   = return Unstarted
-  parseJSON "unscheduled" = return Unscheduled
-
-instance ToJSON StoryState where
-  toJSON = toJSON . toText
-
-instance ToText StoryState where
-  toText Accepted    = "accepted"
-  toText Delivered   = "delivered"
-  toText Finished    = "finished"
-  toText Started     = "started"
-  toText Rejected    = "rejected"
-  toText Planned     = "planned"
-  toText Unstarted   = "unstarted"
-  toText Unscheduled = "unscheduled"
-
-data StoryType
-  = Feature | Bug | Chore | Release
-  deriving (Show, Eq, Ord)
-
-instance FromJSON StoryType where
-  parseJSON "feature" = return Feature
-  parseJSON "bug"     = return Bug
-  parseJSON "chore"   = return Chore
-  parseJSON "release" = return Release
-
-data UpdateStory
-  = SetStoryState StoryState
-
-instance ToJSON UpdateStory where
-  toJSON (SetStoryState state)
-    = object [ "current_state" .= state ]
-
-data Story = Story
-  { sId            :: StoryId
-  , sProjectId     :: ProjectId
-  , sName          :: Text
-  , sDescription   :: Maybe Text
-  , sStoryType     :: StoryType
-  , sCurrentState  :: StoryState
-  , sEstimate      :: Maybe Double
-  , sAcceptedAt    :: Maybe UTCTime
-  -- , sDeadline            :: UTCTime
-  -- , sProjectedCompletion :: UTCTime
-  , sRequestedById :: UserId
-  , sOwnerIds      :: [UserId]
-  -- , sLabels        :: [Label]
-  -- , sFollowerIds   :: [UserId]
-  , sCreatedAt     :: UTCTime
-  , sUpdatedAt     :: UTCTime
-  -- , sBeforeId :: UTCTime
-  -- , sAfterId :: UTCtime
-  -- , sIntegrationId
-  -- , sExternalId
-  , sUrl           :: Text
-  } deriving (Show, Generic)
-
-instance FromJSON Story where
-  parseJSON = genericParseJSON $ aesonPrefix snakeCase
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,19 @@
+import Test.Hspec
+
+--------------------------------------------------------------------------------
+import           Data.Functor((<$>))
+--------------------------------------------------------------------------------
+import           Web.Tracker
+import           Web.Tracker.Story
+--------------------------------------------------------------------------------
+
+main :: IO ()
+main = hspec $
+  describe "Web.Tracker story" $
+    it "should return story data" $ do
+      manager <- mkManager
+      s <- runTrackerAPI token manager $ story (StoryId 104591424)
+      show (sStoryType <$> s) `shouldBe` "Right Feature"
+      show (sName <$> s) `shouldBe` "Right \"Shopper should be able to view contents of shopping cart\""
+    where
+        token = "7f3f76bc6ae8c48e7b528369c999c8d8"
