diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## 0.1.1.0 (2021-10-13)
+
+- Add EventCreatedOn Event attribute
+- Split Data module into specialized modules
+- Support more fields for GerritChange
+- Add the getProjects function
+- withClient takes optional credentials
+
 ## 0.1.0.0 (2021-05-30)
 
 - Basic Gerrit API data types.
diff --git a/gerrit.cabal b/gerrit.cabal
--- a/gerrit.cabal
+++ b/gerrit.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.4
 name:                gerrit
-version:             0.1.0.0
+version:             0.1.1.0
 synopsis:            A gerrit client library
 description:
     Gerrit is a client library to interface with https://www.gerritcodereview.com/
@@ -29,6 +29,7 @@
   build-depends:       base                   >= 4.11.0.0 && < 5
                      , aeson                  >= 1.0.0.0  && < 1.6
                      , bytestring             < 0.11
+                     , time                   >= 1.9.3    && < 2.0
   ghc-options:         -Wall
                        -Wcompat
                        -Widentities
@@ -49,13 +50,17 @@
   import:              common-options
   build-depends:       containers             >= 0.5.8.0  && < 0.7
                      , http-client            >= 0.5.0    && < 0.8
-                     , http-client-tls        >= 0.2.0    && < 0.4
+                     , http-client-openssl    >= 0.3.0    && < 0.4
                      , text                   >= 0.11.1.0 && < 1.3
+                     , aeson-casing           >= 0.2.0.0  && < 0.3.0.0
 
   hs-source-dirs:      src
   exposed-modules:     Gerrit
-                     , Gerrit.Data
-                     , Gerrit.Event
+                     , Gerrit.Data.Review
+                     , Gerrit.Data.Change
+                     , Gerrit.Data.Account
+                     , Gerrit.Data.Event
+                     , Gerrit.Data.Project
   other-modules:       Gerrit.Client
 
 test-suite gerrit-test
diff --git a/src/Gerrit.hs b/src/Gerrit.hs
--- a/src/Gerrit.hs
+++ b/src/Gerrit.hs
@@ -5,12 +5,16 @@
   ( -- * Client
     GerritClient,
     withClient,
+    getClient,
 
     -- * Api
     getVersion,
     getChange,
     queryChanges,
     postReview,
+    getAccountId,
+    getAccount,
+    getProjects,
 
     -- * Main data types
     GerritVersion (..),
@@ -18,22 +22,33 @@
     GerritChange (..),
     GerritChangeStatus (..),
     ReviewResult (..),
+    GerritAccount (..),
+    GerritAccountQuery (..),
+    GerritProjectQuery (..),
 
     -- * Convenient functions
     changeUrl,
-    hasLabel,
+    serverUrl,
   )
 where
 
 import Control.Exception (try)
+import Data.List.NonEmpty (NonEmpty)
 import qualified Data.Map as M
 import Data.Maybe (fromMaybe)
 import Data.Text (Text)
 import qualified Data.Text as T
 import Gerrit.Client
-import Gerrit.Data
+import Gerrit.Data.Account
+import Gerrit.Data.Change
+import Gerrit.Data.Project
+import Gerrit.Data.Review
 import Network.HTTP.Client (HttpException)
 
+-- | Return the base url of the 'GerritClient'
+serverUrl :: GerritClient -> T.Text
+serverUrl = baseUrl
+
 -- | Return the url of a 'GerritChange'
 changeUrl :: GerritClient -> GerritChange -> T.Text
 changeUrl client change = baseUrl client <> T.pack (show (number change))
@@ -42,20 +57,38 @@
 getVersion :: GerritClient -> IO GerritVersion
 getVersion = gerritGet "config/server/version"
 
+-- | Get projects
+getProjects ::
+  -- | Count of projects to get back
+  Int ->
+  -- | The project query type
+  GerritProjectQuery ->
+  -- | Whether or not to ask result from offset
+  Maybe Int ->
+  -- | The client
+  GerritClient ->
+  IO GerritProjectsMessage
+getProjects count query startM = gerritGet ("projects/?" <> projectQS count query startM)
+
 -- | Search for changes
-queryChanges :: Int -> [GerritQuery] -> GerritClient -> IO [GerritChange]
-queryChanges count queries = gerritGet ("changes/?" <> queryString)
-  where
-    queryString = T.intercalate "&" [changeString, countString, option]
-    changeString = "q=" <> T.intercalate "+" (map queryText queries)
-    countString = "n=" <> T.pack (show count)
-    option = "o=CURRENT_REVISION&o=DETAILED_LABELS"
+queryChanges ::
+  -- | Count of changes to get back
+  Int ->
+  -- | The change query
+  [GerritQuery] ->
+  -- | Whether or not to ask result from offset
+  Maybe Int ->
+  -- | The client
+  GerritClient ->
+  IO [GerritChange]
+queryChanges count queries startM = gerritGet ("changes/?" <> changeQS count queries startM)
 
+-- | Get a change by change Id
 getChange :: Int -> GerritClient -> IO (Maybe GerritChange)
 getChange changeNumber client = do
   res <-
     try
-      ( gerritGet ("changes/" <> T.pack (show changeNumber) <> "?o=CURRENT_REVISION&o=DETAILED_LABELS") client
+      ( gerritGet ("changes/" <> T.pack (show changeNumber) <> "?" <> defaultQueryChangeOptions) client
       ) ::
       IO (Either HttpException GerritChange)
   pure $ case res of
@@ -79,18 +112,18 @@
 postReview change message label value' = gerritPost urlPath review
   where
     urlPath = "changes/" <> changeId <> "/revisions/" <> revHash <> "/review"
-    changeId = Gerrit.Data.id change
-    revHash = fromMaybe "" (Gerrit.Data.current_revision change)
+    changeId = Gerrit.Data.Change.id change
+    revHash = fromMaybe "" (Gerrit.Data.Change.current_revision change)
     review =
       ReviewInput
         { riMessage = Just message,
           riLabels = Just (M.fromList [(label, value')])
         }
 
--- | Check if a gerrit change as a label
-hasLabel :: T.Text -> Int -> GerritChange -> Bool
-hasLabel label labelValue change = case M.lookup label (labels change) of
-  Just gerritLabel ->
-    (> 0) $
-      length $ filter (\vote -> fromMaybe 0 (value vote) == labelValue) (Gerrit.Data.all gerritLabel)
-  _ -> False
+-- | Get user account id
+getAccountId :: Int -> NonEmpty GerritAccountQuery -> GerritClient -> IO [GerritAccountId]
+getAccountId count queries = gerritGet ("accounts/?" <> accountQs count queries)
+
+-- | Get user account details
+getAccount :: Int -> NonEmpty GerritAccountQuery -> GerritClient -> IO [GerritAccount]
+getAccount count queries = gerritGet ("accounts/?" <> accountQs count queries <> "&o=DETAILS")
diff --git a/src/Gerrit/Client.hs b/src/Gerrit/Client.hs
--- a/src/Gerrit/Client.hs
+++ b/src/Gerrit/Client.hs
@@ -7,18 +7,17 @@
     withClient,
     gerritGet,
     gerritPost,
+    getClient,
   )
 where
 
 import Data.Aeson (FromJSON, ToJSON, eitherDecode, encode)
 import qualified Data.ByteString.Lazy as BSL
-import Data.Maybe (fromMaybe)
 import Data.Text (Text, unpack)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import Network.HTTP.Client
-import Network.HTTP.Client.TLS (tlsManagerSettings)
-import System.Environment (lookupEnv)
+import Network.HTTP.Client.OpenSSL (newOpenSSLManager, withOpenSSL)
 
 -- | The GerritClient record, use 'withClient' to create
 data GerritClient = GerritClient
@@ -27,27 +26,26 @@
     auth :: Maybe (Text, Text)
   }
 
+-- | Need to be call through withOpenSSL
+getClient :: Text -> Maybe (Text, Text) -> IO GerritClient
+getClient url auth = do
+  let baseUrl = T.dropWhileEnd (== '/') url <> "/"
+  manager <- newOpenSSLManager
+  pure $ GerritClient {..}
+
 -- | Create the 'GerritClient'
 withClient ::
   -- | The gerrit api url
   Text ->
-  -- | A username (password is read from GERRIT_PASSWORD environment)
-  Maybe Text ->
+  -- | Credentials (login, password) [Optional]
+  Maybe (Text, Text) ->
   -- | The callback
-  (GerritClient -> IO ()) ->
+  (GerritClient -> IO a) ->
   -- | withClient performs the IO
-  IO ()
-withClient url username callBack =
-  do
-    manager <- newManager tlsManagerSettings
-    auth <- case username of
-      Just user -> do
-        pass <- lookupEnv "GERRIT_PASSWORD"
-        pure $ Just (user, T.pack $ fromMaybe "" pass)
-      _ -> pure Nothing
-    callBack (GerritClient {..})
-  where
-    baseUrl = T.dropWhileEnd (== '/') url <> "/"
+  IO a
+withClient url creds callBack = withOpenSSL $ do
+  client <- getClient url creds
+  callBack client
 
 gerritDecode :: (FromJSON a, Applicative f) => Response BSL.ByteString -> f a
 gerritDecode response = case eitherDecode $ BSL.drop 5 $ responseBody response of
@@ -74,6 +72,7 @@
 gerritGet :: (FromJSON a) => Text -> GerritClient -> IO a
 gerritGet path GerritClient {..} =
   do
-    request <- parseUrlThrow (unpack $ baseUrl <> path)
+    let url = baseUrl <> path
+    request <- parseUrlThrow (unpack url)
     response <- httpLbs request manager
     gerritDecode response
diff --git a/src/Gerrit/Data.hs b/src/Gerrit/Data.hs
deleted file mode 100644
--- a/src/Gerrit/Data.hs
+++ /dev/null
@@ -1,168 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE OverloadedStrings #-}
-
--- | This module contains the gerrit data type
-module Gerrit.Data
-  ( -- * Main data types
-    GerritVersion (..),
-    GerritQuery (..),
-    GerritChangeStatus (..),
-    GerritChange (..),
-    GerritRevision (..),
-    GerritLabel (..),
-    GerritAccount (..),
-    GerritLabelVote (..),
-    GerritDetailedLabelVote (..),
-    GerritDetailedLabel (..),
-    ReviewResult (..),
-    ReviewInput (..),
-
-    -- * Convenient functions
-    queryText,
-  )
-where
-
-import Data.Aeson
-import Data.Char (isUpper, toLower)
-import qualified Data.Map as M
-import Data.Text (Text)
-import qualified Data.Text as T
-import GHC.Generics (Generic)
-
-newtype GerritVersion = GerritVersion Text
-  deriving (Show, Generic)
-  deriving anyclass (FromJSON)
-
--- https://gerrit-review.googlesource.com/Documentation/json.html
-data GerritRevisionKind = REWORK | TRIVIAL_REBASE | MERGE_FIRST_PARENT_UPDATE | NO_CODE_CHANGE | NO_CHANGE
-  deriving (Eq, Show, Generic, FromJSON)
-
-data GerritChangeStatus = NEW | MERGED | ABANDONED | DRAFT
-  deriving (Eq, Show, Generic, FromJSON)
-
-data GerritLabelVote = REJECTED | APPROVED | DISLIKED | RECOMMENDED
-  deriving (Eq, Show, Ord, Generic)
-
--- We use a custom parseJSON to decode Label Vote as lowercase
-instance FromJSON GerritLabelVote where
-  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = map toLower}
-
-instance FromJSONKey GerritLabelVote where
-  fromJSONKey = genericFromJSONKey defaultJSONKeyOptions {keyModifier = map toLower}
-
--- | Modify record attribute to match json schema
--- Remove the prefix and use snakecase
-customParseJSON :: String -> Options
-customParseJSON prefix = defaultOptions {fieldLabelModifier = recordToJson}
-  where
-    recordToJson = updateCase . drop (length prefix)
-    updateCase [] = []
-    updateCase (x : xs) = toLower x : updateCase' xs
-    updateCase' [] = []
-    updateCase' (x : xs)
-      | isUpper x = '_' : toLower x : updateCase' xs
-      | otherwise = x : updateCase' xs
-
--- https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html
-data ReviewResult = ReviewResult
-  { rrLabels :: Maybe (M.Map Text Int),
-    rrReady :: Maybe Int
-  }
-  deriving (Eq, Show, Ord, Generic)
-
-instance FromJSON ReviewResult where
-  parseJSON = genericParseJSON $ customParseJSON "rr"
-
-instance ToJSON ReviewResult where
-  toJSON = genericToJSON $ customParseJSON "rr"
-
-data ReviewInput = ReviewInput
-  { riMessage :: Maybe Text,
-    riLabels :: Maybe (M.Map Text Int)
-  }
-  deriving (Eq, Show, Ord, Generic)
-
-instance FromJSON ReviewInput where
-  parseJSON = genericParseJSON $ customParseJSON "ri"
-
-instance ToJSON ReviewInput where
-  toJSON = genericToJSON $ (customParseJSON "ri") {omitNothingFields = True}
-
--- https://gerrit-review.googlesource.com/Documentation/user-search.html
-data GerritQuery
-  = Status GerritChangeStatus
-  | Owner Text
-  | CommitMessage Text
-  | Project Text
-  | ChangeId Text
-
--- | Convert a GerritQuery object to the search terms
-queryText :: GerritQuery -> Text
-queryText (Status stat) = "status:" <> T.toLower (T.pack $ show stat)
-queryText (Owner owner) = "owner:" <> owner
-queryText (CommitMessage message) = "message:" <> message
-queryText (Project project') = "project:" <> project'
-queryText (ChangeId changeId) = "change:" <> changeId
-
-data GerritRevision = GerritRevision
-  { ref :: Text,
-    kind :: GerritRevisionKind
-  }
-  deriving (Show, Generic, FromJSON)
-
-newtype GerritAccount = GerritAccount
-  { unused_account_id :: Int
-  }
-  deriving (Show, Generic)
-
--- We use a cusom parseJSON to decode `_account_id` as `account_id`
-instance FromJSON GerritAccount where
-  parseJSON = genericParseJSON aesonOptions
-
-newtype GerritLabel
-  = GerritLabel (M.Map GerritLabelVote GerritAccount)
-  deriving (Show, Generic)
-  deriving anyclass (FromJSON)
-
-data GerritDetailedLabelVote = GerritDetailedLabelVote
-  { value :: Maybe Int,
-    account_id :: Int
-  }
-  deriving (Show, Generic)
-
--- We use a cusom parseJSON to decode record field properly
-instance FromJSON GerritDetailedLabelVote where
-  parseJSON = genericParseJSON aesonOptions
-
-data GerritDetailedLabel = GerritDetailedLabel
-  { all :: [GerritDetailedLabelVote],
-    default_value :: Int
-  }
-  deriving (Show, Generic, FromJSON)
-
-data GerritChange = GerritChange
-  { id :: Text,
-    project :: Text,
-    branch :: Text,
-    subject :: Text,
-    status :: GerritChangeStatus,
-    mergeable :: Maybe Bool,
-    revisions :: M.Map Text (Maybe GerritRevision),
-    current_revision :: Maybe Text,
-    number :: Int,
-    labels :: M.Map Text GerritDetailedLabel
-  }
-  deriving (Show, Generic)
-
--- We use a cusom parseJSON to decode `_number` as `number`
-instance FromJSON GerritChange where
-  parseJSON = genericParseJSON aesonOptions
-
-aesonOptions :: Options
-aesonOptions = defaultOptions {fieldLabelModifier = recordToJson}
-  where
-    recordToJson "number" = "_number"
-    recordToJson "account_id" = "_account_id"
-    recordToJson n = n
diff --git a/src/Gerrit/Data/Account.hs b/src/Gerrit/Data/Account.hs
new file mode 100644
--- /dev/null
+++ b/src/Gerrit/Data/Account.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Gerrit.Data.Account
+  ( GerritAccountId (..),
+    GerritAccount (..),
+    GerritAccountQuery (..),
+    userQueryText,
+    accountQs,
+  )
+where
+
+import Control.Monad (mzero)
+import Data.Aeson
+import Data.List.NonEmpty (NonEmpty, toList)
+import Data.Text (Text)
+import qualified Data.Text as T
+
+-- https://gerrit-review.googlesource.com/Documentation/user-search-accounts.html#_search_operators
+data GerritAccountQuery
+  = CanSee Text
+  | Email Text
+  | Name Text
+  | Username Text
+  | IsActive
+  | IsInactive
+
+userQueryText :: GerritAccountQuery -> Text
+userQueryText guq = case guq of
+  CanSee change -> "cansee:" <> change
+  Email email -> "email:" <> email
+  Name name -> "name:" <> escapeChar name
+  Username username -> "username:" <> username
+  IsActive -> "is:active"
+  IsInactive -> "is:inactive"
+  where
+    escapeChar = T.replace "'" " "
+
+accountQs :: Int -> NonEmpty GerritAccountQuery -> Text
+accountQs count queries = T.intercalate "&" [searchString, countString]
+  where
+    searchString = "q=" <> T.intercalate "+" (map userQueryText $ toList queries)
+    countString = "n=" <> T.pack (show count)
+
+data GerritAccountId = GerritAccountId
+  { gerritAccountId' :: Int,
+    gerritAccountHasMore' :: Maybe Bool
+  }
+  deriving (Eq, Show)
+
+instance FromJSON GerritAccountId where
+  parseJSON (Object v) = GerritAccountId <$> v .: "_account_id" <*> v .:? "_more_accounts"
+  parseJSON _ = mzero
+
+data GerritAccount = GerritAccount
+  { gerritAccountId :: Int,
+    gerritAccountName :: Text,
+    gerritAccountUsername :: Maybe Text,
+    gerritAccountEmail :: Maybe Text,
+    gerritAccountHasMore :: Maybe Bool
+  }
+  deriving (Eq, Show)
+
+instance FromJSON GerritAccount where
+  parseJSON (Object v) =
+    GerritAccount
+      <$> v .: "_account_id"
+      <*> v .: "name"
+      <*> v .:? "username"
+      <*> v .:? "email"
+      <*> v .:? "_more_accounts"
+  parseJSON _ = mzero
diff --git a/src/Gerrit/Data/Change.hs b/src/Gerrit/Data/Change.hs
new file mode 100644
--- /dev/null
+++ b/src/Gerrit/Data/Change.hs
@@ -0,0 +1,235 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Gerrit.Data.Change
+  ( GerritQuery (..),
+    GerritChangeStatus (..),
+    GerritRevisionKind (..),
+    GerritFile (..),
+    GerritCommit (..),
+    GerritRevision (..),
+    GerritDetailedLabelVote (..),
+    GerritDetailedLabel (..),
+    GerritAuthor (..),
+    GerritCommitAuthor (..),
+    GerritChangeMessage (..),
+    GerritChange (..),
+    GerritTime (..),
+    changeQS,
+    queryText,
+    defaultQueryChangeOptions,
+    hasLabel,
+  )
+where
+
+import Data.Aeson
+import Data.Aeson.Casing (aesonPrefix, snakeCase)
+import qualified Data.Map as M
+import Data.Maybe (fromMaybe)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Time.Clock
+import Data.Time.Format (defaultTimeLocale, formatTime, parseTimeM)
+import GHC.Generics (Generic)
+
+aesonOptions :: Options
+aesonOptions = defaultOptions {fieldLabelModifier = recordToJson}
+  where
+    recordToJson "number" = "_number"
+    recordToJson "account_id" = "_account_id"
+    recordToJson "aAccountId" = "_account_id"
+    recordToJson "aName" = "name"
+    recordToJson "aEmail" = "email"
+    recordToJson "aUsername" = "username"
+    recordToJson "more_changes" = "_more_changes"
+    recordToJson n = n
+
+-- https://gerrit-review.googlesource.com/Documentation/user-search.html
+data GerritQuery
+  = Status GerritChangeStatus
+  | Owner Text
+  | CommitMessage Text
+  | Project Text
+  | ChangeId Text
+  | After UTCTime
+
+-- | Convert a GerritQuery object to the search terms
+queryText :: GerritQuery -> Text
+queryText (Status stat) = "status:" <> T.toLower (T.pack $ show stat)
+queryText (Owner owner') = "owner:" <> owner'
+queryText (CommitMessage message) = "message:" <> message
+queryText (Project project') = "project:" <> project'
+queryText (ChangeId changeId) = "change:" <> changeId
+queryText (After date') = "after:" <> T.pack formatedDate
+  where
+    formatedDate = formatTime defaultTimeLocale "%F" date'
+
+defaultQueryChangeOptions :: Text
+defaultQueryChangeOptions =
+  "o="
+    <> T.intercalate
+      "&o="
+      [ "MESSAGES",
+        "DETAILED_ACCOUNTS",
+        "DETAILED_LABELS",
+        "CURRENT_REVISION",
+        "CURRENT_FILES",
+        "CURRENT_COMMIT"
+      ]
+
+-- | Build the Query String for the changes endpoint
+-- >>> changeQS 10 [Project "software-factory/gerrit-haskell"] Nothing
+-- "q=project:software-factory/gerrit-haskell&n=10&o=MESSAGES&o=DETAILED_ACCOUNTS&o=DETAILED_LABELS&o=CURRENT_REVISION&o=CURRENT_FILES&o=CURRENT_COMMIT"
+-- >>> changeQS 10 [Project "software-factory/gerrit-haskell"] $ Just 100
+-- "q=project:software-factory/gerrit-haskell&n=10&o=MESSAGES&o=DETAILED_ACCOUNTS&o=DETAILED_LABELS&o=CURRENT_REVISION&o=CURRENT_FILES&o=CURRENT_COMMIT&start=100"
+changeQS :: Int -> [GerritQuery] -> Maybe Int -> Text
+changeQS count queries startM =
+  let base =
+        T.intercalate
+          "&"
+          [ changeString,
+            countString,
+            defaultQueryChangeOptions
+          ]
+   in base <> startString
+  where
+    changeString = "q=" <> T.intercalate "+" (map queryText queries)
+    countString = "n=" <> T.pack (show count)
+    startString = maybe mempty (\s -> "&start=" <> T.pack (show s)) startM
+
+-- | Check if a gerrit change as a label
+hasLabel :: T.Text -> Int -> GerritChange -> Bool
+hasLabel label labelValue change = case M.lookup label (labels change) of
+  Just gerritLabel ->
+    (> 0) $
+      length $
+        filter
+          (\vote -> fromMaybe 0 (value vote) == labelValue)
+          (fromMaybe [] (Gerrit.Data.Change.all gerritLabel))
+  _ -> False
+
+data GerritChangeStatus = NEW | MERGED | ABANDONED | DRAFT
+  deriving (Eq, Show, Generic, FromJSON)
+
+-- https://gerrit-review.googlesource.com/Documentation/json.html
+data GerritRevisionKind = REWORK | TRIVIAL_REBASE | MERGE_FIRST_PARENT_UPDATE | NO_CODE_CHANGE | NO_CHANGE
+  deriving (Eq, Show, Generic, FromJSON)
+
+data GerritFile = GerritFile
+  { gfStatus :: Maybe Text,
+    gfLinesInserted :: Maybe Int,
+    gfLinesDeleted :: Maybe Int,
+    gfSizeDelta :: Maybe Int,
+    gfSize :: Maybe Int
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritFile where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
+
+data GerritCommit = GerritCommit
+  { cAuthor :: GerritCommitAuthor,
+    cCommitter :: GerritCommitAuthor,
+    cSubject :: Text,
+    cMessage :: Text
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritCommit where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
+
+data GerritRevision = GerritRevision
+  { grRef :: Text,
+    grKind :: GerritRevisionKind,
+    grFiles :: M.Map Text GerritFile,
+    grCommit :: GerritCommit,
+    grUploader :: GerritAuthor
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritRevision where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
+
+data GerritDetailedLabelVote = GerritDetailedLabelVote
+  { value :: Maybe Int,
+    account_id :: Int
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritDetailedLabelVote where
+  parseJSON = genericParseJSON aesonOptions
+
+data GerritDetailedLabel = GerritDetailedLabel
+  { all :: Maybe [GerritDetailedLabelVote],
+    default_value :: Int
+  }
+  deriving (Show, Generic, FromJSON)
+
+data GerritAuthor = GerritAuthor
+  { aAccountId :: Int,
+    aName :: Text,
+    aEmail :: Maybe Text,
+    aUsername :: Maybe Text
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritAuthor where
+  parseJSON = genericParseJSON aesonOptions
+
+data GerritCommitAuthor = GerritCommitAuthor
+  { caName :: Text,
+    caEmail :: Maybe Text,
+    caDate :: GerritTime
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritCommitAuthor where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
+
+newtype GerritTime = GerritTime {unGerritTime :: UTCTime} deriving (Show)
+
+instance FromJSON GerritTime where
+  parseJSON = withText "UTCTimePlus" (parse . T.unpack)
+    where
+      format = "%F %T.000000000"
+      tryParse f s = parseTimeM False defaultTimeLocale f s
+      parse s = GerritTime <$> tryParse format s
+
+data GerritChangeMessage = GerritChangeMessage
+  { mId :: Text,
+    mAuthor :: Maybe GerritAuthor,
+    mDate :: GerritTime,
+    mMessage :: Text
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritChangeMessage where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
+
+data GerritChange = GerritChange
+  { id :: Text,
+    project :: Text,
+    branch :: Text,
+    subject :: Text,
+    status :: GerritChangeStatus,
+    mergeable :: Maybe Bool,
+    revisions :: M.Map Text (Maybe GerritRevision),
+    current_revision :: Maybe Text,
+    number :: Int,
+    labels :: M.Map Text GerritDetailedLabel,
+    messages :: [GerritChangeMessage],
+    owner :: GerritAuthor,
+    created :: GerritTime,
+    updated :: GerritTime,
+    submitted :: Maybe GerritTime,
+    submitter :: Maybe GerritAuthor,
+    insertions :: Int,
+    deletions :: Int,
+    more_changes :: Maybe Bool
+  }
+  deriving (Show, Generic)
+
+instance FromJSON GerritChange where
+  parseJSON = genericParseJSON aesonOptions
diff --git a/src/Gerrit/Data/Event.hs b/src/Gerrit/Data/Event.hs
new file mode 100644
--- /dev/null
+++ b/src/Gerrit/Data/Event.hs
@@ -0,0 +1,509 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Gerrit stream event data type
+module Gerrit.Data.Event
+  ( -- * Main event data types
+    EventType (..),
+    Event (..),
+
+    -- * Common data types
+    User (..),
+    PatchSet (..),
+    Change (..),
+    Ref (..),
+
+    -- * Individual event data types
+    AssigneeChanged (..),
+    ChangeAbandoned (..),
+    ChangeDeleted (..),
+    ChangeMerged (..),
+    ChangeRestored (..),
+    CommentAdded (..),
+    HashtagsChanged (..),
+    ProjectCreated (..),
+    PatchsetCreated (..),
+    RefUpdated (..),
+    ReviewerAdded (..),
+    ReviewerDeleted (..),
+    TopicChanged (..),
+    WorkInProgressStateChanged (..),
+    PrivateStateChanged (..),
+    VoteDeleted (..),
+
+    -- * Convenient functions
+    getChange,
+    getPatchSet,
+    getUser,
+    getCreatedOn,
+    getEventType,
+    eventName,
+  )
+where
+
+import Control.Monad (mzero)
+import Data.Aeson
+  ( FromJSON (..),
+    Options (fieldLabelModifier),
+    Value (Object, String),
+    defaultOptions,
+    genericParseJSON,
+    (.:),
+  )
+import Data.Char (toLower)
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics (Generic)
+import GHC.Int (Int64)
+
+aesonOption :: Text -> Options
+aesonOption prefix = defaultOptions {fieldLabelModifier = recordToJson}
+  where
+    recordToJson = updateCase . drop (Text.length prefix)
+    updateCase [] = []
+    updateCase (x : xs) = toLower x : xs
+
+-------------------------------------------------------------------------------
+-- Shared types
+-------------------------------------------------------------------------------
+data User = User
+  { userName :: Maybe Text,
+    userEmail :: Maybe Text,
+    userUsername :: Maybe Text
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON User where
+  parseJSON = genericParseJSON $ aesonOption "user"
+
+data PatchSet = PatchSet
+  { patchSetNumber :: Int,
+    patchSetRef :: Text,
+    patchSetUploader :: User,
+    patchsetAuthor :: User
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON PatchSet where
+  parseJSON = genericParseJSON $ aesonOption "patchSet"
+
+data Change = Change
+  { changeProject :: Text,
+    changeBranch :: Text,
+    changeSubject :: Text,
+    changeNumber :: Int,
+    changeUrl :: Text,
+    changeOwner :: User
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON Change where
+  parseJSON = genericParseJSON $ aesonOption "change"
+
+data Ref = Ref
+  { refOldRev :: Text,
+    refNewRev :: Text,
+    refRefName :: Text,
+    refProject :: Text
+  }
+  deriving (Show, Eq, Generic)
+
+instance FromJSON Ref where
+  parseJSON = genericParseJSON $ aesonOption "ref"
+
+-------------------------------------------------------------------------------
+-- Individual Event data types
+-------------------------------------------------------------------------------
+data AssigneeChanged = AssigneeChanged
+  { assigneeChangedChange :: Change,
+    assigneeChangedChanger :: User,
+    assigneeChangedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data ChangeAbandoned = ChangeAbandoned
+  { changeAbandonedProject :: Text,
+    changeAbandonedAbandoner :: User,
+    changeAbandonedChange :: Change,
+    changeAbandonedPatchSet :: PatchSet,
+    changeAbandonedReason :: Maybe Text,
+    changeAbandonedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data ChangeDeleted = ChangeDeleted
+  { changeDeletedChange :: Change,
+    changeDeletedDeleter :: User
+  }
+  deriving (Show, Eq, Generic)
+
+data ChangeMerged = ChangeMerged
+  { changeMergedProject :: Text,
+    changeMergedSubmitter :: User,
+    changeMergedChange :: Change,
+    changeMergedPatchSet :: PatchSet,
+    changeMergedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data ChangeRestored = ChangeRestored
+  { changeRestoredChange :: Change,
+    changeRestoredPatchSet :: PatchSet,
+    changeRestoredRestorer :: User,
+    changeRestoredReason :: Maybe Text,
+    changeRestoredEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data CommentAdded = CommentAdded
+  { commentAddedChange :: Change,
+    commentAddedPatchSet :: PatchSet,
+    commentAddedAuthor :: User,
+    commentAddedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data HashtagsChanged = HashtagsChanged
+  { hashtagsChangedChange :: Change,
+    hashtagsChangedEditor :: User,
+    hashtagsChangedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data ProjectCreated = ProjectCreated
+  { projectCreatedProjectName :: Text,
+    projectCreatedHeadName :: Text,
+    projectCreatedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data PatchsetCreated = PatchsetCreated
+  { patchsetCreatedUploader :: User,
+    patchsetCreatedChange :: Change,
+    patchsetCreatedPatchSet :: PatchSet,
+    patchsetCreatedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data RefUpdated = RefUpdated
+  { refUpdatedSubmitter :: User,
+    refUpdatedRefUpdate :: Ref,
+    refUpdatedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data ReviewerAdded = ReviewerAdded
+  { reviewerAddedChange :: Change,
+    reviewerAddedPatchSet :: PatchSet,
+    reviewerAddedAdder :: User,
+    reviewerAddedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data ReviewerDeleted = ReviewerDeleted
+  { reviewerDeletedChange :: Change,
+    reviewerDeletedPatchSet :: PatchSet,
+    reviewerDeletedRemover :: User,
+    reviewerDeletedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data TopicChanged = TopicChanged
+  { topicChangedChange :: Change,
+    topicChangedChanger :: User,
+    topicChangedOldTopic :: Text,
+    topicChangedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data WorkInProgressStateChanged = WorkInProgressStateChanged
+  { workInProgressStateChangedChange :: Change,
+    workInProgressStateChangedPatchSet :: PatchSet,
+    workInProgressStateChangedChanger :: User,
+    workInProgressStateChangedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data PrivateStateChanged = PrivateStateChanged
+  { privateStateChangedChange :: Change,
+    privateStateChangedPatchSet :: PatchSet,
+    privateStateChangedChanger :: User,
+    privateStateChangedChangedEventCreatedOn :: Int64
+  }
+  deriving (Show, Eq, Generic)
+
+data VoteDeleted = VoteDeleted
+  { voteDeletedChange :: Change,
+    voteDeletedPatchSet :: PatchSet,
+    voteDeletedRemover :: User
+  }
+  deriving (Show, Eq, Generic)
+
+-------------------------------------------------------------------------------
+-- Events FromJSON instances
+-------------------------------------------------------------------------------
+instance FromJSON AssigneeChanged where
+  parseJSON = genericParseJSON $ aesonOption "assigneeChanged"
+
+instance FromJSON ChangeAbandoned where
+  parseJSON = genericParseJSON $ aesonOption "changeAbandoned"
+
+instance FromJSON ChangeDeleted where
+  parseJSON = genericParseJSON $ aesonOption "changeDeleted"
+
+instance FromJSON ChangeMerged where
+  parseJSON = genericParseJSON $ aesonOption "changeMerged"
+
+instance FromJSON ChangeRestored where
+  parseJSON = genericParseJSON $ aesonOption "changeRestored"
+
+instance FromJSON CommentAdded where
+  parseJSON = genericParseJSON $ aesonOption "commentAdded"
+
+instance FromJSON HashtagsChanged where
+  parseJSON = genericParseJSON $ aesonOption "hashtagsChanged"
+
+instance FromJSON ProjectCreated where
+  parseJSON = genericParseJSON $ aesonOption "projectCreated"
+
+instance FromJSON PatchsetCreated where
+  parseJSON = genericParseJSON $ aesonOption "patchsetCreated"
+
+instance FromJSON RefUpdated where
+  parseJSON = genericParseJSON $ aesonOption "refUpdated"
+
+instance FromJSON ReviewerAdded where
+  parseJSON = genericParseJSON $ aesonOption "reviewerAdded"
+
+instance FromJSON ReviewerDeleted where
+  parseJSON = genericParseJSON $ aesonOption "reviewerDeleted"
+
+instance FromJSON TopicChanged where
+  parseJSON = genericParseJSON $ aesonOption "topicChanged"
+
+instance FromJSON WorkInProgressStateChanged where
+  parseJSON = genericParseJSON $ aesonOption "workInProgressStateChanged"
+
+instance FromJSON PrivateStateChanged where
+  parseJSON = genericParseJSON $ aesonOption "privateStateChanged"
+
+instance FromJSON VoteDeleted where
+  parseJSON = genericParseJSON $ aesonOption "voteDeleted"
+
+-- | The event "type" data type
+data EventType
+  = AssigneeChangedEvent
+  | ChangeAbandonedEvent
+  | ChangeDeletedEvent
+  | ChangeMergedEvent
+  | ChangeRestoredEvent
+  | CommentAddedEvent
+  | DroppedOutputEvent
+  | HashtagsChangedEvent
+  | ProjectCreatedEvent
+  | PatchsetCreatedEvent
+  | RefUpdatedEvent
+  | ReviewerAddedEvent
+  | ReviewerDeletedEvent
+  | TopicChangedEvent
+  | WorkInProgressStateChangedEvent
+  | PrivateStateChangedEvent
+  | VoteDeletedEvent
+  deriving (Show, Eq)
+
+instance FromJSON EventType where
+  parseJSON (String v) = case v of
+    "assignee-changed" -> pure AssigneeChangedEvent
+    "change-abandoned" -> pure ChangeAbandonedEvent
+    "change-deleted" -> pure ChangeDeletedEvent
+    "change-merged" -> pure ChangeMergedEvent
+    "change-restored" -> pure ChangeRestoredEvent
+    "comment-added" -> pure CommentAddedEvent
+    "dropped-output" -> pure DroppedOutputEvent
+    "hashtags-changed" -> pure HashtagsChangedEvent
+    "project-created" -> pure ProjectCreatedEvent
+    "patchset-created" -> pure PatchsetCreatedEvent
+    "ref-updated" -> pure RefUpdatedEvent
+    "reviewer-added" -> pure ReviewerAddedEvent
+    "reviewer-deleted" -> pure ReviewerDeletedEvent
+    "topic-changed" -> pure TopicChangedEvent
+    "wip-state-changed" -> pure WorkInProgressStateChangedEvent
+    "private-state-changed" -> pure PrivateStateChangedEvent
+    "vote-deleted" -> pure VoteDeletedEvent
+    _ -> mzero
+  parseJSON _ = mzero
+
+-- | The main event union data type
+data Event
+  = EventAssigneeChanged AssigneeChanged
+  | EventChangeAbandoned ChangeAbandoned
+  | EventChangeDeleted ChangeDeleted
+  | EventChangeMerged ChangeMerged
+  | EventChangeRestored ChangeRestored
+  | EventCommentAdded CommentAdded
+  | EventDroppedOutput
+  | EventHashtagsChanged HashtagsChanged
+  | EventProjectCreated ProjectCreated
+  | EventPatchsetCreated PatchsetCreated
+  | EventRefUpdated RefUpdated
+  | EventReviewerAdded ReviewerAdded
+  | EventReviewerDeleted ReviewerDeleted
+  | EventTopicChanged TopicChanged
+  | EventWorkInProgressStateChanged WorkInProgressStateChanged
+  | EventPrivateStateChanged PrivateStateChanged
+  | EventVoteDeleted VoteDeleted
+  deriving (Show, Eq)
+
+instance FromJSON Event where
+  parseJSON o@(Object v) = do
+    eType <- v .: "type"
+    case eType of
+      AssigneeChangedEvent -> EventAssigneeChanged <$> parseJSON o
+      ChangeAbandonedEvent -> EventChangeAbandoned <$> parseJSON o
+      ChangeDeletedEvent -> EventChangeDeleted <$> parseJSON o
+      ChangeMergedEvent -> EventChangeMerged <$> parseJSON o
+      ChangeRestoredEvent -> EventChangeRestored <$> parseJSON o
+      CommentAddedEvent -> EventCommentAdded <$> parseJSON o
+      DroppedOutputEvent -> pure EventDroppedOutput
+      HashtagsChangedEvent -> EventHashtagsChanged <$> parseJSON o
+      ProjectCreatedEvent -> EventProjectCreated <$> parseJSON o
+      PatchsetCreatedEvent -> EventPatchsetCreated <$> parseJSON o
+      RefUpdatedEvent -> EventRefUpdated <$> parseJSON o
+      ReviewerAddedEvent -> EventReviewerAdded <$> parseJSON o
+      ReviewerDeletedEvent -> EventReviewerDeleted <$> parseJSON o
+      TopicChangedEvent -> EventTopicChanged <$> parseJSON o
+      WorkInProgressStateChangedEvent -> EventWorkInProgressStateChanged <$> parseJSON o
+      PrivateStateChangedEvent -> EventPrivateStateChanged <$> parseJSON o
+      VoteDeletedEvent -> EventVoteDeleted <$> parseJSON o
+  parseJSON _ = mzero
+
+-------------------------------------------------------------------------------
+-- Utilities function
+-------------------------------------------------------------------------------
+
+-- | Get the associated change
+getChange :: Event -> Maybe Change
+getChange event = case event of
+  EventAssigneeChanged AssigneeChanged {..} -> Just assigneeChangedChange
+  EventChangeAbandoned ChangeAbandoned {..} -> Just changeAbandonedChange
+  EventChangeDeleted ChangeDeleted {..} -> Just changeDeletedChange
+  EventChangeMerged ChangeMerged {..} -> Just changeMergedChange
+  EventChangeRestored ChangeRestored {..} -> Just changeRestoredChange
+  EventCommentAdded CommentAdded {..} -> Just commentAddedChange
+  EventDroppedOutput -> Nothing
+  EventHashtagsChanged HashtagsChanged {..} -> Just hashtagsChangedChange
+  EventProjectCreated _ -> Nothing
+  EventPatchsetCreated PatchsetCreated {..} -> Just patchsetCreatedChange
+  EventRefUpdated _ -> Nothing
+  EventReviewerAdded ReviewerAdded {..} -> Just reviewerAddedChange
+  EventReviewerDeleted ReviewerDeleted {..} -> Just reviewerDeletedChange
+  EventTopicChanged TopicChanged {..} -> Just topicChangedChange
+  EventWorkInProgressStateChanged WorkInProgressStateChanged {..} -> Just workInProgressStateChangedChange
+  EventPrivateStateChanged PrivateStateChanged {..} -> Just privateStateChangedChange
+  EventVoteDeleted VoteDeleted {..} -> Just voteDeletedChange
+
+-- | Get the associated patchset
+getPatchSet :: Event -> Maybe PatchSet
+getPatchSet event = case event of
+  EventAssigneeChanged _ -> Nothing
+  EventChangeAbandoned ChangeAbandoned {..} -> Just changeAbandonedPatchSet
+  EventChangeDeleted _ -> Nothing
+  EventChangeMerged ChangeMerged {..} -> Just changeMergedPatchSet
+  EventChangeRestored ChangeRestored {..} -> Just changeRestoredPatchSet
+  EventCommentAdded CommentAdded {..} -> Just commentAddedPatchSet
+  EventDroppedOutput -> Nothing
+  EventHashtagsChanged _ -> Nothing
+  EventProjectCreated _ -> Nothing
+  EventPatchsetCreated PatchsetCreated {..} -> Just patchsetCreatedPatchSet
+  EventRefUpdated _ -> Nothing
+  EventReviewerAdded ReviewerAdded {..} -> Just reviewerAddedPatchSet
+  EventReviewerDeleted ReviewerDeleted {..} -> Just reviewerDeletedPatchSet
+  EventTopicChanged _ -> Nothing
+  EventWorkInProgressStateChanged WorkInProgressStateChanged {..} -> Just workInProgressStateChangedPatchSet
+  EventPrivateStateChanged PrivateStateChanged {..} -> Just privateStateChangedPatchSet
+  EventVoteDeleted VoteDeleted {..} -> Just voteDeletedPatchSet
+
+-- | Get the associated author
+getUser :: Event -> Maybe User
+getUser event = case event of
+  EventAssigneeChanged AssigneeChanged {..} -> Just assigneeChangedChanger
+  EventChangeAbandoned ChangeAbandoned {..} -> Just changeAbandonedAbandoner
+  EventChangeDeleted ChangeDeleted {..} -> Just changeDeletedDeleter
+  EventChangeMerged ChangeMerged {..} -> Just changeMergedSubmitter
+  EventChangeRestored ChangeRestored {..} -> Just changeRestoredRestorer
+  EventCommentAdded CommentAdded {..} -> Just commentAddedAuthor
+  EventDroppedOutput -> Nothing
+  EventHashtagsChanged HashtagsChanged {..} -> Just hashtagsChangedEditor
+  EventProjectCreated _ -> Nothing
+  EventPatchsetCreated PatchsetCreated {..} -> Just patchsetCreatedUploader
+  EventRefUpdated _ -> Nothing
+  EventReviewerAdded ReviewerAdded {..} -> Just reviewerAddedAdder
+  EventReviewerDeleted ReviewerDeleted {..} -> Just reviewerDeletedRemover
+  EventTopicChanged TopicChanged {..} -> Just topicChangedChanger
+  EventWorkInProgressStateChanged WorkInProgressStateChanged {..} -> Just workInProgressStateChangedChanger
+  EventPrivateStateChanged PrivateStateChanged {..} -> Just privateStateChangedChanger
+  EventVoteDeleted VoteDeleted {..} -> Just voteDeletedRemover
+
+-- | Get the creation timestamp
+getCreatedOn :: Event -> Maybe Int64
+getCreatedOn event = case event of
+  EventAssigneeChanged AssigneeChanged {..} -> Just assigneeChangedEventCreatedOn
+  EventChangeAbandoned ChangeAbandoned {..} -> Just changeAbandonedEventCreatedOn
+  EventChangeDeleted _ -> Nothing
+  EventChangeMerged ChangeMerged {..} -> Just changeMergedEventCreatedOn
+  EventChangeRestored ChangeRestored {..} -> Just changeRestoredEventCreatedOn
+  EventCommentAdded CommentAdded {..} -> Just commentAddedEventCreatedOn
+  EventDroppedOutput -> Nothing
+  EventHashtagsChanged HashtagsChanged {..} -> Just hashtagsChangedEventCreatedOn
+  EventProjectCreated ProjectCreated {..} -> Just projectCreatedEventCreatedOn
+  EventPatchsetCreated PatchsetCreated {..} -> Just patchsetCreatedEventCreatedOn
+  EventRefUpdated RefUpdated {..} -> Just refUpdatedEventCreatedOn
+  EventReviewerAdded ReviewerAdded {..} -> Just reviewerAddedEventCreatedOn
+  EventReviewerDeleted ReviewerDeleted {..} -> Just reviewerDeletedEventCreatedOn
+  EventTopicChanged TopicChanged {..} -> Just topicChangedEventCreatedOn
+  EventWorkInProgressStateChanged WorkInProgressStateChanged {..} -> Just workInProgressStateChangedEventCreatedOn
+  EventPrivateStateChanged PrivateStateChanged {..} -> Just privateStateChangedChangedEventCreatedOn
+  EventVoteDeleted _ -> Nothing
+
+-- | Get the 'EventType' back from an 'Event'
+getEventType :: Event -> EventType
+getEventType event = case event of
+  EventAssigneeChanged _ -> AssigneeChangedEvent
+  EventChangeAbandoned _ -> ChangeAbandonedEvent
+  EventChangeDeleted _ -> ChangeDeletedEvent
+  EventChangeMerged _ -> ChangeMergedEvent
+  EventChangeRestored _ -> ChangeRestoredEvent
+  EventCommentAdded _ -> CommentAddedEvent
+  EventDroppedOutput -> DroppedOutputEvent
+  EventHashtagsChanged _ -> HashtagsChangedEvent
+  EventProjectCreated _ -> ProjectCreatedEvent
+  EventPatchsetCreated _ -> PatchsetCreatedEvent
+  EventRefUpdated _ -> RefUpdatedEvent
+  EventReviewerAdded _ -> ReviewerAddedEvent
+  EventReviewerDeleted _ -> ReviewerDeletedEvent
+  EventTopicChanged _ -> TopicChangedEvent
+  EventWorkInProgressStateChanged _ -> WorkInProgressStateChangedEvent
+  EventPrivateStateChanged _ -> PrivateStateChangedEvent
+  EventVoteDeleted _ -> VoteDeletedEvent
+
+-- | Get the text representation suitable for event subscription.
+eventName :: EventType -> Text
+eventName eventType = case eventType of
+  AssigneeChangedEvent -> "assignee-changed"
+  ChangeAbandonedEvent -> "change-abandoned"
+  ChangeDeletedEvent -> "change-deleted"
+  ChangeMergedEvent -> "change-merged"
+  ChangeRestoredEvent -> "change-restored"
+  CommentAddedEvent -> "comment-added"
+  DroppedOutputEvent -> "dropped-output"
+  HashtagsChangedEvent -> "hashtags-changed"
+  ProjectCreatedEvent -> "project-created"
+  PatchsetCreatedEvent -> "patchset-created"
+  RefUpdatedEvent -> "ref-updated"
+  ReviewerAddedEvent -> "reviewer-added"
+  ReviewerDeletedEvent -> "reviewer-deleted"
+  TopicChangedEvent -> "topic-changed"
+  WorkInProgressStateChangedEvent -> "wip-state-changed"
+  PrivateStateChangedEvent -> "private-state-changed"
+  VoteDeletedEvent -> "vote-deleted"
diff --git a/src/Gerrit/Data/Project.hs b/src/Gerrit/Data/Project.hs
new file mode 100644
--- /dev/null
+++ b/src/Gerrit/Data/Project.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Gerrit.Data.Project
+  ( GerritProjectInfo (..),
+    GerritProjectQuery (..),
+    projectQS,
+    GerritProjectsMessage,
+  )
+where
+
+import Data.Aeson
+import Data.Aeson.Casing (aesonPrefix, snakeCase)
+import Data.Map
+import Data.Text (Text, intercalate, pack)
+import GHC.Generics (Generic)
+
+data GerritProjectQuery = Regexp Text | Prefix Text
+
+queryText :: GerritProjectQuery -> Text
+queryText (Regexp re) = "r=" <> re
+queryText (Prefix prefix) = "p=" <> prefix
+
+-- >>> projectQS 10 (Regexp "test/.*|rpms/.*") Nothing
+-- "r:test/.*|rpms/.*&n=10"
+projectQS :: Int -> GerritProjectQuery -> Maybe Int -> Text
+projectQS count query startM =
+  intercalate "&" [qtString, countString] <> startString
+  where
+    qtString = queryText query
+    countString = "n=" <> pack (show count)
+    startString = maybe mempty (\s -> "&S=" <> pack (show s)) startM
+
+newtype GerritProjectInfo = GerritProjectInfo
+  {gerritprojectinfoId :: Text}
+  deriving (Eq, Show, Generic)
+
+type GerritProjectsMessage = Map Text GerritProjectInfo
+
+instance FromJSON GerritProjectInfo where
+  parseJSON = genericParseJSON $ aesonPrefix snakeCase
diff --git a/src/Gerrit/Data/Review.hs b/src/Gerrit/Data/Review.hs
new file mode 100644
--- /dev/null
+++ b/src/Gerrit/Data/Review.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | This module contains the gerrit data type
+module Gerrit.Data.Review
+  ( GerritVersion (..),
+    GerritLabel (..),
+    GerritReviewAccount (..),
+    GerritLabelVote (..),
+    ReviewResult (..),
+    ReviewInput (..),
+  )
+where
+
+import Data.Aeson
+import Data.Char (isUpper, toLower)
+import qualified Data.Map as M
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+newtype GerritVersion = GerritVersion Text
+  deriving (Show, Generic)
+  deriving anyclass (FromJSON)
+
+data GerritLabelVote = REJECTED | APPROVED | DISLIKED | RECOMMENDED
+  deriving (Eq, Show, Ord, Generic)
+
+-- We use a custom parseJSON to decode Label Vote as lowercase
+instance FromJSON GerritLabelVote where
+  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = map toLower}
+
+instance FromJSONKey GerritLabelVote where
+  fromJSONKey = genericFromJSONKey defaultJSONKeyOptions {keyModifier = map toLower}
+
+-- | Modify record attribute to match json schema
+-- Remove the prefix and use snakecase
+customParseJSON :: String -> Options
+customParseJSON prefix = defaultOptions {fieldLabelModifier = recordToJson}
+  where
+    recordToJson = updateCase . drop (length prefix)
+    updateCase [] = []
+    updateCase (x : xs) = toLower x : updateCase' xs
+    updateCase' [] = []
+    updateCase' (x : xs)
+      | isUpper x = '_' : toLower x : updateCase' xs
+      | otherwise = x : updateCase' xs
+
+-- https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html
+data ReviewResult = ReviewResult
+  { rrLabels :: Maybe (M.Map Text Int),
+    rrReady :: Maybe Int
+  }
+  deriving (Eq, Show, Ord, Generic)
+
+instance FromJSON ReviewResult where
+  parseJSON = genericParseJSON $ customParseJSON "rr"
+
+instance ToJSON ReviewResult where
+  toJSON = genericToJSON $ customParseJSON "rr"
+
+data ReviewInput = ReviewInput
+  { riMessage :: Maybe Text,
+    riLabels :: Maybe (M.Map Text Int)
+  }
+  deriving (Eq, Show, Ord, Generic)
+
+instance FromJSON ReviewInput where
+  parseJSON = genericParseJSON $ customParseJSON "ri"
+
+instance ToJSON ReviewInput where
+  toJSON = genericToJSON $ (customParseJSON "ri") {omitNothingFields = True}
+
+newtype GerritReviewAccount = GerritReviewAccount
+  { unused_account_id :: Int
+  }
+  deriving (Show, Generic)
+
+-- We use a cusom parseJSON to decode `_account_id` as `account_id`
+instance FromJSON GerritReviewAccount where
+  parseJSON = genericParseJSON defaultOptions
+
+newtype GerritLabel
+  = GerritLabel (M.Map GerritLabelVote GerritReviewAccount)
+  deriving (Show, Generic)
+  deriving anyclass (FromJSON)
diff --git a/src/Gerrit/Event.hs b/src/Gerrit/Event.hs
deleted file mode 100644
--- a/src/Gerrit/Event.hs
+++ /dev/null
@@ -1,448 +0,0 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-
--- | Gerrit stream event data type
-module Gerrit.Event
-  ( -- * Main event data types
-    EventType (..),
-    Event (..),
-
-    -- * Common data types
-    User (..),
-    PatchSet (..),
-    Change (..),
-    Ref (..),
-
-    -- * Individual event data types
-    AssigneeChanged (..),
-    ChangeAbandoned (..),
-    ChangeDeleted (..),
-    ChangeMerged (..),
-    ChangeRestored (..),
-    CommentAdded (..),
-    HashtagsChanged (..),
-    ProjectCreated (..),
-    PatchsetCreated (..),
-    RefUpdated (..),
-    ReviewerAdded (..),
-    ReviewerDeleted (..),
-    TopicChanged (..),
-    WorkInProgressStateChanged (..),
-    PrivateStateChanged (..),
-    VoteDeleted (..),
-
-    -- * Convenient functions
-    getChange,
-    getUser,
-    getEventType,
-    eventName,
-  )
-where
-
-import Control.Monad (mzero)
-import Data.Aeson
-  ( FromJSON (..),
-    Options (fieldLabelModifier),
-    Value (Object, String),
-    defaultOptions,
-    genericParseJSON,
-    (.:),
-  )
-import Data.Char (toLower)
-import Data.Text (Text)
-import qualified Data.Text as Text
-import GHC.Generics (Generic)
-
-aesonOption :: Text -> Options
-aesonOption prefix = defaultOptions {fieldLabelModifier = recordToJson}
-  where
-    recordToJson = updateCase . drop (Text.length prefix)
-    updateCase [] = []
-    updateCase (x : xs) = toLower x : xs
-
--------------------------------------------------------------------------------
--- Shared types
--------------------------------------------------------------------------------
-data User = User
-  { userName :: Maybe Text,
-    userEmail :: Maybe Text,
-    userUsername :: Maybe Text
-  }
-  deriving (Show, Eq, Generic)
-
-instance FromJSON User where
-  parseJSON = genericParseJSON $ aesonOption "user"
-
-data PatchSet = PatchSet
-  { patchSetNumber :: Int,
-    patchSetRef :: Text,
-    patchSetUploader :: User,
-    patchsetAuthor :: User
-  }
-  deriving (Show, Eq, Generic)
-
-instance FromJSON PatchSet where
-  parseJSON = genericParseJSON $ aesonOption "patchSet"
-
-data Change = Change
-  { changeProject :: Text,
-    changeBranch :: Text,
-    changeSubject :: Text,
-    changeUrl :: Text,
-    changeOwner :: User
-  }
-  deriving (Show, Eq, Generic)
-
-instance FromJSON Change where
-  parseJSON = genericParseJSON $ aesonOption "change"
-
-data Ref = Ref
-  { refOldRev :: Text,
-    refNewRev :: Text,
-    refRefName :: Text,
-    refProject :: Text
-  }
-  deriving (Show, Eq, Generic)
-
-instance FromJSON Ref where
-  parseJSON = genericParseJSON $ aesonOption "ref"
-
--------------------------------------------------------------------------------
--- Individual Event data types
--------------------------------------------------------------------------------
-data AssigneeChanged = AssigneeChanged
-  { assigneeChangedChange :: Change,
-    assigneeChangedChanger :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data ChangeAbandoned = ChangeAbandoned
-  { changeAbandonedProject :: Text,
-    changeAbandonedAbandoner :: User,
-    changeAbandonedChange :: Change,
-    changeAbandonedPatchSet :: PatchSet
-  }
-  deriving (Show, Eq, Generic)
-
-data ChangeDeleted = ChangeDeleted
-  { changeDeletedChange :: Change,
-    changeDeletedDeleter :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data ChangeMerged = ChangeMerged
-  { changeMergedProject :: Text,
-    changeMergedSubmitter :: User,
-    changeMergedChange :: Change,
-    changeMergedPatchSet :: PatchSet
-  }
-  deriving (Show, Eq, Generic)
-
-data ChangeRestored = ChangeRestored
-  { changeRestoredChange :: Change,
-    changeRestoredPatchSet :: PatchSet,
-    changeRestoredRestorer :: User,
-    changeRestoredReason :: Text
-  }
-  deriving (Show, Eq, Generic)
-
-data CommentAdded = CommentAdded
-  { commentAddedChange :: Change,
-    commentAddedPatchSet :: PatchSet,
-    commentAddedAuthor :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data HashtagsChanged = HashtagsChanged
-  { hashtagsChangedChange :: Change,
-    hashtagsChangedEditor :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data ProjectCreated = ProjectCreated
-  { projectCreatedProjectName :: Text,
-    projectCreatedHeadName :: Text
-  }
-  deriving (Show, Eq, Generic)
-
-data PatchsetCreated = PatchsetCreated
-  { patchsetCreatedUploader :: User,
-    patchsetCreatedChange :: Change,
-    patchsetCreatedPatchSet :: PatchSet
-  }
-  deriving (Show, Eq, Generic)
-
-data RefUpdated = RefUpdated
-  { refUpdatedSubmitter :: User,
-    refUpdatedRefUpdate :: Ref
-  }
-  deriving (Show, Eq, Generic)
-
-data ReviewerAdded = ReviewerAdded
-  { reviewerAddedChange :: Change,
-    reviewerAddedPatchSet :: PatchSet,
-    reviewerAddedAdder :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data ReviewerDeleted = ReviewerDeleted
-  { reviewerDeletedChange :: Change,
-    reviewerDeletedPatchSet :: PatchSet,
-    reviewerDeletedRemover :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data TopicChanged = TopicChanged
-  { topicChangedChange :: Change,
-    topicChangedChanger :: User,
-    topicChangedOldTopic :: Text
-  }
-  deriving (Show, Eq, Generic)
-
-data WorkInProgressStateChanged = WorkInProgressStateChanged
-  { workInProgressStateChangedChange :: Change,
-    workInProgressStateChangedPatchSet :: PatchSet,
-    workInProgressStateChangedChanger :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data PrivateStateChanged = PrivateStateChanged
-  { privateStateChangedChange :: Change,
-    privateStateChangedPatchSet :: PatchSet,
-    privateStateChangedChanger :: User
-  }
-  deriving (Show, Eq, Generic)
-
-data VoteDeleted = VoteDeleted
-  { voteDeletedChange :: Change,
-    voteDeletedPatchSet :: PatchSet,
-    voteDeletedRemover :: User
-  }
-  deriving (Show, Eq, Generic)
-
--------------------------------------------------------------------------------
--- Events FromJSON instances
--------------------------------------------------------------------------------
-instance FromJSON AssigneeChanged where
-  parseJSON = genericParseJSON $ aesonOption "assigneeChanged"
-
-instance FromJSON ChangeAbandoned where
-  parseJSON = genericParseJSON $ aesonOption "changeAbandoned"
-
-instance FromJSON ChangeDeleted where
-  parseJSON = genericParseJSON $ aesonOption "changeDeleted"
-
-instance FromJSON ChangeMerged where
-  parseJSON = genericParseJSON $ aesonOption "changeMerged"
-
-instance FromJSON ChangeRestored where
-  parseJSON = genericParseJSON $ aesonOption "changeRestored"
-
-instance FromJSON CommentAdded where
-  parseJSON = genericParseJSON $ aesonOption "commentAdded"
-
-instance FromJSON HashtagsChanged where
-  parseJSON = genericParseJSON $ aesonOption "hashtagsChanged"
-
-instance FromJSON ProjectCreated where
-  parseJSON = genericParseJSON $ aesonOption "projectCreated"
-
-instance FromJSON PatchsetCreated where
-  parseJSON = genericParseJSON $ aesonOption "patchsetCreated"
-
-instance FromJSON RefUpdated where
-  parseJSON = genericParseJSON $ aesonOption "refUpdated"
-
-instance FromJSON ReviewerAdded where
-  parseJSON = genericParseJSON $ aesonOption "reviewerAdded"
-
-instance FromJSON ReviewerDeleted where
-  parseJSON = genericParseJSON $ aesonOption "reviewerDeleted"
-
-instance FromJSON TopicChanged where
-  parseJSON = genericParseJSON $ aesonOption "topicChanged"
-
-instance FromJSON WorkInProgressStateChanged where
-  parseJSON = genericParseJSON $ aesonOption "workInProgressStateChanged"
-
-instance FromJSON PrivateStateChanged where
-  parseJSON = genericParseJSON $ aesonOption "privateStateChanged"
-
-instance FromJSON VoteDeleted where
-  parseJSON = genericParseJSON $ aesonOption "voteDeleted"
-
--- | The event "type" data type
-data EventType
-  = AssigneeChangedEvent
-  | ChangeAbandonedEvent
-  | ChangeDeletedEvent
-  | ChangeMergedEvent
-  | ChangeRestoredEvent
-  | CommentAddedEvent
-  | DroppedOutputEvent
-  | HashtagsChangedEvent
-  | ProjectCreatedEvent
-  | PatchsetCreatedEvent
-  | RefUpdatedEvent
-  | ReviewerAddedEvent
-  | ReviewerDeletedEvent
-  | TopicChangedEvent
-  | WorkInProgressStateChangedEvent
-  | PrivateStateChangedEvent
-  | VoteDeletedEvent
-  deriving (Show, Eq)
-
-instance FromJSON EventType where
-  parseJSON (String v) = case v of
-    "assignee-changed" -> pure AssigneeChangedEvent
-    "change-abandoned" -> pure ChangeAbandonedEvent
-    "change-deleted" -> pure ChangeDeletedEvent
-    "change-merged" -> pure ChangeMergedEvent
-    "change-restored" -> pure ChangeRestoredEvent
-    "comment-added" -> pure CommentAddedEvent
-    "dropped-output" -> pure DroppedOutputEvent
-    "hashtags-changed" -> pure HashtagsChangedEvent
-    "project-created" -> pure ProjectCreatedEvent
-    "patchset-created" -> pure PatchsetCreatedEvent
-    "ref-updated" -> pure RefUpdatedEvent
-    "reviewer-added" -> pure ReviewerAddedEvent
-    "reviewer-deleted" -> pure ReviewerDeletedEvent
-    "topic-changed" -> pure TopicChangedEvent
-    "wip-state-changed" -> pure WorkInProgressStateChangedEvent
-    "private-state-changed" -> pure PrivateStateChangedEvent
-    "vote-deleted" -> pure VoteDeletedEvent
-    _ -> mzero
-  parseJSON _ = mzero
-
--- | The main event union data type
-data Event
-  = EventAssigneeChanged AssigneeChanged
-  | EventChangeAbandoned ChangeAbandoned
-  | EventChangeDeleted ChangeDeleted
-  | EventChangeMerged ChangeMerged
-  | EventChangeRestored ChangeRestored
-  | EventCommentAdded CommentAdded
-  | EventDroppedOutput
-  | EventHashtagsChanged HashtagsChanged
-  | EventProjectCreated ProjectCreated
-  | EventPatchsetCreated PatchsetCreated
-  | EventRefUpdated RefUpdated
-  | EventReviewerAdded ReviewerAdded
-  | EventReviewerDeleted ReviewerDeleted
-  | EventTopicChanged TopicChanged
-  | EventWorkInProgressStateChanged WorkInProgressStateChanged
-  | EventPrivateStateChanged PrivateStateChanged
-  | EventVoteDeleted VoteDeleted
-  deriving (Show, Eq)
-
-instance FromJSON Event where
-  parseJSON o@(Object v) = do
-    eType <- v .: "type"
-    case eType of
-      AssigneeChangedEvent -> EventAssigneeChanged <$> parseJSON o
-      ChangeAbandonedEvent -> EventChangeAbandoned <$> parseJSON o
-      ChangeDeletedEvent -> EventChangeDeleted <$> parseJSON o
-      ChangeMergedEvent -> EventChangeMerged <$> parseJSON o
-      ChangeRestoredEvent -> EventChangeRestored <$> parseJSON o
-      CommentAddedEvent -> EventCommentAdded <$> parseJSON o
-      DroppedOutputEvent -> pure EventDroppedOutput
-      HashtagsChangedEvent -> EventHashtagsChanged <$> parseJSON o
-      ProjectCreatedEvent -> EventProjectCreated <$> parseJSON o
-      PatchsetCreatedEvent -> EventPatchsetCreated <$> parseJSON o
-      RefUpdatedEvent -> EventRefUpdated <$> parseJSON o
-      ReviewerAddedEvent -> EventReviewerAdded <$> parseJSON o
-      ReviewerDeletedEvent -> EventReviewerDeleted <$> parseJSON o
-      TopicChangedEvent -> EventTopicChanged <$> parseJSON o
-      WorkInProgressStateChangedEvent -> EventWorkInProgressStateChanged <$> parseJSON o
-      PrivateStateChangedEvent -> EventPrivateStateChanged <$> parseJSON o
-      VoteDeletedEvent -> EventVoteDeleted <$> parseJSON o
-  parseJSON _ = mzero
-
--------------------------------------------------------------------------------
--- Utilities function
--------------------------------------------------------------------------------
-
--- | Get the associated change
-getChange :: Event -> Maybe Change
-getChange event = case event of
-  EventAssigneeChanged AssigneeChanged {..} -> Just assigneeChangedChange
-  EventChangeAbandoned ChangeAbandoned {..} -> Just changeAbandonedChange
-  EventChangeDeleted ChangeDeleted {..} -> Just changeDeletedChange
-  EventChangeMerged ChangeMerged {..} -> Just changeMergedChange
-  EventChangeRestored ChangeRestored {..} -> Just changeRestoredChange
-  EventCommentAdded CommentAdded {..} -> Just commentAddedChange
-  EventDroppedOutput -> Nothing
-  EventHashtagsChanged HashtagsChanged {..} -> Just hashtagsChangedChange
-  EventProjectCreated _ -> Nothing
-  EventPatchsetCreated PatchsetCreated {..} -> Just patchsetCreatedChange
-  EventRefUpdated _ -> Nothing
-  EventReviewerAdded ReviewerAdded {..} -> Just reviewerAddedChange
-  EventReviewerDeleted ReviewerDeleted {..} -> Just reviewerDeletedChange
-  EventTopicChanged TopicChanged {..} -> Just topicChangedChange
-  EventWorkInProgressStateChanged WorkInProgressStateChanged {..} -> Just workInProgressStateChangedChange
-  EventPrivateStateChanged PrivateStateChanged {..} -> Just privateStateChangedChange
-  EventVoteDeleted VoteDeleted {..} -> Just voteDeletedChange
-
--- | Get the associated author
-getUser :: Event -> Maybe User
-getUser event = case event of
-  EventAssigneeChanged AssigneeChanged {..} -> Just assigneeChangedChanger
-  EventChangeAbandoned ChangeAbandoned {..} -> Just changeAbandonedAbandoner
-  EventChangeDeleted ChangeDeleted {..} -> Just changeDeletedDeleter
-  EventChangeMerged ChangeMerged {..} -> Just changeMergedSubmitter
-  EventChangeRestored ChangeRestored {..} -> Just changeRestoredRestorer
-  EventCommentAdded CommentAdded {..} -> Just commentAddedAuthor
-  EventDroppedOutput -> Nothing
-  EventHashtagsChanged HashtagsChanged {..} -> Just hashtagsChangedEditor
-  EventProjectCreated _ -> Nothing
-  EventPatchsetCreated PatchsetCreated {..} -> Just patchsetCreatedUploader
-  EventRefUpdated _ -> Nothing
-  EventReviewerAdded ReviewerAdded {..} -> Just reviewerAddedAdder
-  EventReviewerDeleted ReviewerDeleted {..} -> Just reviewerDeletedRemover
-  EventTopicChanged TopicChanged {..} -> Just topicChangedChanger
-  EventWorkInProgressStateChanged WorkInProgressStateChanged {..} -> Just workInProgressStateChangedChanger
-  EventPrivateStateChanged PrivateStateChanged {..} -> Just privateStateChangedChanger
-  EventVoteDeleted VoteDeleted {..} -> Just voteDeletedRemover
-
--- | Get the 'EventType' back from an 'Event'
-getEventType :: Event -> EventType
-getEventType event = case event of
-  EventAssigneeChanged _ -> AssigneeChangedEvent
-  EventChangeAbandoned _ -> ChangeAbandonedEvent
-  EventChangeDeleted _ -> ChangeDeletedEvent
-  EventChangeMerged _ -> ChangeMergedEvent
-  EventChangeRestored _ -> ChangeRestoredEvent
-  EventCommentAdded _ -> CommentAddedEvent
-  EventDroppedOutput -> DroppedOutputEvent
-  EventHashtagsChanged _ -> HashtagsChangedEvent
-  EventProjectCreated _ -> ProjectCreatedEvent
-  EventPatchsetCreated _ -> PatchsetCreatedEvent
-  EventRefUpdated _ -> RefUpdatedEvent
-  EventReviewerAdded _ -> ReviewerAddedEvent
-  EventReviewerDeleted _ -> ReviewerDeletedEvent
-  EventTopicChanged _ -> TopicChangedEvent
-  EventWorkInProgressStateChanged _ -> WorkInProgressStateChangedEvent
-  EventPrivateStateChanged _ -> PrivateStateChangedEvent
-  EventVoteDeleted _ -> VoteDeletedEvent
-
--- | Get the text representation suitable for event subscription.
-eventName :: EventType -> Text
-eventName eventType = case eventType of
-  AssigneeChangedEvent -> "assignee-changed"
-  ChangeAbandonedEvent -> "change-abandoned"
-  ChangeDeletedEvent -> "change-deleted"
-  ChangeMergedEvent -> "change-merged"
-  ChangeRestoredEvent -> "change-restored"
-  CommentAddedEvent -> "comment-added"
-  DroppedOutputEvent -> "dropped-output"
-  HashtagsChangedEvent -> "hashtags-changed"
-  ProjectCreatedEvent -> "project-created"
-  PatchsetCreatedEvent -> "patchset-created"
-  RefUpdatedEvent -> "ref-updated"
-  ReviewerAddedEvent -> "reviewer-added"
-  ReviewerDeletedEvent -> "reviewer-deleted"
-  TopicChangedEvent -> "topic-changed"
-  WorkInProgressStateChangedEvent -> "wip-state-changed"
-  PrivateStateChangedEvent -> "private-state-changed"
-  VoteDeletedEvent -> "vote-deleted"
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -8,8 +8,9 @@
 import qualified Data.ByteString.Lazy as BSL
 import Data.Maybe (fromJust)
 import Gerrit
-import Gerrit.Data as Gerrit
-import qualified Gerrit.Event as Event
+import Gerrit.Data.Change
+import qualified Gerrit.Data.Event as Event
+import Gerrit.Data.Review
 import System.Directory (listDirectory)
 import Test.Tasty
 import Test.Tasty.HUnit
@@ -17,10 +18,10 @@
 main :: IO ()
 main =
   do
-    files <- listDirectory "./test/data"
-    dataFiles <- traverse (BSL.readFile . ("./test/data/" <>)) files
+    files' <- listDirectory "./test/data"
+    dataFiles <- traverse (BSL.readFile . ("./test/data/" <>)) files'
     withClient "http://example.com/r" Nothing $ \client ->
-      defaultMain (tests (zip files dataFiles) client)
+      defaultMain (tests (zip files' dataFiles) client)
 
 tests :: [(FilePath, ByteString)] -> GerritClient -> TestTree
 tests dataFiles client = testGroup "Tests" $ [unitTests] <> encodingTests dataFiles client
@@ -30,8 +31,15 @@
   testGroup
     "unitTests"
     [ testCase "queryString status" $
-        assertEqual "Query string is valid" "status:new" (queryText (Status NEW))
+        assertEqual "Query string is valid" "status:new" (queryText (Status NEW)),
+      testCase "changeQS w/o start" $
+        assertEqual "Change query is valid" expected1 (changeQS 10 [Project "gerrit-haskell"] Nothing),
+      testCase "changeQS with start" $
+        assertEqual "Change query is valid" expected2 (changeQS 10 [Project "gerrit-haskell"] (Just 100))
     ]
+  where
+    expected1 = "q=project:gerrit-haskell&n=10&o=MESSAGES&o=DETAILED_ACCOUNTS&o=DETAILED_LABELS&o=CURRENT_REVISION&o=CURRENT_FILES&o=CURRENT_COMMIT"
+    expected2 = "q=project:gerrit-haskell&n=10&o=MESSAGES&o=DETAILED_ACCOUNTS&o=DETAILED_LABELS&o=CURRENT_REVISION&o=CURRENT_FILES&o=CURRENT_COMMIT&start=100"
 
 encodingTests :: [(FilePath, ByteString)] -> GerritClient -> [TestTree]
 encodingTests dataFiles client =
@@ -74,7 +82,7 @@
   ]
   where
     prettyEncode obj = encode obj <> "\n"
-    reviewInput = Gerrit.ReviewInput (Just "Thanks!") Nothing
+    reviewInput = ReviewInput (Just "Thanks!") Nothing
     getRaw :: FilePath -> ByteString
     getRaw fp = fromJust $ lookup fp dataFiles
     isReviewResult :: Maybe ReviewResult -> Bool
@@ -104,7 +112,7 @@
       Just _ -> True
       Nothing -> False
     testIsApproved = case decode (getRaw "GerritChange.json") of
-      Just change -> hasLabel "Code-Review" 1 change
+      Just change -> hasLabel "Code-Review" 2 change
       Nothing -> False
     testGetUrl = case decode (getRaw "GerritChange.json") of
       Just change -> changeUrl client change
diff --git a/test/data/GerritChange.json b/test/data/GerritChange.json
--- a/test/data/GerritChange.json
+++ b/test/data/GerritChange.json
@@ -5,33 +5,127 @@
   "hashtags": [],
   "change_id": "Ic6add806c49bccaf6e15d4ef1c62522d56eb9384",
   "subject": "Initial summon",
-  "status": "NEW",
+  "status": "MERGED",
   "created": "2020-07-15 19:39:55.000000000",
-  "updated": "2020-07-15 23:01:14.000000000",
-  "submit_type": "MERGE_IF_NECESSARY",
-  "mergeable": true,
+  "updated": "2020-07-16 18:08:59.000000000",
+  "submitted": "2020-07-16 18:08:59.000000000",
+  "submitter": {
+    "_account_id": 458,
+    "name": "Zuul CI",
+    "email": "zuul@softwarefactory-project.io",
+    "username": "zuul",
+    "avatars": [
+      {
+        "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+        "height": 32
+      },
+      {
+        "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+        "height": 56
+      },
+      {
+        "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+        "height": 100
+      },
+      {
+        "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+        "height": 120
+      }
+    ]
+  },
   "insertions": 295,
   "deletions": 0,
+  "total_comment_count": 0,
   "unresolved_comment_count": 0,
+  "has_review_started": true,
+  "submission_id": "18839-1594922939722-7c5a83e2",
   "_number": 18839,
   "owner": {
-    "_account_id": 7
+    "_account_id": 7,
+    "name": "Tristan de Cacqueray",
+    "email": "tdecacqu@redhat.com",
+    "username": "TristanCacqueray",
+    "avatars": [
+      {
+        "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+        "height": 32
+      },
+      {
+        "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+        "height": 56
+      },
+      {
+        "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+        "height": 100
+      },
+      {
+        "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+        "height": 120
+      }
+    ]
   },
   "labels": {
     "Code-Review": {
       "all": [
         {
-          "value": 1,
+          "value": 0,
+          "_account_id": 458,
+          "name": "Zuul CI",
+          "email": "zuul@softwarefactory-project.io",
+          "username": "zuul",
+          "avatars": [
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+              "height": 32
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+              "height": 56
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+              "height": 100
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+              "height": 120
+            }
+          ]
+        },
+        {
+          "value": 2,
+          "date": "2020-07-16 18:08:48.000000000",
           "permitted_voting_range": {
-            "min": -1,
-            "max": 1
+            "min": 2,
+            "max": 2
           },
-          "_account_id": 458
+          "_account_id": 7,
+          "name": "Tristan de Cacqueray",
+          "email": "tdecacqu@redhat.com",
+          "username": "TristanCacqueray",
+          "avatars": [
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+              "height": 32
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+              "height": 56
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+              "height": 100
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+              "height": 120
+            }
+          ]
         }
       ],
       "values": {
         "-2": "Do not submit",
-        "-1": "I would prefer that you didn't submit this",
+        "-1": "I would prefer that you didn\u0027t submit this",
         " 0": "No score",
         "+1": "Looks good to me, but someone else must approve",
         "+2": "Looks good to me (core reviewer)"
@@ -41,28 +135,683 @@
     "Verified": {
       "all": [
         {
-          "value": 1,
-          "date": "2020-07-17 13:02:15.000000000",
+          "value": 2,
+          "date": "2020-07-16 18:08:59.000000000",
+          "post_submit": true,
           "permitted_voting_range": {
-            "min": -2,
+            "min": 2,
             "max": 2
           },
-          "_account_id": 458
+          "_account_id": 458,
+          "name": "Zuul CI",
+          "email": "zuul@softwarefactory-project.io",
+          "username": "zuul",
+          "avatars": [
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+              "height": 32
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+              "height": 56
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+              "height": 100
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+              "height": 120
+            }
+          ]
         },
         {
-          "_account_id": 42
+          "value": 0,
+          "_account_id": 7,
+          "name": "Tristan de Cacqueray",
+          "email": "tdecacqu@redhat.com",
+          "username": "TristanCacqueray",
+          "avatars": [
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+              "height": 32
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+              "height": 56
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+              "height": 100
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+              "height": 120
+            }
+          ]
         }
       ],
       "values": {
         "-2": "Fails",
-        "-1": "Doesn't seem to work",
+        "-1": "Doesn\u0027t seem to work",
         " 0": "No score",
         "+1": "Works for me",
         "+2": "Verified"
       },
       "default_value": 0
+    },
+    "Workflow": {
+      "all": [
+        {
+          "value": 0,
+          "_account_id": 458,
+          "name": "Zuul CI",
+          "email": "zuul@softwarefactory-project.io",
+          "username": "zuul",
+          "avatars": [
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+              "height": 32
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+              "height": 56
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+              "height": 100
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+              "height": 120
+            }
+          ]
+        },
+        {
+          "value": 1,
+          "date": "2020-07-16 18:08:48.000000000",
+          "permitted_voting_range": {
+            "min": 1,
+            "max": 1
+          },
+          "_account_id": 7,
+          "name": "Tristan de Cacqueray",
+          "email": "tdecacqu@redhat.com",
+          "username": "TristanCacqueray",
+          "avatars": [
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+              "height": 32
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+              "height": 56
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+              "height": 100
+            },
+            {
+              "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+              "height": 120
+            }
+          ]
+        }
+      ],
+      "values": {
+        "-1": "Work in progress",
+        " 0": "Ready for reviews",
+        "+1": "Approved"
+      },
+      "default_value": 0
     }
   },
+  "removable_reviewers": [],
+  "reviewers": {
+    "REVIEWER": [
+      {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      }
+    ]
+  },
+  "pending_reviewers": {},
+  "messages": [
+    {
+      "id": "ee2e77b6628d5a007029fab6ed7a6f6132c031c9",
+      "tag": "autogenerated:gerrit:newPatchSet",
+      "author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-15 19:39:55.000000000",
+      "message": "Uploaded patch set 1.",
+      "_revision_number": 1
+    },
+    {
+      "id": "3e1eb0ecb29947dda8ef79bbe67c193e9465db7a",
+      "tag": "autogenerated:gerrit:newPatchSet",
+      "author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-15 21:03:21.000000000",
+      "message": "Uploaded patch set 2.",
+      "_revision_number": 2
+    },
+    {
+      "id": "ba03042aa7252f4ff133d31f66b75c60f80c067f",
+      "tag": "autogenerated:gerrit:newPatchSet",
+      "author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-15 23:01:02.000000000",
+      "message": "Uploaded patch set 3.",
+      "_revision_number": 3
+    },
+    {
+      "id": "dc3ddbc6022f57505b3924d4c42df19ea0d2d527",
+      "author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-15 23:01:13.000000000",
+      "message": "Patch Set 3:\n\nStarting check jobs. Status at",
+      "_revision_number": 3
+    },
+    {
+      "id": "f053926475b8fd9da58d55ae47d8f7eef3a3a727",
+      "author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-15 23:01:14.000000000",
+      "message": "Patch Set 3: Verified+1\n\nBuild succeeded.\n\n- noop https://softwarefactory-project.io/zuul/t/local/build/c6511d87e5f644e7b566b73741600392 : SUCCESS in 0s",
+      "_revision_number": 3
+    },
+    {
+      "id": "9d688628218ba8972d8ecf51669bec9c83d3a18f",
+      "author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-16 18:08:48.000000000",
+      "message": "Patch Set 3: Code-Review+2 Workflow+1",
+      "_revision_number": 3
+    },
+    {
+      "id": "f2dcf8b15417b0057b2e7384c51d194cce7a0491",
+      "tag": "autogenerated:gerrit:merged",
+      "author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-16 18:08:59.000000000",
+      "message": "Change has been successfully merged by Zuul CI",
+      "_revision_number": 3
+    },
+    {
+      "id": "10a3e0eeee03627b1d57a9e932a479545a2f2ade",
+      "author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-16 18:08:59.000000000",
+      "message": "Patch Set 3: -Verified\n\nStarting gate jobs.",
+      "_revision_number": 3
+    },
+    {
+      "id": "b3d3d3639dc32a9ee80d7b65a82ab57615925d6d",
+      "author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "real_author": {
+        "_account_id": 458,
+        "name": "Zuul CI",
+        "email": "zuul@softwarefactory-project.io",
+        "username": "zuul",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/72ca689f16f3f13bf36869ea655c488e.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
+      },
+      "date": "2020-07-16 18:08:59.000000000",
+      "message": "Patch Set 3: Verified+2\n\nBuild succeeded (gate pipeline).\n\n- noop https://softwarefactory-project.io/zuul/t/local/build/261409bf24ea4b20a8a4304bcbed343a : SUCCESS in 0s",
+      "_revision_number": 3
+    }
+  ],
   "current_revision": "155658085799464e8d198edb7c8bd62de9ec9ae6",
   "revisions": {
     "155658085799464e8d198edb7c8bd62de9ec9ae6": {
@@ -70,7 +819,28 @@
       "_number": 3,
       "created": "2020-07-15 23:01:02.000000000",
       "uploader": {
-        "_account_id": 7
+        "_account_id": 7,
+        "name": "Tristan de Cacqueray",
+        "email": "tdecacqu@redhat.com",
+        "username": "TristanCacqueray",
+        "avatars": [
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d32",
+            "height": 32
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d56",
+            "height": 56
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d100",
+            "height": 100
+          },
+          {
+            "url": "https://www.gravatar.com/avatar/921d0ed12095380e7b79d606bb037261.jpg?d\u003didenticon\u0026r\u003dpg\u0026s\u003d120",
+            "height": 120
+          }
+        ]
       },
       "ref": "refs/changes/39/18839/3",
       "fetch": {
@@ -78,7 +848,80 @@
           "url": "https://softwarefactory-project.io/r/software-factory/gerrit-haskell",
           "ref": "refs/changes/39/18839/3"
         }
+      },
+      "commit": {
+        "parents": [
+          {
+            "commit": "fda786eda9e53208ede9b571174b85e4be39a44d",
+            "subject": "ManageSF commit"
+          }
+        ],
+        "author": {
+          "name": "Tristan Cacqueray",
+          "email": "tdecacqu@redhat.com",
+          "date": "2020-07-15 19:38:27.000000000",
+          "tz": 0
+        },
+        "committer": {
+          "name": "Tristan Cacqueray",
+          "email": "tdecacqu@redhat.com",
+          "date": "2020-07-15 23:00:30.000000000",
+          "tz": 0
+        },
+        "subject": "Initial summon",
+        "message": "Initial summon\n\nChange-Id: Ic6add806c49bccaf6e15d4ef1c62522d56eb9384\n"
+      },
+      "files": {
+        ".zuul.yaml": {
+          "status": "A",
+          "lines_inserted": 5,
+          "size_delta": 71,
+          "size": 71
+        },
+        "LICENSE": {
+          "status": "A",
+          "lines_inserted": 201,
+          "size_delta": 11357,
+          "size": 11357
+        },
+        "README.md": {
+          "status": "A",
+          "lines_inserted": 3,
+          "size_delta": 34,
+          "size": 34
+        },
+        "app/Main.hs": {
+          "status": "A",
+          "lines_inserted": 6,
+          "size_delta": 82,
+          "size": 82
+        },
+        "gerrit-haskell.cabal": {
+          "status": "A",
+          "lines_inserted": 61,
+          "size_delta": 1863,
+          "size": 1863
+        },
+        "src/Gerrit.hs": {
+          "status": "A",
+          "lines_inserted": 13,
+          "size_delta": 279,
+          "size": 279
+        },
+        "stack.yaml": {
+          "status": "A",
+          "lines_inserted": 1,
+          "size_delta": 19,
+          "size": 19
+        },
+        "test/Spec.hs": {
+          "status": "A",
+          "lines_inserted": 5,
+          "size_delta": 101,
+          "size": 101
+        }
       }
     }
-  }
+  },
+  "requirements": []
 }
