diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -5,46 +5,38 @@
 
 Lackey is a Haskell library for generating Ruby consumers of [Servant][] APIs.
 
--   [Installation](#installation)
--   [Usage](#usage)
-
-## Installation
-
-You can install Lackey by adding it to your Cabal file.
-
-```
-build-depends:
-    lackey ==0.1.*
-```
-
-You can also install it manually.
-
-``` sh
-cabal update
-cabal install 'lackey ==0.1.*'
-```
-
-Please see [the change log][] for a detailed list of changes.
-
-## Usage
-
 Use `Lackey.rubyForAPI` to generate a string of Ruby source code for consuming
 a Servant API. For example:
 
 ``` hs
-type API = "things" :> Get '[JSON] [Thing]
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeOperators #-}
 
-api :: Proxy API
-api = Proxy
+import qualified Data.Proxy as Proxy
+import qualified Data.Text as Text
+import qualified Data.Text.IO as Text
+import qualified Lackey
+import Servant.API
 
-ruby :: String
-ruby = rubyForAPI api
+type API = "words" :> Get '[JSON] [String]
+
+api :: Proxy.Proxy API
+api = Proxy.Proxy
+
+ruby :: Text.Text
+ruby = Lackey.rubyForAPI api
+
+main :: IO ()
+main = Text.putStrLn ruby
+-- def get_words(excon)excon.request(:method=>:get,:path=>"/words",:headers=>{},:body=>nil)end
 ```
 
+The generated functions require [Excon][].
+
 [Lackey]: https://github.com/tfausak/lackey
 [Version badge]: https://www.stackage.org/package/lackey/badge/nightly?label=version
 [version]: https://www.stackage.org/package/lackey
 [Build badge]: https://travis-ci.org/tfausak/lackey.svg?branch=master
 [build]: https://travis-ci.org/tfausak/lackey
-[Servant]: http://haskell-servant.github.io
-[the change log]: ./CHANGELOG.md
+[Servant]: https://haskell-servant.readthedocs.org/en/stable/
+[Excon]: https://rubygems.org/gems/excon
diff --git a/lackey.cabal b/lackey.cabal
--- a/lackey.cabal
+++ b/lackey.cabal
@@ -3,9 +3,9 @@
 -- see: https://github.com/sol/hpack
 
 name:           lackey
-version:        0.2.0
-synopsis:       Generate Ruby consumers of Servant APIs.
-description:    Lackey generates Ruby consumers of Servant APIs.
+version:        0.3.0
+synopsis:       Generate Ruby clients from Servant APIs.
+description:    Lackey generates Ruby clients from Servant APIs.
 category:       Web
 homepage:       https://github.com/tfausak/lackey#readme
 bug-reports:    https://github.com/tfausak/lackey/issues
@@ -30,18 +30,12 @@
       library
   ghc-options: -Wall
   build-depends:
-      base >=4.7 && <4.9
-    , servant >=0.4 && <0.6
+      base >=4.8 && <4.9
+    , servant >=0.5 && <0.7
+    , servant-foreign >=0.5 && <0.7
+    , text >=1.2 && <1.3
   exposed-modules:
       Lackey
-      Lackey.Internal
-      Lackey.Internal.Endpoint
-      Lackey.Internal.HasCode
-      Lackey.Internal.HasRuby
-      Lackey.Internal.Header
-      Lackey.Internal.Method
-      Lackey.Internal.PathSegment
-      Lackey.Internal.QueryItem
   default-language: Haskell2010
 
 test-suite lackey-test-suite
@@ -49,11 +43,12 @@
   main-is: Main.hs
   hs-source-dirs:
       test-suite
-  ghc-options: -Wall
+  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N
   build-depends:
       base
     , lackey
     , servant
-    , tasty >=0.10 && <0.12
+    , tasty >=0.11 && <0.12
     , tasty-hspec >=1.1 && <1.2
+    , text
   default-language: Haskell2010
diff --git a/library/Lackey.hs b/library/Lackey.hs
--- a/library/Lackey.hs
+++ b/library/Lackey.hs
@@ -1,17 +1,186 @@
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
 
-{- |
-    This module allows you to generate Ruby code for APIs defined by Servant.
--}
-module Lackey
-    ( rubyForAPI
-    ) where
+module Lackey (rubyForAPI) where
 
-import Data.Proxy (Proxy)
-import Lackey.Internal
+import qualified Data.Char as Char
+import Data.Function ((&))
+import qualified Data.Maybe as Maybe
+import Data.Monoid ((<>))
+import qualified Data.Proxy as Proxy
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Servant.Foreign as Servant
 
-{- |
-    Generate Ruby code from an API description.
--}
-rubyForAPI :: (HasCode a, HasRuby (Ruby a)) => Proxy a -> String
-rubyForAPI proxy = rubyFor (codeFor proxy defaultEndpoint)
+type Language = Servant.NoTypes
+
+languageProxy :: Proxy.Proxy Language
+languageProxy = Proxy.Proxy
+
+type Request = ()
+
+requestProxy :: Proxy.Proxy Request
+requestProxy = Proxy.Proxy
+
+renderRequests :: [Servant.Req Request] -> Text.Text
+renderRequests requests = requests & map renderRequest & Text.intercalate ";"
+
+functionName :: Servant.Req Request -> Text.Text
+functionName request = request & Servant._reqFuncName & Servant.snakeCase
+
+hasBody :: Servant.Req Request -> Bool
+hasBody request =
+    case Servant._reqBody request of
+        Nothing -> False
+        Just _ -> True
+
+bodyArgument :: Text.Text
+bodyArgument = "body"
+
+underscore :: Text.Text -> Text.Text
+underscore text =
+    text & Text.toLower &
+    Text.map
+        (\c ->
+              if Char.isAlphaNum c
+                  then c
+                  else '_')
+
+getHeaders :: Servant.Req Request -> [Text.Text]
+getHeaders request =
+    request & Servant._reqHeaders &
+    Maybe.mapMaybe
+        (\h ->
+              case h of
+                  Servant.HeaderArg x -> Just x
+                  Servant.ReplaceHeaderArg _ _ -> Nothing) &
+    map Servant._argName &
+    map Servant.unPathSegment
+
+getURLPieces :: Servant.Req Request -> [Either Text.Text Text.Text]
+getURLPieces request =
+    let url = request & Servant._reqUrl
+        path =
+            url & Servant._path & map Servant.unSegment &
+            Maybe.mapMaybe
+                (\segment ->
+                      case segment of
+                          Servant.Static _ -> Nothing
+                          Servant.Cap arg -> Just arg) &
+            map Servant._argName &
+            map Servant.unPathSegment
+        query =
+            url & Servant._queryStr & map Servant._queryArgName &
+            map Servant._argName &
+            map Servant.unPathSegment
+    in map Left path ++ map Right query
+
+functionArguments :: Servant.Req Request -> Text.Text
+functionArguments request =
+    Text.concat
+        [ "("
+        , [ [Just "excon"]
+          , request & getURLPieces &
+            map
+                (\piece ->
+                      case piece of
+                          Left capture -> underscore capture
+                          Right param -> underscore param <> ": nil") &
+            map Just
+          , request & getHeaders & map underscore & map (<> ": nil") & map Just
+          , [ if hasBody request
+                  then Just bodyArgument
+                  else Nothing]] &
+          concat &
+          Maybe.catMaybes &
+          Text.intercalate ","
+        , ")"]
+
+requestMethod :: Servant.Req Request -> Text.Text
+requestMethod request =
+    request & Servant._reqMethod & Text.decodeUtf8 & Text.toLower &
+    Text.cons ':'
+
+requestPath :: Servant.Req Request -> Text.Text
+requestPath request =
+    let path =
+            request & Servant._reqUrl & Servant._path & map Servant.unSegment &
+            map
+                (\x ->
+                      case x of
+                          Servant.Static y -> Servant.unPathSegment y
+                          Servant.Cap y ->
+                              let z =
+                                      y & Servant._argName &
+                                      Servant.unPathSegment &
+                                      underscore
+                              in "#{" <> z <> "}") &
+            Text.intercalate "/"
+        query =
+            request & Servant._reqUrl & Servant._queryStr &
+            map Servant._queryArgName &
+            map Servant._argName &
+            map Servant.unPathSegment &
+            map
+                (\x ->
+                      x <> "=#{" <> underscore x <> "}") &
+            Text.intercalate "&"
+        url =
+            "/" <> path <>
+            (if Text.null query
+                 then ""
+                 else "?" <> query)
+    in "\"" <> url <> "\""
+
+requestHeaders :: Servant.Req Request -> Text.Text
+requestHeaders request =
+    [ ["{"]
+    , request & getHeaders &
+      map
+          (\x ->
+                "\"" <> x <> "\"=>" <> underscore x)
+    , ["}"]] &
+    concat &
+    Text.concat
+
+requestBody :: Servant.Req Request -> Text.Text
+requestBody request =
+    if hasBody request
+        then bodyArgument
+        else "nil"
+
+functionBody :: Servant.Req Request -> Text.Text
+functionBody request =
+    Text.concat
+        [ "excon.request("
+        , ":method=>"
+        , requestMethod request
+        , ","
+        , ":path=>"
+        , requestPath request
+        , ","
+        , ":headers=>"
+        , requestHeaders request
+        , ","
+        , ":body=>"
+        , requestBody request
+        , ")"]
+
+renderRequest :: Servant.Req Request -> Text.Text
+renderRequest request =
+    Text.concat
+        [ "def "
+        , functionName request
+        , functionArguments request
+        , functionBody request
+        , "end"]
+
+requestsForAPI
+    :: (Servant.HasForeign Language Request api, Servant.GenerateList Request (Servant.Foreign Request api))
+    => Proxy.Proxy api -> [Servant.Req Request]
+requestsForAPI api = api & Servant.listFromAPI languageProxy requestProxy
+
+rubyForAPI
+    :: (Servant.HasForeign Language Request api, Servant.GenerateList Request (Servant.Foreign Request api))
+    => Proxy.Proxy api -> Text.Text
+rubyForAPI api = api & requestsForAPI & renderRequests
diff --git a/library/Lackey/Internal.hs b/library/Lackey/Internal.hs
deleted file mode 100644
--- a/library/Lackey/Internal.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{- |
-    This module is for internal use only and should be considered private. If
-    you need anything exported from this module, please contact the maintainer.
--}
-module Lackey.Internal
-    ( module Lackey.Internal.Endpoint
-    , module Lackey.Internal.HasCode
-    , module Lackey.Internal.HasRuby
-    , module Lackey.Internal.Method
-    , module Lackey.Internal.PathSegment
-    , module Lackey.Internal.QueryItem
-    ) where
-
-import Lackey.Internal.Endpoint
-import Lackey.Internal.HasCode
-import Lackey.Internal.HasRuby
-import Lackey.Internal.Method
-import Lackey.Internal.PathSegment
-import Lackey.Internal.QueryItem
diff --git a/library/Lackey/Internal/Endpoint.hs b/library/Lackey/Internal/Endpoint.hs
deleted file mode 100644
--- a/library/Lackey/Internal/Endpoint.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Lackey.Internal.Endpoint where
-
-import Lackey.Internal.Header (Header)
-import Lackey.Internal.Method (Method (Get))
-import Lackey.Internal.PathSegment (PathSegment)
-import Lackey.Internal.QueryItem (QueryItem)
-
-data Endpoint = Endpoint
-    { endpointMethod :: Method
-    , endpointPathSegments :: [PathSegment]
-    , endpointQueryItems :: [QueryItem]
-    , endpointHeaders :: [Header]
-    , endpointHasBody :: Bool
-    } deriving (Eq, Ord, Read, Show)
-
-defaultEndpoint :: Endpoint
-defaultEndpoint = Endpoint
-    { endpointMethod = Get
-    , endpointPathSegments = []
-    , endpointQueryItems = []
-    , endpointHeaders = []
-    , endpointHasBody = False
-    }
diff --git a/library/Lackey/Internal/HasCode.hs b/library/Lackey/Internal/HasCode.hs
deleted file mode 100644
--- a/library/Lackey/Internal/HasCode.hs
+++ /dev/null
@@ -1,112 +0,0 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE TypeOperators #-}
-
-module Lackey.Internal.HasCode where
-
-import Data.Proxy (Proxy (Proxy))
-import GHC.TypeLits (KnownSymbol, symbolVal)
-import Lackey.Internal.Endpoint
-import Lackey.Internal.Header
-import Lackey.Internal.Method
-import Lackey.Internal.PathSegment
-import Lackey.Internal.QueryItem
-import Servant.API ((:>), (:<|>)((:<|>)))
-
-import qualified Servant.API as S
-
-class HasCode a where
-    type Ruby a
-    codeFor :: Proxy a -> Endpoint -> Ruby a
-
-instance HasCode S.Raw where
-    type Ruby S.Raw = ()
-    codeFor _ _ = ()
-
-instance (HasCode a, HasCode b) => HasCode (a :<|> b) where
-    type Ruby (a :<|> b) = Ruby a :<|> Ruby b
-    codeFor _ e = codeFor a e :<|> codeFor b e where
-        a = Proxy :: Proxy a
-        b = Proxy :: Proxy b
-
-instance (HasCode c) => HasCode (S.ReqBody a b :> c) where
-    type Ruby (S.ReqBody a b :> c) = Ruby c
-    codeFor _ e = codeFor c (e { endpointHasBody = True}) where
-        c = Proxy :: Proxy c
-
--- Methods
-
-instance HasCode (S.Delete a b) where
-    type Ruby (S.Delete a b) = Endpoint
-    codeFor _ e = e { endpointMethod = Delete }
-
-instance HasCode (S.Get a b) where
-    type Ruby (S.Get a b) = Endpoint
-    codeFor _ e = e { endpointMethod = Get }
-
-instance HasCode (S.Patch a b) where
-    type Ruby (S.Patch a b) = Endpoint
-    codeFor _ e = e { endpointMethod = Patch }
-
-instance HasCode (S.Post a b) where
-    type Ruby (S.Post a b) = Endpoint
-    codeFor _ e = e { endpointMethod = Post }
-
-instance HasCode (S.Put a b) where
-    type Ruby (S.Put a b) = Endpoint
-    codeFor _ e = e { endpointMethod = Put }
-
--- Path segments
-
-instance (KnownSymbol s, HasCode a) => HasCode (s :> a) where
-    type Ruby (s :> a) = Ruby a
-    codeFor _ e = codeFor a (e { endpointPathSegments = segments }) where
-        a = Proxy :: Proxy a
-        segments = endpointPathSegments e ++ [segment]
-        segment = PathLiteral (symbolVal s)
-        s = Proxy :: Proxy s
-
-instance (KnownSymbol s, HasCode b) => HasCode (S.Capture s a :> b) where
-    type Ruby (S.Capture s a :> b) = Ruby b
-    codeFor _ e = codeFor b (e { endpointPathSegments = segments }) where
-        b = Proxy :: Proxy b
-        segments = endpointPathSegments e ++ [segment]
-        segment = PathCapture (symbolVal s)
-        s = Proxy :: Proxy s
-
--- Query items
-
-instance (KnownSymbol s, HasCode a) => HasCode (S.QueryFlag s :> a) where
-    type Ruby (S.QueryFlag s :> a) = Ruby a
-    codeFor _ e = codeFor a (e { endpointQueryItems = items }) where
-        a = Proxy :: Proxy a
-        items = endpointQueryItems e ++ [item]
-        item = QueryFlag (symbolVal s)
-        s = Proxy :: Proxy s
-
-instance (KnownSymbol s, HasCode b) => HasCode (S.QueryParam s a :> b) where
-    type Ruby (S.QueryParam s a :> b) = Ruby b
-    codeFor _ e = codeFor b (e { endpointQueryItems = items }) where
-        b = Proxy :: Proxy b
-        items = endpointQueryItems e ++ [item]
-        item = QueryParam (symbolVal s)
-        s = Proxy :: Proxy s
-
-instance (KnownSymbol s, HasCode b) => HasCode (S.QueryParams s a :> b) where
-    type Ruby (S.QueryParams s a :> b) = Ruby b
-    codeFor _ e = codeFor b (e { endpointQueryItems = items }) where
-        b = Proxy :: Proxy b
-        items = endpointQueryItems e ++ [item]
-        item = QueryParams (symbolVal s)
-        s = Proxy :: Proxy s
-
--- Headers
-
-instance (KnownSymbol s, HasCode b) => HasCode (S.Header s a :> b) where
-    type Ruby (S.Header s a :> b) = Ruby b
-    codeFor  _ e = codeFor b (e { endpointHeaders = headers }) where
-        b = Proxy :: Proxy b
-        headers = endpointHeaders e ++ [header]
-        header = Header (symbolVal s)
-        s = Proxy :: Proxy s
diff --git a/library/Lackey/Internal/HasRuby.hs b/library/Lackey/Internal/HasRuby.hs
deleted file mode 100644
--- a/library/Lackey/Internal/HasRuby.hs
+++ /dev/null
@@ -1,137 +0,0 @@
-{-# LANGUAGE TypeOperators #-}
-
-module Lackey.Internal.HasRuby where
-
-import Lackey.Internal.Endpoint
-import Lackey.Internal.Header
-import Lackey.Internal.PathSegment
-import Lackey.Internal.QueryItem
-import Servant.API ((:<|>)(..))
-
-import qualified Data.Char as Char
-import qualified Data.List as List
-import qualified Data.Maybe as Maybe
-
-infixl 1 &
-(&) :: a -> (a -> b) -> b
-x & f = f x
-
-class HasRuby a where
-    rubyFor :: a -> String
-
-instance HasRuby () where
-    rubyFor _ = ""
-
-instance HasRuby Endpoint where
-    rubyFor endpoint = "\
-        \def " ++ renderName endpoint ++ "(" ++ renderParams endpoint ++ ")\n\
-        \  excon.request(\n\
-        \    method: :" ++ renderMethod endpoint ++ ",\n\
-        \    path: \"" ++ renderPath endpoint ++ "\",\n\
-        \    headers: {" ++ renderHeaders endpoint ++ "},\n\
-        \    body: " ++ (if endpointHasBody endpoint then "body" else "nil") ++ "\n\
-        \  )\n\
-        \end\
-    \"
-
-instance (HasRuby a, HasRuby b) => HasRuby (a :<|> b) where
-    rubyFor (x :<|> y) =
-        let rx = rubyFor x
-            ry = rubyFor y
-            s = if null rx || null ry then "" else "\n\n"
-        in  concat [rx, s, ry]
-
-renderName :: Endpoint -> String
-renderName endpoint =
-    let method = renderMethod endpoint
-
-        renderPathSegment (PathLiteral literal) = literal
-        renderPathSegment (PathCapture capture) = capture
-        pathSegments =
-            let segments = endpointPathSegments endpoint
-                renderedSegments = map renderPathSegment segments
-            in  if null segments
-                then "index" : renderedSegments
-                else renderedSegments
-        path = List.intercalate "_" pathSegments
-
-        renderQueryItem (QueryFlag flag) = flag
-        renderQueryItem (QueryParam param) = param
-        renderQueryItem (QueryParams params) = params
-        queryItems = map renderQueryItem (endpointQueryItems endpoint)
-        query = if null queryItems
-            then ""
-            else "_" ++ List.intercalate "_" queryItems
-
-        renderHeader (Header x) = x
-        headers = map renderHeader (endpointHeaders endpoint)
-        header = if null headers
-            then ""
-            else "_" ++ List.intercalate "_" headers
-
-    in  sanitize (method ++ "_" ++ path ++ query ++ header)
-
-renderParams :: Endpoint -> String
-renderParams endpoint =
-    let renderPathSegment (PathCapture capture) = Just (sanitize capture)
-        renderPathSegment _ = Nothing
-        pathSegments
-            = endpoint
-            & endpointPathSegments
-            & map renderPathSegment
-            & Maybe.catMaybes
-
-        renderQueryItem (QueryFlag flag) = Just (sanitize flag ++ ": false")
-        renderQueryItem (QueryParam param) = Just (sanitize param ++ ": nil")
-        renderQueryItem (QueryParams params) = Just (sanitize params ++ ": []")
-        queryItems
-            = endpoint
-            & endpointQueryItems
-            & Maybe.mapMaybe renderQueryItem
-
-        renderHeader (Header header) = sanitize header ++ ": nil"
-        headers = endpoint & endpointHeaders & map renderHeader
-
-        body = ["body" | endpointHasBody endpoint]
-
-    in  List.intercalate ", "
-        (concat [["excon"], pathSegments, body, queryItems, headers])
-
-renderMethod :: Endpoint -> String
-renderMethod endpoint = endpoint & endpointMethod & show & map Char.toLower
-
-renderPath :: Endpoint -> String
-renderPath endpoint =
-    let renderPathSegment (PathLiteral literal) = '/' : literal
-        renderPathSegment (PathCapture capture) = concat ["/#{", sanitize capture, "}"]
-        pathSegments =
-            let segments = endpointPathSegments endpoint
-                renderedSegments = concatMap renderPathSegment segments
-            in  case segments of
-                [] -> '/' : renderedSegments
-                _ -> renderedSegments
-
-        renderQueryItem (QueryFlag flag) = concat ["#{\"&", flag, "\" if ", sanitize flag, "}"]
-        renderQueryItem (QueryParam param) = concat ["&", param, "=#{", sanitize param, "}"]
-        renderQueryItem (QueryParams params) = concat ["#{", sanitize params, ".map { |x| \"&", params, "[]=#{x}\" }.join}"]
-        queryItems = case endpointQueryItems endpoint of
-            [] -> ""
-            items -> '?' : concatMap renderQueryItem items
-
-    in pathSegments ++ queryItems
-
-renderHeaders :: Endpoint -> String
-renderHeaders endpoint =
-    let headers = endpointHeaders endpoint
-    in  if null headers
-        then ""
-        else
-            let y = headers
-                    & map (\ (Header x) -> concat ["\"", x, "\" => ", sanitize x])
-                    & List.intercalate ", "
-            in  " " ++ y ++ " "
-
-sanitize :: String -> String
-sanitize xs
-    = xs
-    & map (\ x -> if Char.isAlphaNum x then Char.toLower x else '_')
diff --git a/library/Lackey/Internal/Header.hs b/library/Lackey/Internal/Header.hs
deleted file mode 100644
--- a/library/Lackey/Internal/Header.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-module Lackey.Internal.Header where
-
-newtype Header = Header String
-    deriving (Eq, Ord, Read, Show)
diff --git a/library/Lackey/Internal/Method.hs b/library/Lackey/Internal/Method.hs
deleted file mode 100644
--- a/library/Lackey/Internal/Method.hs
+++ /dev/null
@@ -1,9 +0,0 @@
-module Lackey.Internal.Method where
-
-data Method
-    = Delete
-    | Get
-    | Patch
-    | Post
-    | Put
-    deriving (Bounded, Enum, Eq, Ord, Read, Show)
diff --git a/library/Lackey/Internal/PathSegment.hs b/library/Lackey/Internal/PathSegment.hs
deleted file mode 100644
--- a/library/Lackey/Internal/PathSegment.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Lackey.Internal.PathSegment where
-
-data PathSegment
-    = PathLiteral String
-    | PathCapture String
-    deriving (Eq, Ord, Read, Show)
diff --git a/library/Lackey/Internal/QueryItem.hs b/library/Lackey/Internal/QueryItem.hs
deleted file mode 100644
--- a/library/Lackey/Internal/QueryItem.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Lackey.Internal.QueryItem where
-
-data QueryItem
-    = QueryFlag String
-    | QueryParam String
-    | QueryParams String
-    deriving (Eq, Ord, Read, Show)
diff --git a/package.yaml b/package.yaml
--- a/package.yaml
+++ b/package.yaml
@@ -1,31 +1,37 @@
 category: Web
-description: Lackey generates Ruby consumers of Servant APIs.
+description: Lackey generates Ruby clients from Servant APIs.
 extra-source-files:
 - CHANGELOG.md
 - LICENSE.md
 - package.yaml
 - README.md
 - stack.yaml
-ghc-options:
-- -Wall
+ghc-options: -Wall
 github: tfausak/lackey
 library:
   dependencies:
-  - base >=4.7 && <4.9
-  - servant >=0.4 && <0.6
+  - base >=4.8 && <4.9
+  - servant >=0.5 && <0.7
+  - servant-foreign >=0.5 && <0.7
+  - text >=1.2 && <1.3
   source-dirs: library
 license: MIT
 maintainer: Taylor Fausak
 name: lackey
-synopsis: Generate Ruby consumers of Servant APIs.
+synopsis: Generate Ruby clients from Servant APIs.
 tests:
   lackey-test-suite:
     dependencies:
     - base
     - lackey
     - servant
-    - tasty >=0.10 && <0.12
+    - tasty >=0.11 && <0.12
     - tasty-hspec >=1.1 && <1.2
+    - text
+    ghc-options:
+    - -rtsopts
+    - -threaded
+    - -with-rtsopts=-N
     source-dirs: test-suite
     main: Main.hs
-version: '0.2.0'
+version: '0.3.0'
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,5 +1,6 @@
 extra-deps:
-- servant-0.5
+- servant-0.6.1
+- servant-foreign-0.6.1
 install-ghc: true
 require-stack-version: ! '>=1.0.4'
-resolver: lts-5.8
+resolver: nightly-2016-04-07
diff --git a/test-suite/Main.hs b/test-suite/Main.hs
--- a/test-suite/Main.hs
+++ b/test-suite/Main.hs
@@ -1,152 +1,141 @@
 {-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeOperators #-}
 
-module Main where
-
-import Data.Proxy
-import Lackey
-import Servant.API
-import Test.Tasty
+import Data.Monoid ((<>))
+import qualified Data.Proxy as Proxy
+import qualified Data.Text as Text
+import qualified Lackey
+import qualified Servant.API as Servant
+import qualified Test.Tasty as Tasty
 import Test.Tasty.Hspec
 
 main :: IO ()
-main = defaultMain =<< testSpec "Lackey" spec
-
--- Since every generated function has the same structure, it can be abstracted
--- away behind this function.
-ruby :: String -> String -> String -> String -> String -> String -> String
-ruby name params method path headers body = "\
-    \def " ++ name ++ "(excon" ++ params ++ ")\n\
-    \  excon.request(\n\
-    \    method: :" ++ method ++ ",\n\
-    \    path: \"/" ++ path ++ "\",\n\
-    \    headers: {" ++ headers ++ "},\n\
-    \    body: " ++ body ++ "\n\
-    \  )\n\
-    \end\
-\"
+main = do
+    test <- testSpec "Lackey" spec
+    Tasty.defaultMain test
 
 spec :: Spec
-spec = do
+spec = parallel $ do
     describe "rubyForAPI" $ do
         it "supports delete requests" $ do
-            let api = Proxy :: Proxy (Delete '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "delete_index" "" "delete" "" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Delete '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "delete" "" "delete" "" "" False
 
         it "supports get requests" $ do
-            let api = Proxy :: Proxy (Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index" "" "get" "" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" "" "get" "" "" False
 
         it "supports patch requests" $ do
-            let api = Proxy :: Proxy (Patch '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "patch_index" "" "patch" "" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Patch '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "patch" "" "patch" "" "" False
 
         it "supports post requests" $ do
-            let api = Proxy :: Proxy (Post '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "post_index" "" "post" "" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Post '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "post" "" "post" "" "" False
 
         it "supports put requests" $ do
-            let api = Proxy :: Proxy (Put '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "put_index" "" "put" "" "" "nil"
-
-        it "supports path components" $ do
-            let api = Proxy :: Proxy ("resource" :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_resource" "" "get" "resource" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Put '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "put" "" "put" "" "" False
 
         it "supports alternatives" $ do
-            let api = Proxy :: Proxy (Get '[] () :<|> Delete '[] ())
-            rubyForAPI api `shouldBe` concat
-                [ ruby "get_index" "" "get" "" "" "nil"
-                , "\n\n"
-                , ruby "delete_index" "" "delete" "" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Get '[Servant.JSON] () Servant.:<|> Servant.Delete '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe` Text.concat
+                [ ruby "get" "" "get" "" "" False
+                , ";"
+                , ruby "delete" "" "delete" "" "" False
                 ]
 
         it "supports captures" $ do
-            let api = Proxy :: Proxy (Capture "id" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_id" ", id" "get" "#{id}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Capture "id" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get_by_id" ",id" "get" "#{id}" "" False
 
         it "supports query flags" $ do
-            let api = Proxy :: Proxy (QueryFlag "flag" :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_flag" ", flag: false" "get" "?#{\"&flag\" if flag}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryFlag "flag" Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",flag: nil" "get" "?flag=#{flag}" "" False
 
         it "supports query params" $ do
-            let api = Proxy :: Proxy (QueryParam "param" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_param" ", param: nil" "get" "?&param=#{param}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParam "param" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",param: nil" "get" "?param=#{param}" "" False
 
         it "supports multiple query params" $ do
-            let api = Proxy :: Proxy (QueryParams "params" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_params" ", params: []" "get" "?#{params.map { |x| \"&params[]=#{x}\" }.join}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParams "params" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",params: nil" "get" "?params=#{params}" "" False
 
         it "supports request bodies" $ do
-            let api = Proxy :: Proxy (ReqBody '[] () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index" ", body" "get" "" "" "body"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.ReqBody '[Servant.JSON] () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",body" "get" "" "" True
 
         it "supports request headers" $ do
-            let api = Proxy :: Proxy (Header "cookie" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_cookie" ", cookie: nil" "get" "" " \"cookie\" => cookie " "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Header "cookie" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",cookie: nil" "get" "" "\"cookie\"=>cookie" False
 
         it "supports response headers" $ do
-            let api = Proxy :: Proxy (Get '[] (Headers '[] ()))
-            rubyForAPI api `shouldBe`
-                ruby "get_index" "" "get" "" "" "nil"
-
-        it "supports raw" $ do
-            let api = Proxy :: Proxy Raw
-            rubyForAPI api `shouldBe` ""
-
-        it "supports raw as part of a larger API" $ do
-            let api = Proxy :: Proxy (Get '[] () :<|> Raw)
-            rubyForAPI api `shouldBe`
-                ruby "get_index" "" "get" "" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Get '[Servant.JSON] (Servant.Headers '[] ()))
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" "" "get" "" "" False
 
         it "puts the body param after captures" $ do
-            let api = Proxy :: Proxy (Capture "segment" () :> ReqBody '[] () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_segment" ", segment, body" "get" "#{segment}" "" "body"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Capture "segment" () Servant.:> Servant.ReqBody '[Servant.JSON] () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get_by_segment" ",segment,body" "get" "#{segment}" "" True
 
-        it "puts the body param before query params" $ do
-            let api = Proxy :: Proxy (QueryFlag "flag" :> ReqBody '[] () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_flag" ", body, flag: false" "get" "?#{\"&flag\" if flag}" "" "body"
+        it "puts the body param after query params" $ do
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryFlag "flag" Servant.:> Servant.ReqBody '[Servant.JSON] () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",flag: nil,body" "get" "?flag=#{flag}" "" True
 
         it "sanitizes path segments" $ do
-            let api = Proxy :: Proxy ("A!" :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_a_" "" "get" "A!" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy ("A!" Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get_A!" "" "get" "A!" "" False
 
         it "sanitizes captures" $ do
-            let api = Proxy :: Proxy (Capture "A!" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_a_" ", a_" "get" "#{a_}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Capture "A!" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get_by_A!" ",a_" "get" "#{a_}" "" False
 
         it "sanitizes query flags" $ do
-            let api = Proxy :: Proxy (QueryFlag "A!" :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_a_" ", a_: false" "get" "?#{\"&A!\" if a_}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryFlag "A!" Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False
 
         it "sanitizes query params" $ do
-            let api = Proxy :: Proxy (QueryParam "A!" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_a_" ", a_: nil" "get" "?&A!=#{a_}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParam "A!" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False
 
         it "sanitizes multiple query params" $ do
-            let api = Proxy :: Proxy (QueryParams "A!" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_a_" ", a_: []" "get" "?#{a_.map { |x| \"&A![]=#{x}\" }.join}" "" "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParams "A!" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False
 
         it "sanitizes headers" $ do
-            let api = Proxy :: Proxy (Header "A!" () :> Get '[] ())
-            rubyForAPI api `shouldBe`
-                ruby "get_index_a_" ", a_: nil" "get" "" " \"A!\" => a_ " "nil"
+            let api = Proxy.Proxy :: Proxy.Proxy (Servant.Header "A!" () Servant.:> Servant.Get '[Servant.JSON] ())
+            Lackey.rubyForAPI api `shouldBe`
+                ruby "get" ",a_: nil" "get" "" "\"A!\"=>a_" False
+
+-- Since every generated function has the same structure, it can be abstracted
+-- away behind this function.
+ruby :: Text.Text -> Text.Text -> Text.Text -> Text.Text -> Text.Text -> Bool -> Text.Text
+ruby name params method path headers body = "\
+    \def " <> name <> "(excon" <> params <> ")\
+      \excon.request(\
+        \:method=>:" <> method <> ",\
+        \:path=>\"/" <> path <> "\",\
+        \:headers=>{" <> headers <> "},\
+        \:body=>" <> (if body then "body" else "nil") <> "\
+      \)\
+    \end\
+\"
