diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Tomas Carnecky
+
+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.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/kraken.cabal b/kraken.cabal
new file mode 100644
--- /dev/null
+++ b/kraken.cabal
@@ -0,0 +1,41 @@
+name:                kraken
+version:             0.0.1
+
+synopsis:            Kraken.io API client
+description:
+  Kraken is a robust, ultra-fast image optimizer and compressor with
+  best-in-class algorithms.
+
+license:             MIT
+license-file:        LICENSE
+
+author:              Tomas Carnecky
+maintainer:          tomas.carnecky@gmail.com
+
+category:            Network
+
+build-type:          Simple
+cabal-version:       >=1.10
+
+
+source-repository head
+    type:     git
+    location: git://github.com/wereHamster/kraken-haskell.git
+
+
+library
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+  ghc-options:         -Wall
+
+  exposed-modules:
+     Network.Kraken
+
+  build-depends:
+     base >=4.5 && <4.9
+   , bytestring
+   , http-client
+   , http-client-tls
+   , aeson
+   , mtl
diff --git a/src/Network/Kraken.hs b/src/Network/Kraken.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Kraken.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Kraken
+    ( Config(..)
+
+    , Handle
+    , newHandle
+
+    , Quality(..), Resize(..), Format(..), Convert(..), Options(..)
+
+    , compressImage
+    ) where
+
+
+import Control.Monad.Except
+import Data.ByteString (ByteString)
+import Data.ByteString.Lazy (toStrict)
+import Data.Aeson hiding (Success, Error)
+import Data.Aeson.Types hiding (Success, Error, Options)
+import Data.Maybe
+
+import Network.HTTP.Client hiding (Response)
+import Network.HTTP.Client.MultipartFormData
+import Network.HTTP.Client.TLS (tlsManagerSettings)
+
+
+
+data Config = Config
+    { cApiKey    :: !String
+    , cApiSecret :: !String
+    }
+
+
+data Handle = Handle
+    { hConfig  :: Config
+    , hManager :: Manager
+    }
+
+
+data Quality = Lossless | Lossy !Int
+
+data Resize
+    = Exact !Int !Int
+    | Portrait !Int
+    | Landscape !Int
+    | Auto !Int !Int
+    | Fit !Int !Int
+    | Crop !Int !Int
+    | Square !Int
+    | Fill !Int !Int !String
+
+instance ToJSON Resize where
+    toJSON (Exact w h) = strategy "exact" [ "width" .= w, "height" .= h ]
+    toJSON (Portrait h) = strategy "portrait" [ "height" .= h ]
+    toJSON (Landscape w) = strategy "landscape" [ "width" .= w ]
+    toJSON (Auto w h) = strategy "auto" [ "width" .= w, "height" .= h ]
+    toJSON (Fit w h) = strategy "fit" [ "width" .= w, "height" .= h ]
+    toJSON (Crop w h) = strategy "crop" [ "width" .= w, "height" .= h ]
+    toJSON (Square s) = strategy "square" [ "size" .= s ]
+    toJSON (Fill w h bg) = strategy "fill" [ "width" .= w, "height" .= h, "background" .= bg ]
+
+strategy :: String -> [Pair] -> Value
+strategy s r = object $ [ "strategy" .= s ] ++ r
+
+data Format = JPEG | PNG | GIF
+instance ToJSON Format where
+    toJSON JPEG = String "jpeg"
+    toJSON PNG  = String "png"
+    toJSON GIF  = String "gif"
+
+data Convert = Convert
+    { format :: Format
+    , background :: !(Maybe String)
+    , keepExtension :: !(Maybe Bool)
+    }
+
+data Options = Options
+    { quality :: !(Maybe Quality)
+    , resize :: !(Maybe Resize)
+    , convert :: !(Maybe Convert)
+    }
+
+renderOptions :: Options -> [Pair]
+renderOptions o = concat $ catMaybes
+    [ fmap qualityPairs (quality o)
+    , (\r -> ["resize" .= r]) <$> (resize o)
+    , fmap convertPairs (convert o)
+    ]
+
+  where
+
+    qualityPairs Lossless  = [ "lossy" .= False ]
+    qualityPairs (Lossy q) = [ "lossy" .= True, "quality" .= q ]
+
+    convertPairs x =
+        [ "convert" .= object (catMaybes
+            [ Just $ "format" .= format x
+            , ("background" .=) <$> (background x)
+            , ("keep_extension" .=) <$> (keepExtension x)
+            ])
+        ]
+
+
+-------------------------------------------------------------------------------
+-- JSON objects used in the API
+
+data Success = Success
+    { krakedUrl :: !String
+    } deriving (Show)
+
+
+data Error = Error !String
+    deriving (Show)
+
+
+responseParser :: Value -> Parser (Either Error Success)
+responseParser (Object o) = o .: "success" >>= \success ->
+    if success
+        then Right . Success <$> o .: "kraked_url"
+        else Left . Error <$> o .: "error"
+responseParser _ = fail "Kraken API Response"
+
+
+newHandle :: Config -> IO Handle
+newHandle c = do
+    manager <- newManager tlsManagerSettings
+    return $ Handle c manager
+
+
+compressImage :: Handle -> Options -> ByteString -> IO (Either Error ByteString)
+compressImage h opt img = do
+    let Just req0 = parseUrl "https://api.kraken.io/v1/upload"
+    req <- insertRequestBody req0
+    res <- httpLbs (req { checkStatus = \_ _ _ -> Nothing }) (hManager h)
+
+    runExceptT $ do
+        v <- case eitherDecode' (responseBody res) of
+            Left e -> throwError $ Error e
+            Right x -> return x
+
+        r <- case parseEither responseParser v of
+            Left e -> throwError $ Error e
+            Right x -> ExceptT $ return x
+
+        ExceptT $ downloadBinary (hManager h) (krakedUrl r)
+
+  where
+    config = hConfig h
+
+    options = object $
+        [ "auth" .= object
+            [ "api_key"    .= cApiKey config
+            , "api_secret" .= cApiSecret config
+            ]
+        , "wait" .= True
+        ] ++ renderOptions opt
+
+    insertRequestBody :: Request -> IO Request
+    insertRequestBody = formDataBody
+        [ partLBS "options" $ encode options
+        , (partBS "file" img) { partFilename = Just "kraken" }
+        ]
+
+downloadBinary :: Manager -> String -> IO (Either Error ByteString)
+downloadBinary m url = runExceptT $ do
+    req <- ExceptT $ return $ case parseUrl url of
+        Nothing -> throwError $ Error $ "Failed to parse download URL: " ++ url
+        Just x -> return x
+
+    res <- ExceptT $ Right <$> httpLbs req m
+    ExceptT $ return $ Right $ toStrict $ responseBody res
