diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for pixela
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Kazuki Okamoto (c) 2018
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Kazuki Okamoto nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+OWNER 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Pixela client for Haskell
+
+This is [Pixela](https://pixe.la/) client for Haskell.
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/pixela.cabal b/pixela.cabal
new file mode 100644
--- /dev/null
+++ b/pixela.cabal
@@ -0,0 +1,76 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: bd1fc33f3adcd9cefe5837a71f3de4b922a2eb5993aa87d00ef45be247555726
+
+name:           pixela
+version:        0.1.0.0
+synopsis:       Pixela client.
+description:    Pixela (https://pixe.la/) client library.
+category:       Web
+homepage:       https://github.com/kakkun61/pixela#readme
+bug-reports:    https://github.com/kakkun61/pixela/issues
+author:         Kazuki Okamoto
+maintainer:     kazuki.okamoto@kakkun61.com
+copyright:      Kazuki Okamoto
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/kakkun61/pixela
+
+library
+  exposed-modules:
+      Web.Pixela
+  other-modules:
+      Paths_pixela
+  hs-source-dirs:
+      src
+  default-extensions: OverloadedStrings
+  ghc-options: -Wall -fwarn-tabs -fwarn-incomplete-record-updates -fwarn-unused-do-bind
+  build-depends:
+      aeson
+    , base >=4.7 && <5
+    , bytestring
+    , data-default
+    , http-client
+    , http-client-tls
+    , http-types
+    , split
+    , text
+    , unordered-containers
+    , uri-encode
+    , vector
+  default-language: Haskell2010
+
+test-suite pixela-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_pixela
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -fwarn-tabs -fwarn-incomplete-record-updates -fwarn-unused-do-bind
+  build-depends:
+      aeson
+    , base >=4.7 && <5
+    , bytestring
+    , data-default
+    , http-client
+    , http-client-tls
+    , http-types
+    , pixela
+    , split
+    , text
+    , unordered-containers
+    , uri-encode
+    , vector
+  default-language: Haskell2010
diff --git a/src/Web/Pixela.hs b/src/Web/Pixela.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Pixela.hs
@@ -0,0 +1,541 @@
+module Web.Pixela
+  ( -- * Types
+    Client (config)
+  , newClient
+  , Config (..)
+  , Exception (..)
+  , FromParameter (..)
+  , fromParameter'
+  , ToParameter (..)
+  , Token
+  , UserName
+  , Agreement (..)
+  , Majority (..)
+  , GraphId
+  , GraphName
+  , GraphUnit
+  , GraphType (..)
+  , GraphColor (..)
+  , DateFormat
+  , DisplayMode (..)
+  , Url
+  , Date
+  , Quantity
+  , WebhookType
+  , WebhookHash
+    -- * Constants
+  , version
+    -- * User functions
+  , createUser
+  , updateToken
+  , deleteUser
+    -- * Graph functions
+  , createGraph
+  , getGraphs
+  , getGraphsBSL
+  , getGraph
+  , updateGraph
+  , deleteGraph
+    -- * Quantity funcitons
+  , setQuantity
+  , getQuantity
+  , getQuantityBSL
+  , updateQuantity
+  , incrementQuantity
+  , decrementQuantity
+  , deleteQuantity
+    -- * Webhook functions
+  , createWebhook
+  , getWebhooks
+  , getWebhooksBSL
+  , invokeWebhook
+  , deleteWebhook
+  ) where
+
+import Control.Exception hiding (Exception)
+import qualified Control.Exception as E (Exception)
+import Control.Monad
+import Data.Aeson ((.=))
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Lazy.Char8 as BSL
+import qualified Data.ByteString.Char8 as BS
+import Data.Default
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Data.Typeable
+import qualified Data.Vector as Vector
+import Network.HTTP.Client
+import Network.HTTP.Client.TLS
+import Network.HTTP.Types
+import qualified Network.URI.Encode as URI
+
+-- | Pixela client.
+--
+-- @
+-- import Web.Pixela
+-- import Data.Default (def)
+--
+-- main :: IO
+-- main = do
+--   _client <- newClient def
+--   pure ()
+-- @
+data Client =
+  Client
+    { config :: Config
+    , _httpManager :: Manager
+    }
+
+-- | Client configuration.
+data Config =
+  Config
+    { endPoint :: Url
+    , httpManagerSettings :: ManagerSettings
+    , userName :: UserName
+    , token :: Token
+    }
+
+instance Default Config where
+  def =
+    Config
+      "https://pixe.la/v1"
+      tlsManagerSettings
+      ""
+      ""
+
+data Exception =
+  JsonException String
+  deriving (Show, Eq, Typeable)
+
+instance E.Exception Exception
+
+-- | A type class to create a value from 'String'.
+class FromParameter a where
+  -- | If you know this does not fail, you can use 'fromParameter''.
+  fromParameter :: String -> Maybe a
+
+-- | Create value from 'String'.
+-- This function is unsafe, applys 'error' if fails.
+-- 'fromParameter' is safe.
+fromParameter' :: (FromParameter a) => String -> a
+fromParameter' param =
+  case fromParameter param of
+    Just a -> a
+    Nothing -> error $ "pixela: error parsing \"" <> param <> "\""
+
+-- | A type class to convert a value to 'String'.
+class ToParameter a where
+  toParameter :: a -> String
+
+type Token = String
+type UserName = String
+
+-- | Which to agree <https://github.com/a-know/Pixela/wiki/Terms-of-Service terms of service> or not.
+data Agreement = Agree | Disagree deriving (Show, Read, Eq)
+
+-- | Major (adult) or minor (child).
+data Majority = Major | Minor deriving (Show, Read, Eq)
+
+type GraphId = String
+type GraphName = String
+type GraphUnit = String
+
+data GraphType = GraphTypeInt | GraphTypeFloat deriving (Show, Read, Eq)
+
+instance FromParameter GraphType where
+  fromParameter "int" = Just GraphTypeInt
+  fromParameter "float" = Just GraphTypeFloat
+  fromParameter _ = Nothing
+
+instance ToParameter GraphType where
+  toParameter GraphTypeInt = "int"
+  toParameter GraphTypeFloat = "float"
+
+data GraphColor
+  = Shibafu -- ^ Green
+  | Momiji -- ^ Red
+  | Sora -- ^ Blue
+  | Ichou -- ^ Yellow
+  | Ajisai -- ^ Purple
+  | Kuro -- ^ Black
+  deriving (Show, Read, Eq)
+
+instance FromParameter GraphColor where
+  fromParameter "shibafu" = Just Shibafu
+  fromParameter "momiji" = Just Momiji
+  fromParameter "sora" = Just Sora
+  fromParameter "ichou" = Just Ichou
+  fromParameter "ajisai" = Just Ajisai
+  fromParameter "kuro" = Just Kuro
+  fromParameter _ = Nothing
+
+instance ToParameter GraphColor where
+  toParameter Shibafu = "shibafu"
+  toParameter Momiji = "momiji"
+  toParameter Sora = "sora"
+  toParameter Ichou = "ichou"
+  toParameter Ajisai = "ajisai"
+  toParameter Kuro = "kuro"
+
+type DateFormat = String
+
+data DisplayMode = ShortMode | DefaultMode deriving (Show, Read, Eq)
+
+instance FromParameter DisplayMode where
+  fromParameter "short" = Just ShortMode
+  fromParameter "" = Just DefaultMode
+  fromParameter _ = Nothing
+
+instance ToParameter DisplayMode where
+  toParameter ShortMode = "short"
+  toParameter DefaultMode = ""
+
+type Url = String
+type Date = String
+type Quantity = String
+
+data WebhookType = Increment | Decrement deriving (Show, Read, Eq)
+
+instance FromParameter WebhookType where
+  fromParameter "increment" = Just Increment
+  fromParameter "decrement" = Just Decrement
+  fromParameter _ = Nothing
+
+instance ToParameter WebhookType where
+  toParameter Increment = "increment"
+  toParameter Decrement = "decrement"
+
+type WebhookHash = String
+
+-- | Create new client.
+newClient :: Config -> IO Client
+newClient c =
+  Client c <$> newManager (httpManagerSettings c)
+
+-- | Create a user.
+-- <https://pixe.la/#api-detail-post-users>
+createUser :: Agreement -> Majority -> Client -> IO ()
+createUser agreement majority (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      POST
+      (ep </> "users")
+      Nothing
+      ( Just $ Aeson.object
+          [ "token'" .= token'
+          , "username" .= userName'
+          , "agreeTermsOfService" .= case agreement of Agree -> "yes"; Disagree -> "no" :: String
+          , "notMinor" .= case majority of Major -> "yes"; Minor -> "no" :: String
+          ]
+      )
+      manager
+
+-- | Update user token.
+-- <https://pixe.la/#api-detail-put-user>
+updateToken :: Token -> Client -> IO ()
+updateToken newToken (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      PUT
+      (ep </> "users" </> URI.encode userName')
+      (Just token')
+      (Just $ Aeson.object ["newToken" .= newToken])
+      manager
+
+-- | Delete the user.
+-- <https://pixe.la/#api-detail-delete-user>
+deleteUser :: Client -> IO ()
+deleteUser (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      DELETE
+      (ep </> "users" </> URI.encode userName')
+      (Just token')
+      Nothing
+      manager
+
+-- | Create a graph.
+-- <https://pixe.la/#api-detail-post-graphs>
+createGraph :: GraphId -> GraphName -> GraphUnit -> GraphType -> GraphColor -> Client -> IO ()
+createGraph graphId graphName graphUnit graphType graphColor (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      POST
+      (ep </> "users" </> URI.encode userName' </> "graphs")
+      (Just token')
+      ( Just $ Aeson.object
+          [ "id" .= graphId
+          , "name" .= graphName
+          , "unit" .= graphUnit
+          , "type" .= toParameter graphType
+          , "color" .= toParameter graphColor
+          ]
+      )
+      manager
+
+-- | Get the list of infomation of graphs.
+-- <https://pixe.la/#api-detail-get-graphs>
+getGraphs :: Client -> IO Aeson.Value
+getGraphs client =
+  getGraphsBSL client >>= decodeJson
+
+-- | Get the list of infomation of graphs.
+-- <https://pixe.la/#api-detail-get-graphs>
+getGraphsBSL :: Client -> IO BSL.ByteString
+getGraphsBSL (Client (Config ep _ userName' token') manager) =
+  requestBSL
+    GET
+    (ep </> "users" </> URI.encode userName' </> "graphs")
+    (Just token')
+    Nothing
+    manager
+
+-- | Get the graph.
+-- <https://pixe.la/#api-detail-get-graph>
+getGraph :: GraphId -> Maybe DateFormat -> DisplayMode -> Client -> IO BSL.ByteString
+getGraph graphId maybeFormat mode (Client (Config ep _ userName' _) manager) =
+  requestBSL
+    GET
+    (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId)
+    Nothing
+    ( case (mode, maybeFormat) of
+        (DefaultMode, Nothing) -> Nothing
+        _ ->
+          Just $ Aeson.object $
+            case mode of
+              ShortMode -> ["mode" .= toParameter mode]
+              DefaultMode -> []
+            ++
+            case maybeFormat of
+              Just format -> ["date" .= format]
+              Nothing -> []
+    )
+    manager
+
+-- | Update the graph.
+-- <https://pixe.la/#api-detail-put-graph>
+updateGraph :: GraphId -> Maybe GraphName -> Maybe GraphUnit -> Maybe GraphColor -> [Url] -> Client -> IO ()
+updateGraph _ Nothing Nothing Nothing [] _ = pure ()
+updateGraph graphId maybeGraphName maybeGraphUnit maybeGraphColor purgeCacheUrls (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      PUT
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId)
+      (Just token')
+      ( Just $ Aeson.object $
+          case maybeGraphName of
+            Just graphName -> ["name" .= Aeson.String (Text.pack graphName)]
+            Nothing -> []
+          ++
+          case maybeGraphUnit of
+            Just graphUnit -> ["unit" .= Aeson.String (Text.pack graphUnit)]
+            Nothing -> []
+          ++
+          case maybeGraphColor of
+            Just graphColor -> ["color" .= Aeson.String (Text.pack $ toParameter graphColor)]
+            Nothing -> []
+          ++
+          case purgeCacheUrls of
+            _:_ -> ["purgeCacheURLs" .= Aeson.Array (Vector.fromList $ map (Aeson.String . Text.pack) purgeCacheUrls)]
+            [] -> []
+      )
+      manager
+
+-- | Delete the graph.
+-- <https://pixe.la/#api-detail-delete-graph>
+deleteGraph :: GraphId -> Client -> IO ()
+deleteGraph graphId (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      DELETE
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId)
+      (Just token')
+      Nothing
+      manager
+
+-- | Set quantity pixel.
+-- <https://pixe.la/#api-detail-post-pixels>
+setQuantity :: GraphId -> Date -> Quantity -> Client -> IO ()
+setQuantity graphId date quantity (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      POST
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId)
+      (Just token')
+      ( Just $ Aeson.object
+          [ "date" .= date
+          , "quantity" .= quantity
+          ]
+      )
+      manager
+
+-- | Get quantity pixel.
+-- <https://pixe.la/#api-detail-get-pixel>
+getQuantity :: GraphId -> Date -> Client -> IO Aeson.Value
+getQuantity graphId date client =
+  getQuantityBSL graphId date client >>= decodeJson
+
+-- | Get quantity pixel.
+-- <https://pixe.la/#api-detail-get-pixel>
+getQuantityBSL :: GraphId -> Date -> Client -> IO BSL.ByteString
+getQuantityBSL graphId date (Client (Config ep _ userName' token') manager) =
+  requestBSL
+    GET
+    (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId </> URI.encode date)
+    (Just token')
+    Nothing
+    manager
+
+-- | Update quantity pixel.
+-- <https://pixe.la/#api-detail-put-pixel>
+updateQuantity :: GraphId -> Date -> Quantity -> Client -> IO ()
+updateQuantity graphId date quantity (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      PUT
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId </> URI.encode date)
+      (Just token')
+      (Just $ Aeson.object ["quantity" .= quantity])
+      manager
+
+-- | Increment quantity pixel.
+-- <https://pixe.la/#api-detail-pixel-increment>
+incrementQuantity :: GraphId -> Client -> IO ()
+incrementQuantity graphId (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      PUT
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId </> "increment")
+      (Just token')
+      Nothing
+      manager
+
+-- | Decrement quantity pixel.
+-- <https://pixe.la/#api-detail-pixel-decrement>
+decrementQuantity :: GraphId -> Client -> IO ()
+decrementQuantity graphId (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      PUT
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId </> "decrement")
+      (Just token')
+      Nothing
+      manager
+
+-- | Delete quantity pixel.
+-- <https://pixe.la/#api-detail-delete-pixel>
+deleteQuantity :: GraphId -> Date -> Client -> IO ()
+deleteQuantity graphId date (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      DELETE
+      (ep </> "users" </> URI.encode userName' </> "graphs" </> URI.encode graphId </> URI.encode date)
+      (Just token')
+      Nothing
+      manager
+
+-- | Create a webhook.
+-- <https://pixe.la/#api-detail-post-webhooks>
+createWebhook :: GraphId -> WebhookType -> Client -> IO Text
+createWebhook graphId type' (Client (Config ep _ userName' token') manager) = do
+  response <-
+    request
+      POST
+      (ep </> "users" </> URI.encode userName' </> "webhooks")
+      (Just token')
+      ( Just $ Aeson.object
+          [ "graphID" .= graphId
+          , "type" .= toParameter type'
+          ]
+      )
+      manager
+  let
+    throw' = throwIO $ JsonException "hash string cannot be extracted"
+  case response of
+    Aeson.Object o ->
+      case HashMap.lookup "webhookHash" o of
+        Just (Aeson.String hashString) -> pure hashString
+        _ -> throw'
+    _ -> throw'
+
+-- | Get the webhook.
+-- <https://pixe.la/#api-detail-get-webhooks>
+getWebhooks :: Client -> IO Aeson.Value
+getWebhooks client =
+  getWebhooksBSL client >>= decodeJson
+
+-- | Get the webhook.
+-- <https://pixe.la/#api-detail-get-webhooks>
+getWebhooksBSL :: Client -> IO BSL.ByteString
+getWebhooksBSL (Client (Config ep _ userName' token') manager) =
+  requestBSL
+    GET
+    (ep </> "users" </> URI.encode userName' </> "webhooks")
+    (Just token')
+    Nothing
+    manager
+
+-- | Invoke the webhook.
+-- <https://pixe.la/#api-detail-post-webhook>
+invokeWebhook :: WebhookHash -> Client -> IO ()
+invokeWebhook hash (Client (Config ep _ userName' _) manager) =
+  void $
+    request
+      POST
+      (ep </> "users" </> URI.encode userName' </> "webhooks" </> URI.encode hash)
+      Nothing
+      Nothing
+      manager
+
+-- | Delete the webhook.
+-- <https://pixe.la/#api-detail-delete-webhook>
+deleteWebhook :: WebhookHash -> Client -> IO ()
+deleteWebhook hash (Client (Config ep _ userName' token') manager) =
+  void $
+    request
+      DELETE
+      (ep </> "users" </> URI.encode userName' </> "webhooks" </> URI.encode hash)
+      (Just token')
+      Nothing
+      manager
+
+request :: StdMethod -> String -> Maybe Token -> Maybe Aeson.Value -> Manager -> IO Aeson.Value
+request method' uri maybeToken maybeBody manager =
+  requestBSL method' uri maybeToken maybeBody manager >>= decodeJson
+
+requestBSL :: StdMethod -> String -> Maybe Token -> Maybe Aeson.Value -> Manager -> IO BSL.ByteString
+requestBSL method' uri maybeToken maybeBody manager = do
+  request' <- parseUrlThrow uri
+  let
+    request'' =
+      request'
+        { method = renderStdMethod method'
+        , requestHeaders =
+            [("User-Agent", "Pixela Haskell Client " <> version)]
+            ++
+            case maybeToken of
+              Just token' -> [("X-USER-TOKEN", BS.pack token')]
+              Nothing -> []
+        , requestBody =
+            RequestBodyLBS $
+              case maybeBody of
+                Just body -> Aeson.encode body
+                Nothing -> ""
+        }
+  response <- httpLbs request'' manager
+  pure $ responseBody response
+        
+decodeJson :: BSL.ByteString -> IO Aeson.Value
+decodeJson responseBody' =
+  case Aeson.decode $ responseBody' of
+    Nothing -> throwIO $ JsonException "failed to parse response body as JSON"
+    Just b -> pure b
+
+(</>) :: (Semigroup p, IsString p) => p -> p -> p
+p </> q = p <> "/" <> q
+infixr 6 </>
+
+version :: (IsString a) => a
+version = "1.0.0"
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+main :: IO ()
+main = putStrLn "Test suite not yet implemented"
