diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 David Johnson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/hackernews.cabal b/hackernews.cabal
new file mode 100644
--- /dev/null
+++ b/hackernews.cabal
@@ -0,0 +1,51 @@
+name:                hackernews
+version:             0.1.0.0
+description:         API for news.ycombinator.com
+license:             MIT
+synopsis:            API for Hacker News
+license-file:        LICENSE
+author:              David Johnson
+maintainer:          djohnson.m@gmail.com
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  ghc-options:         -Wall
+  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.Util
+  hs-source-dirs:      src
+  build-depends:       HsOpenSSL >= 0.10.5
+                     , aeson
+                     , attoparsec >= 0.12.1.2
+                     , base >=4.7 && <4.8
+                     , bytestring >= 0.10.4.0
+                     , hspec >= 1.11.4
+                     , http-streams
+                     , io-streams >= 1.1.4.6
+                     , text >= 1.2.0.0
+                     , time >= 1.4.2
+  default-language:    Haskell2010
+
+Test-Suite tests
+    type:                exitcode-stdio-1.0
+    hs-source-dirs:      tests
+    main-is:             Test.hs
+    build-depends:       base
+                       , hackernews
+                       , hspec
+                       , hspec >= 1.11.4
+    default-language:    Haskell2010
+    ghc-options:         -Wall
+
+source-repository head
+  type:     git
+  location: https://github.com/dmjio/hackernews
diff --git a/src/Web/HackerNews.hs b/src/Web/HackerNews.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Web.HackerNews
+       ( -- * API Calls
+         getStory
+       , getComment
+       , getPoll
+       , getPollOpt
+       , getUser
+       , getTopStories
+       , getMaxItem
+       , getUpdates
+       -- * Types
+       , Comment   (..)
+       , CommentId (..)
+       , Poll      (..)
+       , PollId    (..)
+       , PollOpt   (..)
+       , PollOptId (..)
+       , Story     (..)
+       , StoryId   (..)
+       , User      (..)
+       , UserId    (..)
+       , Update    (..)
+       , MaxItem
+       , TopStories
+       ) where
+
+import           Data.Monoid                ((<>))
+
+import           Web.HackerNews.Types
+import           Web.HackerNews.Util        (toText)
+import           Web.HackerNews.Client      (getItem)
+
+------------------------------------------------------------------------------
+-- | Retrieve a `Story` by `StoryId`
+getStory :: StoryId -> IO (Maybe Story)
+getStory (StoryId storyid) = getItem $ "item/" <> toText storyid
+
+------------------------------------------------------------------------------
+-- | Retrieve a `Comment` by `CommentId`
+getComment :: CommentId -> IO (Maybe Comment)
+getComment (CommentId commentid) = getItem $ "item/" <> toText commentid
+
+------------------------------------------------------------------------------
+-- | Retrieve a `Poll` by `PollId`
+getPoll :: PollId -> IO (Maybe Poll)
+getPoll (PollId pollid) = getItem $ "item/" <> toText pollid
+
+------------------------------------------------------------------------------
+-- | Retrieve a `PollOpt` by `PollOptId`
+getPollOpt :: PollOptId -> IO (Maybe PollOpt)
+getPollOpt (PollOptId polloptid) = getItem $ "item/" <> toText polloptid
+
+------------------------------------------------------------------------------
+-- | Retrieve a `User` by `UserId`
+getUser :: UserId -> IO (Maybe User)
+getUser (UserId userid) = getItem $ "user/" <> userid
+
+------------------------------------------------------------------------------
+-- | Retrieve the Top Stories on Hacker News
+getTopStories :: IO (Maybe TopStories)
+getTopStories = getItem "topstories"
+
+------------------------------------------------------------------------------
+-- | Retrieve the largest ItemId
+getMaxItem :: IO (Maybe MaxItem)
+getMaxItem = getItem "maxitem"
+
+------------------------------------------------------------------------------
+-- | Retrieve the largest ItemId
+getUpdates :: IO (Maybe Update)
+getUpdates = getItem "updates"
+
diff --git a/src/Web/HackerNews/Client.hs b/src/Web/HackerNews/Client.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Client.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Web.HackerNews.Client ( getItem ) where
+
+import           Data.Aeson                 hiding (Result)
+import           Data.Aeson.Parser          (value)
+import           Data.Attoparsec.ByteString (parseOnly)
+import           Data.Either                (rights)
+
+import           Data.Monoid                ((<>))
+import           Data.Text                  (Text)
+import qualified Data.Text.Encoding         as T
+import           Network.Http.Client
+import           OpenSSL                    (withOpenSSL)
+import qualified System.IO.Streams          as Streams
+
+------------------------------------------------------------------------------
+-- | HackerNews API request method
+getItem
+  :: FromJSON a
+  => Text      
+  -> IO (Maybe a)
+getItem url = withOpenSSL $ do
+  ctx <- baselineContextSSL
+  con <- openConnectionSSL ctx "hacker-news.firebaseio.com" 443
+  req <- buildRequest $ do
+    http GET $ "/v0/" <> T.encodeUtf8 url <> ".json"
+    setAccept "application/json"
+  sendRequest con req emptyBody
+  bytes <- receiveResponse con $ const $ Streams.read
+  closeConnection con
+  return $ case bytes of
+   Nothing -> Nothing
+   Just bs -> do
+     let xs = rights [parseOnly value bs, parseOnly json bs]
+     case xs of
+       []    -> Nothing
+       x : _ ->
+         case fromJSON x of
+          Success a -> Just a
+          _         -> Nothing
+
diff --git a/src/Web/HackerNews/Comment.hs b/src/Web/HackerNews/Comment.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Comment.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE OverloadedStrings #-}
+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.Util (fromSeconds)
+
+------------------------------------------------------------------------------
+-- | Types
+data Comment = Comment {
+    commentBy     :: Text
+  , commentId     :: CommentId
+  , commentKids   :: Maybe [Int]
+  , commentParent :: Int
+  , commentText   :: Text
+  , commentTime   :: UTCTime
+  , commentType   :: Text
+  } deriving Show
+
+newtype CommentId
+  = CommentId Int
+  deriving (Show, Eq)
+
+------------------------------------------------------------------------------
+-- | 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"
+  parseJSON _ = mzero
+
+
+
diff --git a/src/Web/HackerNews/Person.hs b/src/Web/HackerNews/Person.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Person.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE OverloadedStrings #-}
+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)
+
+------------------------------------------------------------------------------
+-- | Types
+data Person = Person {
+    personBy    :: Text
+  , personId    :: PersonId
+  , personKids  :: Maybe [Int]
+  , personScore :: Maybe Int
+  , personTime  :: UTCTime
+  , personTitle :: Maybe Text
+  , personType  :: Text
+  , personUrl   :: Maybe Text
+  } deriving (Show, Eq)
+
+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"
+   parseJSON _ = mzero 
diff --git a/src/Web/HackerNews/Poll.hs b/src/Web/HackerNews/Poll.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Poll.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE OverloadedStrings #-}
+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.Util (fromSeconds)
+
+------------------------------------------------------------------------------
+-- | Types
+data Poll = Poll {
+    pollBy    :: Text
+  , pollId    :: PollId
+  , pollKids  :: [Int]
+  , pollParts :: [Int]
+  , pollScore :: Int
+  , pollText  :: Text
+  , pollTime  :: UTCTime
+  , pollTitle :: Text
+  , pollType  :: Text
+  } deriving (Show, Eq)
+
+data PollOpt = PollOpt {
+    pollOptBy     :: Text
+  , pollOptId     :: PollOptId
+  , pollOptParent :: Int
+  , pollOptScore  :: Int
+  , pollOptText   :: Text
+  , pollOptTime   :: UTCTime
+  , pollOptType   :: Text
+  } deriving (Show, Eq)
+
+newtype PollOptId
+  = PollOptId Int
+  deriving (Show, Eq)
+
+newtype PollId
+  = PollId Int
+  deriving (Show, Eq)
+
+------------------------------------------------------------------------------
+-- | 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"
+  parseJSON _ = mzero
+
+instance FromJSON PollOpt where
+  parseJSON (Object o) =
+     PollOpt <$> o .: "by"
+             <*> (PollOptId <$> o .: "id")
+             <*> o .: "parent"
+             <*> o .: "score"
+             <*> o .: "text"
+             <*> (fromSeconds <$> o .: "time")
+             <*> o .: "type"
+  parseJSON _ = mzero
diff --git a/src/Web/HackerNews/Story.hs b/src/Web/HackerNews/Story.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Story.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE OverloadedStrings #-}
+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.Util (fromSeconds)
+
+------------------------------------------------------------------------------
+-- | Types
+data Story = Story {
+    storyBy    :: Text
+  , storyId    :: Int
+  , storyKids  :: [Int]
+  , storyScore :: Int
+  , storyTime  :: UTCTime
+  , storyTitle :: Text
+  , storyType  :: Text
+  , storyUrl   :: Text
+  } deriving Show
+
+newtype StoryId
+  = StoryId Int
+  deriving (Show, Eq)
+
+------------------------------------------------------------------------------
+-- | JSON Instances
+instance FromJSON Story where
+   parseJSON (Object o) =
+     Story <$> o .: "by"
+           <*> o .: "id"
+           <*> o .: "kids"
+           <*> o .: "score"
+           <*> (fromSeconds <$> o .: "time")
+           <*> o .: "title"
+           <*> o .: "type"
+           <*> o .: "url"
+   parseJSON _ = mzero
+
diff --git a/src/Web/HackerNews/Types.hs b/src/Web/HackerNews/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Types.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Web.HackerNews.Types
+       ( module H
+       , StoryId   (..)
+       , CommentId (..)
+       , PollId    (..)
+       , PollOptId (..)
+       , UserId    (..)
+       , MaxItem
+       , TopStories
+       ) 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
+
+type MaxItem    = Int
+type TopStories = [Int]
+
+
+
+
diff --git a/src/Web/HackerNews/Update.hs b/src/Web/HackerNews/Update.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Update.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Web.HackerNews.Update where
+
+import           Control.Applicative ((<$>), (<*>))
+import           Control.Monad       (MonadPlus (mzero))
+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),
+                                      (.:))
+import           Data.Text           (Text)
+
+------------------------------------------------------------------------------
+-- | Types
+data Update = Update {
+    updateItems    :: [Int]
+  , updateProfiles :: [Text]
+  } deriving (Show, Eq)
+
+------------------------------------------------------------------------------
+-- | JSON Instances
+instance FromJSON Update where
+  parseJSON (Object o) =
+     Update <$> o .: "items"
+            <*> o .: "profiles"
+  parseJSON _ = mzero
+
diff --git a/src/Web/HackerNews/User.hs b/src/Web/HackerNews/User.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/User.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Web.HackerNews.User 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)
+
+------------------------------------------------------------------------------
+-- | Types
+data User = User {
+    userAbout     :: Text
+  , userCreated   :: UTCTime
+  , userDelay     :: Int
+  , userId        :: UserId
+  , userKarma     :: Int
+  , userSubmitted :: [Int]
+  } deriving (Show)
+
+newtype UserId
+      = UserId Text
+      deriving (Show, Eq)
+
+------------------------------------------------------------------------------
+-- | JSON Instances
+instance FromJSON User where
+  parseJSON (Object o) =
+     User <$> o .: "about"
+          <*> (fromSeconds <$> o .: "created")
+          <*> o .: "delay"
+          <*> (UserId <$> o .: "id")
+          <*> o .: "karma"
+          <*> o .: "submitted"
+  parseJSON _ = mzero
+
diff --git a/src/Web/HackerNews/Util.hs b/src/Web/HackerNews/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/HackerNews/Util.hs
@@ -0,0 +1,18 @@
+module Web.HackerNews.Util where
+
+import qualified Data.Text as T
+import           Data.Text    (Text)
+import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+import           Data.Time (UTCTime)
+
+------------------------------------------------------------------------------
+-- | 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
+
+
diff --git a/tests/Test.hs b/tests/Test.hs
new file mode 100644
--- /dev/null
+++ b/tests/Test.hs
@@ -0,0 +1,35 @@
+module Main where
+
+import           Data.Maybe     (isJust)
+import           Test.Hspec     (it, runIO, hspec, describe)
+import           Web.HackerNews
+
+main :: IO ()
+main = hspec $ do
+  describe "Hacker News API Tests" $ do
+    story <- runIO $ getStory (StoryId 8863)
+    it "Retrieves a Story" $ isJust story
+    comment <- runIO $ getComment (CommentId 2921983)
+    it "Retrieves a Comment" $ isJust comment
+    user <- runIO $ getUser (UserId "dmjio")
+    it "Retrieves a User" $ isJust user
+    poll <- runIO $ getPoll (PollId 126809)
+    it "Retrieves a Poll" $ isJust poll
+    pollOpt <- runIO $ getPollOpt (PollOptId 160705)
+    it "Retrieves a Pollopt" $ isJust pollOpt
+    topStories <- runIO getTopStories
+    it "Retrieves Top Stories" $ isJust topStories
+    maxItem <- runIO getMaxItem
+    it "Retrieves Max Item" $ isJust maxItem
+    updates <- runIO getUpdates
+    it "Retrieves Updates" $ isJust updates
+
+
+
+
+
+
+
+
+
+
