diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
-hackernews 
+hackernews
 ==========
 ![Hackage](https://img.shields.io/hackage/v/hackernews.svg)
 ![Hackage Dependencies](https://img.shields.io/hackage-deps/v/hackernews.svg)
 ![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)
-![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)
-![Build Status](https://img.shields.io/circleci/project/dmjio/hackernews.svg)
+![MIT License](http://img.shields.io/badge/license-MIT-brightgreen.svg)
+[![Build Status](https://travis-ci.org/dmjio/hackernews.svg?branch=master)](https://travis-ci.org/dmjio/hackernews)
 
 Hacker News API for Haskell
 
@@ -13,7 +13,7 @@
 
 Now it supports GHCJS and can be used in the browser! Just install it using:
 ```bash
-cabal install --ghcjs --flags=ghcjs
+cabal install --ghcjs
 ```
 
 ###Tests
@@ -22,49 +22,81 @@
 ```
 
 ```bash
-Hacker News API Tests
-  - Retrieves a Story
-  - Retrieves a Comment
-  - Retrieves a User
-  - Retrieves a Poll
-  - Retrieves a Pollopt
-  - Retrieves Top Stories
-  - Retrieves Max Item
-  - Retrieves Updates
+HackerNews API tests
+  should round trip Updates JSON
+  should round trip Item JSON
+  should round trip User JSON
+  should retrieve item
+  should retrieve user
+  should retrieve max item
+  should retrieve top stories
+  should retrieve new stories
+  should retrieve best stories
+  should retrieve ask stories
+  should retrieve show stories
+  should retrieve job stories
+  should retrieve updates
 
-Finished in 0.0019 seconds
-8 examples, 0 failures
+  Finished in 1.2129 seconds
+  13 examples, 0 failures
 ```
 
 ###Usage
-```haskell 
-module Example where
+```haskell
+module Main where
 
-import           Control.Monad  (liftM3)
-import           Web.HackerNews (UserId (..), getUser, hackerNews)
+import Network.HTTP.Client
+import Network.HTTP.Client.TLS
 
+import Web.HackerNews
+
 main :: IO ()
-main = print =<< hackerNews (liftM3 (,,) one two three)
-  where
-    one   = getUser (UserId "dmjio")
-    two   = getUser (UserId "dmj")
-    three = getUser (UserId "abs")
+main = do
+ print =<< getItem mgr (ItemId 1000)
+ print =<< getUser mgr (UserId "dmjio")
+ print =<< getMaxItem mgr
+ print =<< getTopStories mgr
+ print =<< getNewStories mgr
+ print =<< getBestStories mgr
+ print =<< getAskStories mgr
+ print =<< getShowStories mgr
+ print =<< getJobStories mgr
+ print =<< getUpdates mgr
 ```
 
 ```bash
-(Just (User { userAbout = Nothing
-            , userCreated = 2013-08-06 16:49:23 UTC
-            , userDelay = 0
-            , userId = UserId "dmjio"
-            , userKarma = 6
-            , userSubmitted = [8433827,8429256,8429161,8429069,8374809,8341570,7919268,7825469,7350544,7327291,6495994,6352317,6168527,6168524,6167639]})
-      , 
-Just (User { userAbout = Nothing
-           , userCreated = 2007-04-11 05:57:35 UTC
-           , userDelay = 0
-           , userId = UserId "dmj"
-           , userKarma = 1
-           , userSubmitted = [11737]
-           }),
-Nothing)
+Right ( Item {
+	 itemId = Just (ItemId 1000)
+   , itemDeleted = Nothing
+   , itemType = Story
+   , itemBy = Just (UserName "python_kiss")
+   , itemTime = Just (Time 1172394646)
+   , itemText = Nothing
+   , itemDead = Nothing
+   , itemParent = Nothing
+   , itemKids = Nothing
+   , itemURL = Just (URL "http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/")
+   , itemScore = Just (Score 4)
+   , itemTitle = Just (Title "How Important is the .com TLD?")
+   , itemParts = Nothing
+   , itemDescendants = Just (Descendants 0)
+   })
+Right (User {userId = UserId "dmjio"
+		   , userDelay = Nothing
+		   , userCreated = Created 1375807763
+		   , userKarma = Karma 7
+		   , userAbout = Nothing
+		   , userSubmitted = Just (Submitted [11966297,9355613, ...])
+		   })
+Right (MaxItem 12695220)
+Right (TopStories [12694004,12692190,12691597,...])
+Right (NewStories [12695214,12695213,12695195,...])
+Right (BestStories [12649414,12637126,12684980, ...])
+Right (AskStories [12694706,12694401,12694038, ...])
+Right (ShowStories [12694004,12692190,12695037, ...])
+Right (JobStories [12693320,12691627,12690539,...])
+Right (Updates { items = [12694916,12694478,12693674,..],
+				 profiles = [UserName "stefano", UserName "chillydawg", ...]
+			   })
+
 ```
diff --git a/ghc-examples/Example.hs b/ghc-examples/Example.hs
new file mode 100644
--- /dev/null
+++ b/ghc-examples/Example.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Network.HTTP.Client
+import Network.HTTP.Client.TLS
+
+import Web.HackerNews
+
+main :: IO ()
+main = do
+ mgr <- newManager tlsManagerSettings
+ print =<< getItem mgr (ItemId 1000)
+ print =<< getUser mgr (UserId "dmjio")
+ print =<< getMaxItem mgr
+ print =<< getTopStories mgr
+ print =<< getNewStories mgr
+ print =<< getBestStories mgr
+ print =<< getAskStories mgr
+ print =<< getShowStories mgr
+ print =<< getJobStories mgr
+ print =<< getUpdates mgr
diff --git a/ghc-src/Web/HackerNews.hs b/ghc-src/Web/HackerNews.hs
new file mode 100644
--- /dev/null
+++ b/ghc-src/Web/HackerNews.hs
@@ -0,0 +1,215 @@
+{-# LANGUAGE RecordWildCards            #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE TypeFamilies               #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE KindSignatures             #-}
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE TypeOperators              #-}
+{-# LANGUAGE DataKinds                  #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+------------------------------------------------------------------------------
+-- |
+-- Module      : Web.HackerNews
+-- Copyright   : (c) David Johnson, 2014-2016
+-- Maintainer  : djohnson.m@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- Haskell port of <https://github.com/HackerNews/API>
+--
+------------------------------------------------------------------------------
+module Web.HackerNews
+  ( -- * Hacker News API
+    HackerNewsAPI
+   -- * Custom combinators
+  , HackerCapture
+   -- * API functions
+  , getItem
+  , getUser
+  , getMaxItem
+  , getTopStories
+  , getNewStories
+  , getBestStories
+  , getAskStories
+  , getShowStories
+  , getJobStories
+  , getUpdates
+  -- * Core Types
+  , Item        (..)
+  , User        (..)
+  , Updates     (..)
+  , MaxItem     (..)
+  , TopStories  (..)
+  , NewStories  (..)
+  , BestStories (..)
+  , AskStories  (..)
+  , ShowStories (..)
+  , JobStories  (..)
+  --- * Supporting Types
+  , UserId      (..)
+  , ItemId      (..)
+  , Deleted     (..)
+  , ItemType    (..)
+  , UserName    (..)
+  , Time        (..)
+  , ItemText    (..)
+  , Dead        (..)
+  , Parent      (..)
+  , Kids        (..)
+  , URL         (..)
+  , Score       (..)
+  , Title       (..)
+  , Parts       (..)
+  , Descendants (..)
+  , Delay       (..)
+  , Created     (..)
+  , Karma       (..)
+  , About       (..)
+  , Submitted   (..)
+  --- * Error handling
+  , HackerNewsError (..)
+  ) where
+
+import           Control.Monad.Trans.Except
+import           Data.Bifunctor
+import           Data.Monoid
+import           Data.Proxy
+import           Data.String.Conversions
+import qualified Data.Text                  as T
+import           Network.HTTP.Client        (Manager)
+import           Network.HTTP.Types.Status
+import           Servant.API
+import           Servant.Client
+import           Servant.Common.Req
+
+import           Web.HackerNews.Types
+
+-- | HackerNews API
+type HackerNewsAPI =
+       "item" :> HackerCapture ItemId :> Get '[JSON] Item
+  :<|> "user" :> HackerCapture UserId :> Get '[JSON] User
+  :<|> "maxitem.json" :> Get '[JSON] MaxItem
+  :<|> "topstories.json" :> Get '[JSON] TopStories
+  :<|> "newstories.json" :> Get '[JSON] NewStories
+  :<|> "beststories.json" :> Get '[JSON] BestStories
+  :<|> "askstories.json" :> Get '[JSON] AskStories
+  :<|> "showstories.json" :> Get '[JSON] ShowStories
+  :<|> "jobstories.json" :> Get '[JSON] JobStories
+  :<|> "updates.json" :> Get '[JSON] Updates
+
+-- | Custom combinator for appending '.json' to `Item` query
+data HackerCapture (a :: *)
+
+-- | Custom combinator `HasClient` instance
+instance (ToHttpApiData a, HasClient api) => HasClient (HackerCapture a :> api) where
+  type Client (HackerCapture a :> api) = a -> Client api
+  clientWithRoute Proxy req val =
+    clientWithRoute (Proxy :: Proxy api)
+       (appendToPath p req)
+    where
+      p = T.unpack $ toUrlPiece val <> ".json"
+
+-- | HN `BaseURL`
+hackerNewsURL :: BaseUrl
+hackerNewsURL = BaseUrl Https "hacker-news.firebaseio.com" 443 "/v0"
+
+-- | Convert ServantError to HackerNewsError
+toError :: Either ServantError ok -> Either HackerNewsError ok
+toError = first go
+  where
+    go :: ServantError -> HackerNewsError
+    go (FailureResponse Status{..} _ body) =
+      FailureResponseError statusCode (cs statusMessage) (cs body)
+    go (DecodeFailure _ _ "null") = NotFound
+    go (DecodeFailure err _ body) =
+      DecodeFailureError (cs err) (cs body)
+    go (UnsupportedContentType _ body) =
+      UnsupportedContentTypeError (cs body)
+    go (InvalidContentTypeHeader header body) =
+      InvalidContentTypeHeaderError (cs header) (cs body)
+    go (ConnectionError ex) =
+      HNConnectionError $ cs (show ex)
+
+-- | Retrieve `Item`
+getItem :: Manager -> ItemId -> IO (Either HackerNewsError Item)
+getItem mgr itemId =
+  toError <$> do
+    runExceptT $ getItem' itemId mgr hackerNewsURL
+
+-- | Retrieve `User`
+getUser :: Manager -> UserId -> IO (Either HackerNewsError User)
+getUser mgr userId =
+  toError <$> do
+    runExceptT $ getUser' userId mgr hackerNewsURL
+
+-- | Retrieve `MaxItem`
+getMaxItem :: Manager -> IO (Either HackerNewsError MaxItem)
+getMaxItem mgr =
+  toError <$> do
+    runExceptT $ getMaxItem' mgr hackerNewsURL
+
+-- | Retrieve `TopStories`
+getTopStories :: Manager -> IO (Either HackerNewsError TopStories)
+getTopStories mgr =
+  toError <$> do
+    runExceptT $ getTopStories' mgr hackerNewsURL
+
+-- | Retrieve `NewStories`
+getNewStories :: Manager -> IO (Either HackerNewsError NewStories)
+getNewStories mgr =
+  toError <$> do
+    runExceptT $ getNewStories' mgr hackerNewsURL
+
+-- | Retrieve `BestStories`
+getBestStories :: Manager -> IO (Either HackerNewsError BestStories)
+getBestStories mgr =
+  toError <$> do
+    runExceptT $ getBestStories' mgr hackerNewsURL
+
+-- | Retrieve `AskStories`
+getAskStories :: Manager -> IO (Either HackerNewsError AskStories)
+getAskStories mgr =
+  toError <$> do
+    runExceptT $ getAskStories' mgr hackerNewsURL
+
+-- | Retrieve `ShowStories`
+getShowStories :: Manager -> IO (Either HackerNewsError ShowStories)
+getShowStories mgr =
+  toError <$> do
+    runExceptT $ getShowStories' mgr hackerNewsURL
+
+-- | Retrieve `JobStories`
+getJobStories :: Manager -> IO (Either HackerNewsError JobStories)
+getJobStories mgr =
+  toError <$> do
+    runExceptT $ getJobStories' mgr hackerNewsURL
+
+-- | Retrieve `Updates`
+getUpdates :: Manager -> IO (Either HackerNewsError Updates)
+getUpdates mgr =
+  toError <$> do
+    runExceptT $ getUpdates' mgr hackerNewsURL
+
+getItem' :: ItemId -> Manager -> BaseUrl -> ClientM Item
+getUser' :: UserId -> Manager -> BaseUrl -> ClientM User
+getMaxItem' :: Manager -> BaseUrl -> ClientM MaxItem
+getTopStories' :: Manager -> BaseUrl -> ClientM TopStories
+getNewStories' :: Manager -> BaseUrl -> ClientM NewStories
+getBestStories' :: Manager -> BaseUrl -> ClientM BestStories
+getAskStories' :: Manager -> BaseUrl -> ClientM AskStories
+getShowStories' :: Manager -> BaseUrl -> ClientM ShowStories
+getJobStories' :: Manager -> BaseUrl -> ClientM JobStories
+getUpdates' :: Manager -> BaseUrl -> ClientM Updates
+
+getItem'
+  :<|> getUser'
+  :<|> getMaxItem'
+  :<|> getTopStories'
+  :<|> getNewStories'
+  :<|> getBestStories'
+  :<|> getAskStories'
+  :<|> getShowStories'
+  :<|> getJobStories'
+  :<|> getUpdates' = client (Proxy :: Proxy HackerNewsAPI)
+
diff --git a/ghc-tests/Test.hs b/ghc-tests/Test.hs
new file mode 100644
--- /dev/null
+++ b/ghc-tests/Test.hs
@@ -0,0 +1,111 @@
+{-# LANGUAGE OverloadedStrings    #-}
+{-# LANGUAGE ScopedTypeVariables  #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Main where
+
+import Control.Applicative
+import Data.Aeson
+import Data.Either               (isRight)
+import Generics.SOP.Arbitrary
+import Generics.SOP.Universe
+import Network.HTTP.Client
+import Network.HTTP.Client.TLS
+import Test.Hspec                (it, hspec, describe, shouldSatisfy, shouldBe)
+import Test.QuickCheck
+import Test.QuickCheck.Instances ()
+import Web.HackerNews
+
+instance Generic Item
+instance Generic Updates
+instance Generic User
+instance Generic UserName
+instance Generic UserId
+instance Generic Delay
+instance Generic Created
+instance Generic Karma
+instance Generic About
+instance Generic Submitted
+
+instance Arbitrary Item where arbitrary = garbitrary
+instance Arbitrary User where arbitrary = garbitrary
+instance Arbitrary Updates where arbitrary = garbitrary
+
+instance Arbitrary UserName where arbitrary = garbitrary
+instance Arbitrary UserId  where arbitrary = garbitrary
+instance Arbitrary Delay where arbitrary = garbitrary
+instance Arbitrary Created where arbitrary = garbitrary
+instance Arbitrary Karma where arbitrary = garbitrary
+instance Arbitrary About where arbitrary = garbitrary
+instance Arbitrary Submitted where arbitrary = garbitrary
+
+instance Arbitrary ItemId where arbitrary = garbitrary
+instance Generic ItemId
+instance Arbitrary Deleted where arbitrary = garbitrary
+instance Generic Deleted
+instance Arbitrary ItemType where arbitrary = garbitrary
+instance Generic ItemType where
+instance Arbitrary Time where arbitrary = garbitrary
+instance Generic Time
+instance Arbitrary ItemText where arbitrary = garbitrary
+instance Generic ItemText
+instance Arbitrary Dead where arbitrary = garbitrary
+instance Generic Dead
+instance Arbitrary Parent where arbitrary = garbitrary
+instance Generic Parent
+instance Arbitrary Kids where arbitrary = garbitrary
+instance Generic Kids
+instance Arbitrary URL where arbitrary = garbitrary
+instance Generic URL
+instance Arbitrary Score where arbitrary = garbitrary
+instance Generic Score
+instance Arbitrary Title where arbitrary = garbitrary
+instance Generic Title
+instance Arbitrary Parts where arbitrary = garbitrary
+instance Generic Parts
+instance Arbitrary Descendants where arbitrary = garbitrary
+instance Generic Descendants
+
+main :: IO ()
+main = do
+ mgr <- newManager tlsManagerSettings
+ hspec $ do
+  describe "HackerNews API tests" $ do
+    it "should round trip Updates JSON" $ property $ \(x :: Updates) ->
+       Just x == decode (encode x)
+    it "should round trip Item JSON" $ property $ \(x :: Item) ->
+       Just x == decode (encode x)
+    it "should round trip User JSON" $ property $ \(x :: User) ->
+       Just x == decode (encode x)
+    it "should retrieve item" $ do
+      (`shouldSatisfy` isRight) =<< getItem mgr (ItemId 1000)
+    it "should return NotFound " $ do
+      Left x <- getItem mgr (ItemId 0)
+      x `shouldBe` NotFound
+    it "should retrieve user" $ do
+      (`shouldSatisfy` isRight) =<< getUser mgr (UserId "dmjio")
+    it "should retrieve max item" $ do
+      (`shouldSatisfy` isRight) =<< getMaxItem mgr
+    it "should retrieve top stories" $ do
+      (`shouldSatisfy` isRight) =<< getTopStories mgr
+    it "should retrieve new stories" $ do
+      (`shouldSatisfy` isRight) =<< getNewStories mgr
+    it "should retrieve best stories" $ do
+      (`shouldSatisfy` isRight) =<< getBestStories mgr
+    it "should retrieve ask stories" $ do
+      (`shouldSatisfy` isRight) =<< getAskStories mgr
+    it "should retrieve show stories" $ do
+      (`shouldSatisfy` isRight) =<< getShowStories mgr
+    it "should retrieve job stories" $ do
+      (`shouldSatisfy` isRight) =<< getJobStories mgr
+    it "should retrieve updates" $ do
+      (`shouldSatisfy` isRight) =<< getUpdates mgr
+
+
+
+
+
+
+
+
+
+
diff --git a/ghcjs-examples/Example.hs b/ghcjs-examples/Example.hs
new file mode 100644
--- /dev/null
+++ b/ghcjs-examples/Example.hs
@@ -0,0 +1,4 @@
+module Main where
+
+main :: IO ()
+main = putStrLn "hi"
diff --git a/ghcjs-src/Web/HackerNews.hs b/ghcjs-src/Web/HackerNews.hs
new file mode 100644
--- /dev/null
+++ b/ghcjs-src/Web/HackerNews.hs
@@ -0,0 +1,123 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings   #-}
+------------------------------------------------------------------------------
+-- |
+-- Module      : Web.HackerNews
+-- Copyright   : (c) David Johnson, 2014-2016
+-- Maintainer  : djohnson.m@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
+--
+------------------------------------------------------------------------------
+module Web.HackerNews
+       ( -- * API functions
+         getItem
+       , getUser
+       , getMaxItem
+       , getTopStories
+       , getNewStories
+       , getBestStories
+       , getAskStories
+       , getShowStories
+       , getJobStories
+       , getUpdates
+       -- * Core Types
+       , Item        (..)
+       , User        (..)
+       , Updates     (..)
+       , MaxItem     (..)
+       , TopStories  (..)
+       , NewStories  (..)
+       , BestStories (..)
+       , AskStories  (..)
+       , ShowStories (..)
+       , JobStories  (..)
+       --- * Supporting Types
+       , UserId      (..)
+       , ItemId      (..)
+       , Deleted     (..)
+       , ItemType    (..)
+       , UserName    (..)
+       , Time        (..)
+       , ItemText    (..)
+       , Dead        (..)
+       , Parent      (..)
+       , Kids        (..)
+       , URL         (..)
+       , Score       (..)
+       , Title       (..)
+       , Parts       (..)
+       , Descendants (..)
+       , Delay       (..)
+       , Created     (..)
+       , Karma       (..)
+       , About       (..)
+       , Submitted   (..)
+       ) where
+
+------------------------------------------------------------------------------
+import Data.Aeson
+import Data.Monoid
+import JavaScript.Web.XMLHttpRequest
+import Data.String.Conversions
+import Data.JSString
+import Data.JSString.Text
+
+import Web.HackerNews.Types
+
+-- | Retrieve `Item`
+getItem :: ItemId -> IO (Either HackerNewsError Item)
+getItem (ItemId x) = issueAjax (Just "item") $ textToJSString $ cs (show x)
+
+-- | Retrieve `User`
+getUser :: UserId -> IO (Either HackerNewsError User)
+getUser (UserId u) = issueAjax (Just "user") (textToJSString u)
+
+-- | Retrieve `MaxItem`
+getMaxItem :: IO (Either HackerNewsError MaxItem)
+getMaxItem = issueAjax Nothing "maxitem"
+
+-- | Retrieve `TopStories`
+getTopStories :: IO (Either HackerNewsError TopStories)
+getTopStories = issueAjax Nothing "topstories"
+
+-- | Retrieve `NewStories`
+getNewStories :: IO (Either HackerNewsError NewStories)
+getNewStories = issueAjax Nothing "newstories"
+
+-- | Retrieve `BestStories`
+getBestStories :: IO (Either HackerNewsError BestStories)
+getBestStories = issueAjax Nothing "beststories"
+
+-- | Retrieve `AskStories`
+getAskStories :: IO (Either HackerNewsError AskStories)
+getAskStories = issueAjax Nothing "askstories"
+
+-- | Retrieve `ShowStories`
+getShowStories :: IO (Either HackerNewsError ShowStories)
+getShowStories = issueAjax Nothing "showstories"
+
+-- | Retrieve `JobStories`
+getJobStories :: IO (Either HackerNewsError JobStories)
+getJobStories = issueAjax Nothing "jobstories"
+
+-- | Retrieve `Updates`
+getUpdates :: IO (Either HackerNewsError Updates)
+getUpdates = issueAjax Nothing "updates"
+
+issueAjax :: FromJSON a => Maybe JSString -> JSString -> IO (Either HackerNewsError a)
+issueAjax maybePath uri = do
+  response <- xhrByteString request
+  pure $ case contents response of
+    Nothing -> Left NotFound
+    Just "null" -> Left NotFound
+    Just x ->
+      case eitherDecode (cs x) of
+        Left l -> Left $ DecodeFailureError (cs l) mempty
+        Right r -> Right r
+    where
+      request = Request GET url Nothing [] False NoData
+      url = "https://hacker-news.firebaseio.com/v0/"
+              <> maybe mempty (\path -> path <> "/") maybePath
+              <> uri
+              <> ".json"
diff --git a/ghcjs-tests/Test.hs b/ghcjs-tests/Test.hs
new file mode 100644
--- /dev/null
+++ b/ghcjs-tests/Test.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE OverloadedStrings    #-}
+{-# LANGUAGE ScopedTypeVariables  #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE RecordWildCards      #-}
+module Main where
+
+import Control.Applicative
+import Data.Aeson
+import Data.Either               (isRight)
+import Data.JSString
+import Generics.SOP.Arbitrary
+import Generics.SOP.Universe
+import System.Exit
+import Test.Hspec (it, hspec, describe, shouldSatisfy, shouldBe)
+import Test.Hspec.Core.Runner (hspecResult, Summary(..))
+import Test.QuickCheck
+import Test.QuickCheck.Instances ()
+
+import Web.HackerNews.Types
+import Web.HackerNews
+
+instance Generic Item
+instance Generic Updates
+instance Generic User
+instance Generic UserName
+instance Generic UserId
+instance Generic Delay
+instance Generic Created
+instance Generic Karma
+instance Generic About
+instance Generic Submitted
+
+instance Arbitrary Item where arbitrary = garbitrary
+instance Arbitrary User where arbitrary = garbitrary
+instance Arbitrary Updates where arbitrary = garbitrary
+
+instance Arbitrary UserName where arbitrary = garbitrary
+instance Arbitrary UserId  where arbitrary = garbitrary
+instance Arbitrary Delay where arbitrary = garbitrary
+instance Arbitrary Created where arbitrary = garbitrary
+instance Arbitrary Karma where arbitrary = garbitrary
+instance Arbitrary About where arbitrary = garbitrary
+instance Arbitrary Submitted where arbitrary = garbitrary
+
+instance Arbitrary ItemId where arbitrary = garbitrary
+instance Generic ItemId
+instance Arbitrary Deleted where arbitrary = garbitrary
+instance Generic Deleted
+instance Arbitrary ItemType where arbitrary = garbitrary
+instance Generic ItemType where
+instance Arbitrary Time where arbitrary = garbitrary
+instance Generic Time
+instance Arbitrary ItemText where arbitrary = garbitrary
+instance Generic ItemText
+instance Arbitrary Dead where arbitrary = garbitrary
+instance Generic Dead
+instance Arbitrary Parent where arbitrary = garbitrary
+instance Generic Parent
+instance Arbitrary Kids where arbitrary = garbitrary
+instance Generic Kids
+instance Arbitrary URL where arbitrary = garbitrary
+instance Generic URL
+instance Arbitrary Score where arbitrary = garbitrary
+instance Generic Score
+instance Arbitrary Title where arbitrary = garbitrary
+instance Generic Title
+instance Arbitrary Parts where arbitrary = garbitrary
+instance Generic Parts
+instance Arbitrary Descendants where arbitrary = garbitrary
+instance Generic Descendants
+
+main :: IO ()
+main = do
+ Summary{..} <- hspecResult $ do
+  describe "HackerNews API tests" $ do
+    it "should round trip Updates JSON" $ property $ \(x :: Updates) ->
+       Just x == decode (encode x)
+    it "should round trip Item JSON" $ property $ \(x :: Item) ->
+       Just x == decode (encode x)
+    it "should round trip User JSON" $ property $ \(x :: User) ->
+       Just x == decode (encode x)
+    it "should retrieve item" $ do
+      (`shouldSatisfy` isRight) =<< getItem (ItemId 1000)
+    it "should return NotFound " $ do
+      Left x <- getItem (ItemId 0)
+      x `shouldBe` NotFound
+    it "should retrieve user" $ do
+      (`shouldSatisfy` isRight) =<< getUser (UserId "dmjio")
+    it "should retrieve max item" $ do
+      (`shouldSatisfy` isRight) =<< getMaxItem
+    it "should retrieve top stories" $ do
+      (`shouldSatisfy` isRight) =<< getTopStories
+    it "should retrieve new stories" $ do
+      (`shouldSatisfy` isRight) =<< getNewStories
+    it "should retrieve best stories" $ do
+      (`shouldSatisfy` isRight) =<< getBestStories
+    it "should retrieve ask stories" $ do
+      (`shouldSatisfy` isRight) =<< getAskStories
+    it "should retrieve show stories" $ do
+      (`shouldSatisfy` isRight) =<< getShowStories
+    it "should retrieve job stories" $ do
+      (`shouldSatisfy` isRight) =<< getJobStories
+    it "should retrieve updates" $ do
+      (`shouldSatisfy` isRight) =<< getUpdates
+ case summaryFailures of
+   x | x > 0 -> putStrLn "error"
+     | otherwise -> putStrLn "done"
+
+
+
diff --git a/hackernews.cabal b/hackernews.cabal
--- a/hackernews.cabal
+++ b/hackernews.cabal
@@ -1,5 +1,5 @@
 name:                hackernews
-version:             0.5.0.1
+version:             1.0.0.0
 description:         API for news.ycombinator.com
 license:             MIT
 synopsis:            API for Hacker News
@@ -10,64 +10,93 @@
 build-type:          Simple
 cabal-version:       >=1.10
 extra-source-files:
-    README.md       
+    jsbits/options.js
+    README.md
+    LICENSE
+    ghc-examples/Example.hs
+    ghcjs-examples/Example.hs
+    ghc-tests/Test.hs
+    ghcjs-tests/Test.hs
+executable example
+  main-is: Example.hs
+  default-language: Haskell2010
+  if impl (ghcjs)
+    build-depends:
+        base
+      , hackernews == 1.0.*
+      , ghcjs-base
+    hs-source-dirs: ghcjs-examples
+  else
+    build-depends:
+         base
+       , hackernews == 1.0.*
+       , http-client-tls
+       , http-client
+    hs-source-dirs: ghc-examples
 
-flag ghcjs
-     description:    Tell cabal we're using GHCJS
-     default:        False
+executable ghcjs-tests
+  main-is: Test.hs
+  if impl(ghcjs)
+    hs-source-dirs: ghcjs-tests
+    build-depends: base
+               , hackernews == 1.0.*
+               , ghcjs-base
+               , hspec
+               , hspec-core
+               , basic-sop
+               , generics-sop
+               , quickcheck-instances
+               , aeson
+                 , QuickCheck
+  else
+    buildable: False
+  default-language: Haskell2010
 
 library
-  exposed-modules:     Web.HackerNews
-  other-modules:       Web.HackerNews.Types
-                     , Web.HackerNews.Client
-                     , Web.HackerNews.User
-                     , Web.HackerNews.Poll
-                     , Web.HackerNews.Person
-                     , Web.HackerNews.Update
-                     , Web.HackerNews.Comment
-                     , Web.HackerNews.Story
-                     , Web.HackerNews.Job
-                     , Web.HackerNews.Item
-                     , Web.HackerNews.Util
-                     , Web.HackerNews.Endpoint
-  hs-source-dirs:      src
-
-  if flag(ghcjs)
-     ghcjs-options:  -O2
-     build-depends:  ghcjs-base
-                   , aeson       >= 0.8.0.1
-                   , base        == 4.*
-                   , attoparsec  >= 0.12.1.2
-                   , either      >= 4.3.1
-                   , text        >= 1.2.0.0
-                   , time        >= 1.4.2
-                   , transformers >= 0.3.0.0
+  exposed-modules:  Web.HackerNews
+                  , Web.HackerNews.Types
+  hs-source-dirs: src
+  build-depends: servant == 0.8.*
+  if impl(ghcjs)
+    ghcjs-options:  -Wall
+    hs-source-dirs: ghcjs-src
+    build-depends:  aeson == 0.9.*
+                  , attoparsec == 0.13.*
+                  , base < 5
+                  , ghcjs-base
+                  , string-conversions == 0.4.*
+                  , text == 1.2.*
+                  , transformers == 0.4.*
   else
-     ghc-options:         -Wall -rtsopts
-     build-depends:  HsOpenSSL    >= 0.10.5
-                     , aeson        >= 0.8.0.1
-                     , attoparsec   >= 0.12.1.2
-                     , base        == 4.*
-                     , bytestring   >= 0.10.4.0
-                     , either       >= 4.3.1
-                     , http-streams >= 0.7.2.2
-                     , io-streams   >= 1.1.4.6
-                     , text         >= 1.2.0.0
-                     , time         >= 1.4.2
-                     , transformers >= 0.3.0.0
+    ghc-options:  -Wall
+    hs-source-dirs: ghc-src
+    build-depends: aeson
+                 , base < 5
+                 , servant-client == 0.8.*
+                 , http-client == 0.4.*
+                 , string-conversions == 0.4.*
+                 , http-types == 0.9.*
+                 , text == 1.2.*
+                 , transformers == 0.5.*
   default-language:    Haskell2010
 
-Test-Suite tests
-    type:                exitcode-stdio-1.0
-    ghc-options:         -rtsopts -threaded
-    hs-source-dirs:      tests
-    main-is:             Test.hs
-    build-depends:       base
-                       , hackernews
-                       , hspec >= 1.11.4
-                       , transformers
-    default-language:    Haskell2010
-    ghc-options:         -Wall
+Test-Suite ghc-tests
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  main-is:             Test.hs
+  ghc-options:         -rtsopts -threaded -Wall
+  hs-source-dirs:      ghc-tests
+  build-depends:       aeson
+                     , base
+                     , basic-sop
+                     , generics-sop == 0.2.*
+                     , hackernews == 1.0.*
+                     , hspec == 2.2.*
+                     , http-client-tls == 0.2.*
+                     , http-client
+                     , QuickCheck == 2.8.*
+                     , quickcheck-instances == 0.3.*
+                     , transformers
 
 source-repository head
   type:     git
diff --git a/jsbits/options.js b/jsbits/options.js
new file mode 100644
--- /dev/null
+++ b/jsbits/options.js
@@ -0,0 +1,30 @@
+"use strict";
+var system = require('system');
+if (system.args.length !== 2) {
+    console.log('Usage: phantomjsOpen.js URL');
+    phantom.exit(1);
+}
+
+var page = require('webpage').create();
+
+page.onError = function (msg, trace) {
+  if (msg.match("error").length > 0) {
+    phantom.exit(1);
+  }
+};
+
+page.open(system.args[1], function (status) {
+    page.onConsoleMessage = function (msg) {
+	if (msg.match("done")) {
+	    phantom.exit(0);
+	} else if (msg.match("error")) {
+	    phantom.exit(1);
+	} else {
+	    console.log(msg);
+	}
+    };
+    if (status !== "success") {
+        console.log("Unable to open " + system.args[1]);
+        phantom.exit(1);
+    }
+});
diff --git a/src/Web/HackerNews.hs b/src/Web/HackerNews.hs
deleted file mode 100644
--- a/src/Web/HackerNews.hs
+++ /dev/null
@@ -1,96 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
--- |
--- Module      : Web.HackerNews
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews
-       ( -- * Hacker News Monad
-         hackerNews
-         -- * API Calls
-       , getItem
-       , getStory
-       , getComment
-       , getPoll
-       , getPollOpt
-       , getUser
-       , getJob
-       , getTopStories
-       , getMaxItem
-       , getUpdates
-         -- * Types
-       , HackerNews
-       , HackerNewsError (..)
-       , Item      (..)
-       , ItemId    (..)
-       , Comment   (..)
-       , CommentId (..)
-       , Poll      (..)
-       , PollId    (..)
-       , PollOpt   (..)
-       , PollOptId (..)
-       , Story     (..)
-       , StoryId   (..)
-       , User      (..)
-       , UserId    (..)
-       , Job       (..)
-       , JobId     (..)
-       , Update    (..)
-       , MaxItem   (..)
-       , TopStories (..)
-       ) where
-
-import           Web.HackerNews.Types
-import           Web.HackerNews.Client (HackerNews, hackerNews, HackerNewsError(..))
-
-------------------------------------------------------------------------------
--- | Retrieve a `Item` by `ItemId`
-getItem :: ItemId -> HackerNews Item
-getItem = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve a `Story` by `StoryId`
-getStory :: StoryId -> HackerNews Story
-getStory = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve a `Comment` by `CommentId`
-getComment :: CommentId -> HackerNews Comment
-getComment = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve a `Poll` by `PollId`
-getPoll :: PollId -> HackerNews Poll
-getPoll = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve a `PollOpt` by `PollOptId`
-getPollOpt :: PollOptId -> HackerNews PollOpt
-getPollOpt = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve a `User` by `UserId`
-getUser :: UserId -> HackerNews User
-getUser = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve a Job
-getJob :: JobId -> HackerNews Job
-getJob = getEndpoint
-
-------------------------------------------------------------------------------
--- | Retrieve the Top Stories on Hacker News
-getTopStories :: HackerNews TopStories
-getTopStories = getEndpoint TopStoriesId
-
-------------------------------------------------------------------------------
--- | Retrieve the largest ItemId
-getMaxItem :: HackerNews MaxItem
-getMaxItem = getEndpoint MaxItemId
-
-------------------------------------------------------------------------------
--- | Retrieve the latest updates
-getUpdates :: HackerNews Update
-getUpdates = getEndpoint UpdateId
-
diff --git a/src/Web/HackerNews/Client.hs b/src/Web/HackerNews/Client.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Client.hs
+++ /dev/null
@@ -1,163 +0,0 @@
-{-# LANGUAGE CPP, OverloadedStrings, ForeignFunctionInterface, JavaScriptFFI#-}
-------------------------------------------------------------------------------
--- |
--- Module      : Web.HackerNews.Client
--- Copyright   : (c) David Johnson, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
--- | 
-------------------------------------------------------------------------------
-module Web.HackerNews.Client
-       ( hackerNews
-       , buildHNRequest
-       , HackerNews
-       , HackerNewsError (..)
-       ) where
-
-------------------------------------------------------------------------------
-import           Data.Aeson                 hiding (Result)
-import           Data.Aeson.Parser          (value)
-import qualified Data.Text.Encoding         as T
-import           Data.Text                  (Text)
-import           Data.Monoid                ((<>))
-import           Control.Monad.Trans.Either
-import           Data.Either                (rights)
-import           Data.Maybe
-import           Control.Monad.IO.Class     (liftIO)
-import           Data.Attoparsec.ByteString (parseOnly)
-import           Control.Monad              (when)
-#ifdef __GHCJS__
-import           GHCJS.Types
-import           GHCJS.Foreign as F
-#else
-import           Control.Exception          (try, SomeException)
-import           Control.Monad.Trans.Class  (lift)
-import           Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
-import           Network.Http.Client
-import           OpenSSL                    (withOpenSSL)
-import qualified System.IO.Streams          as Streams
-#endif
-
-------------------------------------------------------------------------------
-
-------------------------------------------------------------------------------
--- | Debug flag
-debug :: Bool
-debug = False
-
-------------------------------------------------------------------------------
--- | Core Type
-#ifdef __GHCJS__
-type HackerNews a = EitherT HackerNewsError IO a
-#else
-type HackerNews a = EitherT HackerNewsError (ReaderT Connection IO) a
-#endif
-------------------------------------------------------------------------------
--- | Error Types
-data HackerNewsError =
-    ConnectionError
-  | ParseError
-  | NotFound
-  | RequestError
-  deriving (Show, Eq)
-
-#ifdef __GHCJS__
--- | HackerNews API request method
-hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a)
-hackerNews = runEitherT
-
-
-------------------------------------------------------------------------------
--- | Request Builder for API
-buildHNRequest :: FromJSON a => Text -> HackerNews a
-buildHNRequest path = do
-  let url = "https://hacker-news.firebaseio.com/v0/" <> path <> ".json"
-  res <- liftIO $ ajax url
-  case (arError res) of
-   Just et -> case et of
-     "connection-error" -> left ConnectionError
-     "request-error" -> left RequestError
-     _ -> left NotFound
-   Nothing -> do
-     let t = T.encodeUtf8 $ fromMaybe "" $ arData res
-         xs = rights [parseOnly value t, parseOnly json t]
-     when debug $ liftIO . print $ t
-     case xs of
-      [] -> left ParseError
-      x : _ ->
-        case fromJSON x of
-         Success jsonBody -> right jsonBody
-         _                -> left NotFound
-
-
-data AjaxResult = AjaxResult { arData :: Maybe Text,
-                               arError :: Maybe Text
-                             } deriving (Ord, Eq, Show)
-
-ajax :: Text -> IO AjaxResult
-ajax url = do
-  res <- js_ajax (toJSString url)
-  err <- F.getProp ("error" :: Text) res
-  dat <- F.getProp ("data" :: Text) res
-  let d = getTextDat dat
-      e = getTextDat err
-  return (AjaxResult d e)
-  where getTextDat dt = if isNull dt then Nothing else Just (fromJSString dt)
-
-
-foreign import javascript interruptible "var req = new XMLHttpRequest(); \
-  if (!req)\
-    $c({error: 'connection-error', data: null});\
-  req.onreadystatechange = function() {\
-    if (req.readyState === 4) {\
-      if (req.status === 200) {\
-        $c({data: req.responseText, error: null});\
-      } else\
-        $c({error: 'request-error', data: null});\
-    }\
-  };\
-  req.open('GET', $1, true);\
-  req.send();"
-  js_ajax :: JSString -> IO (JSRef ajaxResult)
-
-#else
-------------------------------------------------------------------------------
--- | HackerNews API request method
-hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a)
-hackerNews requests =
-  withOpenSSL $ do
-    ctx <- baselineContextSSL
-    con <- try (openConnectionSSL ctx "hacker-news.firebaseio.com" 443) :: IO (Either SomeException Connection)
-    case con of
-     Left _ -> return $ Left ConnectionError
-     Right conn -> do
-       result <- flip runReaderT conn $ runEitherT requests
-       closeConnection conn
-       return result
-
-------------------------------------------------------------------------------
--- | Request Builder for API
-buildHNRequest :: FromJSON a => Text -> HackerNews a
-buildHNRequest url = do
-    con <- lift ask
-    bytes <- liftIO $ do
-      req <- buildRequest $ do
-        http GET $ "/v0/" <> T.encodeUtf8 url <> ".json"
-        setHeader "Connection" "Keep-Alive"
-        setAccept "application/json"
-      sendRequest con req emptyBody
-      receiveResponse con $ const Streams.read
-    case bytes of
-      Nothing -> left RequestError
-      Just bs -> do
-        when debug $ liftIO . print $ bs
-        let xs = rights [parseOnly value bs, parseOnly json bs]
-        case xs of
-          []    -> left ParseError
-          x : _ ->
-            case fromJSON x of
-             Success jsonBody -> right jsonBody
-             _                -> left NotFound
-    
-#endif
diff --git a/src/Web/HackerNews/Comment.hs b/src/Web/HackerNews/Comment.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Comment.hs
+++ /dev/null
@@ -1,62 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.HackerNews.Comment
--- Copyright   : (c) David Johnson, Konstantin Zudov 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Comment where
-
-import           Control.Applicative     ((<$>), (<*>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.!=), (.:), (.:?))
-import           Data.Text               (Text)
-import           Data.Time               (UTCTime)
-
-import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)
-import           Web.HackerNews.Util     (fromSeconds)
-
-------------------------------------------------------------------------------
--- | Comment Object
-data Comment = Comment {
-    commentBy      :: Text
-  , commentId      :: CommentId
-  , commentKids    :: Maybe [Int]
-  , commentParent  :: Int
-  , commentText    :: Text
-  , commentTime    :: UTCTime
-  , commentType    :: Text
-  , commentDeleted :: Bool
-  , commentDead    :: Bool
-  } deriving Show
-
-------------------------------------------------------------------------------
--- | `CommentId` for a `Comment` Object
-newtype CommentId
-  = CommentId Int
-  deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Endpoint instances
-instance Endpoint CommentId Comment where
-    endpoint (CommentId id') = itemEndpoint id'
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON Comment where
-  parseJSON (Object o) =
-     Comment <$> o .: "by"
-             <*> (CommentId <$> o .: "id")
-             <*> o .:? "kids"
-             <*> o .: "parent"
-             <*> o .: "text"
-             <*> (fromSeconds <$> o .: "time")
-             <*> o .: "type"
-             <*> o .:? "deleted" .!= False
-             <*> o .:? "dead" .!= False
-  parseJSON _ = mzero
-
-
-
diff --git a/src/Web/HackerNews/Endpoint.hs b/src/Web/HackerNews/Endpoint.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Endpoint.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE MultiParamTypeClasses  #-}
-{-# LANGUAGE OverloadedStrings      #-}
--- |
--- Module      : Web.HackerNews.Endpoint
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Endpoint where
-
-import           Data.Aeson            (FromJSON)
-import           Data.Text             (Text, append)
-
-import           Web.HackerNews.Client (HackerNews, buildHNRequest)
-import           Web.HackerNews.Util   (toText)
-
-------------------------------------------------------------------------------
--- | Endpoint maps the id to the returned type on a type level
--- The function dependency @id -> resp@ specifies that @id@ uniquely determines @resp@
-class Endpoint id resp | id -> resp where
-    endpoint :: id -> Text -- ^ Turn @id@ into path that points to resource
-
-------------------------------------------------------------------------------
--- | Endpoint for `Item`
-itemEndpoint :: Int -> Text
-itemEndpoint = append "item/" . toText
-
-------------------------------------------------------------------------------
--- | Generic function for making requests
-getEndpoint :: (Endpoint a b, FromJSON b) => a -> HackerNews b
-getEndpoint id' = buildHNRequest $ endpoint id'
diff --git a/src/Web/HackerNews/Item.hs b/src/Web/HackerNews/Item.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Item.hs
+++ /dev/null
@@ -1,72 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.Stripe.Stripe
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Item where
-
-import           Control.Applicative     ((<$>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.:))
-import           Data.Text               (Text)
-
-import           Web.HackerNews.Comment  (Comment)
-import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)
-import           Web.HackerNews.Job      (Job)
-import           Web.HackerNews.Poll     (Poll, PollOpt)
-import           Web.HackerNews.Story    (Story)
-
-------------------------------------------------------------------------------
--- | Item Type
-data Item = ItemComment Comment
-          | ItemPoll Poll
-          | ItemPollOpt PollOpt
-          | ItemStory Story
-          | ItemJob Job
-          deriving (Show)
-
-------------------------------------------------------------------------------
--- | Item ID for a `Item` object
-newtype ItemId = ItemId Int deriving (Show,Eq)
-
-------------------------------------------------------------------------------
--- | Max Item ID for a `Item`
-data MaxItemId = MaxItemId deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Max Item Int
-newtype MaxItem = MaxItem Int deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Endpoint Instances for `MaxItemId` and `MaxItem`
-instance Endpoint MaxItemId MaxItem where
-    endpoint _ = "maxitem"
-
-------------------------------------------------------------------------------
--- | Endpoint Instances for `ItemId` & `Item`
-instance Endpoint ItemId Item where
-    endpoint (ItemId id') = itemEndpoint id'
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON Item where
-    parseJSON v@(Object o) = do
-        itemType <- o .: "type"
-        case (itemType :: Text) of
-            "job"     -> ItemJob     <$> parseJSON v
-            "story"   -> ItemStory   <$> parseJSON v
-            "comment" -> ItemComment <$> parseJSON v
-            "poll"    -> ItemPoll    <$> parseJSON v
-            "pollopt" -> ItemPollOpt <$> parseJSON v
-            _         -> mzero
-    parseJSON _ = mzero
-
-------------------------------------------------------------------------------
--- | JSON MaxItem Instance
-instance FromJSON MaxItem where
-    parseJSON = fmap MaxItem . parseJSON
-
diff --git a/src/Web/HackerNews/Job.hs b/src/Web/HackerNews/Job.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Job.hs
+++ /dev/null
@@ -1,62 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.HackerNews.Job
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Job where
-
-import           Control.Applicative     ((<$>), (<*>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.!=), (.:), (.:?))
-import           Data.Text               (Text)
-import           Data.Time               (UTCTime)
-
-import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)
-import           Web.HackerNews.Util     (fromSeconds)
-
-------------------------------------------------------------------------------
--- | Types
-data Job = Job {
-    jobBy      :: Text
-  , jobId      :: JobId
-  , jobScore   :: Int
-  , jobText    :: Text
-  , jobTime    :: UTCTime
-  , jobTitle   :: Text
-  , jobType    :: Text
-  , jobUrl     :: Text
-  , jobDeleted :: Bool
-  , jobDead    :: Bool
-  } deriving (Show)
-
-------------------------------------------------------------------------------
--- | ID for a `Job` type
-newtype JobId
-      = JobId Int
-      deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Endpoint Instances
-instance Endpoint JobId Job where
-    endpoint (JobId id') = itemEndpoint id'
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON Job where
-  parseJSON (Object o) =
-      Job <$> o .: "by"
-          <*> (JobId <$> o .: "id")
-          <*> o .: "score"
-          <*> o .: "text"
-          <*> (fromSeconds <$> o .: "time")
-          <*> o .: "title"
-          <*> o .: "type"
-          <*> o .: "url"
-          <*> o .:? "deleted" .!= False
-          <*> o .:? "dead" .!= False
-  parseJSON _ = mzero
-
diff --git a/src/Web/HackerNews/Person.hs b/src/Web/HackerNews/Person.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Person.hs
+++ /dev/null
@@ -1,54 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
--- |
--- Module      : Web.HackerNews.Person
--- Copyright   : (c) David Johnson, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Person where
-
-import           Control.Applicative ((<$>), (<*>))
-import           Control.Monad       (MonadPlus (mzero))
-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),
-                                      (.!=), (.:), (.:?))
-import           Data.Text           (Text)
-import           Data.Time           (UTCTime)
-
-import           Web.HackerNews.Util (fromSeconds)
-
-------------------------------------------------------------------------------
--- | Person Object
-data Person = Person {
-    personBy      :: Text
-  , personId      :: PersonId
-  , personKids    :: Maybe [Int]
-  , personScore   :: Maybe Int
-  , personTime    :: UTCTime
-  , personTitle   :: Maybe Text
-  , personType    :: Text
-  , personUrl     :: Maybe Text
-  , personDeleted :: Bool
-  , personDead    :: Bool
-  } deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Person ID for a `Person` Object
-newtype PersonId
-  = PersonId Text
-  deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON Person where
-   parseJSON (Object o) =
-     Person <$> o .: "by"
-            <*> (PersonId <$> o .: "id")
-            <*> o .:? "kids"
-            <*> o .:? "score"
-            <*> (fromSeconds <$> o .: "time")
-            <*> o .:? "title"
-            <*> o .: "type"
-            <*> o .:? "url"
-            <*> o .:? "deleted" .!= False
-            <*> o .:? "dead" .!= False
-   parseJSON _ = mzero
diff --git a/src/Web/HackerNews/Poll.hs b/src/Web/HackerNews/Poll.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Poll.hs
+++ /dev/null
@@ -1,103 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.HackerNews.Poll
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Poll where
-
-import           Control.Applicative     ((<$>), (<*>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.!=), (.:), (.:?))
-import           Data.Text               (Text)
-import           Data.Time               (UTCTime)
-
-import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)
-import           Web.HackerNews.Util     (fromSeconds)
-
-------------------------------------------------------------------------------
--- | Poll Object
-data Poll = Poll {
-    pollBy      :: Text
-  , pollId      :: PollId
-  , pollKids    :: [Int]
-  , pollParts   :: [Int]
-  , pollScore   :: Int
-  , pollText    :: Text
-  , pollTime    :: UTCTime
-  , pollTitle   :: Text
-  , pollType    :: Text
-  , pollDeleted :: Bool
-  , pollDead :: Bool
-  } deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Poll Opt Object
-data PollOpt = PollOpt {
-    pollOptBy      :: Text
-  , pollOptId      :: PollOptId
-  , pollOptParent  :: Int
-  , pollOptScore   :: Int
-  , pollOptText    :: Text
-  , pollOptTime    :: UTCTime
-  , pollOptType    :: Text
-  , pollOptDeleted :: Bool
-  , pollOptDead       :: Bool  
-  } deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Poll Option Id for a `PollOpt`
-newtype PollOptId
-  = PollOptId Int
-  deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Poll Id for a `Poll`
-newtype PollId
-  = PollId Int
-  deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | `Endpoint` Instance for `PollOptId`
-instance Endpoint PollOptId PollOpt where
-    endpoint (PollOptId id') = itemEndpoint id'
-
-------------------------------------------------------------------------------
--- | `Endpoint` Instance for `PollId`
-instance Endpoint PollId Poll where
-    endpoint (PollId id') = itemEndpoint id'
-
-------------------------------------------------------------------------------
--- | Poll JSON Instances
-instance FromJSON Poll where
-  parseJSON (Object o) =
-     Poll <$> o .: "by"
-          <*> (PollId <$> o .: "id")
-          <*> o .: "kids"
-          <*> o .: "parts"
-          <*> o .: "score"
-          <*> o .: "text"
-          <*> (fromSeconds <$> o .: "time")
-          <*> o .: "title"
-          <*> o .: "type"
-          <*> o .:? "deleted" .!= False
-          <*> o .:? "dead" .!= False          
-  parseJSON _ = mzero
-
-------------------------------------------------------------------------------
--- | Poll JSON Instances
-instance FromJSON PollOpt where
-  parseJSON (Object o) =
-     PollOpt <$> o .: "by"
-             <*> (PollOptId <$> o .: "id")
-             <*> o .: "parent"
-             <*> o .: "score"
-             <*> o .: "text"
-             <*> (fromSeconds <$> o .: "time")
-             <*> o .: "type"
-             <*> o .:? "deleted" .!= False
-             <*> o .:? "dead" .!= False             
-  parseJSON _ = mzero
diff --git a/src/Web/HackerNews/Story.hs b/src/Web/HackerNews/Story.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Story.hs
+++ /dev/null
@@ -1,80 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.HackerNews.Story
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Story where
-
-import           Control.Applicative     ((<$>), (<*>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.!=), (.:), (.:?))
-import           Data.Text               (Text)
-import           Data.Time               (UTCTime)
-
-import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)
-import           Web.HackerNews.Util     (fromSeconds)
-
-------------------------------------------------------------------------------
--- | Story Object
-data Story = Story {
-    storyBy      :: Text
-  , storyId      :: StoryId
-  , storyKids    :: [Int]
-  , storyScore   :: Int
-  , storyTime    :: UTCTime
-  , storyTitle   :: Text
-  , storyType    :: Text
-  , storyUrl     :: Text
-  , storyDeleted :: Bool
-  , storyDead    :: Bool
-  } deriving Show
-
-------------------------------------------------------------------------------
--- | ID for a `Story`
-newtype StoryId
-  = StoryId Int
-  deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | `Story` ID
-data TopStoriesId  = TopStoriesId deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | TopStories List
-newtype TopStories = TopStories [Int] deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Endpoint Instances for `StoryID` & `Story`
-instance Endpoint StoryId Story where
-    endpoint (StoryId id') = itemEndpoint id'
-
-------------------------------------------------------------------------------
--- | Endpoint Instances for `TopStoriesID` & `TopStories`
-instance Endpoint TopStoriesId TopStories where
-    endpoint _ = "topstories"
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON Story where
-   parseJSON (Object o) =
-     Story <$> o .: "by"
-           <*> (StoryId <$> o .: "id")
-           <*> o .: "kids"
-           <*> o .: "score"
-           <*> (fromSeconds <$> o .: "time")
-           <*> o .: "title"
-           <*> o .: "type"
-           <*> o .: "url"
-           <*> o .:? "deleted" .!= False
-           <*> o .:? "dead" .!= False
-   parseJSON _ = mzero
-
-------------------------------------------------------------------------------
--- | JSON instances `TopStories`
-instance FromJSON TopStories where
-   parseJSON = fmap TopStories . parseJSON
-
diff --git a/src/Web/HackerNews/Types.hs b/src/Web/HackerNews/Types.hs
--- a/src/Web/HackerNews/Types.hs
+++ b/src/Web/HackerNews/Types.hs
@@ -1,24 +1,217 @@
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+------------------------------------------------------------------------------
 -- |
 -- Module      : Web.HackerNews.Types
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
+-- Copyright   : (c) David Johnson, 2014-2016
 -- Maintainer  : djohnson.m@gmail.com
 -- Stability   : experimental
 -- Portability : POSIX
-module Web.HackerNews.Types
-       ( module H
-       ) where
+--
+-- Haskell port of <https://github.com/HackerNews/API>
+--
+------------------------------------------------------------------------------
+module Web.HackerNews.Types where
 
-import           Web.HackerNews.Comment as H
-import           Web.HackerNews.Poll    as H
-import           Web.HackerNews.Story   as H
-import           Web.HackerNews.User    as H
-import           Web.HackerNews.Update  as H
-import           Web.HackerNews.Job     as H
-import           Web.HackerNews.Item    as H
-import           Web.HackerNews.Endpoint as H
+import           Data.Aeson
+import           Data.Aeson.Types
+import           Data.Char
+import qualified Data.Text        as T
+import           GHC.Generics
+import           Servant.API
 
+-- | The item and profile changes are at <https://hacker-news.firebaseio.com/v0/updates>
+data Updates = Updates  {
+      items :: [Int]
+       -- ^ Updated `Item`s
+    , profiles :: [UserName]
+       -- ^ Updated `UserName`s
+   } deriving (Show, Eq, Generic)
 
+instance ToJSON Updates
+instance FromJSON Updates
 
+-- | The current largest item id is at <https://hacker-news.firebaseio.com/v0/maxitem>.
+-- You can walk backward from here to discover all items.
+newtype MaxItem = MaxItem Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
 
+-- | <https://hacker-news.firebaseio.com/v0/topstories>
+newtype TopStories = TopStories [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
 
+-- | <https://hacker-news.firebaseio.com/v0/newstories>
+newtype NewStories = NewStories [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
 
+-- | <https://hacker-news.firebaseio.com/v0/beststories>
+newtype BestStories = BestStories [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | <https://hacker-news.firebaseio.com/v0/askstories>
+newtype AskStories = AskStories [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | <https://hacker-news.firebaseio.com/v0/showstories>
+newtype ShowStories = ShowStories [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | <https://hacker-news.firebaseio.com/v0/jobstories>
+newtype JobStories = JobStories [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | Users are identified by case-sensitive ids, and live under
+-- <https://hacker-news.firebaseio.com/v0/user/>.
+-- Only users that have public activity (comments or story submissions)
+-- on the site are available through the API.
+data User = User {
+    userId :: UserId
+    -- ^ The user's unique username. Case-sensitive. Required.
+  , userDelay :: Maybe Delay
+    -- ^ Delay in minutes between a comment's creation and its visibility to other users.
+  , userCreated :: Created
+    -- ^ Creation date of the user, in Unix Time.
+  , userKarma :: Karma
+    -- ^ The user's karma
+  , userAbout :: Maybe About
+    -- ^ The user's optional self-description. HTML.
+  , userSubmitted :: Maybe Submitted
+    -- ^ List of the user's stories, polls and comments.
+  } deriving (Show, Eq, Generic)
+
+instance ToJSON User where
+  toJSON = genericToJSON defaultOptions {
+    fieldLabelModifier = map toLower . drop 4
+  }
+
+instance FromJSON User where
+  parseJSON = genericParseJSON defaultOptions {
+    fieldLabelModifier = map toLower . drop 4
+  }
+
+-- | The user's karma.
+newtype Karma = Karma Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The user's unique username. Case-sensitive. Required.
+newtype UserId = UserId T.Text
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData, Generic)
+
+-- | Delay in minutes between a comment's creation and its visibility to other users.
+newtype Delay = Delay Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | Creation date of the user, in Unix Time.
+newtype Created = Created Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The user's optional self-description. HTML.
+newtype About = About T.Text
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | List of the user's stories, polls and comments.
+newtype Submitted = Submitted [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The item's unique id.
+newtype ItemId = ItemId Int
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData, Generic)
+
+-- | `true` if the item is deleted.
+newtype Deleted = Deleted Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The type of item. One of "job", "story", "comment", "poll", or "pollopt"
+data ItemType = Job | Story | Comment | Poll | PollOpt
+  deriving (Show, Eq, Generic)
+
+instance ToJSON ItemType where
+  toJSON = genericToJSON
+    defaultOptions { constructorTagModifier = map toLower }
+
+instance FromJSON ItemType where
+  parseJSON = genericParseJSON
+    defaultOptions { constructorTagModifier = map toLower }
+
+-- | The username of the item's author.
+newtype UserName = UserName T.Text
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The comment, story or poll text. HTML.
+newtype ItemText = ItemText T.Text
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | `true` if the item is dead.
+newtype Dead = Dead Bool
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The item's parent. For comments, either another comment or the relevant story.
+-- For pollopts, the relevant poll.
+newtype Parent = Parent Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | Creation date of the item, in Unix Time.
+newtype Time = Time Integer
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The ids of the item's comments, in ranked display order.
+newtype Kids = Kids [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The URL of the story.
+newtype URL = URL T.Text
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The story's score, or the votes for a pollopt.
+newtype Score = Score Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | The title of the story, poll or job.
+newtype Title = Title T.Text
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | A list of related pollopts, in display order.
+newtype Parts = Parts [Int]
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | In the case of stories or polls, the total comment count.
+newtype Descendants = Descendants Int
+  deriving (Show, Eq, ToJSON, FromJSON, Generic)
+
+-- | Stories, comments, jobs, Ask HNs and even polls are just items.
+-- They're identified by their ids, which are unique integers, and live under
+-- <https://hacker-news.firebaseio.com/v0/item/>.
+data Item = Item {
+      itemId          :: Maybe ItemId
+    , itemDeleted     :: Maybe Deleted
+    , itemType        :: ItemType
+    , itemBy          :: Maybe UserName
+    , itemTime        :: Maybe Time
+    , itemText        :: Maybe ItemText
+    , itemDead        :: Maybe Dead
+    , itemParent      :: Maybe Parent
+    , itemKids        :: Maybe Kids
+    , itemURL         :: Maybe URL
+    , itemScore       :: Maybe Score
+    , itemTitle       :: Maybe Title
+    , itemParts       :: Maybe Parts
+    , itemDescendants :: Maybe Descendants
+    } deriving (Show, Eq, Generic)
+
+instance ToJSON Item where
+  toJSON = genericToJSON
+    defaultOptions { fieldLabelModifier = map toLower . drop 4 }
+
+instance FromJSON Item where
+  parseJSON = genericParseJSON
+    defaultOptions { fieldLabelModifier = map toLower . drop 4 }
+
+-- | Error handling for `HackerNewsAPI`
+data HackerNewsError
+  = NotFound
+  | FailureResponseError Int T.Text T.Text
+  | HNConnectionError T.Text
+  | DecodeFailureError T.Text T.Text
+  | InvalidContentTypeHeaderError T.Text T.Text
+  | UnsupportedContentTypeError T.Text
+  deriving (Show, Eq)
diff --git a/src/Web/HackerNews/Update.hs b/src/Web/HackerNews/Update.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Update.hs
+++ /dev/null
@@ -1,46 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.HackerNews.Update
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Update where
-
-import           Control.Applicative     ((<$>), (<*>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.!=), (.:), (.:?))
-import           Data.Text               (Text)
-
-import           Web.HackerNews.Endpoint (Endpoint (endpoint))
-
-------------------------------------------------------------------------------
--- | Update Object
-data Update = Update {
-    updateItems    :: [Int]
-  , updateProfiles :: [Text]
-  , updateDeleted  :: Bool
-  , updateDead     :: Bool
-  } deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Update ID for an `Updated` Object
-data UpdateId = UpdateId deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Endpoint Instances
-instance Endpoint UpdateId Update where
-    endpoint _ = "updates"
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON Update where
-  parseJSON (Object o) =
-     Update <$> o .: "items"
-            <*> o .: "profiles"
-            <*> o .:? "deleted" .!= False
-            <*> o .:? "dead" .!= False
-  parseJSON _ = mzero
-
diff --git a/src/Web/HackerNews/User.hs b/src/Web/HackerNews/User.hs
deleted file mode 100644
--- a/src/Web/HackerNews/User.hs
+++ /dev/null
@@ -1,59 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings     #-}
--- |
--- Module      : Web.HackerNews.User
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.User where
-
-import           Control.Applicative     ((<$>), (<*>))
-import           Control.Monad           (MonadPlus (mzero))
-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),
-                                          (.!=), (.:), (.:?))
-import           Data.Monoid             ((<>))
-import           Data.Text               (Text)
-import           Data.Time               (UTCTime)
-
-import           Web.HackerNews.Endpoint (Endpoint (endpoint))
-import           Web.HackerNews.Util     (fromSeconds, monoidToMaybe)
-
-------------------------------------------------------------------------------
--- | `User` Object
-data User = User {
-    userAbout     :: Maybe Text
-  , userCreated   :: UTCTime
-  , userDelay     :: Int
-  , userId        :: UserId
-  , userKarma     :: Int
-  , userSubmitted :: [Int]
-  , userDeleted   :: Bool
-  , userDead      :: Bool
-  } deriving (Show)
-
-------------------------------------------------------------------------------
--- | User ID for a `User` Object
-newtype UserId
-      = UserId Text
-      deriving (Show, Eq)
-
-------------------------------------------------------------------------------
--- | Endpoint instances
-instance Endpoint UserId User where
-    endpoint (UserId id') = "user/" <> id'
-
-------------------------------------------------------------------------------
--- | JSON Instances
-instance FromJSON User where
-  parseJSON (Object o) =
-     User <$> ((monoidToMaybe =<<) <$> (o .:? "about"))
-          <*> (fromSeconds <$> o .: "created")
-          <*> o .: "delay"
-          <*> (UserId <$> o .: "id")
-          <*> o .: "karma"
-          <*> o .: "submitted"
-          <*> o .:? "deleted" .!= False
-          <*> o .:? "dead" .!= False
-  parseJSON _ = mzero
-
diff --git a/src/Web/HackerNews/Util.hs b/src/Web/HackerNews/Util.hs
deleted file mode 100644
--- a/src/Web/HackerNews/Util.hs
+++ /dev/null
@@ -1,29 +0,0 @@
--- |
--- Module      : Web.HackerNews.Util
--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014
--- Maintainer  : djohnson.m@gmail.com
--- Stability   : experimental
--- Portability : POSIX
-module Web.HackerNews.Util where
-
-import           Data.Monoid           (Monoid, mempty)
-import           Data.Text             (Text)
-import qualified Data.Text             as T
-import           Data.Time             (UTCTime)
-import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)
-
-------------------------------------------------------------------------------
--- | Convert `Integer` to `UTCTime`
-fromSeconds :: Integer -> UTCTime
-fromSeconds = posixSecondsToUTCTime . fromInteger
-
-------------------------------------------------------------------------------
--- | Convert `Show` constrained a to `Text`
-toText :: Show a => a -> Text
-toText = T.pack . show
-
-------------------------------------------------------------------------------
--- | Turns empty monoids into Nothing
--- `listToMaybe` generalized for monoids
-monoidToMaybe :: (Eq a, Monoid a) => a -> Maybe a
-monoidToMaybe m = if m == mempty then Nothing else Just m
diff --git a/tests/Test.hs b/tests/Test.hs
deleted file mode 100644
--- a/tests/Test.hs
+++ /dev/null
@@ -1,34 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import           Data.Either    (isRight)
-import           Test.Hspec     (it, hspec, describe, shouldSatisfy)
-import           Web.HackerNews
-import           Control.Applicative
-
-main :: IO ()
-main = hspec $ do
-  describe "Hacker News API Tests" $ do
-    it "Retrieves all" $ do
-      result <- hackerNews $ (,,,,,,,,) <$> 
-        getStory   (StoryId 8863)       <*>
-        getComment (CommentId 2921983)  <*>
-        getUser    (UserId "dmjio")     <*>
-        getJob     (JobId 8437631)      <*>
-        getPoll    (PollId 126809)      <*>
-        getPollOpt (PollOptId 160705)   <*>
-        getTopStories                   <*>
-        getMaxItem                      <*>
-        getUpdates
-      result `shouldSatisfy` isRight
-  
-
-
-
-
-
-
-
-
-
-
