diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright: (c) Vo Minh Thu, 2014.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/Network/Aeson/Client.hs b/Network/Aeson/Client.hs
new file mode 100644
--- /dev/null
+++ b/Network/Aeson/Client.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TupleSections #-}
+module Network.Aeson.Client where
+
+import Data.Aeson hiding (Result)
+import Data.Attoparsec.ByteString (parseWith, IResult(..))
+import qualified Data.ByteString.Lazy.Char8 as LB
+import qualified Data.ByteString.Char8 as B
+import Data.Maybe (fromMaybe)
+import Network.Http.Client
+import OpenSSL (withOpenSSL)
+import System.IO (hFlush, hPutStrLn, stderr)
+import qualified System.IO.Streams as Streams
+
+-- | Execute a GET agains the specified URI (e.g. `/v1`) using the
+-- supplied parameters.
+apiGet :: FromJSON a =>
+  Maybe (B.ByteString, B.ByteString)
+  -> B.ByteString
+  -> B.ByteString
+  -> [(B.ByteString, Maybe B.ByteString)] -> IO (Maybe a)
+apiGet mbasic base uri parameters = withOpenSSL $ do
+  let url = B.concat [uri, queryString parameters]
+  q <- buildRequest $ do
+    http GET url
+    maybe (return ()) (uncurry setAuthorizationBasic) mbasic
+
+  c <- establishConnection base
+  -- debug q
+  sendRequest c q emptyBody
+  receiveResponse' c
+
+-- | Execute a POST agains the specified URI (e.g. `/v1`) using the
+-- supplied parameters.
+apiPost :: FromJSON a =>
+  Maybe (B.ByteString, B.ByteString)
+  -> B.ByteString
+  -> B.ByteString
+  -> [(B.ByteString, Maybe B.ByteString)] -> LB.ByteString -> IO (Maybe a)
+apiPost mbasic base uri parameters body = withOpenSSL $ do
+  let url = B.concat [uri, queryString parameters]
+  q <- buildRequest $ do
+    http POST url
+    maybe (return ()) (uncurry setAuthorizationBasic) mbasic
+    setContentLength (fromIntegral $ LB.length body)
+    setContentType "application/json"
+
+  c <- establishConnection base
+  -- debug q
+  body' <- Streams.fromLazyByteString body
+  sendRequest c q (inputStreamBody body')
+  receiveResponse' c
+
+-- | Execute a DELETE agains the specified URI (e.g. `/v1`) using the
+-- supplied parameters.
+apiDelete :: FromJSON a =>
+  Maybe (B.ByteString, B.ByteString)
+  -> B.ByteString
+  -> B.ByteString
+  -> [(B.ByteString, Maybe B.ByteString)] -> IO (Result a)
+apiDelete mbasic base uri parameters = withOpenSSL $ do
+  let url = B.concat [uri, queryString parameters]
+  q <- buildRequest $ do
+    http DELETE url
+    maybe (return ()) (uncurry setAuthorizationBasic) mbasic
+
+  c <- establishConnection base
+  -- debug q
+  sendRequest c q emptyBody
+  -- TODO assert 204 for upcloud
+
+  r <- receiveResponse c $ \p' i -> do
+    case getStatusCode p' of
+      204 -> return Ok
+      _ -> do
+        x <- Streams.read i
+        let more = Streams.read i >>= return . fromMaybe ""
+        p <- parseWith more json $ fromMaybe "" x
+        case p of
+          Done _ value -> do
+            -- debug value
+            case fromJSON value of
+              Success value' -> do
+                return $ Value value'
+              _ -> return JsonFailure
+          _ -> return ParseFailure
+  closeConnection c
+  return r
+
+data Result a =
+    Ok
+  -- ^ Success and empty result
+  | Value a
+  -- ^ Succes and non-empty result
+  | JsonFailure
+  -- ^ Can't turn JSON into a proper result
+  | ParseFailure
+  -- ^ Can't parse JSON
+  deriving Show
+
+-- | Execute a PUT agains the specified URI using the
+-- supplied parameters.
+apiPut ::
+  Maybe (B.ByteString, B.ByteString)
+  -> B.ByteString
+  -> B.ByteString
+  -> [(B.ByteString, Maybe B.ByteString)]
+  -> LB.ByteString
+  -> IO ()
+apiPut mbasic base uri parameters body = withOpenSSL $ do
+  let url = B.concat [uri, queryString parameters]
+  q <- buildRequest $ do
+    http PUT url
+    setContentLength (fromIntegral $ LB.length body)
+    setContentType "application/json"
+    maybe (return ()) (uncurry setAuthorizationBasic) mbasic
+
+  c <- establishConnection base
+  -- debug q
+  body' <- Streams.fromLazyByteString body
+  sendRequest c q (inputStreamBody body')
+  -- TODO assert 204 for upcloud
+
+-- | Execute a PATCH agains the specified URI using the
+-- supplied parameters.
+apiPatch ::
+  Maybe (B.ByteString, B.ByteString)
+  -> B.ByteString
+  -> B.ByteString
+  -> [(B.ByteString, Maybe B.ByteString)]
+  -> LB.ByteString
+  -> IO ()
+apiPatch mbasic base uri parameters body = withOpenSSL $ do
+  let url = B.concat [uri, queryString parameters]
+  q <- buildRequest $ do
+    http PATCH url
+    setContentLength (fromIntegral $ LB.length body)
+    setContentType "application/json"
+    maybe (return ()) (uncurry setAuthorizationBasic) mbasic
+
+  c <- establishConnection base
+  -- debug q
+  body' <- Streams.fromLazyByteString body
+  sendRequest c q (inputStreamBody body')
+  -- TODO assert 204 for upcloud
+
+receiveResponse' :: FromJSON a => Connection -> IO (Maybe a)
+receiveResponse' c = do
+  r <- receiveResponse c $ \_ i -> do
+    x <- Streams.read i
+    let more = Streams.read i >>= return . fromMaybe ""
+    p <- parseWith more json $ fromMaybe "" x
+    case p of
+      Done _ value -> do
+        -- debug value
+        case fromJSON value of
+          Success value' -> do
+            return $ Just value'
+          _ -> return Nothing
+      _ -> return Nothing
+  closeConnection c
+  return r
+
+queryString :: [(B.ByteString, Maybe B.ByteString)] -> B.ByteString
+queryString [] = ""
+queryString xs = B.cons '?' . B.intercalate "&" . map f $ xs
+  where f (a, Just b) = B.concat [a, "=", b]
+        f (a, _) = a
+
+debug :: Show a => a -> IO ()
+debug s = do
+  hPutStrLn stderr $ show s
+  hFlush stderr
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/aeson-streams.cabal b/aeson-streams.cabal
new file mode 100644
--- /dev/null
+++ b/aeson-streams.cabal
@@ -0,0 +1,27 @@
+name:                aeson-streams
+version:             0.1.0
+Cabal-Version:       >= 1.8
+synopsis:            An HTTP client library for JSON-based APIs
+description:         An HTTP client library for JSON-based APIs using aeson and http-streams.
+category:            Web
+license:             BSD3
+license-file:        LICENSE
+author:              Vo Minh Thu
+maintainer:          thu@hypered.io
+build-type:          Simple
+homepage:            https://github.com/noteed/aeson-streams
+
+source-repository head
+  type: git
+  location: git://github.com/noteed/aeson-streams.git
+
+library
+  build-depends:       aeson == 0.6.*,
+                       attoparsec == 0.10.*,
+                       base == 4.*,
+                       bytestring > 0.9 && < 0.11,
+                       HsOpenSSL == 0.10.*,
+                       http-streams >= 0.7 && <= 0.9,
+                       io-streams == 1.1.*
+  exposed-modules:     Network.Aeson.Client
+  ghc-options:         -Wall
