diff --git a/IsoHunt/Image.hs b/IsoHunt/Image.hs
new file mode 100644
--- /dev/null
+++ b/IsoHunt/Image.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE DeriveGeneric #-}
+{- |
+The 'Image' data type. This should be imported qualified so as not
+to conflict with the accessors from the 'Response' and 'Item' data types:
+
+> import qualified IsoHunt.Response as Response
+> import qualified IsoHunt.Item as Item
+> import qualified IsoHunt.Image as Image
+>
+> ... Response.title r ... Item.title i ... Image.title im ...
+
+These fields are mostly undocumented; see
+<http://ca.isohunt.com/js/json.php?ihq=ubuntu&start=1&rows=4> for
+an example response.
+
+-}
+module IsoHunt.Image( 
+  Image(..),
+  ) where
+
+import GHC.Generics
+import Data.Text
+import Data.Aeson
+import Data.Typeable
+
+data Image = 
+  Image {
+    title :: Text,
+    url :: Text,
+    link :: Text,
+    width :: Integer,
+    height :: Integer
+    } deriving(Eq, Ord, Show, Typeable, Generic)
+
+instance FromJSON Image
diff --git a/IsoHunt/Item.hs b/IsoHunt/Item.hs
new file mode 100644
--- /dev/null
+++ b/IsoHunt/Item.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE DeriveGeneric #-}
+{- |
+The 'Item' data type. This should be imported qualified so as not
+to conflict with the accessors from the 'Response' and 'Image' data types:
+
+> import qualified IsoHunt.Response as Response
+> import qualified IsoHunt.Item as Item
+> import qualified IsoHunt.Image as Image
+>
+> ... Response.title r ... Item.title i ... Image.title im ...
+
+These fields are mostly undocumented; see
+<http://ca.isohunt.com/js/json.php?ihq=ubuntu&start=1&rows=4> for
+an example response.
+-}
+module IsoHunt.Item(
+  Item(..),
+  ) where
+
+import GHC.Generics
+import Control.Applicative
+import Data.Aeson
+import qualified Data.HashMap.Strict as H
+import Data.Text
+import Data.Typeable
+
+data Item
+ = Item {
+     title :: Text,
+     link :: Text,
+     guid :: Text,
+     enclosureUrl :: Text, -- ^ The link for the *.torrent file on
+     -- isohunt's website, eg <http://ca.isohunt.com/download/52510650/ubuntu.torrent>
+     length :: Integer, -- ^ Size in bytes
+     tracker :: Text,
+     trackerUrl :: Text,
+     kws :: Text,
+     exempts :: Text,
+     category :: Text,
+     originalSite :: Text,
+     originalLink :: Text,
+     size :: Text, -- ^ human-readable filesize (eg \"1.4GB\")
+     files :: Integer,
+     seeds :: Maybe Integer,
+     leechers :: Maybe Integer,
+     downloads :: Maybe Integer,
+     votes :: Integer,
+     comments :: Integer,
+     hash :: Text,
+     pubDate :: Text
+  } deriving (Generic, Eq, Show, Ord, Typeable)
+
+-- we supply a manual instance so that:
+--   * we can handle Seeds, leechers, and downloads occasionally
+--     appearing as "" instead of numbers
+--   * we can change the names of the Haskell fields
+instance FromJSON Item where
+  parseJSON (Object v) =
+    Item <$>
+     v .: "title" <*>
+     v .: "link" <*>
+     v .: "guid" <*>
+     v .: "enclosure_url" <*>
+     v .: "length" <*>
+     v .: "tracker" <*>
+     v .: "tracker_url" <*>
+     v .: "kws" <*>
+     v .: "exempts" <*>
+     v .: "category" <*>
+     v .: "original_site" <*>
+     v .: "original_link" <*>
+     v .: "size" <*>
+     v .: "files" <*>
+     int v "Seeds" <*>
+     int v "leechers" <*>
+     int v "downloads" <*>
+     v .: "votes" <*>
+     v .: "comments" <*>
+     v .: "hash" <*>
+     v .: "pubDate"
+    
+int obj key = case H.lookup key obj of
+               Nothing -> fail $ "key " ++ show key ++ " not present"
+               Just (Number v) -> pure (Just $ floor v)
+               Just _ -> pure Nothing
diff --git a/IsoHunt/Response.hs b/IsoHunt/Response.hs
new file mode 100644
--- /dev/null
+++ b/IsoHunt/Response.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE DeriveGeneric #-}
+{- |
+The 'Response' data type. This should be imported qualified so as not
+to conflict with the accessors from the 'Item' and 'Image' data types:
+
+> import qualified IsoHunt.Response as Response
+> import qualified IsoHunt.Item as Item
+> import qualified IsoHunt.Image as Image
+>
+> ... Response.title r ... Item.title i ... Image.title im ...
+
+These fields are mostly undocumented; see
+<http://ca.isohunt.com/js/json.php?ihq=ubuntu&start=1&rows=4> for
+an example response.
+
+-}
+module IsoHunt.Response(
+  Response(..),
+  ) where
+
+import Control.Applicative
+import Control.Monad
+import qualified Data.Vector as V
+import Data.Aeson
+import Data.Text
+import GHC.Generics
+import Data.HashMap.Strict as H
+import Data.Typeable
+import IsoHunt.Item(Item)
+import IsoHunt.Image(Image)
+
+data Response
+  = Response {
+      title :: Text,
+      link :: Text,
+      category :: Text,
+      pubDate :: Text,
+      description :: Text,
+      language :: Text,
+      maxResults :: Integer,
+      ttl :: Integer,
+      image :: Image,
+      lastBuildDate :: Text,
+      totalResults :: Integer,
+      items :: V.Vector Item, -- ^ search results
+      censored :: Integer
+ } deriving (Eq, Ord, Show, Typeable, Generic)
+
+-- we supply a custom instance because:
+--   * we want to change the names (totalResults vs total_results)
+--   * the items field has a weird JSON representation
+instance FromJSON Response where
+  parseJSON (Object v) = Response <$>
+      v .: "title" <*>
+      v .: "link" <*>
+      v .: "category" <*>
+      v .: "pubDate" <*>
+      v .: "description" <*>
+      v .: "language" <*>
+      v .: "max_results" <*>
+      v .: "ttl" <*>
+      v .: "image" <*>
+      v .: "lastBuildDate" <*>
+      v .: "total_results" <*>
+      list v "items" <*>
+      v .: "censored"
+  parseJSON _ = mzero
+
+list obj key = 
+  case H.lookup key obj of
+    Just (Object v) -> v .: "list"
+    Just _ -> fail $ "key " ++ show key ++ " is not an object"
+    Nothing -> fail $ "key " ++ show key ++ " not found"
diff --git a/IsoHunt/Search.hs b/IsoHunt/Search.hs
new file mode 100644
--- /dev/null
+++ b/IsoHunt/Search.hs
@@ -0,0 +1,152 @@
+{- | 
+IsoHunt API; see <http://ca.isohunt.com/forum/viewtopic.php?p=433527#433527>.
+
+Sample use:
+
+> resp <- search (simpleQuery "ubuntu")
+
+The following terms and conditions apply to the IsoHunt API, as stated
+in the above link:
+
+   In using our search API, you are free to do with it as you wish on condition that if your app is available publicly to users, you must link to torrent details pages on isoHunt.com, whether you link to the .torrent files or not. We reserve the right to ban you from using our API if you don't follow this simple rule. Refer to Louish's iPhone app for a good example of including links to our torrent details pages. Our torrent details pages have URLs like this: <http://isohunt.com/torrent_details/28289948/ubuntu?tab=summary>
+
+   While we don't require developer tokens or place hard limits on api calls usage, excessive calls will also result in bans. If you think your app will consistently sustain multiple calls per second to our api, email admin at this site's domain first. 
+
+  You are free to promote your app using our API, by replying under this post (<http://ca.isohunt.com/forum/viewtopic.php?p=433527#433527>) or post under this forum (<http://isohunt.com/forum/viewforum.php?f=19>). If your app is really good, we'll likely want to spotlight it on isoHunt's frontpage. Multiple posts to promote your app on our forum or comments is not allowed however, and will be treated as spam.
+
+-}
+module IsoHunt.Search( 
+  -- * The main function
+  search,
+  -- * Query
+  Query(..),
+  simpleQuery,
+  Sort(..),
+  Order(..),
+  -- * Response
+  -- $qualifiedimports
+  Response,
+  Item,
+  Image,
+  -- * Exceptions
+  MalformedJSON(..),
+  MalformedResponse(..),
+  ) where
+
+import Data.ByteString.Lazy(ByteString)
+import Control.Monad
+import Control.Applicative
+import           Data.Text(Text)
+import qualified Data.Text
+import qualified Data.Vector as V
+import GHC.Generics
+import Data.Aeson
+import Data.Aeson.TH
+import Data.Typeable
+import Data.Default
+import Text.URI
+import Control.Exception
+import Network.HTTP.Conduit(simpleHttp)
+import IsoHunt.Response
+import IsoHunt.Item
+import IsoHunt.Image
+
+----------------------------- Queries --------------------------
+data Sort
+  = Composite 
+    -- ^ Overall factors such as age, query relevance
+    -- seed/leechers counts and vots
+  | Seeds
+    -- ^ Seeds + leechers
+  | Age
+  | Size
+ deriving(Eq, Ord, Show, Typeable)
+
+data Order
+ = Descending
+ | Ascending
+  deriving(Eq, Ord, Show, Typeable)
+
+-- | See also 'simpleQuery' and 'def' for constructing queries
+data Query 
+ = Query {
+     searchTerm :: !String,
+     start :: !Int, -- ^ start+rows <= 1000
+     rows :: !Int, -- ^ <= 100
+     sort :: !Sort,
+     order :: !Order
+     }
+  deriving(Eq, Ord, Show, Typeable)
+
+instance Default Query where
+  def = Query "" 1 100 Composite Descending
+
+-- | A default query for the given search term
+simpleQuery :: String -> Query
+simpleQuery s = def{searchTerm = s}
+
+{- $qualifiedimports
+The 'Response', 'Item', 'Image' data types all share some field names,
+such as 'title'. To resolve ambiguity in these fields, use qualified 
+imports as follows:
+
+> import qualified IsoHunt.Response as Response
+> import qualified IsoHunt.Item as Item
+> import qualified IsoHunt.Image as Image
+>
+> ... Response.title r ... Item.title i ... Image.title im ...
+
+The fields in these datatypes are mostly undocumented; see
+<http://ca.isohunt.com/js/json.php?ihq=ubuntu&start=1&rows=4> for
+an example response.
+
+-}
+
+
+-- | The response was invalid JSON. The unparsed contents are included.
+data MalformedJSON = MalformedJSON !ByteString
+  deriving(Show, Typeable)
+instance Exception MalformedJSON
+
+-- | The response was valid JSON, but not of the expected
+-- format. Error message and the JSON value are included.
+data MalformedResponse = MalformedResponse !String !Value
+  deriving(Show, Typeable)
+instance Exception MalformedResponse
+
+{- | Search IsoHunt with the given query.
+
+Throws 'MalformedJSON' or 'MalformedResponse' if the result is of an
+expected format.
+-}
+search :: Query -> IO Response
+search q = do
+  resp <- simpleHttp (searchUrl q)
+  case decode resp of
+    Nothing -> throw (MalformedJSON resp)
+    Just json -> 
+      case fromJSON json of
+        Success v -> return v
+        Error msg -> throw (MalformedResponse msg json)
+
+searchUrl :: Query -> String    
+searchUrl q =
+      "http://isohunt.com/js/json.php?" ++
+      pairsToQuery (concat
+        [
+          [("ihq", searchTerm q),
+           ("start", show $ start q),
+           ("rows", show $ rows q),
+           ("order", showOrder $ order q)
+          ],
+          if sort q == Composite 
+          then []
+          else [("sort", showSort $ sort q)]
+        ])
+      where
+        showOrder Ascending = "asc"
+        showOrder Descending = "desc"
+
+        showSort Seeds = "seeds"
+        showSort Age = "age"
+        showSort Size = "size"
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2012, Reiner Pope
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Reiner Pope nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/isohunt.cabal b/isohunt.cabal
new file mode 100644
--- /dev/null
+++ b/isohunt.cabal
@@ -0,0 +1,56 @@
+-- isohunt.cabal auto-generated by cabal init. For additional options,
+-- see
+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.
+Name:                isohunt
+Version:             0.1
+Synopsis:            Bindings to the isoHunt torrent search API
+Description:         
+  Bindings to the isoHunt torrent search API, as described in
+  <http://ca.isohunt.com/forum/viewtopic.php?p=433527#433527>
+  .
+  To get started, see "IsoHunt.Search".
+License:             BSD3
+License-file:        LICENSE
+Author:              Reiner Pope
+Maintainer:          reiner.pope@gmail.com
+-- Copyright:           
+Category:            Web
+Build-type:          Simple
+-- Extra-source-files:  
+Cabal-version:       >=1.6
+homepage:            https://github.com/reinerp/isohunt
+bug-reports:         https://github.com/reinerp/isohunt/issues
+
+source-repository head
+  type:                 git
+  location:             git://github.com/reinerp/isohunt.git
+
+Library
+  Exposed-modules:
+     IsoHunt.Search
+     IsoHunt.Image
+     IsoHunt.Response
+     IsoHunt.Item
+  
+  Build-depends:
+     base >= 4.5 && < 5,
+     aeson < 0.7,
+     text >= 0.7 && <0.12,
+     http-conduit < 1.3,
+     uri < 0.2,
+     data-default < 0.4,
+     ghc-prim,
+     vector >= 0.7.1 && < 0.10,
+     unordered-containers < 0.2,
+     bytestring < 0.10
+
+  ghc-options:
+    -funbox-strict-fields
+
+  extensions:
+    DeriveDataTypeable,
+    OverloadedStrings
+
+  -- Other-modules:       
+  -- Build-tools:         
+  
