diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -41,21 +41,24 @@
 ## Usage
 
 ```haskell
+{-# LANGUAGE DataKinds         #-}
 {-# LANGUAGE OverloadedStrings #-}
 
-import Data.String (fromString)
-import Line.Bot.Client (Line, getProfile, runLine)
-import Line.Bot.Types (Profile)
-import System.Environment (getEnv)
+import           Data.String        (fromString)
+import           Line.Bot.Client
+import           Line.Bot.Types
+import           System.Environment (getEnv)
 
-profile :: Line Profile
-profile = getProfile "U4af4980629..."
+getProfiles :: Id Room -> Line [Profile]
+getProfiles roomId = do
+  userIds <- getRoomMemberUserIds roomId
+  sequence $ getRoomMemberProfile roomId <$> userIds
 
 main = do
-  token <- fromString <$> getEnv "CHANNEL_TOKEN"
-  result <- runLine profile token
+  token  <- fromString <$> getEnv "CHANNEL_TOKEN"
+  result <- runLine (getProfiles "U4af4980629...") token
   case result of
-    Left err -> print err
+    Left err      -> print err
     Right profile -> print profile
 ```
 
@@ -66,3 +69,8 @@
 
 Please report bugs via the
 [github issue tracker](https://github.com/moleike/line-bot-sdk/issues).
+
+## Acknowledgments
+
+Thanks to the authors of [servant-github](https://hackage.haskell.org/package/servant-github), for inspiration.
+
diff --git a/line-bot-sdk.cabal b/line-bot-sdk.cabal
--- a/line-bot-sdk.cabal
+++ b/line-bot-sdk.cabal
@@ -1,5 +1,5 @@
 name:                line-bot-sdk
-version:             0.5.0.1
+version:             0.5.0.2
 synopsis:            Haskell SDK for LINE Messaging API
 homepage:            https://github.com/moleike/line-bot-sdk#readme
 bug-reports:         https://github.com/moleike/line-bot-sdk/issues
@@ -55,6 +55,11 @@
                      , servant-server       >= 0.15 && < 0.16
                      , wai                  >= 3.2.2 && < 3.3
                      , wai-extra            >= 3.0.25 && < 3.1
+                     , exceptions           >= 0.10 && < 0.11
+                     , transformers-base    >= 0.4 && < 0.5
+                     , mtl                  >= 2.2.2 && < 2.3
+                     , monad-control        >= 1.0 && < 1.1
+                     , streaming            >= 0.2 && < 0.3
 
   default-language:    Haskell2010
 
diff --git a/src/Line/Bot/Client.hs b/src/Line/Bot/Client.hs
--- a/src/Line/Bot/Client.hs
+++ b/src/Line/Bot/Client.hs
@@ -3,8 +3,11 @@
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE RecordWildCards       #-}
 {-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TypeFamilies          #-}
 {-# LANGUAGE TypeOperators         #-}
+
 -- |
 -- Module      : Line.Bot.Client
 -- Copyright   : (c) Alexandre Moreno, 2019
@@ -22,17 +25,24 @@
   -- ** Group
   , getGroupMemberProfile
   , leaveGroup
+  , getGroupMemberUserIds
+  , getGroupMemberUserIdsS
   -- ** Room
   , getRoomMemberProfile
   , leaveRoom
+  , getRoomMemberUserIds
+  , getRoomMemberUserIdsS
   -- ** Message
   , replyMessage
   , pushMessage
   , multicastMessage
+  , broadcastMessage
   , getContent
   , getPushMessageCount
   , getReplyMessageCount
   , getMulticastMessageCount
+  , getBroadcastMessageCount
+  , getMessageQuota
   -- ** Account Link
   , issueLinkToken
   -- ** OAuth
@@ -41,25 +51,28 @@
   )
 where
 
+import           Control.Monad.IO.Class
+import           Control.Monad.Reader
 import           Control.Monad.Trans.Class   (lift)
-import           Control.Monad.Trans.Reader  (ReaderT, ask, runReaderT)
-import           Data.ByteString.Lazy        (ByteString)
+import qualified Data.ByteString.Lazy        as LB
 import           Data.Proxy
 import           Data.Time.Calendar          (Day)
-import           Line.Bot.Internal.Auth
+import           Line.Bot.Internal.Auth      (Auth, mkAuth)
 import           Line.Bot.Internal.Endpoints
 import           Line.Bot.Types
 import           Network.HTTP.Client         (newManager)
 import           Network.HTTP.Client.TLS     (tlsManagerSettings)
-import           Servant.API
+import           Servant.API                 hiding (Stream)
 import           Servant.Client
+import           Streaming
+import qualified Streaming.Prelude           as S
 
 host :: BaseUrl
 host = BaseUrl Https "api.line.me" 443 ""
 
 -- | @Line@ is the monad in which bot requests run. Contains the
 -- OAuth access token for a channel
-type Line = ReaderT Auth ClientM
+type Line = ReaderT ChannelToken ClientM
 
 withLineEnv :: (ClientEnv -> IO a) -> IO a
 withLineEnv app = do
@@ -72,78 +85,136 @@
 
 -- | Runs a @Line@ computation with the given channel access token
 runLine :: Line a -> ChannelToken -> IO (Either ServantError a)
-runLine comp = runLine' . runReaderT comp . mkAuth
+runLine comp = runLine' . runReaderT comp
 
+type LineAuth a = Auth -> ClientM a
+
+type family AddLineAuth a :: * where
+  AddLineAuth (LineAuth a) = Line a
+  AddLineAuth (a -> b) = a -> AddLineAuth b
+
+class HasLine a where
+  addLineAuth :: a -> AddLineAuth a
+
+instance HasLine (LineAuth a) where
+  addLineAuth comp = ask >>= \token -> lift $ comp (mkAuth token)
+
+instance HasLine (a -> LineAuth b) where
+  addLineAuth comp = addLineAuth . comp
+
+instance HasLine (a -> b -> LineAuth c) where
+  addLineAuth comp = addLineAuth . comp
+
+line :: (HasLine (Client ClientM api), HasClient ClientM api)
+     => Proxy api -> AddLineAuth (Client ClientM api)
+line api = addLineAuth (client api)
+
 getProfile :: Id User -> Line Profile
-getProfile a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetProfile) auth a
+getProfile = line (Proxy :: Proxy GetProfile)
 
 getGroupMemberProfile :: Id Group -> Id User -> Line Profile
-getGroupMemberProfile a b = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetGroupMemberProfile) auth a b
+getGroupMemberProfile = line (Proxy :: Proxy GetGroupMemberProfile)
 
 leaveGroup :: Id Group -> Line NoContent
-leaveGroup a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy LeaveGroup) auth a
+leaveGroup = line (Proxy :: Proxy LeaveGroup)
 
+getGroupMemberUserIds' :: Id Group -> Maybe String -> Line MemberIds
+getGroupMemberUserIds' = line (Proxy :: Proxy GetGroupMemberUserIds)
+
+getGroupMemberUserIdsS :: Id Group -> Stream (Of (Id User)) Line ()
+getGroupMemberUserIdsS gid = go gid Nothing
+  where
+    go gid token = do
+      MemberIds{..} <- lift $ getGroupMemberUserIds' gid token
+      S.each memberIds
+      case next of
+        Nothing -> return ()
+        token'  -> go gid token'
+
+getGroupMemberUserIds :: Id Group -> Line [Id User]
+getGroupMemberUserIds = S.toList_ . getGroupMemberUserIdsS
+
 getRoomMemberProfile :: Id Room -> Id User -> Line Profile
-getRoomMemberProfile a b = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetRoomMemberProfile) auth a b
+getRoomMemberProfile = line (Proxy :: Proxy GetRoomMemberProfile)
 
 leaveRoom :: Id Room -> Line NoContent
-leaveRoom a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy LeaveRoom) auth a
+leaveRoom = line (Proxy :: Proxy LeaveRoom)
 
+getRoomMemberUserIds' :: Id Room -> Maybe String -> Line MemberIds
+getRoomMemberUserIds' = line (Proxy :: Proxy GetRoomMemberUserIds)
+
+getRoomMemberUserIdsS :: Id Room -> Stream (Of (Id User)) Line ()
+getRoomMemberUserIdsS gid = go gid Nothing
+  where
+    go gid token = do
+      MemberIds{..} <- lift $ getRoomMemberUserIds' gid token
+      S.each memberIds
+      case next of
+        Nothing -> return ()
+        token'  -> go gid token'
+
+getRoomMemberUserIds :: Id Room -> Line [Id User]
+getRoomMemberUserIds = S.toList_ . getRoomMemberUserIdsS
+
 replyMessage' :: ReplyMessageBody -> Line NoContent
-replyMessage' a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy ReplyMessage) auth a
+replyMessage' = line (Proxy :: Proxy ReplyMessage)
 
 replyMessage :: ReplyToken -> [Message] -> Line NoContent
 replyMessage a ms = replyMessage' (ReplyMessageBody a ms)
 
 pushMessage' :: PushMessageBody -> Line NoContent
-pushMessage' a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy PushMessage) auth a
+pushMessage' = line (Proxy :: Proxy PushMessage)
 
 pushMessage :: Id a -> [Message] -> Line NoContent
 pushMessage a ms = pushMessage' (PushMessageBody a ms)
 
 multicastMessage' :: MulticastMessageBody -> Line NoContent
-multicastMessage' a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy MulticastMessage) auth a
+multicastMessage' = line (Proxy :: Proxy MulticastMessage)
 
 multicastMessage :: [Id User] -> [Message] -> Line NoContent
 multicastMessage a ms = multicastMessage' (MulticastMessageBody a ms)
 
--- | TODO: this should use a streaming library for constant memory usage over large data
-getContent :: MessageId -> Line ByteString
-getContent a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetContent) auth a
+broadcastMessage' :: BroadcastMessageBody -> Line NoContent
+broadcastMessage' = line (Proxy :: Proxy BroadcastMessage)
 
+broadcastMessage :: [Message] -> Line NoContent
+broadcastMessage = broadcastMessage' . BroadcastMessageBody
+
+getContent :: MessageId -> Line LB.ByteString
+getContent = line (Proxy :: Proxy GetContent)
+
 getPushMessageCount' :: LineDate -> Line MessageCount
-getPushMessageCount' a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetPushMessageCount) auth a
+getPushMessageCount' = line (Proxy :: Proxy GetPushMessageCount)
 
 getPushMessageCount :: Day -> Line (Maybe Int)
 getPushMessageCount = fmap count . getPushMessageCount' . LineDate
 
 getReplyMessageCount' :: LineDate -> Line MessageCount
-getReplyMessageCount' a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetReplyMessageCount) auth a
+getReplyMessageCount' = line (Proxy :: Proxy GetReplyMessageCount)
 
 getReplyMessageCount :: Day -> Line (Maybe Int)
 getReplyMessageCount = fmap count . getReplyMessageCount' . LineDate
 
 getMulticastMessageCount' :: LineDate -> Line MessageCount
-getMulticastMessageCount' a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy GetMulticastMessageCount) auth a
+getMulticastMessageCount' = line (Proxy :: Proxy GetMulticastMessageCount)
 
 getMulticastMessageCount :: Day -> Line (Maybe Int)
 getMulticastMessageCount = fmap count . getMulticastMessageCount' . LineDate
 
+getBroadcastMessageCount' :: LineDate -> Line MessageCount
+getBroadcastMessageCount' = line (Proxy :: Proxy GetBroadcastMessageCount)
+
+getBroadcastMessageCount :: Day -> Line (Maybe Int)
+getBroadcastMessageCount = fmap count . getBroadcastMessageCount' . LineDate
+
+getMessageQuota' :: Line MessageQuota
+getMessageQuota' = line (Proxy :: Proxy GetMessageQuota)
+
+getMessageQuota :: Line Int
+getMessageQuota = fmap totalUsage getMessageQuota'
+
 issueLinkToken :: Id User -> Line LinkToken
-issueLinkToken a = ask >>= \auth ->
-  lift $ client (Proxy :: Proxy IssueLinkToken) auth a
+issueLinkToken = line (Proxy :: Proxy IssueLinkToken)
 
 issueChannelToken' :: ClientCredentials -> ClientM ShortLivedChannelToken
 issueChannelToken' = client (Proxy :: Proxy IssueChannelToken)
diff --git a/src/Line/Bot/Internal/Endpoints.hs b/src/Line/Bot/Internal/Endpoints.hs
--- a/src/Line/Bot/Internal/Endpoints.hs
+++ b/src/Line/Bot/Internal/Endpoints.hs
@@ -23,142 +23,184 @@
 -- | Combinator for authenticating with the channel access token
 type ChannelAuth = AuthProtect "channel-access-token"
 
-type GetProfile' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "profile"
+type GetProfile' a =
+     "v2":> "bot" :> "profile"
   :> Capture "userId" (Id User)
+  :> ChannelAuth
   :> Get '[JSON] a
 
 type GetProfile = GetProfile' Profile
 
-type GetGroupMemberProfile' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "group"
+type GetGroupMemberProfile' a =
+     "v2":> "bot" :> "group"
   :> Capture "groupId" (Id Group)
   :> "member"
   :> Capture "userId" (Id User)
+  :> ChannelAuth
   :> Get '[JSON] a
 
 type GetGroupMemberProfile = GetGroupMemberProfile' Profile
 
-type LeaveGroup = ChannelAuth
-  :> "v2" :> "bot"
-  :> "group"
+type LeaveGroup =
+     "v2":> "bot" :> "group"
   :> Capture "groupId" (Id Group)
   :> "leave"
+  :> ChannelAuth
   :> PostNoContent '[JSON] NoContent
 
-type GetRoomMemberProfile' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "room"
+type GetGroupMemberUserIds' a =
+     "v2":> "bot" :> "group"
+  :> Capture "groupId" (Id Group)
+  :> "members"
+  :> "ids"
+  :> QueryParam "start" String
+  :> ChannelAuth
+  :> Get '[JSON] a
+
+type GetGroupMemberUserIds = GetGroupMemberUserIds' MemberIds
+
+type GetRoomMemberProfile' a =
+     "v2":> "bot" :> "room"
   :> Capture "roomId" (Id Room)
   :> "member"
   :> Capture "userId" (Id User)
+  :> ChannelAuth
   :> Get '[JSON] a
 
 type GetRoomMemberProfile = GetRoomMemberProfile' Profile
 
-type LeaveRoom = ChannelAuth
-  :> "v2" :> "bot"
-  :> "room"
+type LeaveRoom =
+     "v2":> "bot" :> "room"
   :> Capture "roomId" (Id Room)
   :> "leave"
+  :> ChannelAuth
   :> PostNoContent '[JSON] NoContent
 
-type ReplyMessage' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
-  :> "reply"
+type GetRoomMemberUserIds' a =
+     "v2":> "bot" :> "room"
+  :> Capture "roomId" (Id Room)
+  :> "members"
+  :> "ids"
+  :> QueryParam "start" String
+  :> ChannelAuth
+  :> Get '[JSON] a
+
+type GetRoomMemberUserIds = GetRoomMemberUserIds' MemberIds
+
+type ReplyMessage' a =
+     "v2":> "bot" :> "message" :> "reply"
   :> ReqBody '[JSON] a
+  :> ChannelAuth
   :> PostNoContent '[JSON] NoContent
 
 type ReplyMessage = ReplyMessage' ReplyMessageBody
 
-type PushMessage' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
+type PushMessage' a =
+     "v2":> "bot" :> "message"
   :> "push"
   :> ReqBody '[JSON] a
+  :> ChannelAuth
   :> PostNoContent '[JSON] NoContent
 
 type PushMessage = PushMessage' PushMessageBody
 
-type MulticastMessage' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
+type MulticastMessage' a =
+     "v2":> "bot" :> "message"
   :> "multicast"
   :> ReqBody '[JSON] a
+  :> ChannelAuth
   :> PostNoContent '[JSON] NoContent
 
 type MulticastMessage = MulticastMessage' MulticastMessageBody
 
-type GetContent = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
+type BroadcastMessage' a =
+     "v2":> "bot" :> "message"
+  :> "broadcast"
+  :> ReqBody '[JSON] a
+  :> ChannelAuth
+  :> PostNoContent '[JSON] NoContent
+
+type BroadcastMessage = BroadcastMessage' BroadcastMessageBody
+
+type GetContent =
+     "v2":> "bot" :> "message"
   :> Capture "messageId" MessageId
   :> "content"
+  :> ChannelAuth
   :> Get '[OctetStream] ByteString
 
-type GetContentStream = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
+type GetContentStream =
+     "v2":> "bot" :> "message"
   :> Capture "messageId" MessageId
   :> "content"
+  :> ChannelAuth
   :> StreamGet NoFraming OctetStream (SourceIO ByteString)
 
-type GetReplyMessageCount' a b = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
-  :> "delivery"
+type GetReplyMessageCount' a b =
+     "v2":> "bot" :> "message" :> "delivery"
   :> "reply"
   :> QueryParam' '[Required, Strict] "date" a
+  :> ChannelAuth
   :> Get '[JSON] b
 
 type GetReplyMessageCount = GetReplyMessageCount' LineDate MessageCount
 
-type GetPushMessageCount' a b = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
-  :> "delivery"
+type GetPushMessageCount' a b =
+     "v2":> "bot" :> "message" :> "delivery"
   :> "push"
   :> QueryParam' '[Required, Strict] "date" a
+  :> ChannelAuth
   :> Get '[JSON] b
 
 type GetPushMessageCount = GetPushMessageCount' LineDate MessageCount
 
-type GetMulticastMessageCount' a b = ChannelAuth
-  :> "v2" :> "bot"
-  :> "message"
-  :> "delivery"
+type GetMulticastMessageCount' a b =
+     "v2" :> "bot" :> "message" :> "delivery"
   :> "multicast"
   :> QueryParam' '[Required, Strict] "date" a
+  :> ChannelAuth
   :> Get '[JSON] b
 
 type GetMulticastMessageCount = GetMulticastMessageCount' LineDate MessageCount
 
-type IssueLinkToken' a = ChannelAuth
-  :> "v2" :> "bot"
-  :> "user"
+type GetBroadcastMessageCount' a b =
+     "v2" :> "bot" :> "message" :> "delivery"
+  :> "broadcast"
+  :> QueryParam' '[Required, Strict] "date" a
+  :> ChannelAuth
+  :> Get '[JSON] b
+
+type GetBroadcastMessageCount = GetBroadcastMessageCount' LineDate MessageCount
+
+type GetMessageQuota' a =
+     "v2":> "bot" :> "message" :> "quota"
+  :> "consumption"
+  :> ChannelAuth
+  :> Get '[JSON] a
+
+type GetMessageQuota = GetMessageQuota' MessageQuota
+
+type IssueLinkToken' a =
+     "v2":> "bot" :> "user"
   :> Capture "userId" (Id User)
   :> "linkToken"
+  :> ChannelAuth
   :> Get '[JSON] a
 
 type IssueLinkToken = IssueLinkToken' LinkToken
 
 type IssueChannelToken' a b =
-  ReqBody '[FormUrlEncoded] a
-  :> "v2"
-  :> "oauth"
+     "v2" :> "oauth"
   :> "accessToken"
+  :> ReqBody '[FormUrlEncoded] a
   :> Post '[JSON] b
 
 type IssueChannelToken = IssueChannelToken' ClientCredentials ShortLivedChannelToken
 
 type RevokeChannelToken' a =
-  ReqBody '[FormUrlEncoded] a
-  :> "v2"
-  :> "oauth"
+     "v2" :> "oauth"
   :> "revoke"
+  :> ReqBody '[FormUrlEncoded] a
   :> PostNoContent '[JSON] NoContent
 
 type RevokeChannelToken = RevokeChannelToken' ChannelToken
diff --git a/src/Line/Bot/Types.hs b/src/Line/Bot/Types.hs
--- a/src/Line/Bot/Types.hs
+++ b/src/Line/Bot/Types.hs
@@ -33,6 +33,7 @@
   , ReplyMessageBody(ReplyMessageBody)
   , PushMessageBody(PushMessageBody)
   , MulticastMessageBody(MulticastMessageBody)
+  , BroadcastMessageBody(BroadcastMessageBody)
   , Profile(..)
   , QuickReply(..)
   , QuickReplyButton(..)
@@ -41,6 +42,8 @@
   , ShortLivedChannelToken(..)
   , LineDate(..)
   , MessageCount(..)
+  , MessageQuota(..)
+  , MemberIds(..)
   )
 where
 
@@ -177,6 +180,10 @@
                     , longitude  :: Double
                     , quickReply :: Maybe QuickReply
                     }
+  | MessageFlex     { altText    :: Text
+                    , contents   :: Value
+                    , quickReply :: Maybe QuickReply
+                    }
   deriving (Eq, Show, Generic)
 
 instance ToJSON Message where
@@ -242,6 +249,12 @@
 
 instance ToJSON MulticastMessageBody
 
+newtype BroadcastMessageBody = BroadcastMessageBody
+  { messages :: [Message] }
+  deriving (Show, Generic)
+
+instance ToJSON BroadcastMessageBody
+
 newtype QuickReply = QuickReply
   { items :: [QuickReplyButton] }
   deriving (Eq, Show, Generic)
@@ -333,3 +346,15 @@
     count  <- o .:? "success"
     status <- o .:  "status"
     return MessageCount{..}
+
+newtype MessageQuota = MessageQuota { totalUsage :: Int }
+  deriving (Eq, Show, Generic)
+
+instance FromJSON MessageQuota
+
+data MemberIds = MemberIds
+  { memberIds :: [Id User]
+  , next :: Maybe String
+  } deriving (Eq, Show, Generic)
+
+instance FromJSON MemberIds
diff --git a/src/Line/Bot/Webhook/Events.hs b/src/Line/Bot/Webhook/Events.hs
--- a/src/Line/Bot/Webhook/Events.hs
+++ b/src/Line/Bot/Webhook/Events.hs
@@ -4,9 +4,13 @@
 {-# LANGUAGE ExistentialQuantification  #-}
 {-# LANGUAGE ExtendedDefaultRules       #-}
 {-# LANGUAGE FlexibleContexts           #-}
+{-# LANGUAGE GADTs                      #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE KindSignatures             #-}
 {-# LANGUAGE OverloadedStrings          #-}
 {-# LANGUAGE RecordWildCards            #-}
+{-# LANGUAGE StandaloneDeriving         #-}
+
 -- |
 -- Module      : Line.Bot.Webhook.Events
 -- Copyright   : (c) Alexandre Moreno, 2019
@@ -56,7 +60,7 @@
   { destination :: Id User -- ^ User ID of a bot that should receive webhook events
   , events      :: [Event] -- ^ List of webhook event objects
   }
-  deriving (Eq, Show, Generic)
+  deriving (Show, Generic)
 
 instance FromJSON Events
 
@@ -110,7 +114,7 @@
                       , timestamp  :: EpochMilli
                       , things     :: Things
                       }
-  deriving (Eq, Show, Generic)
+  deriving (Show, Generic)
 
 instance FromJSON Event where
   parseJSON = genericParseJSON defaultOptions
@@ -141,7 +145,7 @@
                     , fileName  :: Text
                     }
   | MessageLocation { messageId :: MessageId
-                    , title     :: Text
+                    , title     :: Maybe Text
                     , address   :: Text
                     , latitude  :: Double
                     , longitude :: Double
@@ -189,24 +193,28 @@
              . toRational
              . (/ 1000)
 
-data Source =
-    SourceUser (Id User)
-  | SourceGroup (Id Group) (Maybe (Id User))
-  | SourceRoom (Id Room) (Maybe (Id User))
-  deriving (Eq, Show)
+data Source = forall a. Source (Id a)
 
+deriving instance Show Source
+
 instance FromJSON Source where
   parseJSON = withObject "Source" $ \o -> do
     messageType <- o .: "type"
     case messageType of
-      "user"  -> SourceUser  <$> o .: "userId"
-      "group" -> SourceGroup <$> o .: "groupId" <*> o .:? "userId"
-      "room"  -> SourceRoom  <$> o .: "roomId"  <*> o .:? "userId"
+      "user"  -> (Source . UserId)  <$> o .: "userId"
+      "group" -> (Source . GroupId) <$> o .: "groupId"
+      "room"  -> (Source . RoomId)  <$> o .: "roomId"
       _       -> fail ("unknown source: " ++ messageType)
 
 
+instance ToJSON Source where
+  toJSON (Source (UserId a))  = object ["type" .= "user", "userId" .= a]
+  toJSON (Source (GroupId a)) = object ["type" .= "group", "groupId" .= a]
+  toJSON (Source (RoomId a))  = object ["type" .= "room", "roomId" .= a]
+
+
 newtype Members = Members { members :: [Source] }
-  deriving (Eq, Show, Generic)
+  deriving (Show, Generic)
 
 instance FromJSON Members
 
diff --git a/test/Line/Bot/ClientSpec.hs b/test/Line/Bot/ClientSpec.hs
--- a/test/Line/Bot/ClientSpec.hs
+++ b/test/Line/Bot/ClientSpec.hs
@@ -56,13 +56,13 @@
       :<|> GetGroupMemberProfile' Value
       :<|> GetRoomMemberProfile' Value
 
-getReplyMessageCountF :: Auth -> LineDate -> Free ClientF MessageCount
+getReplyMessageCountF :: LineDate -> Auth -> Free ClientF MessageCount
 getReplyMessageCountF = F.client (Proxy :: Proxy GetReplyMessageCount)
 
-getPushMessageCountF :: Auth -> LineDate -> Free ClientF MessageCount
+getPushMessageCountF :: LineDate -> Auth -> Free ClientF MessageCount
 getPushMessageCountF = F.client (Proxy :: Proxy GetPushMessageCount)
 
-getMulticastMessageCountF :: Auth -> LineDate -> Free ClientF MessageCount
+getMulticastMessageCountF :: LineDate -> Auth -> Free ClientF MessageCount
 getMulticastMessageCountF = F.client (Proxy :: Proxy GetMulticastMessageCount)
 
 testProfile :: Value
@@ -80,10 +80,11 @@
   manager <- newManager defaultManagerSettings
   app $ mkClientEnv manager $ BaseUrl Http "localhost" port ""
 
-auth = mkAuth "fake"
+token :: ChannelToken
+token = "fake"
 
 runLine :: Line a -> Port -> IO (Either ServantError a)
-runLine comp port = withPort port $ runClientM $ runReaderT comp auth
+runLine comp port = withPort port $ runClientM $ runReaderT comp token
 
 app :: Application
 app = serveWithContext (Proxy :: Proxy API) serverContext $
@@ -106,15 +107,15 @@
       runLine (getRoomMemberProfile "1" "1") >=> (`shouldSatisfy` isRight)
 
   it "should send `date` query param for push message count" $ do
-    let Free (RunRequest Request{..} _) = getPushMessageCountF auth date
+    let Free (RunRequest Request{..} _) = getPushMessageCountF date (mkAuth token)
     toList requestQueryString `shouldBe` [("date", Just "20190407")]
 
   it "should send `date` query param for reply message count" $ do
-    let Free (RunRequest Request{..} _) = getReplyMessageCountF auth date
+    let Free (RunRequest Request{..} _) = getReplyMessageCountF date (mkAuth token)
     toList requestQueryString `shouldBe` [("date", Just "20190407")]
 
   it "should send `date` query param for multicast message count" $ do
-    let Free (RunRequest Request{..} _) = getMulticastMessageCountF auth date
+    let Free (RunRequest Request{..} _) = getMulticastMessageCountF date (mkAuth token)
     toList requestQueryString `shouldBe` [("date", Just "20190407")]
   where
     date = LineDate $ fromGregorian 2019 4 7
