diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-reddit for haskell [![Build Status](https://travis-ci.org/intolerable/reddit.svg?branch=v0.1)](https://travis-ci.org/intolerable/reddit)
+reddit for haskell [![Build Status](https://travis-ci.org/intolerable/reddit.svg?branch=master)](https://travis-ci.org/intolerable/reddit)
 ---
 
 A Haskell library for interacting with the [Reddit API](http://reddit.com/api).
@@ -8,6 +8,7 @@
 Let's get all the posts from the frontpage of Reddit and write a summary of each of them to the console:
 
 ```haskell
+{-# LANGUAGE OverloadedStrings #-}
 import Reddit
 import Reddit.Types.Post
 
@@ -27,9 +28,10 @@
 tshow = Text.pack . show
 ```
 
-Let's check to see which of a group of has the highest link karma:
+Let's check to see which of a group of users has the highest link karma:
 
 ```haskell
+{-# LANGUAGE OverloadedStrings #-}
 import Reddit
 import Reddit.Types.User
 
@@ -39,8 +41,8 @@
 usersToCheck = ["nikita-volkov", "simonmar", "bos", "roche"]
 
 main = runRedditAnon $ do
-    infos <- mapM (getUserInfo . Username) usersToCheck
-    return $ maximumBy (comparing linkKarma) infos
+  infos <- mapM (getUserInfo . Username) usersToCheck
+  return $ maximumBy (comparing linkKarma) infos
 ```
 
 Testing
diff --git a/reddit.cabal b/reddit.cabal
--- a/reddit.cabal
+++ b/reddit.cabal
@@ -1,14 +1,14 @@
 name:                reddit
-version:             0.1.1.0
+version:             0.2.0.0
 synopsis:            Library for interfacing with Reddit's API
 description:
   A library for interfacing with Reddit's API in Haskell. Handles
-  logins, rate-limiting and converted to and from JSON responses.
+  logins, rate-limiting and converting to and from JSON responses.
 
   Supports most user-facing Reddit API functions, as well as some
   moderator endpoints.
 
-  Check out the readme at https://github.com/intolerable/reddit.
+  Check out the readme at <https://github.com/intolerable/reddit>.
   Contributions are welcome.
 
 license:             BSD2
@@ -73,7 +73,6 @@
     Reddit.Routes.Message
     Reddit.Routes.Moderation
     Reddit.Routes.Post
-    Reddit.Routes.Run
     Reddit.Routes.Search
     Reddit.Routes.Subreddit
     Reddit.Routes.Thing
@@ -83,25 +82,29 @@
     Reddit.Types.Empty
     Reddit.Utilities
   default-extensions:
-    FlexibleInstances,
+    FlexibleInstances
+    GADTs
+    GeneralizedNewtypeDeriving
+    LambdaCase
     OverloadedStrings
   default-language: Haskell2010
   hs-source-dirs: src/
   build-depends:
     base >= 4.6 && < 4.9,
-    aeson,
-    api-builder >= 0.9 && < 0.10,
-    bytestring,
-    data-default,
-    http-conduit,
-    http-types,
-    network,
-    stm,
-    text,
-    time,
+    aeson >= 0.9 && < 0.10,
+    api-builder >= 0.10 && < 0.12,
+    bytestring == 0.10.*,
+    data-default-class == 0.0.1,
+    free >= 4 && < 5,
+    http-client >= 0.4.11 && < 0.4.21,
+    http-client-tls >= 0.2 && < 0.2.3,
+    http-types == 0.8.*,
+    network == 2.6.*,
+    text == 1.*,
+    time == 1.5.*,
     transformers == 0.4.*,
-    unordered-containers,
-    vector
+    unordered-containers == 0.2.5.*,
+    vector >= 0.10 && < 0.12
   ghc-options: -Wall
 
 test-suite test
@@ -113,9 +116,9 @@
   build-depends:
     base == 4.*,
     Cabal >= 1.16.0,
+    aeson,
     api-builder,
     bytestring,
-    http-conduit,
     reddit,
     hspec,
     text,
@@ -136,7 +139,8 @@
     Cabal >= 1.16.0,
     api-builder,
     bytestring,
-    http-conduit,
+    http-client,
+    http-client-tls,
     reddit,
     hspec,
     text,
@@ -156,7 +160,6 @@
     base == 4.*,
     Cabal >= 1.16.0,
     api-builder,
-    http-conduit,
     reddit,
     hspec,
     text,
diff --git a/src/Reddit.hs b/src/Reddit.hs
--- a/src/Reddit.hs
+++ b/src/Reddit.hs
@@ -8,7 +8,10 @@
   ( runReddit
   , runRedditAnon
   , runRedditWith
+  , runResumeRedditWith
+  , interpretIO
   , RedditOptions(..)
+  , defaultRedditOptions
   , LoginMethod(..)
   -- * Re-exports
   , APIError(..)
@@ -23,14 +26,18 @@
 import Reddit.Types
 import Reddit.Types.Reddit hiding (info, should)
 
-import Control.Concurrent.STM.TVar
+import Control.Concurrent
+import Control.Monad
 import Control.Monad.IO.Class
+import Control.Monad.Trans.Free
 import Data.ByteString.Char8 (ByteString)
-import Data.Default
+import Data.Default.Class
 import Data.Text (Text)
 import Data.Text.Encoding (encodeUtf8)
-import Network.API.Builder
-import Network.HTTP.Conduit
+import Network.API.Builder as API
+import Network.HTTP.Client
+import Network.HTTP.Client.TLS
+import Network.HTTP.Types
 
 -- | Options for how we should run the 'Reddit' action.
 --
@@ -53,6 +60,10 @@
 instance Default RedditOptions where
   def = RedditOptions True Nothing Anonymous Nothing
 
+-- | The default set of options
+defaultRedditOptions :: RedditOptions
+defaultRedditOptions = def
+
 -- | Should we log in to Reddit? If so, should we use a stored set of credentials
 --   or get a new fresh set?
 data LoginMethod = Anonymous -- ^ Don't login, instead use an anonymous account
@@ -82,23 +93,78 @@
 --   most things, but it's handy if you want to persist a connection over multiple 'Reddit' sessions or
 --   use a custom user agent string.
 runRedditWith :: MonadIO m => RedditOptions -> RedditT m a -> m (Either (APIError RedditError) a)
-runRedditWith (RedditOptions rl man lm ua) (RedditT reddit) = do
-  rli <- liftIO $ newTVarIO $ RateLimits rl Nothing
+runRedditWith opts reddit = liftM dropResume $ runResumeRedditWith opts reddit
+
+-- | Run a 'Reddit' or 'RedditT' action with custom settings. You probably won't need this function for
+--   most things, but it's handy if you want to persist a connection over multiple 'Reddit' sessions or
+--   use a custom user agent string.
+runResumeRedditWith :: MonadIO m => RedditOptions -> RedditT m a -> m (Either (APIError RedditError, Maybe (RedditT m a)) a)
+runResumeRedditWith (RedditOptions rl man lm _ua) reddit = do
   manager <- case man of
     Just m -> return m
-    Nothing -> liftIO $ newManager conduitManagerSettings
-  (res, _, _) <- runAPI builder manager rli $ do
-    customizeRequest $ addHeader ua
-    case lm of
-      StoredDetails (LoginDetails (Modhash mh) cj) ->
-        customizeRequest $ \r ->
-          addHeader ua r { cookieJar = Just cj
-                         , requestHeaders = ("X-Modhash", encodeUtf8 mh):requestHeaders r }
-      Credentials user pass -> do
-        LoginDetails (Modhash mh) cj <- unRedditT $ login user pass
-        customizeRequest $ \r ->
-          addHeader ua r { cookieJar = Just cj
-                         , requestHeaders = ("X-Modhash", encodeUtf8 mh):requestHeaders r }
-      Anonymous -> return ()
-    reddit
+    Nothing -> liftIO $ newManager tlsManagerSettings
+  loginCreds <- case lm of
+    Anonymous -> return $ Right Nothing
+    StoredDetails ld -> return $ Right $ Just ld
+    Credentials user pass -> liftM (fmap Just) $ interpretIO (RedditState loginBaseURL rl manager [] Nothing) $ login user pass
+  case loginCreds of
+    Left (err, _) -> return $ Left (err, Just reddit)
+    Right lds ->
+      interpretIO
+        (RedditState mainBaseURL rl manager [("User-Agent", "reddit-haskell dev version")] lds) reddit
+
+interpretIO :: MonadIO m => RedditState -> RedditT m a -> m (Either (APIError RedditError, Maybe (RedditT m a)) a)
+interpretIO rstate (RedditT r) =
+  runFreeT r >>= \case
+    Pure x -> return $ Right x
+    Free (WithBaseURL u x n) ->
+      interpretIO (rstate { currentBaseURL = u }) x >>= \case
+        Left (err, Just resume) ->
+          return $ Left (err, Just $ resume >>= RedditT . n)
+        Left (err, Nothing) -> return $ Left (err, Nothing)
+        Right res -> interpretIO rstate $ RedditT $ n res
+    Free (FailWith x) -> return $ Left (x, Nothing)
+    Free (Nest x n) ->
+      interpretIO rstate $ RedditT $ wrap $ NestResuming x (n . dropResume)
+    Free (NestResuming x n) -> do
+      res <- interpretIO rstate x
+      interpretIO rstate $ RedditT $ n res
+    Free (RunRoute route n) ->
+      interpretIO rstate $ RedditT $ wrap $ ReceiveRoute route (n . unwrapJSON)
+    Free (ReceiveRoute route n) ->
+      handleReceive route rstate >>= \case
+        Left err@(APIError (RateLimitError secs _)) ->
+          if rateLimit rstate
+            then do
+              liftIO $ threadDelay $ fromInteger secs * 1000 * 1000
+              interpretIO rstate $ RedditT $ wrap $ ReceiveRoute route n
+            else return $ Left (err, Just $ RedditT $ wrap $ ReceiveRoute route n)
+        Left err -> return $ Left (err, Just $ RedditT $ wrap $ ReceiveRoute route n)
+        Right x -> interpretIO rstate $ RedditT $ n x
+
+dropResume :: Either (APIError RedditError, Maybe (RedditT m a)) a -> Either (APIError RedditError) a
+dropResume (Left (x, _)) = Left x
+dropResume (Right x) = Right x
+
+handleReceive :: (MonadIO m, Receivable a) => Route -> RedditState -> m (Either (APIError RedditError) a)
+handleReceive r rstate = do
+  (res, _, _) <- runAPI (builderFromState rstate) (connMgr rstate) () $
+    API.runRoute r
   return res
+
+builderFromState :: RedditState -> Builder
+builderFromState (RedditState burl _ _ hdrs (Just (LoginDetails (Modhash mh) cj))) =
+  Builder "Reddit" burl addAPIType $
+    \req -> addHeaders (("X-Modhash", encodeUtf8 mh):hdrs) req { cookieJar = Just cj }
+builderFromState (RedditState burl _ _ hdrs Nothing) =
+  Builder "Reddit" burl addAPIType (addHeaders hdrs)
+
+addHeaders :: [Header] -> Request -> Request
+addHeaders xs req = req { requestHeaders = requestHeaders req ++ xs }
+
+data RedditState =
+  RedditState { currentBaseURL :: Text
+              , rateLimit :: Bool
+              , connMgr :: Manager
+              , _extraHeaders :: [Header]
+              , _creds :: Maybe LoginDetails }
diff --git a/src/Reddit/Actions/Captcha.hs b/src/Reddit/Actions/Captcha.hs
--- a/src/Reddit/Actions/Captcha.hs
+++ b/src/Reddit/Actions/Captcha.hs
@@ -7,21 +7,18 @@
   ( needsCaptcha
   , newCaptcha ) where
 
-import Reddit.Routes.Run
 import Reddit.Types.Captcha
 import Reddit.Types.Reddit
 import qualified Reddit.Routes.Captcha as Route
 
-import Control.Monad.IO.Class
-
 -- | Find out if the account currently logged in requires a captcha to be submitted for
 --   certain requests (like sending a private message or submitting a post).
-needsCaptcha :: MonadIO m => RedditT m Bool
+needsCaptcha :: Monad m => RedditT m Bool
 needsCaptcha = runRoute Route.needsCaptcha
 
 -- | Returns the ID of a captcha to be completed (the image for which can be found at
 --   <http://reddit.com/captcha/$CAPTCHA_ID>)
-newCaptcha :: MonadIO m => RedditT m CaptchaID
+newCaptcha :: Monad m => RedditT m CaptchaID
 newCaptcha = do
   POSTWrapped c <- runRoute Route.newCaptcha
   return c
diff --git a/src/Reddit/Actions/Comment.hs b/src/Reddit/Actions/Comment.hs
--- a/src/Reddit/Actions/Comment.hs
+++ b/src/Reddit/Actions/Comment.hs
@@ -10,7 +10,6 @@
   , deleteComment
   , removeComment ) where
 
-import Reddit.Routes.Run
 import Reddit.Types.Comment
 import Reddit.Types.Empty
 import Reddit.Types.Error
@@ -21,8 +20,7 @@
 import Reddit.Types.Subreddit
 import qualified Reddit.Routes as Route
 
-import Control.Monad.IO.Class
-import Data.Default
+import Data.Default.Class
 import Data.Text (Text)
 import Network.API.Builder (APIError(..))
 
@@ -30,18 +28,18 @@
 --   This maps to <http://reddit.com/r/$SUBREDDIT/comments>, or <http://reddit.com/comments>
 --   if the subreddit is not specified.
 --   Note that none of the comments returned will have any child comments.
-getNewComments :: MonadIO m => Maybe SubredditName -> RedditT m CommentListing
+getNewComments :: Monad m => Maybe SubredditName -> RedditT m CommentListing
 getNewComments = getNewComments' def
 
 -- | Get a 'CommentListing' for the most recent comments with the specified 'Options' and
 --   'SubredditName'. Note that none of the comments returned will have any child comments.
 --   If the 'Options' is 'def', then this function is identical to 'getNewComments'.
-getNewComments' :: MonadIO m => Options CommentID -> Maybe SubredditName -> RedditT m CommentListing
+getNewComments' :: Monad m => Options CommentID -> Maybe SubredditName -> RedditT m CommentListing
 getNewComments' opts r = runRoute $ Route.newComments opts r
 
 -- | Expand children comments that weren't fetched on initial load.
 --   Equivalent to the web UI's "load more comments" button.
-getMoreChildren :: MonadIO m
+getMoreChildren :: Monad m
                 => PostID -- ^ @PostID@ for the top-level
                 -> [CommentID] -- ^ List of @CommentID@s to expand
                 -> RedditT m [CommentReference]
@@ -53,7 +51,7 @@
   return $ rs ++ more
 
 -- | Given a 'CommentID', 'getCommentInfo' will return the full details for that comment.
-getCommentInfo :: MonadIO m => CommentID -> RedditT m Comment
+getCommentInfo :: Monad m => CommentID -> RedditT m Comment
 getCommentInfo c = do
   res <- getCommentsInfo [c]
   case res of
@@ -63,7 +61,7 @@
 -- | Given a list of 'CommentID's, 'getCommentsInfo' will return another list containing
 --   the full details for all the comments. Note that Reddit's
 --   API imposes a limitation of 100 comments per request, so this function will fail immediately if given a list of more than 100 IDs.
-getCommentsInfo :: MonadIO m => [CommentID] -> RedditT m CommentListing
+getCommentsInfo :: Monad m => [CommentID] -> RedditT m CommentListing
 getCommentsInfo cs =
   if null $ drop 100 cs
     then do
@@ -79,7 +77,7 @@
     sameLength _ _ = False
 
 -- | Edit a comment.
-editComment :: MonadIO m
+editComment :: Monad m
             => CommentID -- ^ Comment to edit
             -> Text -- ^ New comment text
             -> RedditT m Comment
@@ -89,10 +87,10 @@
 
 -- | Deletes one of your own comments. Note that this is different from
 --   removing a comment as a moderator action.
-deleteComment :: MonadIO m => CommentID -> RedditT m ()
+deleteComment :: Monad m => CommentID -> RedditT m ()
 deleteComment = nothing . runRoute . Route.delete
 
 -- | Removes a comment (as a moderator action). Note that this is different
 --   from deleting a comment.
-removeComment :: MonadIO m => CommentID -> RedditT m ()
+removeComment :: Monad m => CommentID -> RedditT m ()
 removeComment = nothing . runRoute . Route.removePost False
diff --git a/src/Reddit/Actions/Flair.hs b/src/Reddit/Actions/Flair.hs
--- a/src/Reddit/Actions/Flair.hs
+++ b/src/Reddit/Actions/Flair.hs
@@ -6,7 +6,6 @@
   , flairCSV ) where
 
 import Reddit.Routes.Flair
-import Reddit.Routes.Run
 import Reddit.Types.Empty
 import Reddit.Types.Flair
 import Reddit.Types.Options
@@ -15,24 +14,23 @@
 import Reddit.Types.User
 
 import Control.Monad
-import Control.Monad.IO.Class
 import Data.Aeson
-import Data.Default
+import Data.Default.Class
 import Data.Text (Text)
 
 -- | Get the flair list for a subreddit. Requires moderator privileges on
 --   the subreddit.
-getFlairList :: MonadIO m => SubredditName -> RedditT m FlairListing
+getFlairList :: Monad m => SubredditName -> RedditT m FlairListing
 getFlairList = getFlairList' def
 
 -- | Get the flair list for a subreddit (with 'Options'). Requires moderator
 --   privileges on the subreddit.
-getFlairList' :: MonadIO m => Options UserID -> SubredditName -> RedditT m FlairListing
+getFlairList' :: Monad m => Options UserID -> SubredditName -> RedditT m FlairListing
 getFlairList' opts r = liftM flistToListing $ runRoute (flairList opts r)
 
 -- | Add link flair to the subreddit-wide template for a subreddit that you moderate.
 --   Requires moderator privileges on the subreddit.
-addLinkFlair :: MonadIO m
+addLinkFlair :: Monad m
              => SubredditName -- ^ The subreddit whose template you want to modify
              -> Text -- ^ The intended CSS class of the new link flair
              -> Text -- ^ The intended text label of the new link flair
@@ -41,6 +39,6 @@
 addLinkFlair r c l e =
   nothing $ runRoute $ addLinkFlairTemplate r c l e
 
-flairCSV :: MonadIO m => SubredditName -> [(Username, Text, Text)] -> RedditT m Value
+flairCSV :: Monad m => SubredditName -> [(Username, Text, Text)] -> RedditT m Value
 flairCSV r sets =
   runRoute $ flairCSVRoute r sets
diff --git a/src/Reddit/Actions/Message.hs b/src/Reddit/Actions/Message.hs
--- a/src/Reddit/Actions/Message.hs
+++ b/src/Reddit/Actions/Message.hs
@@ -7,9 +7,9 @@
   , getUnread'
   , markRead
   , sendMessage
-  , sendMessageWithCaptcha ) where
+  , sendMessageWithCaptcha
+  , replyMessage ) where
 
-import Reddit.Routes.Run
 import Reddit.Types.Captcha
 import Reddit.Types.Empty
 import Reddit.Types.Listing
@@ -19,38 +19,38 @@
 import Reddit.Types.Thing
 import Reddit.Types.User
 import qualified Reddit.Routes.Message as Route
+import qualified Reddit.Routes.Thing as Route
 
-import Control.Monad.IO.Class
-import Data.Default
+import Data.Default.Class
 import Data.Text (Text)
 import Network.API.Builder.Query
 
 -- | Get the message inbox for the current user.
-getInbox :: MonadIO m => RedditT m (Listing MessageKind Message)
+getInbox :: Monad m => RedditT m (Listing MessageKind Message)
 getInbox = runRoute $ Route.inbox False def
 
 -- | Don't use this for watching for new messages, Reddit's ordering on
 -- |   inbox messages is odd and not likely to work how you expect.
-getInbox' :: MonadIO m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)
+getInbox' :: Monad m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)
 getInbox' m o = runRoute $ Route.inbox m o
 
 -- | Get any unread messages for the current user.
-getUnread :: MonadIO m => RedditT m (Listing MessageKind Message)
+getUnread :: Monad m => RedditT m (Listing MessageKind Message)
 getUnread = runRoute $ Route.unread False def
 
 -- | Get unread messages for the current user, with options.
-getUnread' :: MonadIO m
+getUnread' :: Monad m
            => Bool -- ^ Whether the orangered notifier should be marked "off"
            -> Options MessageKind
            -> RedditT m (Listing MessageKind Message)
 getUnread' m o = runRoute $ Route.unread m o
 
 -- | Mark a message as read.
-markRead :: (ToQuery a, Thing a, MonadIO m) => a -> RedditT m ()
+markRead :: (ToQuery a, Thing a, Monad m) => a -> RedditT m ()
 markRead = nothing . runRoute . Route.readMessage
 
 -- | Send a private message to another user.
-sendMessage :: MonadIO m
+sendMessage :: Monad m
             => Username -- ^ The username to send the message to
             -> Text -- ^ The subject of the message being sent
             -> Text -- ^ The body of the message being sent
@@ -58,7 +58,7 @@
 sendMessage u s b = nothing $ runRoute $ Route.sendMessage u s b
 
 -- | Send a private message (with a captcha).
-sendMessageWithCaptcha :: MonadIO m
+sendMessageWithCaptcha :: Monad m
                        => Username -- ^ The username to send the message to
                        -> Text -- ^ The subject of the message being sent
                        -> Text -- ^ The body of the message being sent
@@ -66,3 +66,12 @@
                        -> Text -- ^ The answer to the specified captcha
                        -> RedditT m ()
 sendMessageWithCaptcha u s b i c = nothing $ runRoute $ Route.sendMessage u s b `withCaptcha` (i, c)
+
+-- | Reply to a message
+replyMessage :: (Monad m, Thing a)
+             => a -- ^ Thing to reply to
+             -> Text -- ^ Response contents
+             -> RedditT m MessageID
+replyMessage t b = do
+  POSTWrapped res <- runRoute $ Route.reply t b
+  return res
diff --git a/src/Reddit/Actions/Moderation.hs b/src/Reddit/Actions/Moderation.hs
--- a/src/Reddit/Actions/Moderation.hs
+++ b/src/Reddit/Actions/Moderation.hs
@@ -1,7 +1,6 @@
 module Reddit.Actions.Moderation where
 
 import Reddit.Routes.Moderation
-import Reddit.Routes.Run
 import Reddit.Types.Error
 import Reddit.Types.Listing
 import Reddit.Types.Moderation
@@ -14,15 +13,17 @@
 
 -- | Get a list of existing bans on a subreddit.
 --   User must be a moderator of the subreddit.
-bans :: Options BanID -> SubredditName -> Reddit (Listing BanID Ban)
+bans :: Monad m => Options BanID -> SubredditName -> RedditT m (Listing BanID Ban)
 bans opts r = runRoute $ bansListing opts r
 
 -- | Check to see if a user is banned from a subreddit. Logged-in user must
 --   be a moderator of the subreddit
-lookupBan :: Username -> SubredditName -> Reddit (Maybe Ban)
+lookupBan :: Monad m => Username -> SubredditName -> RedditT m (Maybe Ban)
 lookupBan u r = do
-  Listing _ _ bs <- runRoute $ banLookup u r :: Reddit (Listing BanID Ban)
-  case bs of
-    [b] -> return $ Just b
-    [] -> return Nothing
-    _-> failWith (APIError InvalidResponseError)
+  res <- runRoute $ banLookup u r
+  case res :: Listing BanID Ban of
+    Listing _ _ bs ->
+      case bs of
+        [b] -> return $ Just b
+        [] -> return Nothing
+        _-> failWith (APIError InvalidResponseError)
diff --git a/src/Reddit/Actions/Post.hs b/src/Reddit/Actions/Post.hs
--- a/src/Reddit/Actions/Post.hs
+++ b/src/Reddit/Actions/Post.hs
@@ -26,7 +26,6 @@
   , setContestMode ) where
 
 import qualified Reddit.Routes as Route
-import Reddit.Routes.Run
 import Reddit.Types
 import Reddit.Types.Captcha
 import Reddit.Types.Comment
@@ -34,15 +33,14 @@
 import Reddit.Types.Listing
 import Reddit.Types.Reddit
 
-import Control.Monad.IO.Class
-import Data.Default
+import Data.Default.Class
 import Data.Text (Text)
 import Network.API.Builder.Error (APIError(..))
 import qualified Data.Char as Char
 import qualified Data.Text as Text
 
 -- | Given a 'PostID', 'getPostInfo' will return the full details for that post.
-getPostInfo :: MonadIO m => PostID -> RedditT m Post
+getPostInfo :: Monad m => PostID -> RedditT m Post
 getPostInfo p = do
   res <- getPostsInfo [p]
   case res of
@@ -52,7 +50,7 @@
 -- | Given a list of 'PostID's, 'getPostsInfo' will return another list containing
 --   the full details for all the posts. Note that Reddit's
 --   API imposes a limitation of 100 posts per request, so this function will fail immediately if given a list of more than 100 IDs.
-getPostsInfo :: MonadIO m => [PostID] -> RedditT m PostListing
+getPostsInfo :: Monad m => [PostID] -> RedditT m PostListing
 getPostsInfo ps =
   -- we can only get 100 posts at a time or the api shits itself
   if null $ drop 100 ps
@@ -70,24 +68,24 @@
 
 -- | Get a 'PostListing' for the 'Hot' posts on the site overall.
 --   This maps to <http://reddit.com>.
-getPosts :: MonadIO m => RedditT m PostListing
+getPosts :: Monad m => RedditT m PostListing
 getPosts = getPosts' def Hot Nothing
 
 -- | Get a 'PostListing' for a specified listing.
-getPosts' :: MonadIO m => Options PostID -> ListingType -> Maybe SubredditName -> RedditT m PostListing
+getPosts' :: Monad m => Options PostID -> ListingType -> Maybe SubredditName -> RedditT m PostListing
 getPosts' o l r = runRoute $ Route.postsListing o r (Text.pack $ lower $ show l)
   where lower = map Char.toLower
 
 -- | Save a post.
-savePost :: MonadIO m => PostID -> RedditT m ()
+savePost :: Monad m => PostID -> RedditT m ()
 savePost = nothing . runRoute . Route.savePost
 
 -- | Remove a saved post from your "saved posts" list.
-unsavePost :: MonadIO m => PostID -> RedditT m ()
+unsavePost :: Monad m => PostID -> RedditT m ()
 unsavePost = nothing . runRoute . Route.unsavePost
 
 -- | Submit a new link to Reddit.
-submitLink :: MonadIO m
+submitLink :: Monad m
            => SubredditName -- ^ The subreddit to which you're posting the link
            -> Text -- ^ The title of the link post
            -> Text -- ^ The link that you're posting
@@ -97,7 +95,7 @@
   return res
 
 -- | Submit a new link to Reddit (answering a Captcha to prove we aren't a robot).
-submitLinkWithCaptcha :: MonadIO m
+submitLinkWithCaptcha :: Monad m
                       => SubredditName -- ^ The subreddit to which you're posting the link
                       -> Text -- ^ The title of the link post
                       -> Text -- ^ The link that you're posting
@@ -109,7 +107,7 @@
   return res
 
 -- | Submit a new selfpost to Reddit.
-submitSelfPost :: MonadIO m
+submitSelfPost :: Monad m
                => SubredditName -- ^ The subreddit to which you're posting the selfpost
                -> Text -- ^ The title of the selfpost
                -> Text -- ^ The body of the selfpost
@@ -119,7 +117,7 @@
   return res
 
 -- | Submit a new selfpost to Reddit (answering a Captcha to prove we aren't a robot).
-submitSelfPostWithCaptcha :: MonadIO m
+submitSelfPostWithCaptcha :: Monad m
                           => SubredditName -- ^ The subreddit to which you're posting the selfpost
                           -> Text -- ^ The title of the selfpost
                           -> Text -- ^ The body of the selfpost
@@ -132,12 +130,12 @@
 
 -- | Deletes one of your own posts. Note that this is different from
 --   removing a post as a moderator action.
-deletePost :: MonadIO m => PostID -> RedditT m ()
+deletePost :: Monad m => PostID -> RedditT m ()
 deletePost = nothing . runRoute . Route.delete
 
 -- | Set the link flair for a post you've submitted (or any post on a subreddit
 --   that you moderate).
-setPostFlair :: MonadIO m
+setPostFlair :: Monad m
              => SubredditName -- ^ The subreddit on which to set the flair
              -> PostID -- ^ The post whose flair should be set
              -> Text -- ^ The text label for the post's new flair
@@ -146,49 +144,49 @@
 setPostFlair r p text css = nothing $ runRoute $ Route.postFlair r p text css
 
 -- | Edit the text of a self-post.
-editPost :: MonadIO m => PostID -> Text -> RedditT m ()
+editPost :: Monad m => PostID -> Text -> RedditT m ()
 editPost thing text = nothing $ runRoute $ Route.edit thing text
 
 -- | Get a post and all its comments.
-getPostComments :: MonadIO m => PostID -> RedditT m PostComments
+getPostComments :: Monad m => PostID -> RedditT m PostComments
 getPostComments p = runRoute $ Route.getComments p Nothing
 
 -- | Get a post and a specific sub-tree of comments.
-getPostSubComments :: MonadIO m => PostID -> CommentID -> RedditT m PostComments
+getPostSubComments :: Monad m => PostID -> CommentID -> RedditT m PostComments
 getPostSubComments p c = runRoute $ Route.getComments p (Just c)
 
 -- | Get the comments for a post. Ignore the actual post itself.
-getComments :: MonadIO m => PostID -> RedditT m [CommentReference]
+getComments :: Monad m => PostID -> RedditT m [CommentReference]
 getComments p = do
   PostComments _ c <- getPostComments p
   return c
 
 -- | Set the state of inbox replies for the specified thread.
-setInboxReplies :: MonadIO m => Bool -> PostID -> RedditT m ()
+setInboxReplies :: Monad m => Bool -> PostID -> RedditT m ()
 setInboxReplies enabled = nothing . runRoute . Route.sendReplies enabled
 
 -- | Set the state of contest for the specified thread as a moderator action.
-setContestMode :: MonadIO m => Bool -> PostID -> RedditT m ()
+setContestMode :: Monad m => Bool -> PostID -> RedditT m ()
 setContestMode enabled = nothing . runRoute . Route.setContestMode enabled
 
 -- | Removes a post (as a moderator action). Note that this is different
 --   from deleting a post.
-removePost :: MonadIO m => PostID -> RedditT m ()
+removePost :: Monad m => PostID -> RedditT m ()
 removePost = nothing . runRoute . Route.removePost False
 
 -- | Mark a post as spam as a moderator action.
-markPostSpam :: MonadIO m => PostID -> RedditT m ()
+markPostSpam :: Monad m => PostID -> RedditT m ()
 markPostSpam = nothing . runRoute . Route.removePost True
 
 -- | Sticky a post on the subreddit on which it's posted.
-stickyPost :: MonadIO m
+stickyPost :: Monad m
            => PostID -- ^ The post to be stickied
            -> Maybe Integer -- ^ The position to which it should be stickied
            -> RedditT m ()
 stickyPost p n = nothing $ runRoute $ Route.stickyPost True p n
 
 -- | Unsticky a post from the subreddit on which it's posted.
-unstickyPost :: MonadIO m
+unstickyPost :: Monad m
            => PostID -- ^ The post to be unstickied
            -> Maybe Integer -- ^ The position from which it should be unstickied
            -> RedditT m ()
diff --git a/src/Reddit/Actions/Search.hs b/src/Reddit/Actions/Search.hs
--- a/src/Reddit/Actions/Search.hs
+++ b/src/Reddit/Actions/Search.hs
@@ -1,6 +1,5 @@
 module Reddit.Actions.Search where
 
-import Reddit.Routes.Run
 import Reddit.Routes.Search
 import Reddit.Types.Options
 import Reddit.Types.Post
@@ -10,14 +9,14 @@
 
 import Data.Text (Text)
 
-search :: Maybe SubredditName -> Options PostID -> Search.Order -> Text -> Reddit PostListing
+search :: Monad m => Maybe SubredditName -> Options PostID -> Search.Order -> Text -> RedditT m PostListing
 search sub opts order query =
   runRoute $ searchRoute sub opts order "plain" query
 
-luceneSearch :: Maybe SubredditName -> Options PostID -> Search.Order -> Text -> Reddit PostListing
+luceneSearch :: Monad m => Maybe SubredditName -> Options PostID -> Search.Order -> Text -> RedditT m PostListing
 luceneSearch sub opts order query =
   runRoute $ searchRoute sub opts order "lucene" query
 
-cloudSearch :: Maybe SubredditName -> Options PostID -> Search.Order -> Text -> Reddit PostListing
+cloudSearch :: Monad m => Maybe SubredditName -> Options PostID -> Search.Order -> Text -> RedditT m PostListing
 cloudSearch sub opts order query =
   runRoute $ searchRoute sub opts order "cloudsearch" query
diff --git a/src/Reddit/Actions/Subreddit.hs b/src/Reddit/Actions/Subreddit.hs
--- a/src/Reddit/Actions/Subreddit.hs
+++ b/src/Reddit/Actions/Subreddit.hs
@@ -4,24 +4,22 @@
   , getSubredditSettings
   , setSubredditSettings ) where
 
-import Reddit.Routes.Run
 import Reddit.Types
 import Reddit.Types.Empty
+import Reddit.Types.Reddit
 import Reddit.Types.Subreddit
 import Reddit.Types.SubredditSettings
 import qualified Reddit.Routes as Route
 
-import Control.Monad.IO.Class
-
 -- | Get the info for a specific subreddit. This info includes things like
 --   sidebar contents, description and ID.
-getSubredditInfo :: MonadIO m => SubredditName -> RedditT m Subreddit
+getSubredditInfo :: Monad m => SubredditName -> RedditT m Subreddit
 getSubredditInfo = runRoute . Route.aboutSubreddit
 
 -- | Get the settings for a subreddit that you moderate.
-getSubredditSettings :: MonadIO m => SubredditName -> RedditT m SubredditSettings
+getSubredditSettings :: Monad m => SubredditName -> RedditT m SubredditSettings
 getSubredditSettings = runRoute . Route.subredditSettings
 
 -- | Modify the settings for a subreddit that you moderate.
-setSubredditSettings :: MonadIO m => SubredditID -> SubredditSettings -> RedditT m ()
+setSubredditSettings :: Monad m => SubredditID -> SubredditSettings -> RedditT m ()
 setSubredditSettings r s = nothing $ runRoute (Route.setSubredditSettings r s)
diff --git a/src/Reddit/Actions/Thing.hs b/src/Reddit/Actions/Thing.hs
--- a/src/Reddit/Actions/Thing.hs
+++ b/src/Reddit/Actions/Thing.hs
@@ -5,17 +5,15 @@
   , Reddit.Actions.Thing.delete
   , Reddit.Actions.Thing.report ) where
 
-import Reddit.Routes.Run
 import Reddit.Types
 import Reddit.Types.Empty
 import Reddit.Types.Reddit
 import qualified Reddit.Routes.Thing as Route
 
-import Control.Monad.IO.Class
 import Data.Text (Text)
 
 -- | Reply to a something (a post \/ comment \/ message)
-reply :: (MonadIO m, Thing a)
+reply :: (Monad m, Thing a)
       => a -- ^ Thing to reply to
       -> Text -- ^ Response contents
       -> RedditT m CommentID
@@ -26,13 +24,13 @@
 -- | Delete something you created. Note that this is different to removing
 --   a post / comment as a moderator action. Deleting something you don't
 --   own won't error (but naturally won't delete anything either).
-delete :: (MonadIO m, Thing a)
+delete :: (Monad m, Thing a)
        => a -- ^ Thing to delete
        -> RedditT m ()
 delete = nothing . runRoute . Route.delete
 
 -- | Report something.
-report :: (MonadIO m, Thing a)
+report :: (Monad m, Thing a)
        => a -- ^ Thing to report
        -> Text -- ^ Reason for reporting
        -> RedditT m ()
diff --git a/src/Reddit/Actions/User.hs b/src/Reddit/Actions/User.hs
--- a/src/Reddit/Actions/User.hs
+++ b/src/Reddit/Actions/User.hs
@@ -5,66 +5,75 @@
   , aboutMe
   , getUserComments
   , getUserComments'
+  , getUserPosts
+  , getUserPosts'
   , isUsernameAvailable
   , getBlockedUsers
   , getFriends
   , lookupUserFlair
   , setUserFlair ) where
 
-import Reddit.Routes.Run
 import Reddit.Types.Comment
 import Reddit.Types.Empty
 import Reddit.Types.Flair hiding (user)
 import Reddit.Types.Error
 import Reddit.Types.Listing
 import Reddit.Types.Options
+import Reddit.Types.Post
 import Reddit.Types.Reddit
 import Reddit.Types.Subreddit
 import Reddit.Types.User
 import qualified Reddit.Routes.User as Route
 
 import Control.Monad
-import Control.Monad.IO.Class
-import Data.Default
+import Data.Default.Class
 import Data.Text (Text)
 import Network.API.Builder.Error
 import qualified Data.Text as Text
 
 -- | Get the information Reddit exposes on user behind the specified username
-getUserInfo :: MonadIO m => Username -> RedditT m User
+getUserInfo :: Monad m => Username -> RedditT m User
 getUserInfo = runRoute . Route.aboutUser
 
 -- | Get the listing of comments authored by the specified user.
-getUserComments :: MonadIO m => Username -> RedditT m CommentListing
+getUserComments :: Monad m => Username -> RedditT m CommentListing
 getUserComments = getUserComments' def
 
 -- | Get the listing of comments authored by the specified user, with Options.
-getUserComments' :: MonadIO m => Options CommentID -> Username -> RedditT m CommentListing
+getUserComments' :: Monad m => Options CommentID -> Username -> RedditT m CommentListing
 getUserComments' opts user = runRoute $ Route.userComments opts user
 
+-- | Get the listing of posts authored by the specified user.
+getUserPosts :: Monad m => Username -> RedditT m PostListing
+getUserPosts = getUserPosts' def
+
+-- | Get the listing of posts authored by the specified user, with Options.
+getUserPosts' :: Monad m => Options PostID -> Username -> RedditT m PostListing
+getUserPosts' opts user = runRoute $ Route.userPosts opts user
+
 -- | Check whether the specified username is still available or has been taken.
-isUsernameAvailable :: MonadIO m => Username -> RedditT m Bool
+isUsernameAvailable :: Monad m => Username -> RedditT m Bool
 isUsernameAvailable = runRoute . Route.usernameAvailable
 
 -- | Get information of the currently-logged-in user.
-aboutMe :: MonadIO m => RedditT m User
+aboutMe :: Monad m => RedditT m User
 aboutMe = runRoute Route.aboutMe
 
 -- | Get users blocked by the currently-logged-in user.
-getBlockedUsers :: MonadIO m => RedditT m [Relationship]
+getBlockedUsers :: Monad m => RedditT m [Relationship]
 getBlockedUsers = do
   UserList rs <- runRoute Route.blocked
   return rs
 
 -- | Get friends of the currently-logged-in user.
-getFriends :: MonadIO m => RedditT m [Relationship]
+getFriends :: Monad m => RedditT m [Relationship]
 getFriends = do
   UserList rs <- runRoute Route.friends
   return rs
 
 -- | Check if a user has chosen (or been assign) user flair on a particular
 --   subreddit. Requires moderator privileges on the specified subreddit.
-lookupUserFlair :: MonadIO m => SubredditName -> Username -> RedditT m Flair
+lookupUserFlair :: Monad m => SubredditName -> Username -> RedditT m Flair
 lookupUserFlair r u = do
   res <- liftM flistToListing $ runRoute (Route.lookupUserFlair r u)
   case res of
@@ -73,7 +82,7 @@
 
 -- | Set a user's flair on the specified subreddit. Requires moderator
 --   privileges on the specified subreddit.
-setUserFlair :: MonadIO m => SubredditName -> Username -> Text -> Text -> RedditT m ()
+setUserFlair :: Monad m => SubredditName -> Username -> Text -> Text -> RedditT m ()
 setUserFlair r u txt cls =
   if Text.length txt > 64
     then fail "Flair text too long!"
diff --git a/src/Reddit/Actions/Voting.hs b/src/Reddit/Actions/Voting.hs
--- a/src/Reddit/Actions/Voting.hs
+++ b/src/Reddit/Actions/Voting.hs
@@ -14,37 +14,35 @@
   , downvoteComment
   , unvoteComment ) where
 
-import Reddit.Routes.Run
 import Reddit.Routes.Vote (VoteDirection(..))
 import Reddit.Types
 import Reddit.Types.Empty
+import Reddit.Types.Reddit
 import qualified Reddit.Routes as Route
 
-import Control.Monad.IO.Class
-
-vote :: (MonadIO m, Thing a) => VoteDirection -> a -> RedditT m ()
+vote :: (Monad m, Thing a) => VoteDirection -> a -> RedditT m ()
 vote dir = nothing . runRoute . Route.vote dir
 
 -- | Upvote a post.
-upvotePost :: MonadIO m => PostID -> RedditT m ()
+upvotePost :: Monad m => PostID -> RedditT m ()
 upvotePost = vote UpVote
 
 -- | Downvote a post.
-downvotePost :: MonadIO m => PostID -> RedditT m ()
+downvotePost :: Monad m => PostID -> RedditT m ()
 downvotePost = vote DownVote
 
 -- | Remove a vote from a post.
-unvotePost :: MonadIO m => PostID -> RedditT m ()
+unvotePost :: Monad m => PostID -> RedditT m ()
 unvotePost = vote RemoveVote
 
 -- | Upvote a comment.
-upvoteComment :: MonadIO m => CommentID -> RedditT m ()
+upvoteComment :: Monad m => CommentID -> RedditT m ()
 upvoteComment = vote UpVote
 
 -- | Downvote a comment.
-downvoteComment :: MonadIO m => CommentID -> RedditT m ()
+downvoteComment :: Monad m => CommentID -> RedditT m ()
 downvoteComment = vote RemoveVote
 
 -- | Remove a previously-cast vote from a comment.
-unvoteComment :: MonadIO m => CommentID -> RedditT m ()
+unvoteComment :: Monad m => CommentID -> RedditT m ()
 unvoteComment = vote DownVote
diff --git a/src/Reddit/Actions/Wiki.hs b/src/Reddit/Actions/Wiki.hs
--- a/src/Reddit/Actions/Wiki.hs
+++ b/src/Reddit/Actions/Wiki.hs
@@ -3,24 +3,22 @@
   ( getWikiPage
   , editWikiPage ) where
 
-import Reddit.Routes.Run
 import Reddit.Types.Empty
 import Reddit.Types.Reddit
 import Reddit.Types.Subreddit
 import Reddit.Types.Wiki
 import qualified Reddit.Routes as Route
 
-import Control.Monad.IO.Class
 import Data.Text (Text)
 
 -- | Get the specified wiki page on a particular subreddit. Requires
 --   permission to view the specified wiki page.
-getWikiPage :: MonadIO m => SubredditName -> Text -> RedditT m WikiPage
+getWikiPage :: Monad m => SubredditName -> Text -> RedditT m WikiPage
 getWikiPage sub page = runRoute $ Route.wikiPage sub page
 
 -- | Edit the specified wiki page on a particular subreddit. Requires
 --   permission to edit the specified wiki page.
-editWikiPage :: MonadIO m
+editWikiPage :: Monad m
              => SubredditName -- ^ Subreddit whose wiki to modify
              -> Text -- ^ The name of the page you're editing
              -> Text -- ^ The new markdown content of the page you're editing
diff --git a/src/Reddit/Login.hs b/src/Reddit/Login.hs
--- a/src/Reddit/Login.hs
+++ b/src/Reddit/Login.hs
@@ -1,17 +1,10 @@
 module Reddit.Login
   ( login ) where
 
-import Reddit.Types.Error
 import Reddit.Types.Reddit
 
-import Control.Concurrent (threadDelay)
-import Control.Concurrent.STM.TVar
-import Control.Monad.IO.Class (MonadIO, liftIO)
-import Control.Monad.Trans.Except
-import Control.Monad.Trans.State
 import Data.Text (Text)
-import Network.API.Builder
-import Network.HTTP.Conduit
+import Network.API.Builder hiding (runRoute)
 
 loginRoute :: Text -> Text -> Route
 loginRoute user pass = Route [ "api", "login" ]
@@ -20,29 +13,12 @@
                              , "passwd" =. pass ]
                              "POST"
 
-getLoginDetails :: MonadIO m => Text -> Text -> RedditT m LoginDetails
-getLoginDetails user pass = do
-  resp <- RedditT $ sendRoute () $ loginRoute user pass
-  let cj = responseCookieJar resp
-  case unwrapJSON `fmap` receive resp of
-    Left x@(APIError (RateLimitError wait _)) -> do
-      RateLimits limiting _ <- RedditT $ liftState get >>= liftIO . readTVarIO
-      if limiting
-        then do
-          liftIO $ threadDelay $ (fromIntegral wait + 5) * 1000000
-          getLoginDetails user pass
-        else RedditT $ ExceptT $ return $ Left x
-    Left x -> RedditT $ ExceptT $ return $ Left x
-    Right modhash -> return $ LoginDetails modhash cj
-
+getLoginDetails :: Monad m => Text -> Text -> RedditT m LoginDetails
+getLoginDetails user pass = receiveRoute $ loginRoute user pass
 
 -- | Make a login request with the given username and password.
-login :: MonadIO m
+login :: Monad m
       => Text -- ^ Username to login with
       -> Text -- ^ Password to login with
       -> RedditT m LoginDetails
-login user pass = do
-  RedditT $ baseURL loginBaseURL
-  d <- getLoginDetails user pass
-  RedditT $ baseURL mainBaseURL
-  return d
+login = getLoginDetails
diff --git a/src/Reddit/Routes/Run.hs b/src/Reddit/Routes/Run.hs
deleted file mode 100644
--- a/src/Reddit/Routes/Run.hs
+++ /dev/null
@@ -1,81 +0,0 @@
-module Reddit.Routes.Run
-  ( runRoute ) where
-
-import Reddit.Types.Error
-import Reddit.Types.Reddit
-
-import Control.Applicative
-import Control.Concurrent (threadDelay)
-import Control.Concurrent.STM
-import Control.Monad (liftM, when)
-import Control.Monad.IO.Class (MonadIO, liftIO)
-import Control.Monad.Trans.Except
-import Control.Monad.Trans.State
-import Data.Aeson (FromJSON(..))
-import Data.ByteString.Lazy (ByteString)
-import Data.Time.Clock
-import Network.API.Builder.Routes (Route)
-import Network.HTTP.Conduit
-import Network.HTTP.Types
-import Prelude
-import qualified Network.API.Builder as API
-
-runRoute :: (MonadIO m, FromJSON a) => Route -> RedditT m a
-runRoute route = do
-  RateLimits limiting _ <- RedditT $ API.liftState get >>= liftIO . readTVarIO
-  if limiting
-    then runRouteWithLimiting route
-    else RedditT $ API.unwrapJSON `liftM` API.runRoute route
-
-runRouteWithLimiting :: (MonadIO m, FromJSON a) => Route -> RedditT m a
-runRouteWithLimiting route = do
-  RateLimits _ rli <- RedditT $ API.liftState get >>= liftIO . readTVarIO
-  case rli of
-    Just r -> do
-      when (needsReset r) $ waitForReset $ resetTime r
-      requestAndUpdate
-    Nothing -> requestAndUpdate
-  where
-    requestAndUpdate = do
-      resp <- nest $ RedditT $ API.routeResponse route
-      case resp of
-        Left (API.APIError (RateLimitError resetIn _)) -> do
-          updateFromZero resetIn
-          runRouteWithLimiting route
-        Left x -> RedditT $ ExceptT $ return $ Left x
-        Right x -> do
-          case API.unwrapJSON <$> API.receive x of
-            Left (API.APIError (RateLimitError resetIn _)) -> do
-              updateFromZero resetIn
-              runRouteWithLimiting route
-            Left y -> RedditT $ ExceptT $ return $ Left y
-            Right y -> do
-              updateRateLimitInfo $ responseHeaders x
-              return y
-
-updateRateLimitInfo :: MonadIO m => ResponseHeaders -> RedditT m ()
-updateRateLimitInfo hs = do
-  time <- liftIO getCurrentTime
-  case headersToRateLimitInfo hs time of
-    Just rli ->
-      RedditT $ do
-        r <- API.liftState get
-        liftIO $ atomically $ modifyTVar r (\(RateLimits b _) -> RateLimits b $ Just rli)
-    Nothing -> return ()
-
-updateFromZero :: MonadIO m => Integer -> RedditT m ()
-updateFromZero resetIn = do
-  time <- liftIO getCurrentTime
-  let resetAt = addUTCTime (fromInteger resetIn) time
-  RedditT $ do
-    r <- API.liftState get
-    liftIO $ atomically $ modifyTVar r (\(RateLimits b _) -> RateLimits b $ Just $ RateLimitInfo 0 0 resetAt)
-
-waitForReset :: MonadIO m => UTCTime -> RedditT m ()
-waitForReset dt = do
-  time <- liftIO getCurrentTime
-  let wait = floor $ diffUTCTime dt time
-  liftIO $ threadDelay $ (wait + 5) * 1000000 -- wait five extra seconds to account for timing differences
-
-needsReset :: RateLimitInfo -> Bool
-needsReset = (<= 0) . remaining
diff --git a/src/Reddit/Routes/User.hs b/src/Reddit/Routes/User.hs
--- a/src/Reddit/Routes/User.hs
+++ b/src/Reddit/Routes/User.hs
@@ -2,6 +2,7 @@
 
 import Reddit.Types.Comment (CommentID)
 import Reddit.Types.Options
+import Reddit.Types.Post
 import Reddit.Types.Subreddit
 import Reddit.Types.User
 
@@ -25,6 +26,15 @@
         , "before" =. before opts
         , "after" =. after opts ]
         "GET"
+
+userPosts :: Options PostID -> Username -> Route
+userPosts opts (Username user) = 
+  Route [ "user", user, "submitted" ]
+        [ "limit" =. limit opts 
+        , "before" =. before opts
+        , "after" =. after opts ]
+        "GET"
+
 
 usernameAvailable :: Username -> Route
 usernameAvailable user = Route [ "api", "username_available.json" ]
diff --git a/src/Reddit/Types/Message.hs b/src/Reddit/Types/Message.hs
--- a/src/Reddit/Types/Message.hs
+++ b/src/Reddit/Types/Message.hs
@@ -3,6 +3,7 @@
 import Reddit.Parser
 import Reddit.Types.Comment
 import Reddit.Types.Listing
+import Reddit.Types.Reddit
 import Reddit.Types.Thing
 import Reddit.Types.User
 import Reddit.Utilities
@@ -14,6 +15,7 @@
 import Data.Text (Text)
 import Network.API.Builder.Query
 import Prelude
+import qualified Data.Vector as Vector
 
 data Message = Message { messageID :: MessageKind
                        , new :: Bool
@@ -57,7 +59,7 @@
     _ -> False
 
 data MessageID = MessageID Text
-  deriving (Show, Read, Eq)
+  deriving (Show, Read, Eq, Ord)
 
 instance FromJSON MessageID where
   parseJSON (String s) =
@@ -70,12 +72,20 @@
 instance ToQuery MessageID where
   toQuery k m = toQuery k (fullName m)
 
+instance FromJSON (POSTWrapped MessageID) where
+  parseJSON (Object o) = do
+    ms <- (o .: "json") >>= (.: "data") >>= (.: "things")
+    case Vector.toList ms of
+      [v] -> POSTWrapped <$> (v .: "data" >>= (.: "id"))
+      _ -> mempty
+  parseJSON _ = mempty
+
 messagePrefix :: Text
 messagePrefix = "t4"
 
 data MessageKind = CommentMessage CommentID
                  | PrivateMessage MessageID
-  deriving (Show, Read, Eq)
+  deriving (Show, Read, Eq, Ord)
 
 instance FromJSON MessageKind where
   parseJSON s =
diff --git a/src/Reddit/Types/Options.hs b/src/Reddit/Types/Options.hs
--- a/src/Reddit/Types/Options.hs
+++ b/src/Reddit/Types/Options.hs
@@ -1,6 +1,6 @@
 module Reddit.Types.Options where
 
-import Data.Default
+import Data.Default.Class
 
 data PaginationOption a = Before a
                         | After a
diff --git a/src/Reddit/Types/Reddit.hs b/src/Reddit/Types/Reddit.hs
--- a/src/Reddit/Types/Reddit.hs
+++ b/src/Reddit/Types/Reddit.hs
@@ -1,8 +1,12 @@
 module Reddit.Types.Reddit
   ( Reddit
   , RedditT(..)
+  , RedditF(..)
+  , runRoute
+  , receiveRoute
   , nest
   , failWith
+  , withBaseURL
   , Modhash(..)
   , LoginDetails(..)
   , POSTWrapped(..)
@@ -19,59 +23,62 @@
 import Reddit.Types.Error
 
 import Control.Applicative
-import Control.Concurrent.STM.TVar
 import Control.Monad.IO.Class
+import Control.Monad.Trans.Free
 import Control.Monad.Trans.Class
-import Control.Monad.Trans.Except
-import Control.Monad.Trans.Reader (ask)
-import Control.Monad.Trans.State (get, put)
 import Data.Aeson
+import Data.ByteString.Lazy (ByteString)
 import Data.Monoid
 import Data.Text (Text)
 import Data.Time.Clock
-import Network.API.Builder
-import Network.HTTP.Conduit hiding (path)
+import Network.API.Builder hiding (runRoute)
+import Network.HTTP.Client hiding (path)
 import Network.HTTP.Types
 import Prelude
 import Text.Read (readMaybe)
 import qualified Data.ByteString.Char8 as BS
-import qualified Data.Text as Text
 
 type Reddit a = RedditT IO a
 
-newtype RedditT m a = RedditT { unRedditT :: APIT (TVar RateLimits) RedditError m a }
+data RedditF m a where
+  FailWith :: APIError RedditError -> RedditF m a
+  Nest :: RedditT m b -> (Either (APIError RedditError) b -> a) -> RedditF m a
+  NestResuming :: RedditT m b -> (Either (APIError RedditError, Maybe (RedditT m b)) b -> a) -> RedditF m a
+  ReceiveRoute :: Receivable b => Route -> (b -> a) -> RedditF m a
+  RunRoute :: FromJSON b => Route -> (b -> a) -> RedditF m a
+  WithBaseURL :: Text -> RedditT m b -> (b -> a) -> RedditF m a
 
-instance Functor m => Functor (RedditT m) where
-  fmap f (RedditT a) = RedditT (fmap f a)
+instance Functor (RedditF m) where
+  fmap _ (FailWith x) = FailWith x
+  fmap f (Nest a x) = Nest a (fmap f x)
+  fmap f (NestResuming a x) = NestResuming a (fmap f x)
+  fmap f (ReceiveRoute r x) = ReceiveRoute r (fmap f x)
+  fmap f (RunRoute r x) = RunRoute r (fmap f x)
+  fmap f (WithBaseURL u a x) = WithBaseURL u a (fmap f x)
 
-instance (Functor m, Monad m) => Applicative (RedditT m) where
-  pure a = RedditT (pure a)
-  (RedditT f) <*> (RedditT a) = RedditT (f <*> a)
+newtype RedditT m a = RedditT (FreeT (RedditF m) m a)
+  deriving (Functor, Applicative, Monad)
 
-instance Monad m => Monad (RedditT m) where
-  return a = RedditT (return a)
-  (RedditT a) >>= f = RedditT (a >>= unRedditT . f)
-  fail = failWith . APIError . FailError . Text.pack
+instance MonadTrans RedditT where
+  lift = RedditT . lift
 
 instance MonadIO m => MonadIO (RedditT m) where
-  liftIO a = RedditT (liftIO a)
+  liftIO = RedditT . liftIO
 
-instance MonadTrans RedditT where
-  lift = RedditT . lift . lift . lift . lift
+runRoute :: (FromJSON a, Monad m) => Route -> RedditT m a
+runRoute r = RedditT $ liftF $ RunRoute r id
 
-nest :: MonadIO m => RedditT m a -> RedditT m (Either (APIError RedditError) a)
-nest (RedditT a) = do
-  b <- RedditT $ liftBuilder get
-  rl <- RedditT $ liftState get
-  m <- RedditT $ liftManager ask
-  (res, b', rl') <- lift $ runAPI b m rl a
-  RedditT $ do
-    liftBuilder $ put b'
-    liftState $ put rl'
-  return res
+receiveRoute :: (Receivable a, Monad m) => Route -> RedditT m a
+receiveRoute r = RedditT $ liftF $ ReceiveRoute r id
 
+nest :: Monad m => RedditT m a -> RedditT m (Either (APIError RedditError) a)
+nest f = RedditT $ liftF $ Nest f id
+
+withBaseURL :: Monad m => Text -> RedditT m a -> RedditT m a
+withBaseURL u f = RedditT $ liftF $ WithBaseURL u f id
+
 failWith :: Monad m => APIError RedditError -> RedditT m a
-failWith = RedditT . throwE
+failWith = RedditT . liftF . FailWith
 
 newtype Modhash = Modhash Text
   deriving (Show, Read, Eq)
@@ -83,6 +90,11 @@
 
 data LoginDetails = LoginDetails Modhash CookieJar
   deriving (Show, Eq)
+
+instance Receivable LoginDetails where
+  receive x = do
+    (resp, mh) <- receive x
+    return $ LoginDetails (unwrapJSON mh) (responseCookieJar (resp :: Response ByteString))
 
 newtype POSTWrapped a = POSTWrapped a
   deriving (Show, Read, Eq)
diff --git a/src/Reddit/Types/SubredditSettings.hs b/src/Reddit/Types/SubredditSettings.hs
--- a/src/Reddit/Types/SubredditSettings.hs
+++ b/src/Reddit/Types/SubredditSettings.hs
@@ -10,7 +10,7 @@
 
 import Control.Applicative
 import Data.Aeson
-import Data.Default
+import Data.Default.Class
 import Data.Monoid hiding (Any(..))
 import Data.Text (Text)
 import Network.API.Builder.Query
