diff --git a/Network/Lastfm.hs b/Network/Lastfm.hs
new file mode 100644
--- /dev/null
+++ b/Network/Lastfm.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
+-- | Response module
+{-# OPTIONS_HADDOCK prune #-}
+module Network.Lastfm
+  ( Lastfm, Response
+  , callAPI, callAPIsigned
+  , module Network.Lastfm.Types
+  ) where
+
+import Codec.Binary.UTF8.String (encodeString)
+import Control.Applicative ((<$>))
+import Control.Arrow (second)
+import Control.Monad ((<=<))
+import Control.Monad.Error (ErrorT, runErrorT, throwError)
+import Control.Monad.Trans (liftIO)
+import qualified Data.ByteString.Lazy.Char8 as BS
+import Data.Digest.Pure.MD5 (md5)
+import Data.Function (on)
+import Data.List (sortBy)
+import Data.URLEncoded (urlEncode, export)
+import Network.Curl
+import Network.Lastfm.Types
+import Text.XML.Light
+
+-- | Type synonym for Lastfm response or error.
+type Lastfm a = IO (Either LastfmError a)
+-- | Type synonym for Lastfm response
+type Response = String
+
+-- | Low level function. Sends POST query to Lastfm API.
+callAPI :: [(String, String)] -> Lastfm Response
+callAPI = runErrorT . query . map (second encodeString)
+
+-- | Low level function. Sends signed POST query to Lastfm API.
+callAPIsigned :: Secret -> [(String, String)] -> Lastfm Response
+callAPIsigned (Secret s) xs = runErrorT $ query zs
+  where ys = map (second encodeString) . filter (not . null . snd) $ xs
+        zs = ("api_sig", sign ys) : ys
+
+        sign :: [(String, String)] -> String
+        sign = show . md5 . BS.pack . (++ s) . concatMap (uncurry (++)) . sortBy (compare `on` fst)
+
+query :: [(String, String)] -> ErrorT LastfmError IO Response
+query xs = do
+  !response <- liftIO $ withCurlDo $ respBody <$> (curlGetResponse_ "http://ws.audioscrobbler.com/2.0/?"
+                             [ CurlPostFields . map (export . urlEncode) $ xs
+                             , CurlFailOnError False
+                             , CurlUserAgent "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 Iceweasel/10.0"
+                             ]
+                             :: IO CurlResponse)
+  maybe (return response) (throwError . LastfmAPIError . toEnum . (subtract 1)) (getError response)
+  where getError :: String -> Maybe Int
+        getError response = do xml <- parseXMLDoc response
+                               read <$> (findAttr (unqual "code") <=< findChild (unqual "error")) xml
diff --git a/Network/Lastfm/API/Album.hs b/Network/Lastfm/API/Album.hs
--- a/Network/Lastfm/API/Album.hs
+++ b/Network/Lastfm/API/Album.hs
@@ -5,141 +5,123 @@
   , getTopTags, removeTag, search, share
   ) where
 
-import Control.Exception (throw)
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), Album, APIKey, Artist, Autocorrect, Country, Language, Limit
-                            , Mbid, Message, Page, Public, Recipient, SessionKey, Tag, User)
+import Control.Arrow ((|||))
+import Network.Lastfm
 
 -- | Tag an album using a list of user supplied tags.
 --
 -- More: <http://www.lastfm.ru/api/show/album.addTags>
-addTags :: (Artist, Album) -> [Tag] -> APIKey -> SessionKey -> Lastfm ()
-addTags (artist, album) tags apiKey sessionKey = dispatch go
-  where go
-          | null tags        = throw $ WrapperCallError method "empty tag list."
-          | length tags > 10 = throw $ WrapperCallError method "tag list length has exceeded maximum."
-          | otherwise        = void $ callAPI method
-          [ "artist" ?< artist
-          , "album" ?< album
-          , "tags" ?< tags
-          , "api_key" ?< apiKey
-          , "sk" ?< sessionKey
-          ]
-          where method = "album.addTags"
+addTags :: (Artist, Album) -> [Tag] -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addTags (artist, album) tags apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "album.addTags")
+  , (#) artist
+  , (#) album
+  , (#) tags
+  , (#) apiKey
+  , (#) sessionKey
+  ]
 
 -- | Get a list of Buy Links for a particular Album. It is required that you supply either the artist and track params or the mbid param.
 --
 -- More: <http://www.lastfm.ru/api/show/album.getBuylinks>
 getBuyLinks :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> Country -> APIKey -> Lastfm Response
-getBuyLinks a autocorrect country apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "country" ?< country
-  , "api_key" ?< apiKey
+getBuyLinks a autocorrect country apiKey = callAPI $
+  target a ++
+  [ (#) (Method "album.getBuyLinks")
+  , (#) autocorrect
+  , (#) country
+  , (#) apiKey
   ]
-  where method = "album.getBuyLinks"
-        target = case a of
-                   Left (artist, album) -> ["artist" ?< artist, "album" ?< album]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get the metadata for an album on Last.fm using the album name or a musicbrainz id. See playlist.fetch on how to get the album playlist.
 --
 -- More: <http://www.lastfm.ru/api/show/album.getInfo>
-getInfo :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> Maybe Language -> Maybe User -> APIKey -> Lastfm Response
-getInfo a autocorrect lang username apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "lang" ?< lang
-  , "username" ?< username
-  , "api_key" ?< apiKey
+getInfo :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> Maybe Language -> Maybe Username -> APIKey -> Lastfm Response
+getInfo a autocorrect lang username apiKey = callAPI $
+  target a ++
+  [ (#) (Method "album.getInfo")
+  , (#) autocorrect
+  , (#) lang
+  , (#) username
+  , (#) apiKey
   ]
-  where method = "album.getInfo"
-        target = case a of
-                   Left (artist, album) -> ["artist" ?< artist, "album" ?< album]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get shouts for this album.
 --
 -- More: <http://www.lastfm.ru/api/show/album.getShouts>
 getShouts :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getShouts a autocorrect page limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getShouts a autocorrect page limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "album.getShouts")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "album.getShouts"
-        target = case a of
-                   Left (artist, album) -> ["artist" ?< artist, "album" ?< album]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get the tags applied by an individual user to an album on Last.fm.
 --
 -- More: <http://www.lastfm.ru/api/show/album.getTags>
-getTags :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> Either User SessionKey -> APIKey -> Lastfm Response
-getTags a autocorrect b apiKey = dispatch $ callAPI method $ target ++ auth ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
-  ]
-  where method = "album.getTags"
-        target = case a of
-                   Left (artist, album) -> ["artist" ?< artist, "album" ?< album]
-                   Right mbid           -> ["mbid" ?< mbid]
-        auth = case b of
-                 Left user        -> ["user" ?< user]
-                 Right sessionKey -> ["sk" ?< sessionKey]
+getTags :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> Either User (SessionKey, Secret) -> APIKey -> Lastfm Response
+getTags a autocorrect b apiKey = case b of
+  Left user -> callAPI $ target a ++ [(#) user] ++ args
+  Right (sessionKey, secret) -> callAPIsigned secret $ target a ++ [(#) sessionKey] ++ args
+  where args =
+          [ (#) (Method "album.getTags")
+          , (#) autocorrect
+          , (#) apiKey
+          ]
 
 -- | Get the top tags for an album on Last.fm, ordered by popularity.
 --
 -- More: <http://www.lastfm.ru/api/show/album.getTopTags>
 getTopTags :: Either (Artist, Album) Mbid -> Maybe Autocorrect -> APIKey -> Lastfm Response
-getTopTags a autocorrect apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
+getTopTags a autocorrect apiKey = callAPI $
+  target a ++
+  [ (#) (Method "album.getTopTags")
+  , (#) autocorrect
+  , (#) apiKey
   ]
-  where method = "album.getTopTags"
-        target = case a of
-                   Left (artist, album) -> ["artist" ?< artist, "album" ?< album]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Remove a user's tag from an album.
 --
 -- More: <http://www.lastfm.ru/api/show/album.removeTag>
-removeTag :: Artist -> Album -> Tag -> APIKey -> SessionKey -> Lastfm ()
-removeTag artist album tag apiKey sessionKey = dispatch $ void $ callAPI "album.removeTag"
-  [ "artist" ?< artist
-  , "album" ?< album
-  , "tag" ?< tag
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeTag :: Artist -> Album -> Tag -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeTag artist album tag apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "album.removeTag")
+  , (#) artist
+  , (#) album
+  , (#) tag
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Search for an album by name. Returns album matches sorted by relevance.
 --
 -- More: <http://www.lastfm.ru/api/show/album.search>
 search :: Album -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-search album page limit apiKey = dispatch $ callAPI "album.search"
-  [ "album" ?< album
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+search album page limit apiKey = callAPI
+  [ (#) (Method "album.search")
+  , (#) album
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Share an album with one or more Last.fm users or other friends.
 --
 -- More: <http://www.lastfm.ru/api/show/album.share>
-share :: Artist -> Album -> [Recipient] -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Lastfm ()
-share artist album recipients message public apiKey sessionKey = dispatch go
-  where go
-          | null recipients        = throw $ WrapperCallError method "empty recipient list."
-          | length recipients > 10 = throw $ WrapperCallError method "recipient list length has exceeded maximum."
-          | otherwise              = void $ callAPI method
-            [ "artist" ?< artist
-            , "album" ?< album
-            , "public" ?< public
-            , "message" ?< message
-            , "recipient" ?< recipients
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            ]
-            where method = "album.share"
+share :: Artist -> Album -> Recipient -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Secret -> Lastfm Response
+share artist album recipient message public apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "album.share")
+  , (#) artist
+  , (#) album
+  , (#) public
+  , (#) message
+  , (#) recipient
+  , (#) apiKey
+  , (#) sessionKey
+  ]
+
+target :: Either (Artist, Album) Mbid -> [(String, String)]
+target = (\(artist, album) -> [(#) artist, (#) album]) ||| return . (#)
diff --git a/Network/Lastfm/API/Artist.hs b/Network/Lastfm/API/Artist.hs
--- a/Network/Lastfm/API/Artist.hs
+++ b/Network/Lastfm/API/Artist.hs
@@ -6,262 +6,231 @@
   , getTopFans, getTopTags, getTopTracks, removeTag, search, share, shout
   ) where
 
-import Control.Exception (throw)
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), APIKey, Artist, Autocorrect, FestivalsOnly, Language, Limit
-                            , Mbid, Message, Order, Page, Public, Recipient, SessionKey, Tag, User
-                            )
+import Control.Arrow ((|||))
+import Network.Lastfm
 
 -- | Tag an album using a list of user supplied tags.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.addTags>
-addTags :: Artist -> [Tag] -> APIKey -> SessionKey -> Lastfm ()
-addTags artist tags apiKey sessionKey = dispatch go
-  where go
-          | null tags        = throw $ WrapperCallError method "empty tag list."
-          | length tags > 10 = throw $ WrapperCallError method "tag list length has exceeded maximum."
-          | otherwise        = void $ callAPI method
-            [ "artist" ?< artist
-            , "tags" ?< tags
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            ]
-            where method = "artist.addTags"
+addTags :: Artist -> [Tag] -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addTags artist tags apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "artist.addTags")
+  , (#) artist
+  , (#) tags
+  , (#) apiKey
+  , (#) sessionKey
+  ]
 
 -- | Use the last.fm corrections data to check whether the supplied artist has a correction to a canonical artist
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getCorrection>
 getCorrection :: Artist -> APIKey -> Lastfm Response
-getCorrection artist apiKey = dispatch $ callAPI "artist.getCorrection"
-  [ "artist" ?< artist
-  , "api_key" ?< apiKey
+getCorrection artist apiKey = callAPI
+  [ (#) (Method "artist.getCorrection")
+  , (#) artist
+  , (#) apiKey
   ]
 
 -- | Get a list of upcoming events for this artist.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getEvents>
 getEvents :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> Maybe FestivalsOnly -> APIKey -> Lastfm Response
-getEvents a autocorrect page limit festivalsOnly apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "festivalsonly" ?< festivalsOnly
-  , "api_key" ?< apiKey
+getEvents a autocorrect page limit festivalsOnly apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getEvents")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) festivalsOnly
+  , (#) apiKey
   ]
-  where method = "artist.getEvents"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get Images for this artist in a variety of sizes.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getImages>
 getImages :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> Maybe Order -> APIKey -> Lastfm Response
-getImages a autocorrect page limit order apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "order" ?< order
-  , "api_key" ?< apiKey
+getImages a autocorrect page limit order apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getImages")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) order
+  , (#) apiKey
   ]
-  where method = "artist.getImages"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get the metadata for an artist. Includes biography.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getInfo>
-getInfo :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Language -> Maybe User -> APIKey -> Lastfm Response
-getInfo a autocorrect language user apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "lang" ?< language
-  , "username" ?< user
-  , "api_key" ?< apiKey
+getInfo :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Language -> Maybe Username -> APIKey -> Lastfm Response
+getInfo a autocorrect language username apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getInfo")
+  , (#) autocorrect
+  , (#) language
+  , (#) username
+  , (#) apiKey
   ]
-  where method = "artist.getInfo"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get a paginated list of all the events this artist has played at in the past.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getPastEvents>
 getPastEvents :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getPastEvents a autocorrect page limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getPastEvents a autocorrect page limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getPastEvents")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "artist.getPastEvents"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get a podcast of free mp3s based on an artist.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getPodcast>
 getPodcast :: Either Artist Mbid -> Maybe Autocorrect -> APIKey -> Lastfm Response
-getPodcast a autocorrect apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
+getPodcast a autocorrect apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getPodcast")
+  , (#) autocorrect
+  , (#) apiKey
   ]
-  where method = "artist.getPodcast"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get shouts for this artist. Also available as an rss feed.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getShouts>
 getShouts :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getShouts a autocorrect page limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getShouts a autocorrect page limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getShouts")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "artist.getShouts"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get all the artists similar to this artist.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getSimilar>
 getSimilar :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Limit -> APIKey -> Lastfm Response
-getSimilar a autocorrect limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getSimilar a autocorrect limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getSimilar")
+  , (#) autocorrect
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "artist.getSimilar"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get the tags applied by an individual user to an artist on Last.fm. If accessed as an authenticated service /and/ you don't supply a user parameter then this service will return tags for the authenticated user.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getTags>
-getTags :: Either Artist Mbid -> Maybe Autocorrect -> Either User SessionKey -> APIKey -> Lastfm Response
-getTags a autocorrect b apiKey = dispatch $ callAPI method $ target ++ auth ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
-  ]
-  where method = "artist.getTags"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
-        auth = case b of
-                 Left user        -> ["user" ?< user]
-                 Right sessionKey -> ["sk" ?< sessionKey]
+getTags :: Either Artist Mbid -> Maybe Autocorrect -> Either User (SessionKey, Secret) -> APIKey -> Lastfm Response
+getTags a autocorrect b apiKey = case b of
+  Left user -> callAPI $ target a ++ [(#) user] ++ args
+  Right (sessionKey, secret) -> callAPIsigned secret $ target a ++ [(#) sessionKey] ++ args
+  where args =
+          [ (#) (Method "artist.getTags")
+          , (#) autocorrect
+          , (#) apiKey
+          ]
 
 -- | Get the top albums for an artist on Last.fm, ordered by popularity.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getTopAlbums>
 getTopAlbums :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopAlbums a autocorrect page limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopAlbums a autocorrect page limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getTopAlbums")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "artist.getTopAlbums"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get the top fans for an artist on Last.fm, based on listening data.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getTopFans>
 getTopFans :: Either Artist Mbid -> Maybe Autocorrect -> APIKey -> Lastfm Response
-getTopFans a autocorrect apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
+getTopFans a autocorrect apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getTopFans")
+  , (#) autocorrect
+  , (#) apiKey
   ]
-  where method = "artist.getTopFans"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get the top tags for an artist on Last.fm, ordered by popularity.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getTopTags>
 getTopTags :: Either Artist Mbid -> Maybe Autocorrect -> APIKey -> Lastfm Response
-getTopTags a autocorrect apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
+getTopTags a autocorrect apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getTopTags")
+  , (#) autocorrect
+  , (#) apiKey
   ]
-  where method = "artist.getTopTags"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Get the top tracks by an artist on Last.fm, ordered by popularity.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.getTopTracks>
 getTopTracks :: Either Artist Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopTracks a autocorrect page limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopTracks a autocorrect page limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "artist.getTopTracks")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "artist.getTopTracks"
-        target = case a of
-                   Left artist -> ["artist" ?< artist]
-                   Right mbid  -> ["mbid" ?< mbid]
 
 -- | Remove a user's tag from an artist.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.removeTag>
-removeTag :: Artist -> Tag -> APIKey -> SessionKey -> Lastfm ()
-removeTag artist tag apiKey sessionKey = dispatch $ void $ callAPI "artist.removeTag"
-  [ "artist" ?< artist
-  , "tag" ?< tag
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeTag :: Artist -> Tag -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeTag artist tag apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "artist.removeTag")
+  , (#) artist
+  , (#) tag
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Search for an artist by name. Returns artist matches sorted by relevance.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.search>
 search :: Artist -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-search artist page limit apiKey = dispatch $ callAPI "artist.search"
-  [ "artist" ?< artist
-  , "api_key" ?< apiKey
-  , "page" ?< page
-  , "limit" ?< limit
+search artist page limit apiKey = callAPI
+  [ (#) (Method "artist.search")
+  , (#) artist
+  , (#) apiKey
+  , (#) page
+  , (#) limit
   ]
 
 -- | Share an artist with Last.fm users or other friends.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.share>
-share :: Artist -> [Recipient] -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Lastfm ()
-share artist recipients message public apiKey sessionKey = dispatch go
-  where go
-          | null recipients        = throw $ WrapperCallError method "empty recipient list."
-          | length recipients > 10 = throw $ WrapperCallError method "recipient list length has exceeded maximum."
-          | otherwise              = void $ callAPI method
-            [ "artist" ?< artist
-            , "recipient" ?< recipients
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            , "public" ?< public
-            , "message" ?< message
-            ]
-            where method = "artist.share"
+share :: Artist -> Recipient -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Secret -> Lastfm Response
+share artist recipient message public apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "artist.share")
+  , (#) artist
+  , (#) recipient
+  , (#) apiKey
+  , (#) sessionKey
+  , (#) public
+  , (#) message
+  ]
 
 -- | Shout in this artist's shoutbox.
 --
 -- More: <http://www.lastfm.ru/api/show/artist.shout>
-shout :: Artist -> Message -> APIKey -> SessionKey -> Lastfm ()
-shout artist message apiKey sessionKey = dispatch $ void $ callAPI "artist.shout"
-  [ "artist" ?< artist
-  , "message" ?< message
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+shout :: Artist -> Message -> APIKey -> SessionKey -> Secret -> Lastfm Response
+shout artist message apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "artist.shout")
+  , (#) artist
+  , (#) message
+  , (#) apiKey
+  , (#) sessionKey
   ]
+
+target :: Either Artist Mbid -> [(String, String)]
+target = return . (#) ||| return . (#)
diff --git a/Network/Lastfm/API/Auth.hs b/Network/Lastfm/API/Auth.hs
--- a/Network/Lastfm/API/Auth.hs
+++ b/Network/Lastfm/API/Auth.hs
@@ -5,34 +5,38 @@
   , getAuthorizeTokenLink
   ) where
 
-import Control.Applicative ((<$>))
-import Control.Monad ((<=<), liftM)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, AuthToken, SessionKey(..), Token(..), User, unpack)
+import Network.Lastfm
 
 -- | Create a web service session for a user. Used for authenticating a user when the password can be inputted by the user. Only suitable for standalone mobile devices.
 --
 -- More: <http://www.lastfm.ru/api/show/auth.getMobileSession>
-getMobileSession :: User -> APIKey -> AuthToken -> Lastfm Response
-getMobileSession user apiKey token = dispatch $ callAPI "auth.getMobileSession"
-  [ "username" ?< user
-  , "authToken" ?< token
-  , "api_key" ?< apiKey
+getMobileSession :: Username -> APIKey -> AuthToken -> Lastfm Response
+getMobileSession username apiKey token = callAPI
+  [ (#) (Method "auth.getMobileSession")
+  , (#) username
+  , (#) token
+  , (#) apiKey
   ]
 
 -- | Fetch a session key for a user.
 --
 -- More: <http://www.lastfm.ru/api/show/auth.getSession>
 getSession :: APIKey -> Token -> Lastfm Response
-getSession apiKey token = dispatch $ callAPI "auth.getSession" ["api_key" ?< apiKey, "token" ?< token]
+getSession apiKey token = callAPI
+  [ (#) (Method "auth.getSession")
+  , (#) apiKey
+  , (#) token
+  ]
 
 -- | Fetch an unathorized request token for an API account.
 --
 -- More: <http://www.lastfm.ru/api/show/auth.getToken>
 getToken :: APIKey -> Lastfm Response
-getToken apiKey = dispatch $ callAPI "auth.getToken" ["api_key" ?< apiKey]
+getToken apiKey = callAPI
+  [ (#) (Method "auth.getToken")
+  , (#) apiKey
+  ]
 
 -- | Construct the link to authorize token.
 getAuthorizeTokenLink :: APIKey -> Token -> String
-getAuthorizeTokenLink apiKey token = "http://www.last.fm/api/auth/?api_key=" ++ unpack apiKey ++ "&token=" ++ unpack token
+getAuthorizeTokenLink apiKey token = "http://www.last.fm/api/auth/?api_key=" ++ value apiKey ++ "&token=" ++ value token
diff --git a/Network/Lastfm/API/Chart.hs b/Network/Lastfm/API/Chart.hs
--- a/Network/Lastfm/API/Chart.hs
+++ b/Network/Lastfm/API/Chart.hs
@@ -5,8 +5,7 @@
   , getTopArtists, getTopTags, getTopTracks
   ) where
 
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, Limit, Page)
+import Network.Lastfm
 
 -- | Get the hyped artists chart.
 --
@@ -45,8 +44,9 @@
 getTopTracks = get "getTopTracks"
 
 get :: String -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-get method page limit apiKey = dispatch $ callAPI ("chart." ++ method)
-  [ "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+get method page limit apiKey = callAPI
+  [ (#) (Method $ "chart." ++ method)
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
diff --git a/Network/Lastfm/API/Event.hs b/Network/Lastfm/API/Event.hs
--- a/Network/Lastfm/API/Event.hs
+++ b/Network/Lastfm/API/Event.hs
@@ -4,81 +4,76 @@
   ( attend, getAttendees, getInfo, getShouts, share, shout
   ) where
 
-import Control.Exception (throw)
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), APIKey, Event, Limit, Message, Page
-                            , Public, Recipient, SessionKey, Status
-                            )
+import Network.Lastfm
 
 -- | Set a user's attendance status for an event.
 --
 -- More: <http://www.lastfm.ru/api/show/event.attend>
-attend :: Event -> Status -> APIKey -> SessionKey -> Lastfm ()
-attend event status apiKey sessionKey = dispatch $ void $ callAPI "event.attend"
-  [ "event" ?< event
-  , "status" ?< status
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+attend :: Event -> Status -> APIKey -> SessionKey -> Secret -> Lastfm Response
+attend event status apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "event.attend")
+  , (#) event
+  , (#) status
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Get a list of attendees for an event.
 --
 -- More: <http://www.lastfm.ru/api/show/event.getAttendees>
 getAttendees :: Event -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getAttendees event page limit apiKey = dispatch $ callAPI "event.getAttendees"
-  [ "event" ?< event
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getAttendees event page limit apiKey = callAPI
+  [ (#) (Method "event.getAttendees")
+  , (#) event
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the metadata for an event on Last.fm. Includes attendance and lineup information.
 --
 -- More: <http://www.lastfm.ru/api/show/event.getInfo>
 getInfo :: Event -> APIKey -> Lastfm Response
-getInfo event apiKey = dispatch $ callAPI "event.getInfo"
-  [ "event" ?< event
-  , "api_key" ?< apiKey
+getInfo event apiKey = callAPI
+  [ (#) (Method "event.getInfo")
+  , (#) event
+  , (#) apiKey
   ]
 
 -- | Get shouts for this event.
 --
 -- More: <http://www.lastfm.ru/api/show/event.getShouts>
 getShouts :: Event -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getShouts event page limit apiKey = dispatch $ callAPI "event.getShouts"
-  [ "event" ?< event
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getShouts event page limit apiKey = callAPI
+  [ (#) (Method "event.getShouts")
+  , (#) event
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Share an event with one or more Last.fm users or other friends.
 --
 -- More: <http://www.lastfm.ru/api/show/event.share>
-share :: Event -> [Recipient] -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Lastfm ()
-share event recipients message public apiKey sessionKey = dispatch go
-  where go
-          | null recipients        = throw $ WrapperCallError method "empty recipient list."
-          | length recipients > 10 = throw $ WrapperCallError method "recipient list length has exceeded maximum."
-          | otherwise              = void $ callAPI method
-            [ "event" ?< event
-            , "public" ?< public
-            , "message" ?< message
-            , "recipient" ?< recipients
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            ]
-            where method = "event.share"
+share :: Event -> Recipient -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Secret -> Lastfm Response
+share event recipient message public apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "event.share")
+  , (#) event
+  , (#) public
+  , (#) message
+  , (#) recipient
+  , (#) apiKey
+  , (#) sessionKey
+  ]
 
 -- | Shout in this event's shoutbox.
 --
 -- More: <http://www.lastfm.ru/api/show/event.shout>
-shout :: Event -> Message -> APIKey -> SessionKey -> Lastfm ()
-shout event message apiKey sessionKey = dispatch $ void $ callAPI "event.shout"
-  [ "event" ?< event
-  , "message" ?< message
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+shout :: Event -> Message -> APIKey -> SessionKey -> Secret -> Lastfm Response
+shout event message apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "event.shout")
+  , (#) event
+  , (#) message
+  , (#) apiKey
+  , (#) sessionKey
   ]
diff --git a/Network/Lastfm/API/Geo.hs b/Network/Lastfm/API/Geo.hs
--- a/Network/Lastfm/API/Geo.hs
+++ b/Network/Lastfm/API/Geo.hs
@@ -6,10 +6,7 @@
   , getMetroWeeklyChartlist, getMetros, getTopArtists, getTopTracks
   ) where
 
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), APIKey, Country, Distance, From, Latitude
-                            , Limit, Location, Longitude, Metro, Page, To
-                            )
+import Network.Lastfm
 
 -- | Get all events in a specific location by country or city name.
 --
@@ -22,95 +19,104 @@
           -> Maybe Limit
           -> APIKey
           -> Lastfm Response
-getEvents latitude longitude location distance page limit apiKey = dispatch $ callAPI "geo.getEvents"
-  [ "lat" ?< latitude
-  , "long" ?< longitude
-  , "location" ?< location
-  , "distance" ?< distance
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getEvents latitude longitude location distance page limit apiKey = callAPI
+  [ (#) (Method "geo.getEvents")
+  , (#) latitude
+  , (#) longitude
+  , (#) location
+  , (#) distance
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get a chart of artists for a metro.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroArtistChart>
-getMetroArtistChart :: Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
+getMetroArtistChart :: Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
 getMetroArtistChart = getMetroChart "geo.getMetroArtistChart"
 
 -- | Get a chart of hyped (up and coming) artists for a metro.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroHypeArtistChart>
-getMetroHypeArtistChart :: Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
+getMetroHypeArtistChart :: Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
 getMetroHypeArtistChart = getMetroChart "geo.getMetroHypeArtistChart"
 
 -- | Get a chart of hyped (up and coming) tracks for a metro.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroHypeTrackChart>
-getMetroHypeTrackChart :: Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
+getMetroHypeTrackChart :: Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
 getMetroHypeTrackChart = getMetroChart "geo.getMetroHypeTrackChart"
 
 -- | Get a chart of tracks for a metro.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroTrackChart>
-getMetroTrackChart :: Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
+getMetroTrackChart :: Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
 getMetroTrackChart = getMetroChart "geo.getMetroTrackChart"
 
 -- | Get a chart of the artists which make that metro unique.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroUniqueArtistChart>
-getMetroUniqueArtistChart :: Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
+getMetroUniqueArtistChart :: Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
 getMetroUniqueArtistChart = getMetroChart "geo.getMetroUniqueArtistChart"
 
 -- | Get a chart of the tracks which make that metro unique.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroUniqueTrackChart>
-getMetroUniqueTrackChart :: Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
+getMetroUniqueTrackChart :: Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
 getMetroUniqueTrackChart = getMetroChart "geo.getMetroUniqueTrackChart"
 
 -- | Get a list of available chart periods for this metro, expressed as date ranges which can be sent to the chart services.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetroWeeklyChartlist>
 getMetroWeeklyChartlist :: Metro -> APIKey -> Lastfm Response
-getMetroWeeklyChartlist metro apiKey = dispatch $ callAPI "geo.getMetroWeeklyChartlist" ["metro" ?< metro, "api_key" ?< apiKey]
+getMetroWeeklyChartlist metro apiKey = callAPI
+  [ (#) (Method "geo.getMetroWeeklyChartlist")
+  , (#) metro
+  , (#) apiKey
+  ]
 
 -- | Get a list of valid countries and metros for use in the other webservices.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getMetros>
 getMetros :: Maybe Country -> APIKey -> Lastfm Response
-getMetros country apiKey = dispatch $ callAPI "geo.getMetros"
-  [ "country" ?< country
-  , "api_key" ?< apiKey
+getMetros country apiKey = callAPI
+  [ (#) (Method "geo.getMetros")
+  , (#) country
+  , (#) apiKey
   ]
 
 -- | Get the most popular artists on Last.fm by country.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getTopArtists>
 getTopArtists :: Country -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopArtists country page limit apiKey = dispatch $ callAPI "geo.getTopArtists"
-  [ "country" ?< country
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopArtists country page limit apiKey = callAPI
+  [ (#) (Method "geo.getTopArtists")
+  , (#) country
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the most popular tracks on Last.fm last week by country.
 --
 -- More: <http://www.lastfm.ru/api/show/geo.getTopTracks>
 getTopTracks :: Country -> Maybe Location -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopTracks country location page limit apiKey = dispatch $ callAPI "geo.getTopTracks"
-  [ "country" ?< country
-  , "location" ?< location
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopTracks country location page limit apiKey = callAPI
+  [ (#) (Method "geo.getTopTracks")
+  , (#) country
+  , (#) location
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
-getMetroChart :: String -> Country -> Metro -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getMetroChart method country metro from to apiKey = dispatch $ callAPI method
-  [ "country" ?< country
-  , "metro" ?< metro
-  , "start" ?< from
-  , "end" ?< to
-  , "api_key" ?< apiKey
+getMetroChart :: String -> Country -> Metro -> Maybe Start -> Maybe End -> APIKey -> Lastfm Response
+getMetroChart method country metro start end apiKey = callAPI
+  [ (#) (Method method)
+  , (#) country
+  , (#) metro
+  , (#) start
+  , (#) end
+  , (#) apiKey
   ]
diff --git a/Network/Lastfm/API/Group.hs b/Network/Lastfm/API/Group.hs
--- a/Network/Lastfm/API/Group.hs
+++ b/Network/Lastfm/API/Group.hs
@@ -4,55 +4,62 @@
   ( getHype, getMembers, getWeeklyChartList, getWeeklyAlbumChart, getWeeklyArtistChart, getWeeklyTrackChart
   ) where
 
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, From, Group, Limit, Page, To)
+import Network.Lastfm
 
 -- | Get the hype list for a group.
 --
 -- More: <http://www.lastfm.ru/api/show/group.getHype>
 getHype :: Group -> APIKey -> Lastfm Response
-getHype group apiKey = dispatch $ callAPI "group.getHype"
-  [ "group" ?< group
-  , "api_key" ?< apiKey
+getHype group apiKey = callAPI
+  [ (#) (Method "group.getHype")
+  , (#) group
+  , (#) apiKey
   ]
 
 -- | Get a list of members for this group.
 --
 -- More: <http://www.lastfm.ru/api/show/group.getMembers>
 getMembers :: Group -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getMembers group page limit apiKey = dispatch $ callAPI "group.getMembers"
-  [ "group" ?< group
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getMembers group page limit apiKey = callAPI
+  [ (#) (Method "group.getMembers")
+  , (#) group
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get an album chart for a group, for a given date range. If no date range is supplied, it will return the most recent album chart for this group.
 --
 -- More: <http://www.lastfm.ru/api/show/group.getWeeklyAlbumChart>
 getWeeklyChartList :: Group -> APIKey -> Lastfm Response
-getWeeklyChartList group apiKey = dispatch $ callAPI "group.getWeeklyChartList" ["group" ?< group, "api_key" ?< apiKey]
+getWeeklyChartList group apiKey = callAPI
+  [ (#) (Method "group.getWeeklyChartList")
+  , (#) group
+  , (#) apiKey
+  ]
 
 -- | Get an artist chart for a group, for a given date range. If no date range is supplied, it will return the most recent artist chart for this group.
 --
 -- More: <http://www.lastfm.ru/api/show/group.getWeeklyArtistChart>
 getWeeklyAlbumChart :: Group -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getWeeklyAlbumChart group from to apiKey = dispatch $ callAPI "group.getWeeklyAlbumChart"
-  [ "group" ?< group
-  , "api_key" ?< apiKey
-  , "from" ?< from
-  , "to" ?< to
+getWeeklyAlbumChart group from to apiKey = callAPI
+  [ (#) (Method "group.getWeeklyAlbumChart")
+  , (#) group
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
 
 -- | Get a list of available charts for this group, expressed as date ranges which can be sent to the chart services.
 --
 -- More: <http://www.lastfm.ru/api/show/group.getWeeklyChartList>
 getWeeklyArtistChart :: Group -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getWeeklyArtistChart group from to apiKey = dispatch $ callAPI "group.getWeeklyArtistChart"
-  [ "group" ?< group
-  , "api_key" ?< apiKey
-  , "from" ?< from
-  , "to" ?< to
+getWeeklyArtistChart group from to apiKey = callAPI
+  [ (#) (Method "group.getWeeklyArtistChart")
+  , (#) group
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
 
 
@@ -60,9 +67,10 @@
 --
 -- More: <http://www.lastfm.ru/api/show/group.getWeeklyTrackChart>
 getWeeklyTrackChart :: Group -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getWeeklyTrackChart group from to apiKey = dispatch $ callAPI "group.getWeeklyTrackChart"
-  [ "group" ?< group
-  , "api_key" ?< apiKey
-  , "from" ?< from
-  , "to" ?< to
+getWeeklyTrackChart group from to apiKey = callAPI
+  [ (#) (Method "group.getWeeklyTrackChart")
+  , (#) group
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
diff --git a/Network/Lastfm/API/Library.hs b/Network/Lastfm/API/Library.hs
--- a/Network/Lastfm/API/Library.hs
+++ b/Network/Lastfm/API/Library.hs
@@ -5,119 +5,127 @@
   , removeAlbum, removeArtist, removeScrobble, removeTrack
   ) where
 
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), Album, APIKey, Artist, Limit, Page, SessionKey, Timestamp, Track, User)
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 
 -- | Add an album or collection of albums to a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.addAlbum>
-addAlbum :: Artist -> Album -> APIKey -> SessionKey -> Lastfm ()
-addAlbum artist album apiKey sessionKey = dispatch $ void $ callAPI "library.addAlbum"
-  [ "artist" ?< artist
-  , "album" ?< album
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+addAlbum :: Artist -> Album -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addAlbum artist album apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.addAlbum")
+  , (#) artist
+  , (#) album
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Add an artist to a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.addArtist>
-addArtist :: Artist -> APIKey -> SessionKey -> Lastfm ()
-addArtist artist apiKey sessionKey = dispatch $ void $ callAPI "library.addArtist"
-  [ "artist" ?< artist
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+addArtist :: Artist -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addArtist artist apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.addArtist")
+  , (#) artist
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Add a track to a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.addTrack>
-addTrack :: Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-addTrack artist track apiKey sessionKey = dispatch $ void $ callAPI "library.addTrack"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+addTrack :: Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addTrack artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.addTrack")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | A paginated list of all the albums in a user's library, with play counts and tag counts.
 --
 -- More: <http://www.lastfm.ru/api/show/library.getAlbums>
 getAlbums :: User -> Maybe Artist -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getAlbums user artist page limit apiKey = dispatch $ callAPI "library.getAlbums"
-  [ "user" ?< user
-  , "artist" ?< artist
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getAlbums user artist page limit apiKey = callAPI
+  [ (#) (Method "library.getAlbums")
+  , (#) user
+  , (#) artist
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | A paginated list of all the artists in a user's library, with play counts and tag counts.
 --
 -- More: <http://www.lastfm.ru/api/show/library.getArtists>
 getArtists :: User -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getArtists user page limit apiKey = dispatch $ callAPI "library.getArtists"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getArtists user page limit apiKey = callAPI
+  [ (#) (Method "library.getArtists")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | A paginated list of all the tracks in a user's library, with play counts and tag counts.
 --
 -- More: <http://www.lastfm.ru/api/show/library.getTracks>
 getTracks :: User -> Maybe Artist -> Maybe Album -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTracks user artist album page limit apiKey = dispatch $ callAPI "library.getTracks"
-  [ "user" ?< user
-  , "artist" ?< artist
-  , "album" ?< album
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTracks user artist album page limit apiKey = callAPI
+  [ (#) (Method "library.getTracks")
+  , (#) user
+  , (#) artist
+  , (#) album
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Remove an album from a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.removeAlbum>
-removeAlbum :: Artist -> Album -> APIKey -> SessionKey -> Lastfm ()
-removeAlbum artist album apiKey sessionKey = dispatch $ void $ callAPI "library.removeAlbum"
-  [ "artist" ?< artist
-  , "album" ?< album
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeAlbum :: Artist -> Album -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeAlbum artist album apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.removeAlbum")
+  , (#) artist
+  , (#) album
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Remove an artist from a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.removeArtist>
-removeArtist :: Artist -> APIKey -> SessionKey -> Lastfm ()
-removeArtist artist apiKey sessionKey = dispatch $ void $ callAPI "library.removeArtist"
-  [ "artist" ?< artist
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeArtist :: Artist -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeArtist artist apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.removeArtist")
+  , (#) artist
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Remove a scrobble from a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.removeScrobble>
-removeScrobble :: Artist -> Track -> Timestamp -> APIKey -> SessionKey -> Lastfm ()
-removeScrobble artist track timestamp apiKey sessionKey = dispatch $ void $ callAPI "library.removeScrobble"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "timestamp" ?< timestamp
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeScrobble :: Artist -> Track -> Timestamp -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeScrobble artist track timestamp apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.removeScrobble")
+  , (#) artist
+  , (#) track
+  , (#) timestamp
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Remove a track from a user's Last.fm library.
 --
 -- More: <http://www.lastfm.ru/api/show/library.removeTrack>
-removeTrack :: Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-removeTrack artist track apiKey sessionKey = dispatch $ void $ callAPI "library.removeTrack"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeTrack :: Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeTrack artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "library.removeTrack")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
diff --git a/Network/Lastfm/API/Playlist.hs b/Network/Lastfm/API/Playlist.hs
--- a/Network/Lastfm/API/Playlist.hs
+++ b/Network/Lastfm/API/Playlist.hs
@@ -4,30 +4,30 @@
   ( addTrack, create
   ) where
 
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, Artist, Playlist, SessionKey, Title, Description, Track)
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 
 -- | Add a track to a Last.fm user's playlist.
 --
 -- More: <http://www.lastfm.ru/api/show/playlist.addTrack>
-addTrack :: Playlist -> Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-addTrack playlist artist track apiKey sessionKey = dispatch $ void $ callAPI "playlist.addTrack"
-  [ "playlistID" ?< playlist
-  , "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+addTrack :: Playlist -> Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addTrack playlist artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "playlist.addTrack")
+  , (#) playlist
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Create a Last.fm playlist on behalf of a user.
 --
 -- More: <http://www.lastfm.ru/api/show/playlist.create>
-create :: Maybe Title -> Maybe Description -> APIKey -> SessionKey -> Lastfm ()
-create title description apiKey sessionKey = dispatch $ void $ callAPI "playlist.create"
-  [ "api_key" ?< apiKey
-  , "sk" ?< sessionKey
-  , "title" ?< title
-  , "description" ?< description
+create :: Maybe Title -> Maybe Description -> APIKey -> SessionKey -> Secret -> Lastfm Response
+create title description apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "playlist.create")
+  , (#) title
+  , (#) description
+  , (#) apiKey
+  , (#) sessionKey
   ]
diff --git a/Network/Lastfm/API/Radio.hs b/Network/Lastfm/API/Radio.hs
--- a/Network/Lastfm/API/Radio.hs
+++ b/Network/Lastfm/API/Radio.hs
@@ -4,12 +4,8 @@
   ( getPlaylist, search, tune
   ) where
 
-import Control.Exception (throw)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), APIKey, Bitrate, BuyLinks, Discovery, Language
-                            , Multiplier, Name, RTP, Station, SessionKey, unpack
-                            )
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 
 -- | Fetch new radio content periodically in an XSPF format.
 --
@@ -21,38 +17,37 @@
             -> Bitrate
             -> APIKey
             -> SessionKey
+            -> Secret
             -> Lastfm Response
-getPlaylist discovery rtp buylinks multiplier bitrate apiKey sessionKey = dispatch go
-  where go
-          | unpack multiplier /= "1.0" && unpack multiplier /= "2.0" = throw $ WrapperCallError method "unsupported multiplier."
-          | unpack bitrate /= "64" && unpack bitrate /= "128" = throw $ WrapperCallError method "unsupported bitrate."
-          | otherwise = callAPI method
-            [ "discovery" ?< discovery
-            , "rtp" ?< rtp
-            , "buylinks" ?< buylinks
-            , "speed_multiplier" ?< multiplier
-            , "bitrate" ?< bitrate
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            ]
-            where method = "radio.getPlaylist"
+getPlaylist discovery rtp buylinks multiplier bitrate apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "radio.getPlaylist")
+  , (#) discovery
+  , (#) rtp
+  , (#) buylinks
+  , (#) multiplier
+  , (#) bitrate
+  , (#) apiKey
+  , (#) sessionKey
+  ]
 
 -- | Resolve the name of a resource into a station depending on which resource it is most likely to represent.
 --
 -- More: <http://www.lastfm.ru/api/show/radio.search>
 search :: Name -> APIKey -> Lastfm Response
-search name apiKey = dispatch $ callAPI "radio.search"
-  [ "name" ?< name
-  , "api_key" ?< apiKey
+search name apiKey = callAPI
+  [ (#) (Method "radio.search")
+  , (#) name
+  , (#) apiKey
   ]
 
 -- | Tune in to a Last.fm radio station.
 --
 -- More: <http://www.lastfm.ru/api/show/radio.tune>
-tune :: Maybe Language -> Station -> APIKey -> SessionKey -> Lastfm Response
-tune language station apiKey sessionKey = dispatch $ callAPI "radio.tune"
-  [ "lang" ?< language
-  , "station" ?< station
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+tune :: Maybe Language -> Station -> APIKey -> SessionKey -> Secret -> Lastfm Response
+tune language station apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "radio.tune")
+  , (#) language
+  , (#) station
+  , (#) apiKey
+  , (#) sessionKey
   ]
diff --git a/Network/Lastfm/API/Tag.hs b/Network/Lastfm/API/Tag.hs
--- a/Network/Lastfm/API/Tag.hs
+++ b/Network/Lastfm/API/Tag.hs
@@ -5,95 +5,106 @@
   , getWeeklyArtistChart, getWeeklyChartList, search
   ) where
 
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, From, Language, Limit, Page, Tag, To)
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 
 -- | Get the metadata for a tag.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getInfo>
 getInfo :: Tag -> Maybe Language -> APIKey -> Lastfm Response
-getInfo tag language apiKey = dispatch $ callAPI "tag.getInfo"
-  [ "tag" ?< tag
-  , "lang" ?< language
-  , "api_key" ?< apiKey
+getInfo tag language apiKey = callAPI
+  [ (#) (Method "tag.getInfo")
+  , (#) tag
+  , (#) language
+  , (#) apiKey
   ]
 
 -- | Search for tags similar to this one. Returns tags ranked by similarity, based on listening data.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getSimilar>
 getSimilar :: Tag -> APIKey -> Lastfm Response
-getSimilar tag apiKey = dispatch $ callAPI "tag.getSimilar"
-  [ "tag" ?< tag
-  , "api_key" ?< apiKey
+getSimilar tag apiKey = callAPI
+  [ (#) (Method "tag.getSimilar")
+  , (#) tag
+  , (#) apiKey
   ]
 
 -- | Get the top albums tagged by this tag, ordered by tag count.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getTopAlbums>
 getTopAlbums :: Tag -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopAlbums tag page limit apiKey = dispatch $ callAPI "tag.getTopAlbums"
-  [ "tag" ?< tag
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopAlbums tag page limit apiKey = callAPI
+  [ (#) (Method "tag.getTopAlbums")
+  , (#) tag
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the top artists tagged by this tag, ordered by tag count.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getTopArtists>
 getTopArtists :: Tag -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopArtists tag limit page apiKey = dispatch $ callAPI "tag.getTopArtists"
-  [ "tag" ?< tag
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopArtists tag limit page apiKey = callAPI
+  [ (#) (Method "tag.getTopArtists")
+  , (#) tag
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Fetches the top global tags on Last.fm, sorted by popularity (number of times used).
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getTopTags>
 getTopTags :: APIKey -> Lastfm Response
-getTopTags apiKey = dispatch $ callAPI "tag.getTopArtists" ["api_key" ?< apiKey]
+getTopTags apiKey = callAPI
+  [ (#) (Method "tag.getTopArtists")
+  , (#) apiKey
+  ]
 
 -- | Get the top tracks tagged by this tag, ordered by tag count.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getTopTracks>
 getTopTracks :: Tag -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopTracks tag limit page apiKey = dispatch $ callAPI "tag.getTopTracks"
-  [ "tag" ?< tag
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopTracks tag limit page apiKey = callAPI
+  [ (#) (Method "tag.getTopTracks")
+  , (#) tag
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get an artist chart for a tag, for a given date range. If no date range is supplied, it will return the most recent artist chart for this tag.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getWeeklyArtistChart>
 getWeeklyArtistChart :: Tag -> Maybe From -> Maybe To -> Maybe Limit -> APIKey -> Lastfm Response
-getWeeklyArtistChart tag from to limit apiKey = dispatch $ callAPI "tag.getWeeklyArtistChart"
-  [ "tag" ?< tag
-  , "from" ?< from
-  , "to" ?< to
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getWeeklyArtistChart tag from to limit apiKey = callAPI
+  [ (#) (Method "tag.getWeeklyArtistChart")
+  , (#) tag
+  , (#) from
+  , (#) to
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get a list of available charts for this tag, expressed as date ranges which can be sent to the chart services.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.getWeeklyChartList>
 getWeeklyChartList :: Tag -> APIKey -> Lastfm Response
-getWeeklyChartList tag apiKey = dispatch $ callAPI "tag.getWeeklyChartList"
-  [ "tag" ?< tag
-  , "api_key" ?< apiKey
+getWeeklyChartList tag apiKey = callAPI
+  [ (#) (Method "tag.getWeeklyChartList")
+  , (#) tag
+  , (#) apiKey
   ]
 
 -- | Search for a tag by name. Returns matches sorted by relevance.
 --
 -- More: <http://www.lastfm.ru/api/show/tag.search>
 search :: Tag -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-search tag page limit apiKey = dispatch $ callAPI "tag.search"
-  [ "tag" ?< tag
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+search tag page limit apiKey = callAPI
+  [ (#) (Method "tag.search")
+  , (#) tag
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
diff --git a/Network/Lastfm/API/Tasteometer.hs b/Network/Lastfm/API/Tasteometer.hs
--- a/Network/Lastfm/API/Tasteometer.hs
+++ b/Network/Lastfm/API/Tasteometer.hs
@@ -4,39 +4,23 @@
   ( compare
   ) where
 
-import Control.Exception (throw)
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 import Prelude hiding (compare)
 
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, Limit, Value(..))
+(?<) :: Argument a => a -> Int -> (String, String)
+a ?< n = (key a ++ show n, value a)
 
 -- | Get a Tasteometer score from two inputs, along with a list of shared artists. If the input is a User some additional information is returned.
 --
 -- More: <http://www.lastfm.ru/api/show/tasteometer.compare>
 compare :: Value -> Value -> Maybe Limit -> APIKey -> Lastfm Response
-compare value1 value2 limit apiKey = dispatch go
-  where go
-          | isNull value1 = throw $ WrapperCallError method "empty first artists list."
-          | isNull value2 = throw $ WrapperCallError method "empty second artists list."
-          | isExceededMaximum value1 = throw $ WrapperCallError method "first artists list length has exceeded maximum (100)."
-          | isExceededMaximum value2 = throw $ WrapperCallError method "second artists list length has exceeded maximum (100)."
-          | otherwise = callAPI method
-            [ "type1" ?< type1
-            , "type2" ?< type2
-            , "value1" ?< value1
-            , "value2" ?< value2
-            , "limit" ?< limit
-            , "api_key" ?< apiKey
-            ]
-            where method = "tasteometer.compare"
-
-                  type1 = show value1
-                  type2 = show value2
-
-                  isNull :: Value -> Bool
-                  isNull (ValueUser _) = False
-                  isNull (ValueArtists as) = null as
-
-                  isExceededMaximum :: Value -> Bool
-                  isExceededMaximum (ValueUser _) = False
-                  isExceededMaximum (ValueArtists as) = length as > 100
+compare value1 value2 limit apiKey = callAPI
+  [ (#) (Method "tasteometer.compare")
+  , (,) "type1" (show value1)
+  , (,) "type2" (show value2)
+  , (?<) value1 1
+  , (?<) value2 2
+  , (#) limit
+  , (#) apiKey
+  ]
diff --git a/Network/Lastfm/API/Track.hs b/Network/Lastfm/API/Track.hs
--- a/Network/Lastfm/API/Track.hs
+++ b/Network/Lastfm/API/Track.hs
@@ -6,182 +6,162 @@
   , love, removeTag, scrobble, search, share, unban, unlove, updateNowPlaying
   ) where
 
-import Control.Exception (throw)
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), Album, AlbumArtist, APIKey, Artist, Autocorrect, ChosenByUser, Context, Country
-                            , Duration, Fingerprint, Limit, Mbid, Message, Page, Public, Recipient, SessionKey
-                            , StreamId, Tag, Timestamp, Track, TrackNumber, User
-                            )
+import Control.Arrow ((|||))
+import Network.Lastfm
 
 -- | Tag a track using a list of user supplied tags.
 --
 -- More: <http://www.lastfm.ru/api/show/track.addTags>
-addTags :: Artist -> Track -> [Tag] -> APIKey -> SessionKey -> Lastfm ()
-addTags artist track tags apiKey sessionKey = dispatch go
-  where go
-          | null tags        = throw $ WrapperCallError method "empty tag list."
-          | length tags > 10 = throw $ WrapperCallError method "tag list length has exceeded maximum."
-          | otherwise        = void $ callAPI method
-            [ "artist" ?< artist
-            , "track" ?< track
-            , "tags" ?< tags
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            ]
-            where method = "track.addTags"
+addTags :: Artist -> Track -> [Tag] -> APIKey -> SessionKey -> Secret -> Lastfm Response
+addTags artist track tags apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.addTags")
+  , (#) artist
+  , (#) track
+  , (#) tags
+  , (#) apiKey
+  , (#) sessionKey
+  ]
 
 -- | Ban a track for a given user profile.
 --
 -- More: <http://www.lastfm.ru/api/show/track.ban>
-ban :: Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-ban artist track apiKey sessionKey = dispatch $ void $ callAPI "track.ban"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+ban :: Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+ban artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.ban")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Get a list of Buy Links for a particular track.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getBuylinks>
 getBuyLinks :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Country -> APIKey -> Lastfm Response
-getBuyLinks a autocorrect country apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "country" ?< country
-  , "api_key" ?< apiKey
+getBuyLinks a autocorrect country apiKey = callAPI $
+  target a ++
+  [ (#) (Method "track.getBuyLinks")
+  , (#) autocorrect
+  , (#) country
+  , (#) apiKey
   ]
-  where method = "track.getBuyLinks"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Use the last.fm corrections data to check whether the supplied track has a correction to a canonical track.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getCorrection>
 getCorrection :: Artist -> Track -> APIKey -> Lastfm Response
-getCorrection artist track apiKey = dispatch $ callAPI "track.getCorrection"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
+getCorrection artist track apiKey = callAPI
+  [ (#) (Method "track.getCorrection")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
   ]
 
 -- | Retrieve track metadata associated with a fingerprint id generated by the Last.fm Fingerprinter. Returns track elements, along with a 'rank' value between 0 and 1 reflecting the confidence for each match.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getFingerprintMetadata>
 getFingerprintMetadata :: Fingerprint -> APIKey -> Lastfm Response
-getFingerprintMetadata fingerprint apiKey = dispatch $ callAPI "track.getFingerprintMetadata"
-  [ "fingerprintid" ?< fingerprint
-  , "api_key" ?< apiKey
+getFingerprintMetadata fingerprint apiKey = callAPI
+  [ (#) (Method "track.getFingerprintMetadata")
+  , (#) fingerprint
+  , (#) apiKey
   ]
 
 -- | Get the metadata for a track on Last.fm.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getInfo>
-getInfo :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Maybe User -> APIKey -> Lastfm Response
-getInfo a autocorrect username apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "username" ?< username
-  , "api_key" ?< apiKey
+getInfo :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Maybe Username -> APIKey -> Lastfm Response
+getInfo a autocorrect username apiKey = callAPI $
+  target a ++
+  [ (#) (Method "track.getInfo")
+  , (#) autocorrect
+  , (#) username
+  , (#) apiKey
   ]
-  where method = "track.getInfo"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get shouts for this track. Also available as an rss feed.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getShouts>
 getShouts :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getShouts a autocorrect page limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getShouts a autocorrect page limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "track.getShouts")
+  , (#) autocorrect
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "track.getShouts"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get the similar tracks for this track on Last.fm, based on listening data.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getSimilar>
 getSimilar :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Maybe Limit -> APIKey -> Lastfm Response
-getSimilar a autocorrect limit apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getSimilar a autocorrect limit apiKey = callAPI $
+  target a ++
+  [ (#) (Method "track.getSimilar")
+  , (#) autocorrect
+  , (#) limit
+  , (#) apiKey
   ]
-  where method = "track.getSimilar"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get the tags applied by an individual user to a track on Last.fm.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getTags>
-getTags :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Either User SessionKey -> APIKey -> Lastfm Response
-getTags a autocorrect b apiKey = dispatch $ callAPI method $ target ++ auth ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
-  ]
-  where method = "track.getTags"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
-        auth = case b of
-                 Left user        -> ["user" ?< user]
-                 Right sessionKey -> ["sk" ?< sessionKey]
+getTags :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> Either User (SessionKey, Secret) -> APIKey -> Lastfm Response
+getTags a autocorrect b apiKey = case b of
+  Left user -> callAPI $ target a ++ [(#) user] ++ args
+  Right (sessionKey, secret) -> callAPIsigned secret $ target a ++ [(#) sessionKey] ++ args
+  where args =
+          [ (#) (Method "track.getTags")
+          , (#) autocorrect
+          , (#) apiKey
+          ]
 
 -- | Get the top fans for this track on Last.fm, based on listening data.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getTopFans>
 getTopFans :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> APIKey -> Lastfm Response
-getTopFans a autocorrect apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
+getTopFans a autocorrect apiKey = callAPI $
+  target a ++
+  [ (#) (Method "track.getTopFans")
+  , (#) autocorrect
+  , (#) apiKey
   ]
-  where method = "track.getTopFans"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Get the top tags for this track on Last.fm, ordered by tag count.
 --
 -- More: <http://www.lastfm.ru/api/show/track.getTopTags>
 getTopTags :: Either (Artist, Track) Mbid -> Maybe Autocorrect -> APIKey -> Lastfm Response
-getTopTags a autocorrect apiKey = dispatch $ callAPI method $ target ++
-  [ "autocorrect" ?< autocorrect
-  , "api_key" ?< apiKey
+getTopTags a autocorrect apiKey = callAPI $
+  target a ++
+  [ (#) (Method "track.getTopTags")
+  , (#) autocorrect
+  , (#) apiKey
   ]
-  where method = "track.getTopTags"
-        target = case a of
-                   Left (artist, track) -> ["artist" ?< artist, "track" ?< track]
-                   Right mbid           -> ["mbid" ?< mbid]
 
 -- | Love a track for a user profile.
 --
 -- More: <http://www.lastfm.ru/api/show/track.love>
-love :: Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-love artist track apiKey sessionKey = dispatch $ void $ callAPI "track.love"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+love :: Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+love artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.love")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Remove a user's tag from a track.
 --
 -- More: <http://www.lastfm.ru/api/show/track.removeTag>
-removeTag :: Artist -> Track -> Tag -> APIKey -> SessionKey -> Lastfm ()
-removeTag artist track tag apiKey sessionKey = dispatch $ void $ callAPI "track.removeTag"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "tag" ?< tag
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+removeTag :: Artist -> Track -> Tag -> APIKey -> SessionKey -> Secret -> Lastfm Response
+removeTag artist track tag apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.removeTag")
+  , (#) artist
+  , (#) track
+  , (#) tag
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Used to add a track-play to a user's profile.
@@ -192,74 +172,75 @@
            , Maybe Context, Maybe TrackNumber, Maybe Mbid )
          -> APIKey
          -> SessionKey
-         -> Lastfm ()
-scrobble (timestamp, album, artist, track, albumArtist, duration, streamId, chosenByUser, context, trackNumber, mbid) apiKey sessionKey = dispatch $ void $ callAPI "track.scrobble"
-  [ "timestamp" ?< timestamp
-  , "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
-  , "album" ?< album
-  , "albumArtist" ?< albumArtist
-  , "duration" ?< duration
-  , "streamId" ?< streamId
-  , "chosenByUser" ?< chosenByUser
-  , "context" ?< context
-  , "trackNumber" ?< trackNumber
-  , "mbid" ?< mbid
+         -> Secret
+         -> Lastfm Response
+scrobble (timestamp, album, artist, track, albumArtist, duration, streamId, chosenByUser, context, trackNumber, mbid) apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.scrobble")
+  , (#) timestamp
+  , (#) artist
+  , (#) track
+  , (#) album
+  , (#) albumArtist
+  , (#) duration
+  , (#) streamId
+  , (#) chosenByUser
+  , (#) context
+  , (#) trackNumber
+  , (#) mbid
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Search for a track by track name. Returns track matches sorted by relevance.
 --
 -- More: <http://www.lastfm.ru/api/show/track.search>
 search :: Track -> Maybe Page -> Maybe Limit -> Maybe Artist -> APIKey -> Lastfm Response
-search limit page track artist apiKey = dispatch $ callAPI "track.search"
-  [ "track" ?< track
-  , "page" ?< page
-  , "limit" ?< limit
-  , "artist" ?< artist
-  , "api_key" ?< apiKey
+search track page limit artist apiKey = callAPI
+  [ (#) (Method "track.search")
+  , (#) track
+  , (#) page
+  , (#) limit
+  , (#) artist
+  , (#) apiKey
   ]
 
 -- | Share a track twith one or more Last.fm users or other friends.
 --
 -- More: <http://www.lastfm.ru/api/show/track.share>
-share :: Artist -> Track -> [Recipient] -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Lastfm ()
-share artist track recipients message public apiKey sessionKey = dispatch go
-  where go
-          | null recipients        = throw $ WrapperCallError method "empty recipient list."
-          | length recipients > 10 = throw $ WrapperCallError method "recipient list length has exceeded maximum."
-          | otherwise              = void $ callAPI method
-            [ "artist" ?< artist
-            , "track" ?< track
-            , "recipient" ?< recipients
-            , "api_key" ?< apiKey
-            , "sk" ?< sessionKey
-            , "public" ?< public
-            , "message" ?< message
-            ]
-            where method = "track.share"
+share :: Artist -> Track -> Recipient -> Maybe Message -> Maybe Public -> APIKey -> SessionKey -> Secret -> Lastfm Response
+share artist track recipient message public apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.share")
+  , (#) artist
+  , (#) track
+  , (#) recipient
+  , (#) public
+  , (#) message
+  , (#) apiKey
+  , (#) sessionKey
+  ]
 
 -- | Unban a track for a user profile.
 --
 -- More: <http://www.lastfm.ru/api/show/track.unban>
-unban :: Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-unban artist track apiKey sessionKey = dispatch $ void $ callAPI "track.unban"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+unban :: Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+unban artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.unban")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Unlove a track for a user profile.
 --
 -- More: <http://www.lastfm.ru/api/show/track.unlove>
-unlove :: Artist -> Track -> APIKey -> SessionKey -> Lastfm ()
-unlove artist track apiKey sessionKey = dispatch $ void $ callAPI "track.unlove"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+unlove :: Artist -> Track -> APIKey -> SessionKey -> Secret -> Lastfm Response
+unlove artist track apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.unlove")
+  , (#) artist
+  , (#) track
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 
@@ -276,16 +257,21 @@
                  -> Maybe Duration
                  -> APIKey
                  -> SessionKey
-                 -> Lastfm ()
-updateNowPlaying artist track album albumArtist context trackNumber mbid duration apiKey sessionKey = dispatch $ void $ callAPI "track.updateNowPlaying"
-  [ "artist" ?< artist
-  , "track" ?< track
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
-  , "album" ?< album
-  , "albumArtist" ?< albumArtist
-  , "context" ?< context
-  , "trackNumber" ?< trackNumber
-  , "mbid" ?< mbid
-  , "duration" ?< duration
+                 -> Secret
+                 -> Lastfm Response
+updateNowPlaying artist track album albumArtist context trackNumber mbid duration apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "track.updateNowPlaying")
+  , (#) artist
+  , (#) track
+  , (#) album
+  , (#) albumArtist
+  , (#) context
+  , (#) trackNumber
+  , (#) mbid
+  , (#) duration
+  , (#) apiKey
+  , (#) sessionKey
   ]
+
+target :: Either (Artist, Track) Mbid -> [(String, String)]
+target = (\(artist, track) -> [(#) artist, (#) track]) ||| return . (#)
diff --git a/Network/Lastfm/API/User.hs b/Network/Lastfm/API/User.hs
--- a/Network/Lastfm/API/User.hs
+++ b/Network/Lastfm/API/User.hs
@@ -8,116 +8,115 @@
   , getWeeklyChartList, getWeeklyTrackChart, shout
   ) where
 
-import Control.Monad (void)
-
-import Network.Lastfm.Response
-import Network.Lastfm.Types ( (?<), APIKey, Artist, FestivalsOnly, From, Limit, Message, Page
-                            , Period, RecentTracks, SessionKey, Tag, TaggingType, To, User, UseRecs
-                            )
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 
 -- | Get a list of tracks by a given artist scrobbled by this user, including scrobble time. Can be limited to specific timeranges, defaults to all time.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getArtistTracks>
-getArtistTracks :: User
-                -> Artist
-                -> Maybe From
-                -> Maybe To
-                -> Maybe Page
-                -> APIKey
-                -> Lastfm Response
-getArtistTracks user artist startTimestamp endTimestamp page apiKey = dispatch $ callAPI "user.getArtistTracks"
-  [ "user" ?< user
-  , "artist" ?< artist
-  , "startTimestamp" ?< startTimestamp
-  , "page" ?< page
-  , "endTimestamp" ?< endTimestamp
-  , "api_key" ?< apiKey
+getArtistTracks :: User -> Artist -> Maybe StartTimestamp -> Maybe EndTimestamp -> Maybe Page -> APIKey -> Lastfm Response
+getArtistTracks user artist startTimestamp endTimestamp page apiKey = callAPI
+  [ (#) (Method "user.getArtistTracks")
+  , (#) user
+  , (#) artist
+  , (#) startTimestamp
+  , (#) page
+  , (#) endTimestamp
+  , (#) apiKey
   ]
 
 -- | Returns the tracks banned by the user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getBannedTracks>
 getBannedTracks :: User -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getBannedTracks user page limit apiKey = dispatch $ callAPI "user.getBannedTracks"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getBannedTracks user page limit apiKey = callAPI
+  [ (#) (Method "user.getBannedTracks")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get a list of upcoming events that this user is attending.
 --
 -- Mpre: <http://www.lastfm.ru/api/show/user.getEvents>
 getEvents :: User -> Maybe Page -> Maybe Limit -> Maybe FestivalsOnly -> APIKey -> Lastfm Response
-getEvents user page limit festivalsOnly apiKey = dispatch $ callAPI "user.getEvents"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "festivalsonly" ?< festivalsOnly
-  , "api_key" ?< apiKey
+getEvents user page limit festivalsOnly apiKey = callAPI
+  [ (#) (Method "user.getEvents")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) festivalsOnly
+  , (#) apiKey
   ]
 
 -- | Get a list of the user's friends on Last.fm.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getFriends>
 getFriends :: User -> Maybe RecentTracks -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getFriends user recentTracks page limit apiKey = dispatch $ callAPI "user.getFriends"
-  [ "user" ?< user
-  , "recenttracks" ?< recentTracks
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getFriends user recentTracks page limit apiKey = callAPI
+  [ (#) (Method "user.getFriends")
+  , (#) user
+  , (#) recentTracks
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get information about a user profile.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getInfo>
 getInfo :: Maybe User -> APIKey -> Lastfm Response
-getInfo user apiKey = dispatch $ callAPI "user.getInfo"
-  [ "user" ?< user
-  , "api_key" ?< apiKey
+getInfo user apiKey = callAPI
+  [ (#) (Method "user.getInfo")
+  , (#) user
+  , (#) apiKey
   ]
 
 -- | Get tracks loved by a user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getLovedTracks>
 getLovedTracks :: User -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getLovedTracks user page limit apiKey = dispatch $ callAPI "user.getLovedTracks"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getLovedTracks user page limit apiKey = callAPI
+  [ (#) (Method "user.getLovedTracks")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get a list of a user's neighbours on Last.fm.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getNeighbours>
 getNeighbours :: User -> Maybe Limit -> APIKey -> Lastfm Response
-getNeighbours user limit apiKey = dispatch $ callAPI "user.getNeighbours"
-  [ "user" ?< user
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getNeighbours user limit apiKey = callAPI
+  [ (#) (Method "user.getNeighbours")
+  , (#) user
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Gets a list of forthcoming releases based on a user's musical taste.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getNewReleases>
 getNewReleases :: User -> Maybe UseRecs -> APIKey -> Lastfm Response
-getNewReleases user useRecs apiKey = dispatch $ callAPI "user.getNewReleases"
-  [ "user" ?< user
-  , "userecs" ?< useRecs
-  , "api_key" ?< apiKey
+getNewReleases user useRecs apiKey = callAPI
+  [ (#) (Method "user.getNewReleases")
+  , (#) user
+  , (#) useRecs
+  , (#) apiKey
   ]
 
 -- | Get a paginated list of all events a user has attended in the past.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getPastEvents>
 getPastEvents :: User -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getPastEvents user page limit apiKey = dispatch $ callAPI "user.getPastEvents"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getPastEvents user page limit apiKey = callAPI
+  [ (#) (Method "user.getPastEvents")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the user's personal tags.
@@ -130,174 +129,193 @@
                 -> Maybe Limit
                 -> APIKey
                 -> Lastfm Response
-getPersonalTags user tag taggingType page limit apiKey = dispatch $ callAPI "user.getPersonalTags"
-  [ "user" ?< user
-  , "tag" ?< tag
-  , "taggingtype" ?< taggingType
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getPersonalTags user tag taggingType page limit apiKey = callAPI
+  [ (#) (Method "user.getPersonalTags")
+  , (#) user
+  , (#) tag
+  , (#) taggingType
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get a list of a user's playlists on Last.fm.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getPlaylists>
 getPlaylists :: User -> APIKey -> Lastfm Response
-getPlaylists user apiKey = dispatch $ callAPI "user.getPlaylists"
-  [ "user" ?< user
-  , "api_key" ?< apiKey
+getPlaylists user apiKey = callAPI
+  [ (#) (Method "user.getPlaylists")
+  , (#) user
+  , (#) apiKey
   ]
 
 -- | Get a list of the recent Stations listened to by this user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getRecentStations>
-getRecentStations :: User -> Maybe Page -> Maybe Limit -> APIKey -> SessionKey -> Lastfm Response
-getRecentStations user page limit apiKey sessionKey = dispatch $ callAPI "user.getRecentStations"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+getRecentStations :: User -> Maybe Page -> Maybe Limit -> APIKey -> SessionKey -> Secret -> Lastfm Response
+getRecentStations user page limit apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "user.getRecentStations")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Get a list of the recent tracks listened to by this user. Also includes the currently playing track with the nowplaying="true" attribute if the user is currently listening.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getRecentTracks>
 getRecentTracks :: User -> Maybe Page -> Maybe Limit -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getRecentTracks user page limit from to apiKey = dispatch $ callAPI "user.getRecentTracks"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "from" ?< from
-  , "to" ?< to
-  , "api_key" ?< apiKey
+getRecentTracks user page limit from to apiKey = callAPI
+  [ (#) (Method "user.getRecentTracks")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
 
 -- | Get Last.fm artist recommendations for a user.
 --
 -- Mpre: <http://www.lastfm.ru/api/show/user.getRecommendedArtists>
-getRecommendedArtists :: Maybe Page -> Maybe Limit -> APIKey -> SessionKey -> Lastfm Response
-getRecommendedArtists page limit apiKey sessionKey = dispatch $ callAPI "user.getRecommendedArtists"
-  [ "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+getRecommendedArtists :: Maybe Page -> Maybe Limit -> APIKey -> SessionKey -> Secret -> Lastfm Response
+getRecommendedArtists page limit apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "user.getRecommendedArtists")
+  , (#) page
+  , (#) limit
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Get a paginated list of all events recommended to a user by Last.fm, based on their listening profile.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getRecommendedEvents>
-getRecommendedEvents :: Maybe Page -> Maybe Limit -> APIKey -> SessionKey -> Lastfm Response
-getRecommendedEvents page limit apiKey sessionKey = dispatch $ callAPI "user.getRecommendedEvents"
-  [ "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+getRecommendedEvents :: Maybe Page -> Maybe Limit -> APIKey -> SessionKey -> Secret -> Lastfm Response
+getRecommendedEvents page limit apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "user.getRecommendedEvents")
+  , (#) page
+  , (#) limit
+  , (#) apiKey
+  , (#) sessionKey
   ]
 
 -- | Get shouts for this user. Also available as an rss feed.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getShouts>
 getShouts :: User -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getShouts user page limit apiKey = dispatch $ callAPI "user.getShouts"
-  [ "user" ?< user
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getShouts user page limit apiKey = callAPI
+  [ (#) (Method "user.getShouts")
+  , (#) user
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the top albums listened to by a user. You can stipulate a time period. Sends the overall chart by default.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getTopAlbums>
 getTopAlbums :: User -> Maybe Period -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopAlbums user period page limit apiKey = dispatch $ callAPI "user.getTopAlbums"
-  [ "user" ?< user
-  , "period" ?< period
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopAlbums user period page limit apiKey = callAPI
+  [ (#) (Method "user.getTopAlbums")
+  , (#) user
+  , (#) period
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the top artists listened to by a user. You can stipulate a time period. Sends the overall chart by default.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getTopArtists>
 getTopArtists :: User -> Maybe Period -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopArtists user period page limit apiKey = dispatch $ callAPI "user.getTopArtists"
-  [ "user" ?< user
-  , "period" ?< period
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopArtists user period page limit apiKey = callAPI
+  [ (#) (Method "user.getTopArtists")
+  , (#) user
+  , (#) period
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the top tags used by this user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getTopTags>
 getTopTags :: User -> Maybe Limit -> APIKey -> Lastfm Response
-getTopTags user limit apiKey = dispatch $ callAPI "user.getTopTags"
-  [ "user" ?< user
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopTags user limit apiKey = callAPI
+  [ (#) (Method "user.getTopTags")
+  , (#) user
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get the top tracks listened to by a user. You can stipulate a time period. Sends the overall chart by default.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getTopTracks>
 getTopTracks :: User -> Maybe Period -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getTopTracks user period page limit apiKey = dispatch $ callAPI "user.getTopTracks"
-  [ "user" ?< user
-  , "period" ?< period
-  , "page" ?< page
-  , "limit" ?< limit
-  , "api_key" ?< apiKey
+getTopTracks user period page limit apiKey = callAPI
+  [ (#) (Method "user.getTopTracks")
+  , (#) user
+  , (#) period
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Get an album chart for a user profile, for a given date range. If no date range is supplied, it will return the most recent album chart for this user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getWeeklyAlbumChart>
 getWeeklyAlbumChart :: User -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getWeeklyAlbumChart user from to apiKey = dispatch $ callAPI "user.getWeeklyAlbumChart"
-  [ "user" ?< user
-  , "from" ?< from
-  , "to" ?< to
-  , "api_key" ?< apiKey
+getWeeklyAlbumChart user from to apiKey = callAPI
+  [ (#) (Method "user.getWeeklyAlbumChart")
+  , (#) user
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
 
 -- | Get an artist chart for a user profile, for a given date range. If no date range is supplied, it will return the most recent artist chart for this user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getWeeklyArtistChart>
 getWeeklyArtistChart :: User -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getWeeklyArtistChart user from to apiKey = dispatch $ callAPI "user.getWeeklyArtistChart"
-  [ "user" ?< user
-  , "from" ?< from
-  , "to" ?< to
-  , "api_key" ?< apiKey
+getWeeklyArtistChart user from to apiKey = callAPI
+  [ (#) (Method "user.getWeeklyArtistChart")
+  , (#) user
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
 
 -- | Get a list of available charts for this user, expressed as date ranges which can be sent to the chart services.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getWeeklyChartList>
 getWeeklyChartList :: User -> APIKey -> Lastfm Response
-getWeeklyChartList user apiKey = dispatch $ callAPI "user.getWeeklyChartList" ["user" ?< user, "api_key" ?< apiKey]
+getWeeklyChartList user apiKey = callAPI
+  [ (#) (Method "user.getWeeklyChartList")
+  , (#) user
+  , (#) apiKey
+  ]
 
 -- | Get a track chart for a user profile, for a given date range. If no date range is supplied, it will return the most recent track chart for this user.
 --
 -- More: <http://www.lastfm.ru/api/show/user.getWeeklyTrackChart>
 getWeeklyTrackChart :: User -> Maybe From -> Maybe To -> APIKey -> Lastfm Response
-getWeeklyTrackChart user from to apiKey = dispatch $ callAPI "user.getWeeklyTrackChart"
-  [ "user" ?< user
-  , "from" ?< from
-  , "to" ?< to
-  , "api_key" ?< apiKey
+getWeeklyTrackChart user from to apiKey = callAPI
+  [ (#) (Method "user.getWeeklyTrackChart")
+  , (#) user
+  , (#) from
+  , (#) to
+  , (#) apiKey
   ]
 
 -- | Shout on this user's shoutbox.
 --
 -- More: <http://www.lastfm.ru/api/show/user.shout>
-shout :: User -> Message -> APIKey -> SessionKey -> Lastfm ()
-shout user message apiKey sessionKey = dispatch $ void $ callAPI "user.shout"
-  [ "user" ?< user
-  , "message" ?< message
-  , "api_key" ?< apiKey
-  , "sk" ?< sessionKey
+shout :: User -> Message -> APIKey -> SessionKey -> Secret -> Lastfm Response
+shout user message apiKey sessionKey secret = callAPIsigned secret
+  [ (#) (Method "user.shout")
+  , (#) user
+  , (#) message
+  , (#) apiKey
+  , (#) sessionKey
   ]
diff --git a/Network/Lastfm/API/Venue.hs b/Network/Lastfm/API/Venue.hs
--- a/Network/Lastfm/API/Venue.hs
+++ b/Network/Lastfm/API/Venue.hs
@@ -4,39 +4,42 @@
   ( getEvents, getPastEvents, search
   ) where
 
-import Network.Lastfm.Response
-import Network.Lastfm.Types ((?<), APIKey, Country, FestivalsOnly, Limit, Name, Page, Venue)
+import Control.Monad.Error (runErrorT)
+import Network.Lastfm
 
 -- | Get a list of upcoming events at this venue.
 --
 -- More: <http://www.lastfm.ru/api/show/venue.getEvents>
 getEvents :: Venue -> Maybe FestivalsOnly -> APIKey -> Lastfm Response
-getEvents venue festivalsOnly apiKey = dispatch $ callAPI "venue.getEvents"
-  [ "venue" ?< venue
-  , "api_key" ?< apiKey
-  , "festivalsonly" ?< festivalsOnly
+getEvents venue festivalsOnly apiKey = callAPI
+  [ (#) (Method "venue.getEvents")
+  , (#) venue
+  , (#) festivalsOnly
+  , (#) apiKey
   ]
 
 -- | Get a paginated list of all the events held at this venue in the past.
 --
 -- More: <http://www.lastfm.ru/api/show/venue.getPastEvents>
 getPastEvents :: Venue -> Maybe FestivalsOnly -> Maybe Page -> Maybe Limit -> APIKey -> Lastfm Response
-getPastEvents venue festivalsOnly page limit apiKey = dispatch $ callAPI "venue.getPastEvents"
-  [ "venue" ?< venue
-  , "api_key" ?< apiKey
-  , "festivalsonly" ?< festivalsOnly
-  , "page" ?< page
-  , "limit" ?< limit
+getPastEvents venue festivalsOnly page limit apiKey = callAPI
+  [ (#) (Method "venue.getPastEvents")
+  , (#) venue
+  , (#) festivalsOnly
+  , (#) page
+  , (#) limit
+  , (#) apiKey
   ]
 
 -- | Search for a venue by venue name.
 --
 -- More: <http://www.lastfm.ru/api/show/venue.search>
-search :: Name -> Maybe Page -> Maybe Limit -> Maybe Country -> APIKey -> Lastfm Response
-search venue page limit country apiKey = dispatch $ callAPI "venue.search"
-  [ "venue" ?< venue
-  , "api_key" ?< apiKey
-  , "page" ?< page
-  , "limit" ?< limit
-  , "country" ?< country
+search :: Venuename -> Maybe Page -> Maybe Limit -> Maybe Country -> APIKey -> Lastfm Response
+search venue page limit country apiKey = callAPI
+  [ (#) (Method "venue.search")
+  , (#) venue
+  , (#) page
+  , (#) limit
+  , (#) country
+  , (#) apiKey
   ]
diff --git a/Network/Lastfm/Response.hs b/Network/Lastfm/Response.hs
deleted file mode 100644
--- a/Network/Lastfm/Response.hs
+++ /dev/null
@@ -1,143 +0,0 @@
-{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable #-}
--- | Response module
-{-# OPTIONS_HADDOCK prune #-}
-module Network.Lastfm.Response
-  ( Lastfm, Response, LastfmError(WrapperCallError), dispatch
-  , withSecret
-  , callAPI
-  ) where
-
-import Codec.Binary.UTF8.String (encodeString)
-import Control.Applicative ((<$>))
-import Control.Arrow (second)
-import Control.Exception (Exception, handle, throw)
-import Control.Monad ((<=<), liftM)
-import Data.Digest.Pure.MD5 (md5)
-import Data.Function (on)
-import Data.IORef
-import Data.List (sortBy)
-import Data.Typeable (Typeable)
-import Data.URLEncoded (urlEncode, export)
-import Network.Curl hiding (Content)
-import System.IO.Unsafe (unsafePerformIO)
-import Text.XML.Light
-
-import qualified Data.ByteString.Lazy.Char8 as BS
-
-data APIError
-  = DoesntExist
-  | InvalidService
-  | InvalidMethod
-  | AuthenticationFailed
-  | InvalidFormat
-  | InvalidParameters
-  | InvalidResource
-  | OperationFailed
-  | InvalidSessionKey
-  | InvalidAPIKey
-  | ServiceOffline
-  | SubscribersOnly
-  | InvalidMethodSignature
-  | TokenHasNotAuthorized
-  | NotForStreaming
-  | TemporaryUnavailable
-  | LoginRequired
-  | TrialExpired
-  | DoesntExistAgain
-  | NotEnoughContent
-  | NotEnoughMembers
-  | NotEnoughFans
-  | NotEnoughNeighbours
-  | NoPeakRadio
-  | RadioNotFound
-  | SuspendedAPIKey
-  | Deprecated
-  | RateLimitExceeded
-    deriving (Enum)
-
-instance Show APIError where
-  show DoesntExist = "DoesntExist: This error does not exist"
-  show InvalidService = "InvalidService: This service does not exist"
-  show InvalidMethod = "InvalidMethod: No method with that name in this package"
-  show AuthenticationFailed = "AuthenticationFailed: You do not have permissions to access the service"
-  show InvalidFormat = "InvalidFormat: This service doesn't exist in that format"
-  show InvalidParameters = "InvalidParameters: Your request is missing a required parameter"
-  show InvalidResource = "InvalidResource: Invalid resource specified"
-  show OperationFailed = "OperationFailed: Something else went wrong"
-  show InvalidSessionKey = "InvalidSessionKey: Please re-authenticate"
-  show InvalidAPIKey = "InvalidAPIKey: You must be granted a valid key by last.fm"
-  show ServiceOffline = "ServiceOffline: This service is temporarily offline. Try again later."
-  show SubscribersOnly  = "SubscribersOnly : This station is only available to paid last.fm subscribers"
-  show InvalidMethodSignature = "InvalidMethodSignature: Invalid method signature supplied"
-  show TokenHasNotAuthorized = "TokenHasNotAuthorized: This token has not been authorized"
-  show NotForStreaming = "NotForStreaming: This item is not available for streaming."
-  show TemporaryUnavailable = "TemporaryUnavailable: The service is temporarily unavailable, please try again."
-  show LoginRequired = "LoginRequired: Login: User requires to be logged in"
-  show TrialExpired = "TrialExpired: This user has no free radio plays left. Subscription required."
-  show DoesntExistAgain = "DoesntExistAgain: This error does not exist"
-  show NotEnoughContent = "NotEnoughContent: There is not enough content to play this station"
-  show NotEnoughMembers = "NotEnoughMembers: This group does not have enough members for radio"
-  show NotEnoughFans = "NotEnoughFans: This artist does not have enough fans for for radio"
-  show NotEnoughNeighbours = "NotEnoughNeighbours: There are not enough neighbours for radio"
-  show NoPeakRadio = "NoPeakRadio: This user is not allowed to listen to radio during peak usage"
-  show RadioNotFound = "RadioNotFound: Radio station not found"
-  show SuspendedAPIKey = "SuspendedAPIKey: Access for your account has been suspended, please contact Last.fm"
-  show Deprecated = "Deprecated: This type of request is no longer supported"
-  show RateLimitExceeded = "RateLimitExceeded: Your IP has made too many requests in a short period"
-
--- Various Lastfm errors.
-data LastfmError
-  = LastfmAPIError APIError -- ^ Internal Lastfm errors
-  | WrapperCallError Method Message -- ^ Wrapper errors
-    deriving (Show, Typeable)
-
-instance Exception LastfmError
-
--- | Type synonym for Lastfm response or error.
-type Lastfm a = IO (Either LastfmError a)
--- | Type synonym for Lastfm response
-type Response = String
-type Key = String
-type Value = String
-type Secret = String
-type Sign = String
-type Method = String
-type Message = String
-
-secret :: IORef Secret
-secret = unsafePerformIO $ newIORef ""
-
--- | All authentication requiring functions should be wrapped into 'withSecret'
-withSecret :: Secret -> IO a -> IO a
-withSecret s f = writeIORef secret s >> f
-
-url :: String
-url = "http://ws.audioscrobbler.com/2.0/?"
-
--- | Low level function. Captures all exceptions and transform them into Either Error type.
-dispatch :: IO a -> Lastfm a
-dispatch f = handle (\(e :: LastfmError) -> return (Left e)) (liftM Right f)
-
--- | Low level function. Sends POST query to Lastfm API.
-callAPI :: Method -> [(Key, Value)] -> IO Response
-callAPI m xs = withCurlDo $ do
-                 s <- readIORef secret
-                 let ys = ("method", m) : (map (second encodeString) . filter (not . null . snd) $ xs)
-                 let zs = if not $ null s then ("api_sig", sign s ys) : ys else ys
-                 response <- respBody <$> (curlGetResponse_ url
-                                            [ CurlPostFields . map (export . urlEncode) $ zs
-                                            , CurlFailOnError False
-                                            , CurlUserAgent "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 Iceweasel/10.0"
-                                            ]
-                                            :: IO CurlResponse)
-                 case isError response of
-                   Just n  -> throw $ LastfmAPIError (toEnum $ n - 1)
-                   Nothing -> return response
-
-  where -- | Some API calls should be signed. (http://www.lastfm.ru/api/authspec Section 8)
-        sign :: Secret -> [(Key, Value)] -> Sign
-        sign s = show . md5 . BS.pack . (++ s) . concatMap (uncurry (++)) . sortBy (compare `on` fst)
-
-        isError :: String -> Maybe Int
-        isError response = do xml <- parseXMLDoc response
-                              read <$> (findAttr (unqual "code") <=< findChild (unqual "error")) xml
diff --git a/Network/Lastfm/Types.hs b/Network/Lastfm/Types.hs
--- a/Network/Lastfm/Types.hs
+++ b/Network/Lastfm/Types.hs
@@ -1,113 +1,253 @@
-{-# LANGUAGE FlexibleInstances, OverlappingInstances, TypeSynonymInstances, GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE FlexibleInstances, OverlappingInstances, TypeSynonymInstances #-}
 module Network.Lastfm.Types where
 
+import Control.Monad.Error (Error)
 import Data.List (intercalate)
 
-newtype Album = Album String deriving (Show, LastfmValue)
-newtype AlbumArtist = AlbumArtist String deriving (Show, LastfmValue)
-newtype APIKey = APIKey String deriving (Show, LastfmValue)
-newtype Artist = Artist String deriving (Show, LastfmValue)
-newtype AuthToken = AuthToken String deriving (Show, LastfmValue)
-newtype Autocorrect = Autocorrect Bool deriving (Show, LastfmValue)
-newtype Bitrate = Bitrate Int deriving (Show, LastfmValue)
-newtype BuyLinks = BuyLinks Bool deriving (Show, LastfmValue)
-newtype ChosenByUser = ChosenByUser String deriving (Show, LastfmValue)
-newtype Context = Context String deriving (Show, LastfmValue)
-newtype Country = Country String deriving (Show, LastfmValue)
-newtype Description = Description String deriving (Show, LastfmValue)
-newtype Discovery = Discovery Bool deriving (Show, LastfmValue)
-newtype Distance = Distance Int deriving (Show, LastfmValue)
-newtype Duration = Duration String deriving (Show, LastfmValue)
-newtype Event = Event Int deriving (Show, LastfmValue)
-newtype FestivalsOnly = FestivalsOnly Bool deriving (Show, LastfmValue)
-newtype Fingerprint = Fingerprint Integer deriving (Show, LastfmValue)
-newtype From = From Integer deriving (Show, LastfmValue)
-newtype Group = Group String deriving (Show, LastfmValue)
-newtype Language = Language String deriving (Show, LastfmValue)
-newtype Latitude = Latitude String deriving (Show, LastfmValue)
-newtype Limit = Limit Int deriving (Show, LastfmValue)
-newtype Location = Location String deriving (Show, LastfmValue)
-newtype Longitude = Longitude String deriving (Show, LastfmValue)
-newtype Mbid = Mbid String deriving (Show, LastfmValue)
-newtype Message = Message String deriving (Show, LastfmValue)
-newtype Metro = Metro String deriving (Show, LastfmValue)
-newtype Multiplier = Multiplier Double deriving (Show, LastfmValue)
-newtype Name = Name String deriving (Show, LastfmValue)
-newtype Page = Page Int deriving (Show, LastfmValue)
-newtype Playlist = Playlist Int deriving (Show, LastfmValue)
-newtype Public = Public Bool deriving (Show, LastfmValue)
-newtype RecentTracks = RecentTracks Bool deriving (Show, LastfmValue)
-newtype Recipient = Recipient String deriving (Show, LastfmValue)
-newtype RTP = RTP Bool deriving (Show, LastfmValue)
-newtype SessionKey = SessionKey String deriving (Show, LastfmValue)
-newtype Station = Station String deriving (Show, LastfmValue)
-newtype StreamId = StreamId String deriving (Show, LastfmValue)
-newtype Tag = Tag String deriving (Show, LastfmValue)
-newtype TaggingType = TaggingType String deriving (Show, LastfmValue)
-newtype Timestamp = Timestamp Integer deriving (Show, LastfmValue)
-newtype Title = Title String deriving (Show, LastfmValue)
-newtype To = To Integer deriving (Show, LastfmValue)
-newtype Token = Token String deriving (Show, LastfmValue)
-newtype Track = Track String deriving (Show, LastfmValue)
-newtype TrackNumber = TrackNumber String deriving (Show, LastfmValue)
-newtype User = User String deriving (Show, LastfmValue)
-newtype UseRecs = UseRecs Bool deriving (Show, LastfmValue)
-newtype Venue = Venue Int deriving (Show, LastfmValue)
+newtype Secret = Secret String deriving Show
 
-data Order = Popularity | DateAdded deriving Show
+(#) :: Argument a => a -> (String, String)
+(#) x = (key x, value x)
 
-instance LastfmValue Order where
-  unpack Popularity = "popularity"
-  unpack DateAdded  = "dateadded"
+class Argument a where
+  key :: a -> String
+  value :: a -> String
 
-data Status = Yes | Maybe | No deriving Show
+instance Argument a => Argument (Maybe a) where key = maybe "" key; value = maybe "" value
+instance Argument a => Argument [a] where
+  key (a:_) = key a ++ "s"
+  key [] = ""
+  value = intercalate "," . map value
 
-instance LastfmValue Status where
-  unpack Yes = "0"
-  unpack Maybe = "1"
-  unpack No = "2"
+newtype Album = Album String
+newtype AlbumArtist = AlbumArtist String
+newtype APIKey = APIKey String
+newtype Artist = Artist String
+newtype AuthToken = AuthToken String
+newtype ChosenByUser = ChosenByUser String
+newtype Context = Context String
+newtype Country = Country String
+newtype Description = Description String
+newtype Group = Group String
+newtype Language = Language String
+newtype Latitude = Latitude String
+newtype Location = Location String
+newtype Longitude = Longitude String
+newtype Mbid = Mbid String
+newtype Message = Message String
+newtype Method = Method String
+newtype Metro = Metro String
+newtype Name = Name String
+newtype Recipient = Recipient String
+newtype SessionKey = SessionKey String
+newtype Station = Station String
+newtype StreamId = StreamId String
+newtype Tag = Tag String
+newtype TaggingType = TaggingType String
+newtype Title = Title String
+newtype Token = Token String
+newtype Track = Track String
+newtype User = User String
+newtype Username = Username String
+newtype Venuename = Venuename String
+instance Argument Album where key = const "album"; value (Album a) = a
+instance Argument AlbumArtist where key = const "albumartist"; value (AlbumArtist a) = a
+instance Argument APIKey where key = const "api_key"; value (APIKey a) = a
+instance Argument Artist where key = const "artist"; value (Artist a) = a
+instance Argument AuthToken where key = const "authToken"; value (AuthToken a) = a
+instance Argument ChosenByUser where key = const "chosenByUser"; value (ChosenByUser a) = a
+instance Argument Context where key = const "context"; value (Context a) = a
+instance Argument Country where key = const "country"; value (Country a) = a
+instance Argument Description where key = const "description"; value (Description a) = a
+instance Argument Group where key = const "group"; value (Group a) = a
+instance Argument Language where key = const "lang"; value (Language a) = a
+instance Argument Latitude where key = const "lat"; value (Latitude a) = a
+instance Argument Location where key = const "location"; value (Location a) = a
+instance Argument Longitude where key = const "long"; value (Longitude a) = a
+instance Argument Mbid where key = const "mbid"; value (Mbid a) = a
+instance Argument Message where key = const "message"; value (Message a) = a
+instance Argument Method where key = const "method"; value (Method a) = a
+instance Argument Metro where key = const "metro"; value (Metro a) = a
+instance Argument Name where key = const "name"; value (Name a) = a
+instance Argument Recipient where key = const "recipient"; value (Recipient a) = a
+instance Argument SessionKey where key = const "sk"; value (SessionKey a) = a
+instance Argument Station where key = const "station"; value (Station a) = a
+instance Argument StreamId where key = const "streamId"; value (StreamId a) = a
+instance Argument Tag where key = const "tag"; value (Tag a) = a
+instance Argument TaggingType where key = const "taggingtype"; value (TaggingType a) = a
+instance Argument Title where key = const "title"; value (Title a) = a
+instance Argument Token where key = const "token"; value (Token a) = a
+instance Argument Track where key = const "track"; value (Track a) = a
+instance Argument User where key = const "user"; value (User a) = a
+instance Argument Username where key = const "username"; value (Username a) = a
+instance Argument Venuename where key = const "venue"; value (Venuename a) = a
 
-class LastfmValue a where
-  unpack :: a -> String
+boolToString :: Bool -> String
+boolToString True = "1"
+boolToString False = "0"
 
+newtype Autocorrect = Autocorrect Bool
+newtype BuyLinks = BuyLinks Bool
+newtype Discovery = Discovery Bool
+newtype FestivalsOnly = FestivalsOnly Bool
+newtype Public = Public Bool
+newtype RecentTracks = RecentTracks Bool
+newtype RTP = RTP Bool
+newtype UseRecs = UseRecs Bool
+instance Argument Autocorrect where key = const "autocorrect"; value (Autocorrect a) = boolToString a
+instance Argument BuyLinks where key = const "buylinks"; value (BuyLinks a) = boolToString a
+instance Argument Discovery where key = const "discovery"; value (Discovery a) = boolToString a
+instance Argument FestivalsOnly where key = const "festivalsonly"; value (FestivalsOnly a) = boolToString a
+instance Argument Public where key = const "public"; value (Public a) = boolToString a
+instance Argument RecentTracks where key = const "recenttracks"; value (RecentTracks a) = boolToString a
+instance Argument RTP where key = const "rtp"; value (RTP a) = boolToString a
+instance Argument UseRecs where key = const "userecs"; value (UseRecs a) = boolToString a
+
+newtype Distance = Distance Int
+newtype Duration = Duration Int
+newtype Event = Event Int
+newtype Limit = Limit Int
+newtype Page = Page Int
+newtype Playlist = Playlist Int
+newtype TrackNumber = TrackNumber Int
+newtype Venue = Venue Int
+instance Argument Distance where key = const "distance"; value (Distance a) = show a
+instance Argument Duration where key = const "duration"; value (Duration a) = show a
+instance Argument Event where key = const "event"; value (Event a) = show a
+instance Argument Limit where key = const "limit"; value (Limit a) = show a
+instance Argument Page where key = const "page"; value (Page a) = show a
+instance Argument Playlist where key = const "playlistID"; value (Playlist a) = show a
+instance Argument TrackNumber where key = const "tracknumber"; value (TrackNumber a) = show a
+instance Argument Venue where key = const "venue"; value (Venue a) = show a
+
+newtype End = End Integer
+newtype EndTimestamp = EndTimestamp Integer
+newtype Fingerprint = Fingerprint Integer
+newtype From = From Integer
+newtype Start = Start Integer
+newtype StartTimestamp = StartTimestamp Integer
+newtype Timestamp = Timestamp Integer
+newtype To = To Integer
+instance Argument End where key = const "end"; value (End a) = show a
+instance Argument EndTimestamp where key = const "endTimestamp"; value (EndTimestamp a) = show a
+instance Argument Fingerprint where key = const "fingerprintid"; value (Fingerprint a) = show a
+instance Argument From where key = const "from"; value (From a) = show a
+instance Argument Start where key = const "start"; value (Start a) = show a
+instance Argument StartTimestamp where key = const "startTimestamp"; value (StartTimestamp a) = show a
+instance Argument Timestamp where key = const "timestamp"; value (Timestamp a) = show a
+instance Argument To where key = const "to"; value (To a) = show a
+
+data Bitrate = B64 | B128
+
+instance Argument Bitrate where
+  key = const "bitrate"
+  value B64 = "64"
+  value B128 = "128"
+
+data Multiplier = M1 | M2
+instance Argument Multiplier where
+  key = const "speed_multiplier"
+  value M1 = "1.0"
+  value M2 = "2.0"
+
+data Order = Popularity | DateAdded
+instance Argument Order where
+  key = const "order"
+  value Popularity = "popularity"
+  value DateAdded  = "dateadded"
+
+data Status = Yes | Maybe | No
+instance Argument Status where
+  key = const "status"
+  value Yes = "0"
+  value Maybe = "1"
+  value No = "2"
+
 data Value = ValueUser User
            | ValueArtists [Artist]
-
 instance Show Value where
   show (ValueUser _)    = "user"
   show (ValueArtists _) = "artists"
-
-instance LastfmValue Value where
-  unpack (ValueUser u)     = unpack u
-  unpack (ValueArtists as) = unpack as
+instance Argument Value where
+  key = const "value"
+  value (ValueUser u)     = value u
+  value (ValueArtists as) = value as
 
 data Period = Week | Quater | HalfYear | Year | Overall
-              deriving (Show)
+instance Argument Period where
+  key = const "period"
+  value Week     = "7day"
+  value Quater   = "3month"
+  value HalfYear = "6month"
+  value Year     = "12month"
+  value Overall  = "overall"
 
-instance LastfmValue Period where
-  unpack Week     = "7day"
-  unpack Quater   = "3month"
-  unpack HalfYear = "6month"
-  unpack Year     = "12month"
-  unpack Overall  = "overall"
+data APIError
+  = DoesntExist
+  | InvalidService
+  | InvalidMethod
+  | AuthenticationFailed
+  | InvalidFormat
+  | InvalidParameters
+  | InvalidResource
+  | OperationFailed
+  | InvalidSessionKey
+  | InvalidAPIKey
+  | ServiceOffline
+  | SubscribersOnly
+  | InvalidMethodSignature
+  | TokenHasNotAuthorized
+  | NotForStreaming
+  | TemporaryUnavailable
+  | LoginRequired
+  | TrialExpired
+  | DoesntExistAgain
+  | NotEnoughContent
+  | NotEnoughMembers
+  | NotEnoughFans
+  | NotEnoughNeighbours
+  | NoPeakRadio
+  | RadioNotFound
+  | SuspendedAPIKey
+  | Deprecated
+  | RateLimitExceeded
+    deriving (Enum)
 
-instance LastfmValue Bool where
-  unpack True = "1"
-  unpack False = "0"
-instance LastfmValue Int where
-  unpack = show
-instance LastfmValue Integer where
-  unpack = show
-instance LastfmValue Double where
-  unpack = show
-instance LastfmValue String where
-  unpack = id
-instance LastfmValue a => LastfmValue [a] where
-  unpack = intercalate "," . map unpack
-instance LastfmValue a => LastfmValue (Maybe a) where
-  unpack (Just a) = unpack a
-  unpack Nothing  = ""
+instance Show APIError where
+  show DoesntExist = "DoesntExist: This error does not exist"
+  show InvalidService = "InvalidService: This service does not exist"
+  show InvalidMethod = "InvalidMethod: No method with that name in this package"
+  show AuthenticationFailed = "AuthenticationFailed: You do not have permissions to access the service"
+  show InvalidFormat = "InvalidFormat: This service doesn't exist in that format"
+  show InvalidParameters = "InvalidParameters: Your request is missing a required parameter"
+  show InvalidResource = "InvalidResource: Invalid resource specified"
+  show OperationFailed = "OperationFailed: Something else went wrong"
+  show InvalidSessionKey = "InvalidSessionKey: Please re-authenticate"
+  show InvalidAPIKey = "InvalidAPIKey: You must be granted a valid key by last.fm"
+  show ServiceOffline = "ServiceOffline: This service is temporarily offline. Try again later."
+  show SubscribersOnly  = "SubscribersOnly : This station is only available to paid last.fm subscribers"
+  show InvalidMethodSignature = "InvalidMethodSignature: Invalid method signature supplied"
+  show TokenHasNotAuthorized = "TokenHasNotAuthorized: This token has not been authorized"
+  show NotForStreaming = "NotForStreaming: This item is not available for streaming."
+  show TemporaryUnavailable = "TemporaryUnavailable: The service is temporarily unavailable, please try again."
+  show LoginRequired = "LoginRequired: Login: User requires to be logged in"
+  show TrialExpired = "TrialExpired: This user has no free radio plays left. Subscription required."
+  show DoesntExistAgain = "DoesntExistAgain: This error does not exist"
+  show NotEnoughContent = "NotEnoughContent: There is not enough content to play this station"
+  show NotEnoughMembers = "NotEnoughMembers: This group does not have enough members for radio"
+  show NotEnoughFans = "NotEnoughFans: This artist does not have enough fans for for radio"
+  show NotEnoughNeighbours = "NotEnoughNeighbours: There are not enough neighbours for radio"
+  show NoPeakRadio = "NoPeakRadio: This user is not allowed to listen to radio during peak usage"
+  show RadioNotFound = "RadioNotFound: Radio station not found"
+  show SuspendedAPIKey = "SuspendedAPIKey: Access for your account has been suspended, please contact Last.fm"
+  show Deprecated = "Deprecated: This type of request is no longer supported"
+  show RateLimitExceeded = "RateLimitExceeded: Your IP has made too many requests in a short period"
 
-(?<) :: LastfmValue a => String -> a -> (String, String)
-a ?< b = (a, unpack b)
+-- Various Lastfm errors.
+data LastfmError
+  = LastfmAPIError APIError -- ^ Internal Lastfm errors
+  | WrapperCallError String String -- ^ Wrapper errors
+    deriving Show
+
+instance Error LastfmError
 
diff --git a/liblastfm.cabal b/liblastfm.cabal
--- a/liblastfm.cabal
+++ b/liblastfm.cabal
@@ -1,25 +1,35 @@
-Name:		liblastfm
-Version:	0.0.1.0
-Description:	Wrapper to Lastfm API
-License:	MIT
-License-file:	LICENSE
-Author:		Matvey Aksenov, Dmitry Malikov
-Maintainer:	Matvey Aksenov <matvey.aksenov@gmail.com>
-Category:	Network
-Synopsis:	Wrapper to Lastfm API
-Cabal-Version:	>= 1.6
-Build-Type:	Simple
+Name: liblastfm
+Version: 0.0.2.1
+Description: Wrapper to Lastfm API
+License: MIT
+License-file: LICENSE
+Author: Matvey Aksenov, Dmitry Malikov
+Maintainer: Matvey Aksenov <matvey.aksenov@gmail.com>
+Category: Network
+Synopsis: Wrapper to Lastfm API
+Cabal-Version: >= 1.6
+Build-Type: Simple
 
 Library
-  Build-Depends:	base >= 3 && < 5, bytestring, curl, pureMD5, urlencoded, xml, utf8-string
-  Exposed-Modules:	Network.Lastfm.Response, Network.Lastfm.Types, Network.Lastfm.API.Album, Network.Lastfm.API.Artist, Network.Lastfm.API.Auth, Network.Lastfm.API.Chart, Network.Lastfm.API.Event, Network.Lastfm.API.Geo, Network.Lastfm.API.Group, Network.Lastfm.API.Library, Network.Lastfm.API.Playlist, Network.Lastfm.API.Radio, Network.Lastfm.API.Tag, Network.Lastfm.API.Tasteometer, Network.Lastfm.API.Track, Network.Lastfm.API.User, Network.Lastfm.API.Venue
-  Extensions:		FlexibleInstances, OverlappingInstances, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeSynonymInstances
+  Build-Depends: base >= 3 && < 5, bytestring, mtl, curl, pureMD5, urlencoded, xml, utf8-string
+  Exposed-Modules: Network.Lastfm
+                   Network.Lastfm.Types
+                   Network.Lastfm.API.Album
+                   Network.Lastfm.API.Artist
+                   Network.Lastfm.API.Auth
+                   Network.Lastfm.API.Chart
+                   Network.Lastfm.API.Event
+                   Network.Lastfm.API.Geo
+                   Network.Lastfm.API.Group
+                   Network.Lastfm.API.Library
+                   Network.Lastfm.API.Playlist
+                   Network.Lastfm.API.Radio
+                   Network.Lastfm.API.Tag
+                   Network.Lastfm.API.Tasteometer
+                   Network.Lastfm.API.Track
+                   Network.Lastfm.API.User
+                   Network.Lastfm.API.Venue
 
 source-repository head
   type:     git
   location: https://github.com/supki/haskell-liblastfm
-
-source-repository this
-  type:     git
-  location: https://github.com/supki/haskell-liblastfm
-  tag:      v0.0.1.0
