diff --git a/api-builder.cabal b/api-builder.cabal
--- a/api-builder.cabal
+++ b/api-builder.cabal
@@ -1,5 +1,5 @@
 name: api-builder
-version: 0.1.1.0
+version: 0.2.0.0
 synopsis: Library for easily building REST API wrappers in Haskell
 license: BSD3
 author: Fraser Murray
@@ -11,14 +11,14 @@
 
 library
   exposed-modules:
-    APIBuilder
-    APIBuilder.API
-    APIBuilder.Builder
-    APIBuilder.Decoding
-    APIBuilder.Error
-    APIBuilder.Examples.StackOverflow
-    APIBuilder.Query
-    APIBuilder.Routes
+    Network.API.Builder
+    Network.API.Builder.API
+    Network.API.Builder.Builder
+    Network.API.Builder.Decoding
+    Network.API.Builder.Error
+    Network.API.Builder.Examples.StackOverflow
+    Network.API.Builder.Query
+    Network.API.Builder.Routes
   build-depends:
     base >= 4.6 && < 4.8,
     aeson == 0.7.*,
diff --git a/src/APIBuilder.hs b/src/APIBuilder.hs
deleted file mode 100644
--- a/src/APIBuilder.hs
+++ /dev/null
@@ -1,10 +0,0 @@
--- | Exports a bunch of stuff. Check 'APIBuilder.API', 'APIBuilder.Builder',
---   'APIBuilder.Decoding', 'APIBuilder.Error', and 'APIBuilder.Routes'.
-module APIBuilder
-  ( module Exports ) where
-
-import APIBuilder.API as Exports
-import APIBuilder.Builder as Exports
-import APIBuilder.Decoding as Exports
-import APIBuilder.Error as Exports
-import APIBuilder.Routes as Exports
diff --git a/src/APIBuilder/API.hs b/src/APIBuilder/API.hs
deleted file mode 100644
--- a/src/APIBuilder/API.hs
+++ /dev/null
@@ -1,116 +0,0 @@
-module APIBuilder.API (
-  -- * API
-    API
-  , APIT
-  -- ** Running the API
-  , runAPI
-  , runRoute
-  , routeResponse
-  , routeRequest
-  -- ** Lifting
-  , liftEither
-  , liftBuilder
-  , liftState
-  -- ** Changing the @Builder@ within the API
-  , name
-  , baseURL
-  , customizeRoute
-  , customizeRequest ) where
-
-import APIBuilder.Builder
-import APIBuilder.Decoding
-import APIBuilder.Error
-import APIBuilder.Routes
-
-import Control.Exception
-import Control.Monad.Trans.Either
-import Control.Monad.Trans.State
-import Control.Monad.Trans.Class (lift)
-import Control.Monad.IO.Class (MonadIO, liftIO)
-import Data.Aeson (FromJSON)
-import Data.Text (Text)
-import qualified Data.Text as T
-import Data.ByteString.Lazy (ByteString)
-import Network.HTTP.Conduit
-
--- | Main API type. @s@ is the API's internal state, @e@ is the API's custom error type,
---   and @a@ is the result when the API runs. Based on the @APIT@ transformer.
-type API s e a = APIT s e IO a
-
--- | Main API transformer type. @s@ is the API's internal state, @e@ is the API's custom error type,
---   and @a@ is the result when the API runs.
-type APIT s e m a = EitherT (APIError e) (StateT Builder (StateT s m)) a
-
--- | Lifts an action that works on an @API@ to an action that works on an @API@.
---   This function is provided solely for future-proofing in the case that more transformers
---   need to be stacked on top - it's implemented simply as @id@ for the moment.
-liftEither :: Monad m => EitherT (APIError e) (StateT Builder (StateT s m)) a -> APIT s e m a
-liftEither = id
-
--- | Lifts an action that operates on a @Builder@ to one that works on an @API@. Useful
---   mainly for gaining access to a @Builder@ from inside an @API@.
-liftBuilder :: Monad m => StateT Builder (StateT s m) a -> APIT s e m a
-liftBuilder = lift
-
--- | Lifts an action on an @API@'s state type @s@ to one that works on the @API@. Good
---   for messing with the state from inside the @API@.
-liftState :: Monad m => StateT s m a -> APIT s e m a
-liftState = lift . lift
-
--- | Runs an @API@ by executing its transformer stack and dumping it all into @IO@.
-runAPI :: MonadIO m
-       => Builder -- ^ initial @Builder@ for the @API@
-       -> s -- ^ initial state @s@ for the @API@
-       -> APIT s e m a -- ^ the actual @API@ to run
-       -> m (Either (APIError e) a) -- ^ IO action that returns either an error or the result
-runAPI b s api = evalStateT (evalStateT (runEitherT api) b) s
-
--- | Runs a @Route@. Infers the type to convert to from the JSON with the @a@ in @API@,
---   and infers the error type from @e@.
-runRoute :: (FromJSON a, FromJSON e, MonadIO m) => Route -> APIT s e m a
-runRoute route = routeResponse route >>= hoistEither . decode . responseBody
-
-
--- | Runs a @Route@, but only returns the response and does nothing towards
---   decoding the response.
-routeResponse :: (MonadIO m) => Route -> APIT s e m (Response ByteString)
-routeResponse route = do
-  b <- liftBuilder get
-  req <- hoistEither $ routeRequest b route `eitherOr` InvalidURLError
-  resp <- do
-    r <- liftIO $ try $ withManager (httpLbs req)
-    hoistEither $ either (Left . HTTPError) Right r
-  return resp
-
-eitherOr :: Maybe a -> b -> Either b a
-a `eitherOr` b =
-  case a of
-    Just x -> Right x
-    Nothing -> Left b
-
--- | Try to construct a @Request@ from a @Route@ (with the help of the @Builder@). Returns @Nothing@ if
---   the URL is invalid or there is another error with the @Route@.
-routeRequest :: Builder -> Route -> Maybe Request
-routeRequest b route =
-  let initialURL = parseUrl (T.unpack $ routeURL (_baseURL b) (_customizeRoute b route)) in
-  fmap (\url -> _customizeRequest b $ url { method = httpMethod route }) initialURL
-
--- | Modify the @name@ of the @Builder@ from inside an API. Using this is probably not the best idea,
---   it's nice if the @Builder@'s name is stable at least.
-name :: Monad m => Text -> APIT s e m ()
-name t = liftBuilder $ modify (\b -> b { _name = t })
-
--- | Modify the @baseURL@ of the @Builder@ from inside an API.
---   Can be useful for changing the API's endpoints for certain requests.
-baseURL :: Monad m => Text -> APIT s e m ()
-baseURL t = liftBuilder $ modify (\b -> b { _baseURL = t })
-
--- | Modify every @Route@ before it runs. Useful for adding extra params to every query,
---   for example.
-customizeRoute :: Monad m => (Route -> Route) -> APIT s e m ()
-customizeRoute f = liftBuilder $ modify (\b -> b { _customizeRoute = f })
-
--- | Modify every @Request@ before the API fetches it. Useful for adding headers to every request,
---   for example.
-customizeRequest :: Monad m => (Request -> Request) -> APIT s e m ()
-customizeRequest f = liftBuilder $ modify (\b -> b { _customizeRequest = f })
diff --git a/src/APIBuilder/Builder.hs b/src/APIBuilder/Builder.hs
deleted file mode 100644
--- a/src/APIBuilder/Builder.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module APIBuilder.Builder
-  ( Builder(..)
-  , basicBuilder ) where
-
-import APIBuilder.Routes
-
-import Network.HTTP.Conduit (Request)
-import Data.Text (Text)
-import qualified Data.Text as T
-
--- | Builder type for the API. Keeps track of the API's name and base URL, and how
---   to modify Routes and Requests before they're run.
-data Builder = Builder { _name :: Text
-                       , _baseURL :: Text
-                       , _customizeRoute :: Route -> Route
-                       , _customizeRequest :: Request -> Request }
-
-instance Show Builder where
-  show b = "Builder { name = " ++ T.unpack (_name b) ++ "}"
-
--- | Makes a basic builder, i.e. one that simply has a name and base URL
---   and doesn't fiddle with Routes / Requests.
-basicBuilder :: Text -- ^ name
-             -> Text -- ^ base url
-             -> Builder -- ^ a simple @Builder@
-basicBuilder n b = Builder n b id id
diff --git a/src/APIBuilder/Decoding.hs b/src/APIBuilder/Decoding.hs
deleted file mode 100644
--- a/src/APIBuilder/Decoding.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module APIBuilder.Decoding
-  ( decode ) where
-
-import APIBuilder.Error
-
-import Data.Aeson.Types (parseEither)
-import Data.Aeson.Parser (value)
-import Data.Aeson (FromJSON, parseJSON)
-import Data.Attoparsec.Lazy (parse, eitherResult)
-import qualified Data.ByteString.Lazy as BS
-
--- | Try to parse a value from a JSON @ByteString@, and if not, try to
---   parse a useful error from the JSON. If we can't do that, complain about
---   a @ParseError@.
-decode :: (FromJSON a, FromJSON e) => BS.ByteString -> Either (APIError e) a
-decode s =
-  case eitherDecode s of
-    Right x -> Right x
-    Left err ->
-      case eitherDecode s of
-        Right x -> Left $ APIError x
-        Left _ -> Left $ ParseError err
-
--- | @Data.Aeson@'s @decode@ function will only parse a value if it's
---   an array or an object, in accordance with some RFC somewhere. Unfortunately
---   not all JSON APIs respect this, so we have to mess with parsers and values and
---   whatnot.
-eitherDecode :: (FromJSON a) => BS.ByteString -> Either String a
-eitherDecode s = eitherResult (parse value s) >>= parseEither parseJSON
diff --git a/src/APIBuilder/Error.hs b/src/APIBuilder/Error.hs
deleted file mode 100644
--- a/src/APIBuilder/Error.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module APIBuilder.Error
-  ( APIError(..) ) where
-
-import Network.HTTP.Conduit (HttpException)
-
--- | Error type for the @API@, where @a@ is the type that should be returned when
---   something goes wrong on the other end - i.e. any error that isn't directly related
---   to this library.
-data APIError a = APIError a -- ^ A type that represents any error that happens on the API end.
-                             --   Define your own custom type with a @FromJSON@ instance if you
-                             --   want to handle them, or you can use @()@ if you just want to
-                             --   ignore them all.
-                | HTTPError HttpException -- ^ Something went wrong when we tried to do a HTTP operation.
-                | InvalidURLError -- ^ You're trying to create an invalid URL somewhere - check your
-                                  --   @Builder@'s base URL and your @Route@s.
-                | ParseError String -- ^ Failed when parsing the response, and it wasn't an error on their end.
-  deriving Show
diff --git a/src/APIBuilder/Examples/StackOverflow.hs b/src/APIBuilder/Examples/StackOverflow.hs
deleted file mode 100644
--- a/src/APIBuilder/Examples/StackOverflow.hs
+++ /dev/null
@@ -1,43 +0,0 @@
--- | Defines a basic example of API use - check the readme for more detail
---   or check the tutorial at <https://github.com/intolerable/api-builder>
-module APIBuilder.Examples.StackOverflow where
-
-import APIBuilder
-import Control.Applicative
-import Data.Aeson
-import Data.Monoid (mempty)
-import Data.Text (Text)
-
-data Question = Question { title :: Text
-                         , isAnswered :: Bool
-                         , score :: Int
-                         , tags :: [Text] }
-  deriving (Show, Eq)
-
-newtype Questions = Questions [Question]
-  deriving (Show, Eq)
-
-instance FromJSON Question where
-  parseJSON (Object o) =
-    Question <$> o .: "title"
-             <*> o .: "is_answered"
-             <*> o .: "score"
-             <*> o .: "tags"
-  parseJSON _ = mempty
-
-instance FromJSON Questions where
-  parseJSON (Object o) = Questions <$> o .: "items"
-  parseJSON _ = mempty
-
-stackOverflow :: Builder
-stackOverflow = basicBuilder "StackOverflow API" "http://api.stackexchange.com"
-
-answersRoute :: Route
-answersRoute = Route { fragments = [ "2.2", "questions" ]
-                     , urlParams = [ "order" =. ("desc" :: Text)
-                                   , "sort" =. ("activity" :: Text)
-                                   , "site" =. ("stackoverflow" :: Text) ]
-                     , httpMethod = "GET" }
-
-getAnswers :: IO (Either (APIError ()) Questions)
-getAnswers = runAPI stackOverflow () $ runRoute answersRoute
diff --git a/src/APIBuilder/Query.hs b/src/APIBuilder/Query.hs
deleted file mode 100644
--- a/src/APIBuilder/Query.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module APIBuilder.Query where
-
-import Data.Maybe
-import Data.Text (Text)
-import qualified Data.Text as Text
-
-class ToQuery a where
-  toQuery :: a -> Maybe Text
-
-instance ToQuery Integer where
-  toQuery = Just . Text.pack . show
-
-instance ToQuery Bool where
-  toQuery True = Just "true"
-  toQuery False = Just "false"
-
-instance ToQuery Int where
-  toQuery = Just . Text.pack . show
-
-instance ToQuery Text where
-  toQuery = Just
-
-instance ToQuery a => ToQuery (Maybe a) where
-  toQuery (Just a) = toQuery a
-  toQuery Nothing = Nothing
-
-instance ToQuery a => ToQuery [a] where
-  toQuery = Just . Text.intercalate "," . mapMaybe toQuery
diff --git a/src/APIBuilder/Routes.hs b/src/APIBuilder/Routes.hs
deleted file mode 100644
--- a/src/APIBuilder/Routes.hs
+++ /dev/null
@@ -1,61 +0,0 @@
-module APIBuilder.Routes
-  ( Route(..)
-  , URLFragment
-  , URLParam
-  , (=.)
-  , routeURL ) where
-
-import Control.Arrow ((***))
-import Data.Maybe (mapMaybe)
-import Data.Monoid ((<>))
-import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Network.HTTP.Base as HTTP (urlEncodeVars)
-import qualified Network.HTTP.Types.Method as HTTP
-
-import APIBuilder.Query
-
--- | Alias for @Text@ to store the URL fragments for each @Route@.
-type URLFragment = Text
-
--- | Alias to @(Text, Maybe Text)@ used to store each query that gets
---   tacked onto the request.
-type URLParam = (Text, Maybe Text)
-
--- | Convenience function for building @URLParam@s. Right-hand argument must
---   have a @ToQuery@ instance so it can be converted to the appropriate
---   representation in a query string. Query values do not need to be
---   escaped.
---
--- >>> "api_type" =. ("json" :: Text)
--- ("api_type", Just "json")
-(=.) :: ToQuery a => Text -> a -> (Text, Maybe Text)
-k =. v = (k, toQuery v)
-
--- | Main type for routes in the API. Used to represent the URL minus the actual
---   endpoint URL as well as the query string and the HTTP method used to communicate
---   with the server.
-data Route = Route { fragments :: [URLFragment]
-                   , urlParams :: [URLParam]
-                   , httpMethod :: HTTP.Method }
-  deriving (Show, Read, Eq)
-
--- | Converts a Route to a URL. Drops any @Nothing@ values from the query, separates the
---   fragments with "/" and tacks them onto the end of the base URL.
-routeURL :: Text -- ^ base URL for the @Route@ (you can usually get this from the @Builder@)
-         -> Route -- ^ the @Route@ to process
-         -> Text -- ^ the finalized URL as a @Text@
-routeURL baseURL (Route fs ps _) =
-  let path = T.intercalate "/" fs
-  in baseURL <> "/" <> path <> pathParamsSep fs <> buildParams ps
-
-pathParamsSep :: [URLFragment] -> Text
-pathParamsSep [] = "?"
-pathParamsSep xs = if T.isInfixOf "." (last xs) then "?" else "/?"
-
-buildParams :: [URLParam] -> Text
-buildParams = T.pack . HTTP.urlEncodeVars . map (T.unpack *** T.unpack) . mapMaybe collectParams
-
-collectParams :: URLParam -> Maybe (Text, Text)
-collectParams (a, Just x) = Just (a,x)
-collectParams (_, Nothing) = Nothing
diff --git a/src/Network/API/Builder.hs b/src/Network/API/Builder.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder.hs
@@ -0,0 +1,11 @@
+-- | Exports a bunch of stuff. Check 'Network.API.Builder.API', 'Network.API.Builder.Builder',
+--   'Network.API.Builder.Decoding', 'Network.API.Builder.Error', and 'Network.API.Builder.Routes'.
+module Network.API.Builder
+  ( module Exports ) where
+
+import Network.API.Builder.API as Exports
+import Network.API.Builder.Builder as Exports
+import Network.API.Builder.Decoding as Exports
+import Network.API.Builder.Error as Exports
+import Network.API.Builder.Query as Exports
+import Network.API.Builder.Routes as Exports
diff --git a/src/Network/API/Builder/API.hs b/src/Network/API/Builder/API.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/API.hs
@@ -0,0 +1,136 @@
+module Network.API.Builder.API (
+  -- * API
+    API
+  , APIT
+  -- ** Running the API
+  , execAPI
+  , runAPI
+  , runRoute
+  , routeResponse
+  , routeRequest
+  -- ** Lifting
+  , liftManager
+  , liftEither
+  , liftBuilder
+  , liftState
+  -- ** Changing the @Builder@ within the API
+  , name
+  , baseURL
+  , customizeRoute
+  , customizeRequest ) where
+
+import Network.API.Builder.Builder
+import Network.API.Builder.Decoding
+import Network.API.Builder.Error
+import Network.API.Builder.Routes
+
+import Control.Exception
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Either
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.State
+import Data.Aeson (FromJSON)
+import Data.ByteString.Lazy (ByteString)
+import Data.Text (Text)
+import Network.HTTP.Conduit
+import qualified Data.Text as T
+
+-- | Main API type. @s@ is the API's internal state, @e@ is the API's custom error type,
+--   and @a@ is the result when the API runs. Based on the @APIT@ transformer.
+type API s e a = APIT s e IO a
+
+-- | Main API transformer type. @s@ is the API's internal state, @e@ is the API's custom error type,
+--   and @a@ is the result when the API runs.
+type APIT s e m a = EitherT (APIError e) (ReaderT Manager (StateT Builder (StateT s m))) a
+
+-- | Lifts an action that works on an @API@ to an action that works on an @API@.
+--   This function is provided solely for future-proofing in the case that more transformers
+--   need to be stacked on top - it's implemented simply as @id@ for the moment.
+liftEither :: Monad m => EitherT (APIError e) (ReaderT Manager (StateT Builder (StateT s m))) a -> APIT s e m a
+liftEither = id
+
+liftManager :: Monad m => ReaderT Manager (StateT Builder (StateT s m)) a -> APIT s e m a
+liftManager = lift
+
+-- | Lifts an action that operates on a @Builder@ to one that works on an @API@. Useful
+--   mainly for gaining access to a @Builder@ from inside an @API@.
+liftBuilder :: Monad m => StateT Builder (StateT s m) a -> APIT s e m a
+liftBuilder = lift . lift
+
+-- | Lifts an action on an @API@'s state type @s@ to one that works on the @API@. Good
+--   for messing with the state from inside the @API@.
+liftState :: Monad m => StateT s m a -> APIT s e m a
+liftState = lift . lift . lift
+
+-- | Runs an @API@ by executing its transformer stack and dumping it all into @IO@. Only returns the actual result.
+execAPI :: MonadIO m
+       => Builder -- ^ initial @Builder@ for the @API@
+       -> s -- ^ initial state @s@ for the @API@
+       -> APIT s e m a -- ^ the actual @API@ to run
+       -> m (Either (APIError e) a) -- ^ IO action that returns either an error or the result
+execAPI b s api = do
+  m <- liftIO $ newManager conduitManagerSettings
+  (res, _, _) <- runAPI b m s api
+  liftIO $ closeManager m
+  return res
+
+-- | Runs an @API@ by executing its transformer stack and dumping it all into @IO@. Returns the actual result as well as the final states of the @Builder@ and custom state @s@.
+runAPI :: MonadIO m
+       => Builder -- ^ initial @Builder@ for the @API@
+       -> Manager -- ^ manager for working with conduit functions
+       -> s -- ^ initial state @s@ for the @API@
+       -> APIT s e m a -- ^ the actual @API@ to run
+       -> m (Either (APIError e) a, Builder, s) -- ^ IO action that returns either an error or the result, as well as the final states
+runAPI b m s api = do
+  ((res, b'), s') <- runStateT (runStateT (runReaderT (runEitherT api) m) b) s
+  return (res, b', s')
+
+-- | Runs a @Route@. Infers the type to convert to from the JSON with the @a@ in @API@,
+--   and infers the error type from @e@.
+runRoute :: (FromJSON a, FromJSON e, MonadIO m) => Route -> APIT s e m a
+runRoute route = routeResponse route >>= hoistEither . decode . responseBody
+
+-- | Runs a @Route@, but only returns the response and does nothing towards
+--   decoding the response.
+routeResponse :: (MonadIO m) => Route -> APIT s e m (Response ByteString)
+routeResponse route = do
+  b <- liftBuilder get
+  m <- liftManager ask
+  req <- hoistEither $ routeRequest b route `eitherOr` InvalidURLError
+  do
+    r <- liftIO $ try $ httpLbs req m
+    hoistEither $ either (Left . HTTPError) Right r
+
+eitherOr :: Maybe a -> b -> Either b a
+a `eitherOr` b =
+  case a of
+    Just x -> Right x
+    Nothing -> Left b
+
+-- | Try to construct a @Request@ from a @Route@ (with the help of the @Builder@). Returns @Nothing@ if
+--   the URL is invalid or there is another error with the @Route@.
+routeRequest :: Builder -> Route -> Maybe Request
+routeRequest b route =
+  let initialURL = parseUrl (T.unpack $ routeURL (_baseURL b) (_customizeRoute b route)) in
+  fmap (\url -> _customizeRequest b $ url { method = httpMethod route }) initialURL
+
+-- | Modify the @name@ of the @Builder@ from inside an API. Using this is probably not the best idea,
+--   it's nice if the @Builder@'s name is stable at least.
+name :: Monad m => Text -> APIT s e m ()
+name t = liftBuilder $ modify (\b -> b { _name = t })
+
+-- | Modify the @baseURL@ of the @Builder@ from inside an API.
+--   Can be useful for changing the API's endpoints for certain requests.
+baseURL :: Monad m => Text -> APIT s e m ()
+baseURL t = liftBuilder $ modify (\b -> b { _baseURL = t })
+
+-- | Modify every @Route@ before it runs. Useful for adding extra params to every query,
+--   for example.
+customizeRoute :: Monad m => (Route -> Route) -> APIT s e m ()
+customizeRoute f = liftBuilder $ modify (\b -> b { _customizeRoute = f })
+
+-- | Modify every @Request@ before the API fetches it. Useful for adding headers to every request,
+--   for example.
+customizeRequest :: Monad m => (Request -> Request) -> APIT s e m ()
+customizeRequest f = liftBuilder $ modify (\b -> b { _customizeRequest = f })
diff --git a/src/Network/API/Builder/Builder.hs b/src/Network/API/Builder/Builder.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/Builder.hs
@@ -0,0 +1,26 @@
+module Network.API.Builder.Builder
+  ( Builder(..)
+  , basicBuilder ) where
+
+import Network.API.Builder.Routes
+
+import Data.Text (Text)
+import Network.HTTP.Conduit (Request)
+import qualified Data.Text as T
+
+-- | Builder type for the API. Keeps track of the API's name and base URL, and how
+--   to modify Routes and Requests before they're run.
+data Builder = Builder { _name :: Text
+                       , _baseURL :: Text
+                       , _customizeRoute :: Route -> Route
+                       , _customizeRequest :: Request -> Request }
+
+instance Show Builder where
+  show b = "Builder { name = " ++ T.unpack (_name b) ++ "}"
+
+-- | Makes a basic builder, i.e. one that simply has a name and base URL
+--   and doesn't fiddle with Routes / Requests.
+basicBuilder :: Text -- ^ name
+             -> Text -- ^ base url
+             -> Builder -- ^ a simple @Builder@
+basicBuilder n b = Builder n b id id
diff --git a/src/Network/API/Builder/Decoding.hs b/src/Network/API/Builder/Decoding.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/Decoding.hs
@@ -0,0 +1,29 @@
+module Network.API.Builder.Decoding
+  ( decode ) where
+
+import Network.API.Builder.Error
+
+import Data.Aeson (FromJSON, parseJSON)
+import Data.Aeson.Parser (value)
+import Data.Aeson.Types (parseEither)
+import Data.Attoparsec.Lazy (parse, eitherResult)
+import qualified Data.ByteString.Lazy as BS
+
+-- | Try to parse a value from a JSON @ByteString@, and if not, try to
+--   parse a useful error from the JSON. If we can't do that, complain about
+--   a @ParseError@.
+decode :: (FromJSON a, FromJSON e) => BS.ByteString -> Either (APIError e) a
+decode s =
+  case eitherDecode s of
+    Right x -> Right x
+    Left err ->
+      case eitherDecode s of
+        Right x -> Left $ APIError x
+        Left _ -> Left $ ParseError err
+
+-- | @Data.Aeson@'s @decode@ function will only parse a value if it's
+--   an array or an object, in accordance with some RFC somewhere. Unfortunately
+--   not all JSON APIs respect this, so we have to mess with parsers and values and
+--   whatnot.
+eitherDecode :: (FromJSON a) => BS.ByteString -> Either String a
+eitherDecode s = eitherResult (parse value s) >>= parseEither parseJSON
diff --git a/src/Network/API/Builder/Error.hs b/src/Network/API/Builder/Error.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/Error.hs
@@ -0,0 +1,24 @@
+module Network.API.Builder.Error
+  ( APIError(..) ) where
+
+import Network.HTTP.Conduit (HttpException)
+
+-- | Error type for the @API@, where @a@ is the type that should be returned when
+--   something goes wrong on the other end - i.e. any error that isn't directly related
+--   to this library.
+data APIError a = APIError a -- ^ A type that represents any error that happens on the API end.
+                             --   Define your own custom type with a @FromJSON@ instance if you
+                             --   want to handle them, or you can use @()@ if you just want to
+                             --   ignore them all.
+                | HTTPError HttpException -- ^ Something went wrong when we tried to do a HTTP operation.
+                | InvalidURLError -- ^ You're trying to create an invalid URL somewhere - check your
+                                  --   @Builder@'s base URL and your @Route@s.
+                | ParseError String -- ^ Failed when parsing the response, and it wasn't an error on their end.
+  deriving Show
+
+instance Eq a => Eq (APIError a) where
+  (HTTPError _) == _ = False
+  _ == (HTTPError _) = False
+  (APIError a) == (APIError b) = a == b
+  InvalidURLError == InvalidURLError = True
+  (ParseError a) == (ParseError b) = a == b
diff --git a/src/Network/API/Builder/Examples/StackOverflow.hs b/src/Network/API/Builder/Examples/StackOverflow.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/Examples/StackOverflow.hs
@@ -0,0 +1,43 @@
+-- | Defines a basic example of API use - check the readme for more detail
+--   or check the tutorial at <https://github.com/intolerable/api-builder>
+module Network.API.Builder.Examples.StackOverflow where
+
+import Network.API.Builder
+import Control.Applicative
+import Data.Aeson
+import Data.Monoid (mempty)
+import Data.Text (Text)
+
+data Question = Question { title :: Text
+                         , isAnswered :: Bool
+                         , score :: Int
+                         , tags :: [Text] }
+  deriving (Show, Eq)
+
+newtype Questions = Questions [Question]
+  deriving (Show, Eq)
+
+instance FromJSON Question where
+  parseJSON (Object o) =
+    Question <$> o .: "title"
+             <*> o .: "is_answered"
+             <*> o .: "score"
+             <*> o .: "tags"
+  parseJSON _ = mempty
+
+instance FromJSON Questions where
+  parseJSON (Object o) = Questions <$> o .: "items"
+  parseJSON _ = mempty
+
+stackOverflow :: Builder
+stackOverflow = basicBuilder "StackOverflow API" "http://api.stackexchange.com"
+
+answersRoute :: Route
+answersRoute = Route { fragments = [ "2.2", "questions" ]
+                     , urlParams = [ "order" =. ("desc" :: Text)
+                                   , "sort" =. ("activity" :: Text)
+                                   , "site" =. ("stackoverflow" :: Text) ]
+                     , httpMethod = "GET" }
+
+getAnswers :: IO (Either (APIError ()) Questions)
+getAnswers = execAPI stackOverflow () $ runRoute answersRoute
diff --git a/src/Network/API/Builder/Query.hs b/src/Network/API/Builder/Query.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/Query.hs
@@ -0,0 +1,29 @@
+module Network.API.Builder.Query where
+
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+
+class ToQuery a where
+  toQuery :: a -> Maybe Text
+
+instance ToQuery Integer where
+  toQuery = Just . Text.pack . show
+
+instance ToQuery Bool where
+  toQuery True = Just "true"
+  toQuery False = Just "false"
+
+instance ToQuery Int where
+  toQuery = Just . Text.pack . show
+
+instance ToQuery Text where
+  toQuery = Just
+
+instance ToQuery a => ToQuery (Maybe a) where
+  toQuery (Just a) = toQuery a
+  toQuery Nothing = Nothing
+
+instance ToQuery a => ToQuery [a] where
+  toQuery [] = Nothing
+  toQuery xs = Just $ Text.intercalate "," $ mapMaybe toQuery xs
diff --git a/src/Network/API/Builder/Routes.hs b/src/Network/API/Builder/Routes.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/API/Builder/Routes.hs
@@ -0,0 +1,61 @@
+module Network.API.Builder.Routes
+  ( Route(..)
+  , URLFragment
+  , URLParam
+  , (=.)
+  , routeURL ) where
+
+import Control.Arrow ((***))
+import Data.Maybe (mapMaybe)
+import Data.Monoid ((<>))
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Network.HTTP.Base as HTTP (urlEncodeVars)
+import qualified Network.HTTP.Types.Method as HTTP
+
+import Network.API.Builder.Query
+
+-- | Alias for @Text@ to store the URL fragments for each @Route@.
+type URLFragment = Text
+
+-- | Alias to @(Text, Maybe Text)@ used to store each query that gets
+--   tacked onto the request.
+type URLParam = (Text, Maybe Text)
+
+-- | Convenience function for building @URLParam@s. Right-hand argument must
+--   have a @ToQuery@ instance so it can be converted to the appropriate
+--   representation in a query string. Query values do not need to be
+--   escaped.
+--
+-- >>> "api_type" =. ("json" :: Text)
+-- ("api_type", Just "json")
+(=.) :: ToQuery a => Text -> a -> (Text, Maybe Text)
+k =. v = (k, toQuery v)
+
+-- | Main type for routes in the API. Used to represent the URL minus the actual
+--   endpoint URL as well as the query string and the HTTP method used to communicate
+--   with the server.
+data Route = Route { fragments :: [URLFragment]
+                   , urlParams :: [URLParam]
+                   , httpMethod :: HTTP.Method }
+  deriving (Show, Read, Eq)
+
+-- | Converts a Route to a URL. Drops any @Nothing@ values from the query, separates the
+--   fragments with "/" and tacks them onto the end of the base URL.
+routeURL :: Text -- ^ base URL for the @Route@ (you can usually get this from the @Builder@)
+         -> Route -- ^ the @Route@ to process
+         -> Text -- ^ the finalized URL as a @Text@
+routeURL baseURL (Route fs ps _) =
+  let path = T.intercalate "/" fs
+  in baseURL <> "/" <> path <> pathParamsSep fs <> buildParams ps
+
+pathParamsSep :: [URLFragment] -> Text
+pathParamsSep [] = "?"
+pathParamsSep xs = if T.isInfixOf "." (last xs) then "?" else "/?"
+
+buildParams :: [URLParam] -> Text
+buildParams = T.pack . HTTP.urlEncodeVars . map (T.unpack *** T.unpack) . mapMaybe collectParams
+
+collectParams :: URLParam -> Maybe (Text, Text)
+collectParams (a, Just x) = Just (a,x)
+collectParams (_, Nothing) = Nothing
