diff --git a/api-builder.cabal b/api-builder.cabal
--- a/api-builder.cabal
+++ b/api-builder.cabal
@@ -1,91 +1,101 @@
-name: api-builder
-version: 0.14.0.0
-synopsis: Library for easily building REST API wrappers in Haskell
-license: BSD3
-license-file: LICENSE
-author: Fraser Murray
-maintainer: fraser.m.murray@gmail.com
-homepage: https://github.com/intolerable/api-builder
-copyright: (c) Fraser Murray 2014
-category: Network
-build-type: Simple
-cabal-version: >= 1.10
+-- This file has been generated from package.yaml by hpack version 0.20.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 7c4b7181e34f5c26900835396c9c93f9249c246a35bacfea6a01f9a84dff464b
 
+name:           api-builder
+version:        0.15.0.0
+synopsis:       Library for easily building REST API wrappers in Haskell
+category:       Network
+homepage:       https://github.com/intolerable/api-builder
+author:         Fraser Murray
+maintainer:     fraser.m.murray@gmail.com
+copyright:      (c) Fraser Murray 2014
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+
 source-repository head
   type: git
   location: git://github.com/intolerable/api-builder.git
 
-source-repository this
-  type: git
-  location: git://github.com/intolerable/api-builder.git
-  tag: v0.11.0.0
-
 library
-  exposed-modules:
-    Network.API.Builder
-    Network.API.Builder.API
-    Network.API.Builder.Builder
-    Network.API.Builder.Error
-    Network.API.Builder.Examples.StackOverflow
-    Network.API.Builder.Query
-    Network.API.Builder.Receive
-    Network.API.Builder.Routes
-    Network.API.Builder.Send
-    Network.API.Builder.Send.Multipart
+  hs-source-dirs:
+      src
+  default-extensions: OverloadedStrings FlexibleInstances
+  ghc-options: -Wall
   build-depends:
-    base >= 4.6 && < 4.12,
-    aeson >= 0.9 && < 1.3,
-    bifunctors >= 4.0 && < 6.0,
-    bytestring == 0.10.*,
-    HTTP == 4000.*,
-    http-client >= 0.4.30 && < 0.6,
-    http-client-tls >= 0.2 && < 0.4,
-    http-types >= 0.8 && < 0.11,
-    text == 1.*,
-    tls >= 1.3 && < 1.5,
-    transformers >= 0.4 && < 0.6
-  hs-source-dirs: src/
+      HTTP ==4000.*
+    , aeson >=0.9 && <1.3
+    , base >=4.6 && <4.12
+    , bifunctors >=4.0 && <6.0
+    , bytestring ==0.10.*
+    , http-client >=0.4.30 && <0.6
+    , http-client-tls >=0.2 && <0.4
+    , http-types >=0.8 && <0.13
+    , text ==1.*
+    , tls >=1.3 && <1.5
+    , transformers >=0.4 && <0.6
+  exposed-modules:
+      Network.API.Builder
+      Network.API.Builder.API
+      Network.API.Builder.Builder
+      Network.API.Builder.Error
+      Network.API.Builder.Examples.StackOverflow
+      Network.API.Builder.Query
+      Network.API.Builder.Receive
+      Network.API.Builder.Routes
+      Network.API.Builder.Send
+      Network.API.Builder.Send.Multipart
+  other-modules:
+      Paths_api_builder
   default-language: Haskell2010
-  default-extensions:
-    FlexibleInstances
-    OverloadedStrings
-  ghc-options: -Wall
 
 test-suite test
-  hs-source-dirs: test
-  main-is: Spec.hs
-  default-extensions:
-    OverloadedStrings
-    ScopedTypeVariables
-  default-language: Haskell2010
   type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  hs-source-dirs:
+      test
+  default-extensions: OverloadedStrings FlexibleInstances ScopedTypeVariables
+  ghc-options: -Wall
   build-depends:
-    base == 4.*,
-    Cabal >= 1.16.0,
-    api-builder,
-    aeson,
-    bytestring,
-    hspec,
-    http-client,
-    text,
-    transformers
-  GHC-options: -Wall
+      Cabal >=1.16.0
+    , aeson
+    , api-builder
+    , base ==4.*
+    , bytestring
+    , hspec
+    , http-client
+    , text
+    , transformers
+  other-modules:
+      Network.API.Builder.ErrorSpec
+      Network.API.Builder.QuerySpec
+      Network.API.Builder.RoutesSpec
+      Network.API.Builder.SendSpec
+      Paths_api_builder
+  default-language: Haskell2010
 
 test-suite test-io
-  hs-source-dirs: test-io
-  main-is: Spec.hs
-  default-extensions:
-    LambdaCase
-  default-language: Haskell2010
   type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  hs-source-dirs:
+      test-io
+  default-extensions: OverloadedStrings FlexibleInstances LambdaCase
+  ghc-options: -Wall
   build-depends:
-    base == 4.*,
-    Cabal >= 1.16.0,
-    api-builder,
-    aeson,
-    bytestring,
-    containers,
-    hspec,
-    text,
-    transformers
-  GHC-options: -Wall
+      Cabal >=1.16.0
+    , aeson
+    , api-builder
+    , base ==4.*
+    , bytestring
+    , containers
+    , hspec
+    , text
+    , transformers
+  other-modules:
+      Network.API.Builder.Examples.StackOverflowSpec
+      Paths_api_builder
+  default-language: Haskell2010
diff --git a/test-io/Network/API/Builder/Examples/StackOverflowSpec.hs b/test-io/Network/API/Builder/Examples/StackOverflowSpec.hs
new file mode 100644
--- /dev/null
+++ b/test-io/Network/API/Builder/Examples/StackOverflowSpec.hs
@@ -0,0 +1,40 @@
+module Network.API.Builder.Examples.StackOverflowSpec where
+
+import Network.API.Builder.Examples.StackOverflow
+
+import Control.Monad
+import Data.List (nub)
+import Test.Hspec
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "getAnswers" $ do
+
+    it "can get answers" $
+      getAnswers >>= \case
+        Left _ -> expectationFailure "getAnswers failed"
+        Right (Questions qs) -> do
+          nub qs `shouldMatchList` qs
+          forM_ qs $ \q -> do
+            tags q `shouldSatisfy` isNubbed
+            title q `shouldSatisfy` (not . Text.null)
+
+    it "can get answers via https" $
+      getAnswersSSL >>= \case
+        Left _ -> expectationFailure "getAnswers failed"
+        Right (Questions qs) -> do
+          nub qs `shouldMatchList` qs
+          forM_ qs $ \q -> do
+            tags q `shouldSatisfy` isNubbed
+            title q `shouldSatisfy` (not . Text.null)
+
+isNubbed :: Ord a => [a] -> Bool
+isNubbed = f Set.empty
+  where
+    f _ [] = True
+    f s (x:xs) = not (Set.member x s) && f (Set.insert x s) xs
diff --git a/test/Network/API/Builder/ErrorSpec.hs b/test/Network/API/Builder/ErrorSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/API/Builder/ErrorSpec.hs
@@ -0,0 +1,25 @@
+module Network.API.Builder.ErrorSpec where
+
+import Network.API.Builder.Error
+
+import Test.Hspec
+import Test.Hspec.QuickCheck
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "APIError" $ do
+    describe "Eq" $ do
+      it "should check for equality properly" $ do
+        HTTPError undefined == (HTTPError undefined :: APIError ())
+          `shouldBe` False
+        APIError () `shouldBe` APIError ()
+        InvalidURLError `shouldBe` (InvalidURLError :: APIError ())
+
+      prop "ParseError s == ParseError s" $ \s ->
+        ParseError s == (ParseError s :: APIError ())
+
+      prop "APIError x == APIError x" $ \(x :: String) ->
+        APIError x == APIError x
diff --git a/test/Network/API/Builder/QuerySpec.hs b/test/Network/API/Builder/QuerySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/API/Builder/QuerySpec.hs
@@ -0,0 +1,33 @@
+module Network.API.Builder.QuerySpec where
+
+import Network.API.Builder.Query
+
+import Test.Hspec
+import Test.Hspec.QuickCheck
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "ToQuery" $ do
+
+    it "can convert an Integer" $ do
+      toQuery "integer" (5 :: Integer) `shouldBe`
+        [("integer", "5")]
+
+    it "can convert a Boolean" $ do
+      toQuery "bool" False `shouldBe` [("bool", "false")]
+
+    it "can convert a list of Ints" $ do
+      toQuery "list" ([1,2,3,4,5] :: [Int]) `shouldBe` [("list", "1,2,3,4,5")]
+
+    it "can convert Maybe values" $ do
+      toQuery "maybe" (Just False) `shouldBe` [("maybe", "false")]
+      toQuery "maybe" (Nothing :: Maybe Bool) `shouldBe` []
+
+    prop "toQuery x y == toQuery x (Just y)" $ \(y :: Int) ->
+      toQuery "x" y == toQuery "x" (Just y)
+
+skip :: String -> Expectation -> Expectation
+skip _ _ = return ()
diff --git a/test/Network/API/Builder/RoutesSpec.hs b/test/Network/API/Builder/RoutesSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/API/Builder/RoutesSpec.hs
@@ -0,0 +1,24 @@
+module Network.API.Builder.RoutesSpec where
+
+import Network.API.Builder.Routes
+
+import Test.Hspec
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "routeURL" $ do
+    it "should be able to create a basic url" $ do
+      routeURL "" (Route [ "api", "index" ] [ ] "GET")
+        `shouldBe` "/api/index"
+      routeURL "" (Route [ "api", "index.json" ] [ ] "GET")
+        `shouldBe` "/api/index.json"
+      routeURL "" (Route [ ] [ "test" =. False ] "GET")
+        `shouldBe` "?test=false"
+      routeURL "" (Route [ "about.json" ] [ "test" =. True ] "GET")
+        `shouldBe` "/about.json?test=true"
+      routeURL "" (Route [ "about.json" ] [ "test" =. True ] "GET")
+        `shouldBe` "/about.json?test=true"
+
diff --git a/test/Network/API/Builder/SendSpec.hs b/test/Network/API/Builder/SendSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/API/Builder/SendSpec.hs
@@ -0,0 +1,100 @@
+module Network.API.Builder.SendSpec where
+
+import Network.API.Builder.Builder
+import Network.API.Builder.Routes
+import Network.API.Builder.Send
+
+import Data.Aeson
+import Data.ByteString.Lazy (ByteString)
+import qualified Network.HTTP.Client as HTTP
+import Test.Hspec
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "Sendable" $ do
+
+    describe "()" $ do
+      it "should be able to construct a basic request" $ do
+        case send exampleBuilder (Route ["api.json"] [] "GET") () of
+          Just req -> do
+            HTTP.secure req `shouldBe` True
+            HTTP.host req `shouldBe` "example.com"
+            HTTP.method req `shouldBe` "GET"
+            HTTP.port req `shouldBe` 443
+            HTTP.queryString req `shouldBe` ""
+            HTTP.path req `shouldBe` "/api.json"
+          Nothing -> expectationFailure "req construction failed"
+      it "should be able to construct request with query params" $ do
+        case send exampleBuilder (Route ["api.json"] ["a_query" =. True] "GET") () of
+          Just req -> do
+            HTTP.secure req `shouldBe` True
+            HTTP.host req `shouldBe` "example.com"
+            HTTP.method req `shouldBe` "GET"
+            HTTP.port req `shouldBe` 443
+            HTTP.queryString req `shouldBe` "?a_query=true"
+            HTTP.path req `shouldBe` "/api.json"
+          Nothing -> expectationFailure "req construction failed"
+      it "should be able to construct a POST request" $ do
+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") () of
+          Just req -> do
+            HTTP.secure req `shouldBe` True
+            HTTP.host req `shouldBe` "example.com"
+            HTTP.method req `shouldBe` "POST"
+            HTTP.port req `shouldBe` 443
+            HTTP.queryString req `shouldBe` ""
+            HTTP.path req `shouldBe` "/api.json"
+            case HTTP.requestBody req of
+              HTTP.RequestBodyBS "query=false" -> return ()
+              _ -> expectationFailure "incorrect POST body"
+          Nothing -> expectationFailure "req construction failed"
+
+    describe "PostQuery" $ do
+      it "should be able to construct a POST request with no body" $ do
+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") PostQuery of
+          Just req -> do
+            HTTP.secure req `shouldBe` True
+            HTTP.host req `shouldBe` "example.com"
+            HTTP.method req `shouldBe` "POST"
+            HTTP.port req `shouldBe` 443
+            HTTP.queryString req `shouldBe` "?query=false"
+            HTTP.path req `shouldBe` "/api.json"
+            case HTTP.requestBody req of
+              HTTP.RequestBodyLBS "" -> return ()
+              _ -> expectationFailure "incorrect POST body"
+          Nothing -> expectationFailure "req construction failed"
+
+    describe "Value" $ do
+      it "should be able to construct a POST request with the correct JSON body" $ do
+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") $ object ["hello" .= Null] of
+          Just req -> do
+            HTTP.secure req `shouldBe` True
+            HTTP.host req `shouldBe` "example.com"
+            HTTP.method req `shouldBe` "POST"
+            HTTP.port req `shouldBe` 443
+            HTTP.queryString req `shouldBe` "?query=false"
+            HTTP.path req `shouldBe` "/api.json"
+            case HTTP.requestBody req of
+              HTTP.RequestBodyLBS "{\"hello\":null}" -> return ()
+              _ -> expectationFailure "incorrect POST body"
+          Nothing -> expectationFailure "req construction failed"
+
+    describe "ByteString" $ do
+      it "should be able to construct a POST request with the correct ByteString body" $ do
+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") ("hello world" :: ByteString) of
+          Just req -> do
+            HTTP.secure req `shouldBe` True
+            HTTP.host req `shouldBe` "example.com"
+            HTTP.method req `shouldBe` "POST"
+            HTTP.port req `shouldBe` 443
+            HTTP.queryString req `shouldBe` "?query=false"
+            HTTP.path req `shouldBe` "/api.json"
+            case HTTP.requestBody req of
+              HTTP.RequestBodyLBS "hello world" -> return ()
+              _ -> expectationFailure "incorrect POST body"
+          Nothing -> expectationFailure "req construction failed"
+
+exampleBuilder :: Builder
+exampleBuilder = basicBuilder "example" "https://example.com"
