diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+`bodhi` uses [PVP Versioning](https://pvp.haskell.org).
+
+## 0.1.0 (2021-12-27)
+- initial Hackage release
+
+## 0.0.1
+- Initial query bindings
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Jens Petersen
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# bodhi
+
+[![Hackage](https://img.shields.io/hackage/v/bodhi.svg?logo=haskell)](https://hackage.haskell.org/package/bodhi)
+[![Stackage Lts](http://stackage.org/package/bodhi/badge/lts)](http://stackage.org/lts/package/bodhi)
+[![Stackage Nightly](http://stackage.org/package/bodhi/badge/nightly)](http://stackage.org/nightly/package/bodhi)
+[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
+
+Fedora Bodhi REST client library
+
+[Bodhi](https://bodhi.fedoraproject.org/) is Fedora package update system.
+The API is documented at <https://bodhi.fedoraproject.org/docs/>.
diff --git a/bodhi.cabal b/bodhi.cabal
new file mode 100644
--- /dev/null
+++ b/bodhi.cabal
@@ -0,0 +1,55 @@
+cabal-version:       1.22
+name:                bodhi
+version:             0.1.0
+synopsis:            Fedora Bodhi REST client library
+description:
+            A REST client library for the Fedora Bodhi updates server.
+            The binding is currently fairly low-level returning JSON Objects.
+            So far it covers nearly all of the GET part of the API.
+homepage:            https://github.com/juhp/bodhi-hs
+bug-reports:         https://github.com/juhp/bodhi-hs/issues
+license:             MIT
+license-file:        LICENSE
+author:              Jens Petersen
+maintainer:          Jens Petersen <juhpetersen@gmail.com>
+copyright:           2020 Jens Petersen
+category:            Network
+build-type:          Simple
+extra-doc-files:     README.md
+                     CHANGELOG.md
+                     examples/Main.hs
+tested-with:         GHC == 8.0.2
+                     GHC == 8.2.2
+                     GHC == 8.4.4
+                     GHC == 8.6.5
+                     GHC == 8.8.4
+                     GHC == 8.10.6
+
+source-repository head
+  type:                git
+  location:            https://github.com/juhp/bodhi-hs.git
+
+library
+  build-depends:       aeson
+                     , base >= 4 && < 5
+                     , http-query
+                     , text
+                     , time
+  ghc-options:         -Wall
+  if impl(ghc >= 8.0)
+    ghc-options:       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wredundant-constraints
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wmissing-export-lists
+                       -Wpartial-fields
+  if impl(ghc >= 8.10)
+    ghc-options:       -Wunused-packages
+
+  default-language:    Haskell2010
+  hs-source-dirs:      src
+  exposed-modules:     Fedora.Bodhi
diff --git a/examples/Main.hs b/examples/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/Main.hs
@@ -0,0 +1,13 @@
+module Main (main) where
+
+import System.Environment
+import Fedora.Bodhi
+
+import Data.Time.LocalTime
+
+main :: IO ()
+main = do
+  args <- getArgs
+  let pkg = head args
+  moverride <- bodhiOverride pkg
+  print (lookupKey' "expiration_date" <$> moverride :: Maybe LocalTime)
diff --git a/src/Fedora/Bodhi.hs b/src/Fedora/Bodhi.hs
new file mode 100644
--- /dev/null
+++ b/src/Fedora/Bodhi.hs
@@ -0,0 +1,166 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{- |
+Copyright: (c) 2020 Jens Petersen
+SPDX-License-Identifier: MIT
+Maintainer: Jens Petersen <juhpetersen@gmail.com>
+
+Fedora Bodhi REST client library
+-}
+
+module Fedora.Bodhi
+  ( bodhiBuild
+  , bodhiBuilds
+  , bodhiComment
+  , bodhiComments
+  , bodhiCSRF
+  , bodhiOverride
+  , bodhiOverrides
+  , bodhiOverrideDates
+  , bodhiPackages
+  , bodhiRelease
+  , bodhiReleases
+  , bodhiUpdate
+  , bodhiUpdates
+  , bodhiUser
+  , bodhiUsers
+  , lookupKey
+  , lookupKey'
+  , queryBodhi
+  , makeKey
+  , makeItem
+  , maybeKey
+  , Query
+  , QueryItem
+  ) where
+
+import Data.Aeson.Types
+import Data.Text (Text)
+import Data.Time.LocalTime
+import Network.HTTP.Query
+
+server :: String
+server = "bodhi.fedoraproject.org"
+
+-- | Returns build JSON for NVR
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/builds.html#service-0
+bodhiBuild :: String -> IO Object
+bodhiBuild nvr =
+  queryBodhi [] $ "builds" +/+ nvr
+
+-- | returns JSON list of builds
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/builds.html#service-1
+bodhiBuilds :: Query -> IO [Object]
+bodhiBuilds params =
+  lookupKey' "builds" <$> queryBodhi params "builds/"
+
+-- | Returns comment JSON for id
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/comments.html#service-0
+bodhiComment :: String -> IO Object
+bodhiComment cid =
+  queryBodhi [] $ "comments" +/+ cid
+
+-- | returns JSON list of comments
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/comments.html#service-1
+bodhiComments :: Query -> IO [Object]
+bodhiComments params =
+  lookupKey' "comments" <$> queryBodhi params "comments/"
+
+-- | Get CSRF token
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/csrf.html
+bodhiCSRF :: IO (Maybe Text)
+bodhiCSRF =
+  lookupKey "csrf_token" <$> queryBodhi [] "csrf"
+
+-- | Returns override JSON for NVR
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/overrides.html#service-0
+bodhiOverride :: String -> IO (Maybe Object)
+bodhiOverride nvr =
+  lookupKey "override" <$> queryBodhi [] ("overrides" +/+ nvr)
+
+-- | Returns override expiration and submission dates for NVR
+bodhiOverrideDates :: String -> IO (Maybe (LocalTime,LocalTime))
+bodhiOverrideDates nvr = do
+  mobj <- bodhiOverride nvr
+  case mobj of
+    Nothing -> do
+      putStrLn $ "Override for " ++ nvr ++ " not found"
+      return Nothing
+    Just obj -> return $ readDates obj
+  where
+    readDates :: Object -> Maybe (LocalTime,LocalTime)
+    readDates =
+      parseMaybe $ \obj -> do
+        expire <- obj .: "expiration_date"
+        submit <- obj .: "submission_date"
+        return (expire,submit)
+
+-- | returns JSON list of overrides
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/overrides.html#service-1
+bodhiOverrides :: Query -> IO [Object]
+bodhiOverrides params =
+  lookupKey' "overrides" <$> queryBodhi params "overrides/"
+
+-- | Packages query
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/packages.html#service-0
+bodhiPackages :: Query -> IO [Object]
+bodhiPackages params =
+  lookupKey' "packages" <$> queryBodhi params "packages/"
+
+-- | read releases metadata from Bodhi
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/releases.html#service-0
+bodhiRelease :: String -> IO Object
+bodhiRelease rel =
+  queryBodhi [] $ "releases" +/+ rel
+
+-- | read releases metadata from Bodhi
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/releases.html#service-1
+bodhiReleases :: Query -> IO [Object]
+-- FIXME handle errors:
+-- fromList [("status",String "error"),("errors",Array [Object (fromList [("location",String "body"),("name",String "name"),("description",String "No such release")])])]
+bodhiReleases params =
+  lookupKey' "releases" <$> queryBodhi params "releases/"
+
+-- | read an update from Bodhi
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/updates.html#service-0
+bodhiUpdate :: String -> IO (Maybe Object)
+bodhiUpdate update =
+  lookupKey "update" <$> queryBodhi [] ("updates" +/+ update)
+
+-- | search for updates on Bodhi
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/updates.html#service-2
+bodhiUpdates :: Query -> IO [Object]
+bodhiUpdates params =
+  lookupKey' "updates" <$> queryBodhi params "updates/"
+
+-- | user info from Bodhi
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/users.html#service-0
+bodhiUser :: String -> IO Object
+bodhiUser user =
+  queryBodhi [] $ "users" +/+ user
+
+-- | list users from Bodhi
+--
+-- https://bodhi.fedoraproject.org/docs/server_api/rest/users.html#service-1
+bodhiUsers :: Query -> IO [Object]
+bodhiUsers params =
+  lookupKey' "users" <$> queryBodhi params "users/"
+
+-- | low-level query
+queryBodhi :: FromJSON a => Query -> String -> IO a
+queryBodhi params path =
+  let url = "https://" ++ server +/+ path
+  in webAPIQuery url params
