diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2016 Joshua Koike
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/discord-rest.cabal b/discord-rest.cabal
new file mode 100644
--- /dev/null
+++ b/discord-rest.cabal
@@ -0,0 +1,48 @@
+name:                discord-rest
+version:             0.2.2
+synopsis:            An API wrapper for Discord in Haskell
+description:         Provides an api wrapper and framework for writing
+                     bots against the Discord <https://discordapp.com/> API.
+                     If for some reason hackage/stackage is failing to build
+                     documentation, a backup set is hosted at <https://jano017.github.io/Discord.hs/>
+homepage:            https://github.com/jano017/Discord.hs
+license:             MIT
+license-file:        LICENSE
+author:              Joshua Koike
+maintainer:          jkoike2013@gmail.com
+-- copyright:
+category:            Network
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Network.Discord.Rest
+                     , Network.Discord.Rest.Channel
+                     , Network.Discord.Rest.Guild
+                     , Network.Discord.Rest.User
+  other-modules:       Network.Discord.Rest.Prelude
+                     , Network.Discord.Rest.HTTP
+  -- other-extensions:
+  build-depends:       base==4.*
+                     , discord-types
+                     , aeson>=1.0 && <1.2
+                     , bytestring==0.10.*
+                     , comonad==5.*
+                     , data-default==0.7.*
+                     , hashable==1.2.*
+                     , hslogger==1.2.*
+                     , http-client==0.5.*
+                     , mtl==2.2.*
+                     , req==0.2.*
+                     , stm==2.4.*
+                     , text==1.2.*
+                     , time>=1.6 && <1.9
+                     , url==2.1.*
+  ghc-options:         -Wall
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+source-repository head
+  type : git
+  location: https://github.com/jano017/Discord.hs
diff --git a/src/Network/Discord/Rest.hs b/src/Network/Discord/Rest.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Discord/Rest.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE ExistentialQuantification, MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings, FlexibleInstances #-}
+{-# OPTIONS_HADDOCK prune, not-home #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+-- | Provides framework to interact with REST api gateways. Implementations specific to the
+--   Discord API are provided in Network.Discord.Rest.Channel, Network.Discord.Rest.Guild,
+--   and Network.Discord.Rest.User.
+module Network.Discord.Rest
+  ( module Network.Discord.Rest
+  , module Network.Discord.Rest.Prelude
+  , module Network.Discord.Rest.Channel
+  , module Network.Discord.Rest.Guild
+  , module Network.Discord.Rest.User
+  ) where
+    import Data.Maybe (fromJust)
+
+    import qualified Network.HTTP.Req as R
+    import Data.Aeson.Types
+    import Network.URL
+
+    import Network.Discord.Rest.Channel
+    import Network.Discord.Rest.Guild
+    import Network.Discord.Rest.Prelude
+    import Network.Discord.Rest.User
+    import Network.Discord.Rest.HTTP (baseUrl)
+
+    -- | Obtains a new gateway to connect to.
+    getGateway :: DiscordRest m => m URL
+    getGateway = do
+      r <- R.req R.GET (baseUrl R./: "gateway") R.NoReqBody R.jsonResponse mempty
+      return . fromJust $ importURL =<< parseMaybe getURL (R.responseBody r)
+      where
+        getURL :: Value -> Parser String
+        getURL = withObject "url" (.: "url")
diff --git a/src/Network/Discord/Rest/Channel.hs b/src/Network/Discord/Rest/Channel.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Discord/Rest/Channel.hs
@@ -0,0 +1,140 @@
+{-# LANGUAGE GADTs, OverloadedStrings, InstanceSigs, TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
+-- | Provides actions for Channel API interactions
+module Network.Discord.Rest.Channel
+  (
+    ChannelRequest(..)
+  ) where
+
+    import Data.Aeson
+    import Data.ByteString.Lazy
+    import Data.Hashable
+    import Data.Monoid (mempty, (<>))
+    import Data.Text as T
+    import Network.HTTP.Client (RequestBody (..))
+    import Network.HTTP.Client.MultipartFormData (partFileRequestBody)
+    import Network.HTTP.Req (reqBodyMultipart)
+
+    import Network.Discord.Rest.Prelude
+    import Network.Discord.Types
+    import Network.Discord.Rest.HTTP
+
+    -- | Data constructor for Channel requests. See <https://discordapp.com/developers/docs/resources/Channel Channel API>
+    data ChannelRequest a where
+      -- | Gets a channel by its id.
+      GetChannel              :: Snowflake -> ChannelRequest Channel
+      -- | Edits channels options.
+      ModifyChannel           :: ToJSON a  => Snowflake -> a -> ChannelRequest Channel
+      -- | Deletes a channel if its id doesn't equal to the id of guild.
+      DeleteChannel           :: Snowflake -> ChannelRequest Channel
+      -- | Gets a messages from a channel with limit of 100 per request.
+      GetChannelMessages      :: Snowflake -> Range -> ChannelRequest [Message]
+      -- | Gets a message in a channel by its id.
+      GetChannelMessage       :: Snowflake -> Snowflake -> ChannelRequest Message
+      -- | Sends a message to a channel.
+      CreateMessage           :: Snowflake -> Text -> Maybe Embed -> ChannelRequest Message
+      -- | Sends a message with a file to a channel.
+      UploadFile              :: Snowflake -> FilePath -> ByteString -> ChannelRequest Message
+      -- | Edits a message content.
+      EditMessage             :: Message   -> Text -> Maybe Embed -> ChannelRequest Message
+      -- | Deletes a message.
+      DeleteMessage           :: Message   -> ChannelRequest ()
+      -- | Deletes a group of messages.
+      BulkDeleteMessage       :: Snowflake -> [Message] -> ChannelRequest ()
+      -- | Edits a permission overrides for a channel.
+      EditChannelPermissions  :: ToJSON a  => Snowflake -> Snowflake -> a -> ChannelRequest ()
+      -- | Gets all instant invites to a channel.
+      GetChannelInvites       :: Snowflake -> ChannelRequest Object
+      -- | Creates an instant invite to a channel.
+      CreateChannelInvite     :: ToJSON a  => Snowflake -> a -> ChannelRequest Object
+      -- | Deletes a permission override from a channel.
+      DeleteChannelPermission :: Snowflake -> Snowflake -> ChannelRequest ()
+      -- | Sends a typing indicator a channel which lasts 10 seconds.
+      TriggerTypingIndicator  :: Snowflake -> ChannelRequest ()
+      -- | Gets all pinned messages of a channel.
+      GetPinnedMessages       :: Snowflake -> ChannelRequest [Message]
+      -- | Pins a message.
+      AddPinnedMessage        :: Snowflake -> Snowflake -> ChannelRequest ()
+      -- | Unpins a message.
+      DeletePinnedMessage     :: Snowflake -> Snowflake -> ChannelRequest ()
+
+    instance Hashable (ChannelRequest a) where
+      hashWithSalt s (GetChannel chan) = hashWithSalt s ("get_chan"::Text, chan)
+      hashWithSalt s (ModifyChannel chan _) = hashWithSalt s ("mod_chan"::Text, chan)
+      hashWithSalt s (DeleteChannel chan) = hashWithSalt s ("mod_chan"::Text, chan)
+      hashWithSalt s (GetChannelMessages chan _) = hashWithSalt s ("msg"::Text, chan)
+      hashWithSalt s (GetChannelMessage chan _) = hashWithSalt s ("get_msg"::Text, chan)
+      hashWithSalt s (CreateMessage chan _ _) = hashWithSalt s ("msg"::Text, chan)
+      hashWithSalt s (UploadFile chan _ _)  = hashWithSalt s ("msg"::Text, chan)
+      hashWithSalt s (EditMessage (Message _ chan _ _ _ _ _ _ _ _ _ _ _ _) _ _) =
+        hashWithSalt s ("get_msg"::Text, chan)
+      hashWithSalt s (DeleteMessage (Message _ chan _ _ _ _ _ _ _ _ _ _ _ _)) =
+        hashWithSalt s ("get_msg"::Text, chan)
+      hashWithSalt s (BulkDeleteMessage chan _) = hashWithSalt s ("del_msgs"::Text, chan)
+      hashWithSalt s (EditChannelPermissions chan _ _) = hashWithSalt s ("perms"::Text, chan)
+      hashWithSalt s (GetChannelInvites chan) = hashWithSalt s ("invites"::Text, chan)
+      hashWithSalt s (CreateChannelInvite chan _) = hashWithSalt s ("invites"::Text, chan)
+      hashWithSalt s (DeleteChannelPermission chan _) = hashWithSalt s ("perms"::Text, chan)
+      hashWithSalt s (TriggerTypingIndicator chan)  = hashWithSalt s ("tti"::Text, chan)
+      hashWithSalt s (GetPinnedMessages chan) = hashWithSalt s ("pins"::Text, chan)
+      hashWithSalt s (AddPinnedMessage chan _) = hashWithSalt s ("pin"::Text, chan)
+      hashWithSalt s (DeletePinnedMessage chan _) = hashWithSalt s ("pin"::Text, chan)
+
+    instance (FromJSON a) => DoFetch ChannelRequest a where
+      doFetch req = go req
+        where
+          maybeEmbed :: Maybe Embed -> [(Text, Value)]
+          maybeEmbed = maybe [] $ \embed -> ["embed" .= embed]
+
+          url = baseUrl /: "channels"
+
+          go :: DiscordRest m => ChannelRequest a -> m a
+          go r@(GetChannel chan) = makeRequest r
+            $ Get (url // chan) mempty
+          go r@(ModifyChannel chan patch) = makeRequest r
+            $ Patch (url // chan)
+              (ReqBodyJson patch) mempty
+          go r@(DeleteChannel chan) = makeRequest r
+            $ Delete (url // chan) mempty
+          go r@(GetChannelMessages chan range) = makeRequest r
+            $ Get (url // chan /: "messages") (toQueryString range)
+          go r@(GetChannelMessage chan msg) = makeRequest r
+            $ Get (url // chan /: "messages" // msg) mempty
+          go r@(CreateMessage chan msg embed) = makeRequest r
+            $ Post (url // chan /: "messages")
+              (ReqBodyJson . object $ ["content" .= msg] <> maybeEmbed embed)
+              mempty
+          go r@(UploadFile chan fileName file) = do
+            body <- reqBodyMultipart [partFileRequestBody "file" fileName $ RequestBodyLBS file]
+            makeRequest r $ Post (url // chan /: "messages")
+              body mempty
+          go r@(EditMessage (Message msg chan _ _ _ _ _ _ _ _ _ _ _ _) new embed) = makeRequest r
+            $ Patch (url // chan /: "messages" // msg)
+              (ReqBodyJson . object $ ["content" .= new] <> maybeEmbed embed)
+              mempty
+          go r@(DeleteMessage (Message msg chan _ _ _ _ _ _ _ _ _ _ _ _)) = makeRequest r
+            $ Delete (url // chan /: "messages" // msg) mempty
+          go r@(BulkDeleteMessage chan msgs) = makeRequest r
+            $ Post (url // chan /: "messages" /: "bulk-delete")
+              (ReqBodyJson $ object ["messages" .= Prelude.map messageId msgs])
+              mempty
+          go r@(EditChannelPermissions chan perm patch) = makeRequest r
+            $ Put (url // chan /: "permissions" // perm)
+              (ReqBodyJson patch) mempty
+          go r@(GetChannelInvites chan) = makeRequest r
+            $ Get (url // chan /: "invites") mempty
+          go r@(CreateChannelInvite chan patch) = makeRequest r
+            $ Post (url // chan /: "invites")
+              (ReqBodyJson patch) mempty
+          go r@(DeleteChannelPermission chan perm) = makeRequest r
+            $ Delete (url // chan /: "permissions" // perm) mempty
+          go r@(TriggerTypingIndicator chan) = makeRequest r
+            $ Post (url // chan /: "typing")
+              NoReqBody mempty
+          go r@(GetPinnedMessages chan) = makeRequest r
+            $ Get (url // chan /: "pins") mempty
+          go r@(AddPinnedMessage chan msg) = makeRequest r
+            $ Put (url // chan /: "pins" // msg)
+              NoReqBody mempty
+          go r@(DeletePinnedMessage chan msg) = makeRequest r
+            $ Delete (url // chan /: "pins" // msg) mempty
diff --git a/src/Network/Discord/Rest/Guild.hs b/src/Network/Discord/Rest/Guild.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Discord/Rest/Guild.hs
@@ -0,0 +1,227 @@
+{-# LANGUAGE GADTs, OverloadedStrings, InstanceSigs, TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
+-- | Provides actions for Guild API interactions.
+module Network.Discord.Rest.Guild
+  (
+    GuildRequest(..)
+  ) where
+
+    import Data.Aeson
+    import Data.Hashable
+    import Data.Monoid (mempty)
+    import Data.Text as T
+    import Network.HTTP.Req ((=:))
+
+    import Network.Discord.Rest.Prelude
+    import Network.Discord.Types
+    import Network.Discord.Rest.HTTP
+
+    -- | Data constructor for Guild requests. See
+    --   <https://discordapp.com/developers/docs/resources/guild Guild API>
+    data GuildRequest a where
+      -- | Returns the new 'Guild' object for the given id
+      GetGuild                 :: Snowflake -> GuildRequest Guild
+      -- | Modify a guild's settings. Returns the updated 'Guild' object on success. Fires a
+      --   Guild Update 'Event'.
+      ModifyGuild              :: ToJSON a => Snowflake -> a -> GuildRequest Guild
+      -- | Delete a guild permanently. User must be owner. Fires a Guild Delete 'Event'.
+      DeleteGuild              :: Snowflake -> GuildRequest Guild
+      -- | Returns a list of guild 'Channel' objects
+      GetGuildChannels         :: Snowflake -> GuildRequest [Channel]
+      -- | Create a new 'Channel' object for the guild. Requires 'MANAGE_CHANNELS'
+      --   permission. Returns the new 'Channel' object on success. Fires a Channel Create
+      --   'Event'
+      CreateGuildChannel       :: ToJSON a => Snowflake -> a -> GuildRequest Channel
+      -- | Modify the positions of a set of channel objects for the guild. Requires
+      --   'MANAGE_CHANNELS' permission. Returns a list of all of the guild's 'Channel'
+      --   objects on success. Fires multiple Channel Update 'Event's.
+      ModifyChanPosition       :: ToJSON a => Snowflake -> a -> GuildRequest [Channel]
+      -- | Returns a guild 'Member' object for the specified user
+      GetGuildMember           :: Snowflake -> Snowflake -> GuildRequest Member
+      -- | Returns a list of guild 'Member' objects that are members of the guild.
+      ListGuildMembers         :: Snowflake -> Range -> GuildRequest [Member]
+      -- | Adds a user to the guild, provided you have a valid oauth2 access token
+      --   for the user with the guilds.join scope. Returns the guild 'Member' as the body.
+      --   Fires a Guild Member Add 'Event'. Requires the bot to have the
+      --   CREATE_INSTANT_INVITE permission.
+      AddGuildMember           :: ToJSON a => Snowflake -> Snowflake -> a
+                                    -> GuildRequest Member
+      -- | Modify attributes of a guild 'Member'. Fires a Guild Member Update 'Event'.
+      ModifyGuildMember        :: ToJSON a => Snowflake -> Snowflake -> a
+                                    -> GuildRequest ()
+      -- | Remove a member from a guild. Requires 'KICK_MEMBER' permission. Fires a
+      --   Guild Member Remove 'Event'.
+      RemoveGuildMember        :: Snowflake -> Snowflake -> GuildRequest ()
+      -- | Returns a list of 'User' objects that are banned from this guild. Requires the
+      --   'BAN_MEMBERS' permission
+      GetGuildBans             :: Snowflake -> GuildRequest [User]
+      -- | Create a guild ban, and optionally Delete previous messages sent by the banned
+      --   user. Requires the 'BAN_MEMBERS' permission. Fires a Guild Ban Add 'Event'.
+      CreateGuildBan           :: Snowflake -> Snowflake -> Integer -> GuildRequest ()
+      -- | Remove the ban for a user. Requires the 'BAN_MEMBERS' permissions.
+      --   Fires a Guild Ban Remove 'Event'.
+      RemoveGuildBan           :: Snowflake -> Snowflake -> GuildRequest ()
+      -- | Returns a list of 'Role' objects for the guild. Requires the 'MANAGE_ROLES'
+      --   permission
+      GetGuildRoles            :: Snowflake -> GuildRequest [Role]
+      -- | Create a new 'Role' for the guild. Requires the 'MANAGE_ROLES' permission.
+      --   Returns the new role object on success. Fires a Guild Role Create 'Event'.
+      CreateGuildRole          :: Snowflake -> GuildRequest Role
+      -- | Modify the positions of a set of role objects for the guild. Requires the
+      --   'MANAGE_ROLES' permission. Returns a list of all of the guild's 'Role' objects
+      --   on success. Fires multiple Guild Role Update 'Event's.
+      ModifyGuildRolePositions :: ToJSON a => Snowflake -> [a] -> GuildRequest [Role]
+      -- | Modify a guild role. Requires the 'MANAGE_ROLES' permission. Returns the
+      --   updated 'Role' on success. Fires a Guild Role Update 'Event's.
+      ModifyGuildRole          :: ToJSON a => Snowflake -> Snowflake -> a
+                                    -> GuildRequest Role
+      -- | Delete a guild role. Requires the 'MANAGE_ROLES' permission. Fires a Guild Role
+      --   Delete 'Event'.
+      DeleteGuildRole          :: Snowflake -> Snowflake -> GuildRequest Role
+      -- | Returns an object with one 'pruned' key indicating the number of members
+      --   that would be removed in a prune operation. Requires the 'KICK_MEMBERS'
+      --   permission.
+      GetGuildPruneCount       :: Snowflake -> Integer -> GuildRequest Object
+      -- | Begin a prune operation. Requires the 'KICK_MEMBERS' permission. Returns an
+      --   object with one 'pruned' key indicating the number of members that were removed
+      --   in the prune operation. Fires multiple Guild Member Remove 'Events'.
+      BeginGuildPrune          :: Snowflake -> Integer -> GuildRequest Object
+      -- | Returns a list of 'VoiceRegion' objects for the guild. Unlike the similar /voice
+      --   route, this returns VIP servers when the guild is VIP-enabled.
+      GetGuildVoiceRegions     :: Snowflake -> GuildRequest [VoiceRegion]
+      -- | Returns a list of 'Invite' objects for the guild. Requires the 'MANAGE_GUILD'
+      --   permission.
+      GetGuildInvites          :: Snowflake -> GuildRequest [Invite]
+      -- | Return a list of 'Integration' objects for the guild. Requires the 'MANAGE_GUILD'
+      --   permission.
+      GetGuildIntegrations     :: Snowflake -> GuildRequest [Integration]
+      -- | Attach an 'Integration' object from the current user to the guild. Requires the
+      --   'MANAGE_GUILD' permission. Fires a Guild Integrations Update 'Event'.
+      CreateGuildIntegration   :: ToJSON a => Snowflake -> a -> GuildRequest ()
+      -- | Modify the behavior and settings of a 'Integration' object for the guild.
+      --   Requires the 'MANAGE_GUILD' permission. Fires a Guild Integrations Update 'Event'.
+      ModifyGuildIntegration   :: ToJSON a => Snowflake -> Snowflake -> a -> GuildRequest ()
+      -- | Delete the attached 'Integration' object for the guild. Requires the
+      --   'MANAGE_GUILD' permission. Fires a Guild Integrations Update 'Event'.
+      DeleteGuildIntegration   :: Snowflake -> Snowflake -> GuildRequest ()
+      -- | Sync an 'Integration'. Requires the 'MANAGE_GUILD' permission.
+      SyncGuildIntegration     :: Snowflake -> Snowflake -> GuildRequest ()
+      -- | Returns the 'GuildEmbed' object. Requires the 'MANAGE_GUILD' permission.
+      GetGuildEmbed            :: Snowflake -> GuildRequest GuildEmbed
+      -- | Modify a 'GuildEmbed' object for the guild. All attributes may be passed in with
+      --   JSON and modified. Requires the 'MANAGE_GUILD' permission. Returns the updated
+      --   'GuildEmbed' object.
+      ModifyGuildEmbed         :: Snowflake -> GuildEmbed -> GuildRequest GuildEmbed
+
+    instance Hashable (GuildRequest a) where
+      hashWithSalt s (GetGuild g)              = hashWithSalt s ("guild"::Text, g)
+      hashWithSalt s (ModifyGuild g _)         = hashWithSalt s ("guild"::Text, g)
+      hashWithSalt s (DeleteGuild g)           = hashWithSalt s ("guild"::Text, g)
+      hashWithSalt s (GetGuildChannels g)      = hashWithSalt s ("guild_chan"::Text, g)
+      hashWithSalt s (CreateGuildChannel g _)  = hashWithSalt s ("guild_chan"::Text, g)
+      hashWithSalt s (ModifyChanPosition g _)  = hashWithSalt s ("guild_chan"::Text, g)
+      hashWithSalt s (GetGuildMember g _)      = hashWithSalt s ("guild_memb"::Text, g)
+      hashWithSalt s (ListGuildMembers g _)    = hashWithSalt s ("guild_membs"::Text, g)
+      hashWithSalt s (AddGuildMember g _ _)    = hashWithSalt s ("guild_memb"::Text, g)
+      hashWithSalt s (ModifyGuildMember g _ _) = hashWithSalt s ("guild_memb"::Text, g)
+      hashWithSalt s (RemoveGuildMember g _)   = hashWithSalt s ("guild_memb"::Text, g)
+      hashWithSalt s (GetGuildBans g)          = hashWithSalt s ("guild_bans"::Text, g)
+      hashWithSalt s (CreateGuildBan g _ _)    = hashWithSalt s ("guild_ban" ::Text, g)
+      hashWithSalt s (RemoveGuildBan g _)      = hashWithSalt s ("guild_ban" ::Text, g)
+      hashWithSalt s (GetGuildRoles  g)        = hashWithSalt s ("guild_roles"::Text, g)
+      hashWithSalt s (CreateGuildRole g)       = hashWithSalt s ("guild_roles"::Text, g)
+      hashWithSalt s (ModifyGuildRolePositions g _)
+                                               = hashWithSalt s ("guild_roles"::Text, g)
+      hashWithSalt s (ModifyGuildRole g _ _)   = hashWithSalt s ("guild_role" ::Text, g)
+      hashWithSalt s (DeleteGuildRole g _ )    = hashWithSalt s ("guild_role" ::Text, g)
+      hashWithSalt s (GetGuildPruneCount g _)  = hashWithSalt s ("guild_prune"::Text, g)
+      hashWithSalt s (BeginGuildPrune    g _)  = hashWithSalt s ("guild_prune"::Text, g)
+      hashWithSalt s (GetGuildVoiceRegions g)  = hashWithSalt s ("guild_voice"::Text, g)
+      hashWithSalt s (GetGuildInvites g)       = hashWithSalt s ("guild_invit"::Text, g)
+      hashWithSalt s (GetGuildIntegrations g)  = hashWithSalt s ("guild_integ"::Text, g)
+      hashWithSalt s (CreateGuildIntegration g _)
+                                               = hashWithSalt s ("guild_integ"::Text, g)
+      hashWithSalt s (ModifyGuildIntegration g _ _)
+                                               = hashWithSalt s ("guild_intgr"::Text, g)
+      hashWithSalt s (DeleteGuildIntegration g _)
+                                               = hashWithSalt s ("guild_intgr"::Text, g)
+      hashWithSalt s (SyncGuildIntegration g _)= hashWithSalt s ("guild_sync" ::Text, g)
+      hashWithSalt s (GetGuildEmbed g)         = hashWithSalt s ("guild_embed"::Text, g)
+      hashWithSalt s (ModifyGuildEmbed g _)    = hashWithSalt s ("guild_embed"::Text, g)
+
+    instance (FromJSON a) => DoFetch GuildRequest a where
+      doFetch = go
+        where
+          url = baseUrl /: "guilds"
+          go :: DiscordRest m => GuildRequest a -> m a
+          go r@(GetGuild guild) = makeRequest r
+            $ Get (url // guild) mempty
+          go r@(ModifyGuild guild patch) = makeRequest r
+            $ Patch (url // guild) (ReqBodyJson patch) mempty
+          go r@(DeleteGuild guild) = makeRequest r
+            $ Delete (url // guild) mempty
+          go r@(GetGuildChannels guild) = makeRequest r
+            $ Get (url // guild /: "channels") mempty
+          go r@(CreateGuildChannel guild patch) = makeRequest r
+            $ Post (url // guild /: "channels") (ReqBodyJson patch) mempty
+          go r@(ModifyChanPosition guild patch) = makeRequest r
+            $ Post (url // guild /: "channels") (ReqBodyJson patch) mempty
+          go r@(GetGuildMember guild member) = makeRequest r
+            $ Get (url // guild /: "members" // member) mempty
+          go r@(ListGuildMembers guild range) = makeRequest r
+            $ Get (url // guild /: "members") (toQueryString range)
+          go r@(AddGuildMember guild user patch) = makeRequest r
+            $ Put (url // guild /: "members" // user) (ReqBodyJson patch) mempty
+          go r@(ModifyGuildMember guild member patch) = makeRequest r
+            $ Patch (url // guild /: "members" // member) (ReqBodyJson patch) mempty
+          go r@(RemoveGuildMember guild user) = makeRequest r
+            $ Delete (url // guild /: "members" // user) mempty
+          go r@(GetGuildBans guild) = makeRequest r
+            $ Get (url // guild /: "bans") mempty
+          go r@(CreateGuildBan guild user msgs) = makeRequest r
+            $ Put (url // guild /: "bans" // user)
+              (ReqBodyJson $ object [ "delete-message-days" .= msgs])
+              mempty
+          go r@(RemoveGuildBan guild ban) = makeRequest r
+            $ Delete (url // guild /: "bans" // ban) mempty
+          go r@(GetGuildRoles guild) = makeRequest r
+            $ Get (url // guild /: "roles") mempty
+          go r@(CreateGuildRole guild) = makeRequest r
+            $ Post (url // guild /: "roles")
+              NoReqBody mempty
+          go r@(ModifyGuildRolePositions guild patch) = makeRequest r
+            $ Post (url // guild /: "roles")
+            (ReqBodyJson patch) mempty
+          go r@(ModifyGuildRole guild role patch) = makeRequest r
+            $ Post (url // guild /: "roles" // role)
+              (ReqBodyJson patch) mempty
+          go r@(DeleteGuildRole guild role) = makeRequest r
+            $ Delete (url // guild /: "roles" // role) mempty
+          go r@(GetGuildPruneCount guild days) = makeRequest r
+            $ Get (url // guild /: "prune") ("days" =: days)
+          go r@(BeginGuildPrune guild days) = makeRequest r
+            $ Post (url // guild /: "prune")
+              NoReqBody ("days" =: days)
+          go r@(GetGuildVoiceRegions guild) = makeRequest r
+            $ Get (url // guild /: "regions") mempty
+          go r@(GetGuildInvites guild) = makeRequest r
+            $ Get (url // guild /: "invites") mempty
+          go r@(GetGuildIntegrations guild) = makeRequest r
+            $ Get (url // guild /: "integrations") mempty
+          go r@(CreateGuildIntegration guild patch) = makeRequest r
+            $ Post (url // guild /: "integrations")
+              (ReqBodyJson patch) mempty
+          go r@(ModifyGuildIntegration guild integ patch) = makeRequest r
+            $ Patch (url // guild /: "integrations" // integ)
+              (ReqBodyJson patch) mempty
+          go r@(DeleteGuildIntegration guild integ) = makeRequest r
+            $ Delete (url // guild /: "integrations" // integ)
+              mempty
+          go r@(SyncGuildIntegration guild integ) = makeRequest r
+            $ Post (url // guild /: "integrations" // integ)
+              NoReqBody mempty
+          go r@(GetGuildEmbed guild) = makeRequest r
+            $ Get (url // guild /: "integrations") mempty
+          go r@(ModifyGuildEmbed guild patch) = makeRequest r
+            $ Patch (url // guild /: "embed")
+              (ReqBodyJson patch) mempty
diff --git a/src/Network/Discord/Rest/HTTP.hs b/src/Network/Discord/Rest/HTTP.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Discord/Rest/HTTP.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE GADTs, OverloadedStrings, InstanceSigs, TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
+{-# LANGUAGE DataKinds, ScopedTypeVariables, Rank2Types #-}
+-- | Provide HTTP primitives
+module Network.Discord.Rest.HTTP
+  ( JsonRequest(..)
+  , R.ReqBodyJson(..)
+  , R.NoReqBody(..)
+  , baseUrl
+  , fetch
+  , makeRequest
+  , (//)
+  , (R./:)
+  ) where
+
+    import Data.Semigroup ((<>))
+
+    import Control.Monad (when)
+    import Data.Aeson
+    import Data.ByteString.Char8 (pack, ByteString)
+    import Data.Hashable
+    import Data.Maybe (fromMaybe)
+    import qualified Data.Text as T (pack)
+    import qualified Network.HTTP.Req as R
+
+    import Network.Discord.Rest.Prelude
+    import Network.Discord.Types
+
+    -- | The base url (Req) for API requests
+    baseUrl :: R.Url 'R.Https
+    baseUrl = R.https "discordapp.com" R./: "api" R./: apiVersion
+      where apiVersion = "v6"
+
+    -- | Construct base options with auth from Discord state
+    baseRequestOptions :: DiscordRest m => m Option
+    baseRequestOptions = do
+      a <- auth
+      v <- version
+      return $ R.header "Authorization" (pack . show $ a)
+            <> R.header "User-Agent" (pack $ "DiscordBot (https://github.com/jano017/Discord.hs,"
+                                          ++ v ++ ")")
+    infixl 5 //
+    (//) :: Show a => R.Url scheme -> a -> R.Url scheme
+    url // part = url R./: (T.pack $ show part)
+
+    type Option = R.Option 'R.Https
+
+    -- | Represtents a HTTP request made to an API that supplies a Json response
+    data JsonRequest r where
+      Delete ::  FromJSON r                => R.Url 'R.Https      -> Option -> JsonRequest r
+      Get    ::  FromJSON r                => R.Url 'R.Https      -> Option -> JsonRequest r
+      Patch  :: (FromJSON r, R.HttpBody a) => R.Url 'R.Https -> a -> Option -> JsonRequest r
+      Post   :: (FromJSON r, R.HttpBody a) => R.Url 'R.Https -> a -> Option -> JsonRequest r
+      Put    :: (FromJSON r, R.HttpBody a) => R.Url 'R.Https -> a -> Option -> JsonRequest r
+
+    fetch :: (FromJSON r, DiscordRest m) => JsonRequest r -> m (R.JsonResponse r)
+    fetch (Delete url      opts) = R.req R.DELETE url R.NoReqBody R.jsonResponse =<< (<> opts) <$> baseRequestOptions
+    fetch (Get    url      opts) = R.req R.GET    url R.NoReqBody R.jsonResponse =<< (<> opts) <$> baseRequestOptions
+    fetch (Patch  url body opts) = R.req R.PATCH  url body        R.jsonResponse =<< (<> opts) <$> baseRequestOptions
+    fetch (Post   url body opts) = R.req R.POST   url body        R.jsonResponse =<< (<> opts) <$> baseRequestOptions
+    fetch (Put    url body opts) = R.req R.PUT    url body        R.jsonResponse =<< (<> opts) <$> baseRequestOptions
+
+    makeRequest :: (FromJSON r, DiscordRest m, DoFetch f r) 
+      => f r -> JsonRequest r -> m r
+    makeRequest req action = do
+      waitRateLimit req
+      resp <- fetch action
+      when (parseHeader resp "X-RateLimit-Remaining" 1 < 1) $
+        setRateLimit req $ parseHeader resp "X-RateLimit-Reset" 0
+      return $ R.responseBody resp
+      where
+        parseHeader :: R.HttpResponse resp => resp -> ByteString -> Int -> Int
+        parseHeader resp header def = fromMaybe def $ decodeStrict =<< R.responseHeader resp header
+
+    instance Hashable (JsonRequest r) where
+      hashWithSalt s (Delete url _)   = hashWithSalt s $ show url
+      hashWithSalt s (Get    url _)   = hashWithSalt s $ show url
+      hashWithSalt s (Patch  url _ _) = hashWithSalt s $ show url
+      hashWithSalt s (Post   url _ _) = hashWithSalt s $ show url
+      hashWithSalt s (Put    url _ _) = hashWithSalt s $ show url
+    
+    -- | Base implementation of DoFetch, allows arbitrary HTTP requests to be performed
+    instance (FromJSON r) => DoFetch JsonRequest r where
+      doFetch req = R.responseBody <$> fetch req
diff --git a/src/Network/Discord/Rest/Prelude.hs b/src/Network/Discord/Rest/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Discord/Rest/Prelude.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE DataKinds, OverloadedStrings, MultiParamTypeClasses #-}
+{-# LANGUAGE AllowAmbiguousTypes, FlexibleInstances, UndecidableInstances #-}
+{-# LANGUAGE FunctionalDependencies, KindSignatures #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+-- | Utility and base types and functions for the Discord Rest API
+module Network.Discord.Rest.Prelude where
+  import Control.Concurrent (threadDelay)
+  import Control.Monad (when)
+  import Control.Exception (throwIO)
+
+  import Control.Monad.IO.Class
+  import Data.Default
+  import Data.Hashable
+  import Data.Monoid ((<>))
+  import Data.Time.Clock.POSIX
+  import Network.HTTP.Req (Option, Scheme(..), (=:), MonadHttp(..))
+  import System.Log.Logger
+
+  import Network.Discord.Types
+
+  -- | The base url for API requests
+  baseURL :: String
+  baseURL = "https://discordapp.com/api/v6"
+
+  class (MonadIO m, DiscordAuth m) => DiscordRest m where
+    getRateLimit  :: DoFetch f a => f a -> m (Maybe Int)
+
+    setRateLimit  :: DoFetch f a => f a -> Int -> m ()
+
+    waitRateLimit :: DoFetch f a => f a -> m ()
+    waitRateLimit endpoint = do
+      rl <- getRateLimit endpoint
+      case rl of
+        Nothing -> return ()
+        Just l -> do
+          now <- liftIO (fmap round getPOSIXTime :: IO Int)
+          when (l > now) . liftIO $ do
+            infoM "Discord-hs.Rest" "Hit rate limit, backing off"
+            threadDelay $ 1000000 * (l - now)
+            infoM "Discord-hs.Rest" "Done waiting"
+          return ()
+
+  instance (MonadIO m, DiscordRest m) => MonadHttp m where
+    handleHttpException = liftIO . throwIO
+
+  -- | Class over which performing a data retrieval action is defined
+  class Hashable (a b) => DoFetch (a :: * -> *) b | a b -> b where
+    doFetch :: DiscordRest m => a b -> m b
+  
+  -- | Represents a range of 'Snowflake's
+  data Range = Range { after :: Snowflake, before :: Snowflake, limit :: Int}
+
+  instance Default Range where
+    def = Range 0 18446744073709551615 100
+  
+  -- | Convert a Range to a query string
+  toQueryString :: Range -> Option 'Https
+  toQueryString (Range a b l)
+    =  "after"  =: show a 
+    <> "before" =: show b 
+    <> "limit"  =: show l
diff --git a/src/Network/Discord/Rest/User.hs b/src/Network/Discord/Rest/User.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Discord/Rest/User.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE GADTs, OverloadedStrings, InstanceSigs, TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
+-- | Provide actions for User API interactions.
+module Network.Discord.Rest.User
+  (
+    UserRequest(..)
+  ) where
+
+    import Data.Aeson
+    import Data.Hashable
+    import Data.Monoid (mempty)
+    import Data.Text as T
+
+    import Network.Discord.Rest.Prelude
+    import Network.Discord.Types
+    import Network.Discord.Rest.HTTP
+
+    -- | Data constructor for User requests. See
+    --   <https://discordapp.com/developers/docs/resources/user User API>
+    data UserRequest a where
+      -- | Returns the 'User' object of the requester's account. For OAuth2, this requires
+      --   the identify scope, which will return the object without an email, and optionally
+      --   the email scope, which returns the object with an email.
+      GetCurrentUser       :: UserRequest User
+      -- | Returns a 'User' for a given user ID
+      GetUser              :: Snowflake -> UserRequest User
+      -- | Modify the requestors user account settings. Returns a 'User' object on success.
+      ModifyCurrentUser    :: ToJSON a => a -> UserRequest User
+      -- | Returns a list of user 'Guild' objects the current user is a member of.
+      --   Requires the guilds OAuth2 scope.
+      GetCurrentUserGuilds :: Range -> UserRequest Guild
+      -- | Leave a guild.
+      LeaveGuild           :: Snowflake -> UserRequest ()
+      -- | Returns a list of DM 'Channel' objects
+      GetUserDMs           :: UserRequest [Channel]
+      -- | Create a new DM channel with a user. Returns a DM 'Channel' object.
+      CreateDM             :: Snowflake -> UserRequest Channel
+
+    instance Hashable (UserRequest a) where
+      hashWithSalt s (GetCurrentUser)         = hashWithSalt s  ("me"::Text)
+      hashWithSalt s (GetUser _)              = hashWithSalt s  ("user"::Text)
+      hashWithSalt s (ModifyCurrentUser _)    = hashWithSalt s  ("modify_user"::Text)
+      hashWithSalt s (GetCurrentUserGuilds _) = hashWithSalt s  ("get_user_guilds"::Text)
+      hashWithSalt s (LeaveGuild g)           = hashWithSalt s  ("leave_guild"::Text, g)
+      hashWithSalt s (GetUserDMs)             = hashWithSalt s  ("get_dms"::Text)
+      hashWithSalt s (CreateDM _)             = hashWithSalt s  ("make_dm"::Text)
+
+    instance (FromJSON a) => DoFetch UserRequest a where
+      doFetch = go
+        where
+          url = baseUrl /: "users"
+          go :: DiscordRest m => UserRequest a -> m a
+          go r@(GetCurrentUser) = makeRequest r
+            $ Get (url /: "@me") mempty
+
+          go r@(GetUser user) = makeRequest r
+            $ Get (url // user ) mempty
+
+          go r@(ModifyCurrentUser patch) = makeRequest r
+            $ Patch (url /: "@me")  (ReqBodyJson patch) mempty
+
+          go r@(GetCurrentUserGuilds range) = makeRequest r
+            $ Get url $ toQueryString range
+
+          go r@(LeaveGuild guild) = makeRequest r
+            $ Delete (url /: "@me" /: "guilds" // guild) mempty
+
+          go r@(GetUserDMs) = makeRequest r
+            $ Get (url /: "@me" /: "channels") mempty
+
+          go r@(CreateDM user) = makeRequest r
+            $ Post (url /: "@me" /: "channels")
+            (ReqBodyJson $ object ["recipient_id" .= user])
+            mempty
