diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Ricky Elrod
+
+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 Ricky Elrod 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/copr.cabal b/copr.cabal
new file mode 100644
--- /dev/null
+++ b/copr.cabal
@@ -0,0 +1,46 @@
+name:                copr
+version:             1.0.0
+synopsis:            Haskell interface to the Fedora Copr system
+description:
+  This provides a Haskell interface to the Fedora Copr public build system.
+homepage:            https://github.com/CodeBlock/copr-hs
+license:             BSD3
+license-file:        LICENSE
+author:              Ricky Elrod
+maintainer:          ricky@elrod.me
+copyright:           2014 Ricky Elrod
+category:            Fedora
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Fedora.Copr
+                     , Fedora.Copr.CoprBuild
+                     , Fedora.Copr.CoprProject
+                     , Fedora.Copr.CoprStatus
+                     , Fedora.Copr.ListCoprs
+
+  build-depends:       aeson >= 0.6
+                     , base >=4.6 && <4.7
+                     , bytestring >= 0.10
+                     , containers >= 0.5
+                     , http-streams >= 0.7
+                     , io-streams >= 1.1
+                     , semigroups >= 0.12
+                     , text >= 0.11
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+test-suite hlint
+  hs-source-dirs:      tests
+  main-is:             hlint.hs
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends:       base
+                     , hlint >= 1.7
+
+source-repository head
+  type:     git
+  location: git://github.com/CodeBlock/copr-hs.git
diff --git a/src/Fedora/Copr.hs b/src/Fedora/Copr.hs
new file mode 100644
--- /dev/null
+++ b/src/Fedora/Copr.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module : Fedora.Copr
+-- Copyright : (c) Ricky Elrod 2014
+-- License : BSD3
+-- Maintainer : ricky@elrod.me
+-- Stability : experimental
+-- Portability : portable
+--
+-- Provides a Haskell interface to the Fedora
+-- <http://copr.fedoraproject.org/ Copr> build system
+-- <http://copr.fedoraproject.org/api API>.
+
+module Fedora.Copr
+    (
+      module C
+    , CoprConfig (..)
+    , Username
+    , defaultConfig
+    , withConfig
+    , addBuild
+    , buildStatus
+    , coprs
+    , new
+    ) where
+
+import Fedora.Copr.CoprBuild   as C (CoprBuild (), CoprBuildResponse ())
+import Fedora.Copr.CoprProject as C (CoprProject (), NewCoprResponse ())
+import Fedora.Copr.CoprStatus  as C (CoprStatusResponse ())
+import Fedora.Copr.ListCoprs   as C (Coprs ())
+
+import Data.Aeson
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Char8 as C8
+import qualified Data.ByteString.Lazy as LS
+import Data.Monoid (mappend)
+import Network.Http.Client
+import qualified System.IO.Streams.ByteString as SBS
+
+data CoprConfig = CoprConfig {
+    domain :: Hostname     -- ^ The domain on which Copr is hosted.
+  , port   :: Port         -- ^ The port on which Copr operates.
+  , login  :: S.ByteString -- ^ The API login (/not/ the same as username).
+  , token  :: S.ByteString -- ^ The API token.
+} deriving (Eq, Show)
+
+type Username = S.ByteString
+type ProjectName = S.ByteString
+
+defaultConfig :: CoprConfig
+defaultConfig = CoprConfig {
+    domain = "copr.fedoraproject.org"
+  , port = 80
+  , login = ""
+  , token = ""
+}
+
+-- | A utility wrapper for calling API methods with a 'CoprConfig'.
+--
+--   You can use this to do things like:
+--
+--   >>> let c = defaultConfig { login = "your_login", token = "your_token" }
+--   >>> withConfig c $ coprs "codeblock"
+withConfig :: CoprConfig -> (CoprConfig -> IO a) -> IO a
+withConfig = flip id
+
+-- | Sets things that are common to all requests that we make.
+prepareRequest :: CoprConfig -> Method -> S.ByteString -> RequestBuilder ()
+prepareRequest c m url = do
+  http m url
+  setAccept "application/json"
+  setContentType "application/json"
+  setAuthorizationBasic (login c) (token c)
+
+finishRequest :: FromJSON a => Connection -> IO a
+finishRequest cnx = do
+  x <- receiveResponse cnx jsonHandler
+  closeConnection cnx
+  return x
+
+-- | Perform a GET request to the API, with authentication.
+--
+--   Requests that don't need authentication result in the authentication
+--   details being ignored, so we don't have to worry about not sending them
+--   in that case.
+apiGet :: FromJSON a => S.ByteString -> CoprConfig -> IO a
+apiGet url c = do
+  cnx <- openConnection (domain c) (port c)
+  q <- buildRequest $ prepareRequest c GET url
+  sendRequest cnx q emptyBody
+  finishRequest cnx
+
+-- | Perform a POST request to the API, with authentication.
+apiPost :: (ToJSON a, FromJSON b) => S.ByteString -> a -> CoprConfig -> IO b
+apiPost url d c = do
+  cnx <- openConnection (domain c) (port c)
+  q <- buildRequest $ do
+    prepareRequest c POST url
+    setContentLength $ LS.length (encode d)
+  body <- SBS.fromLazyByteString (encode d)
+  sendRequest cnx q (inputStreamBody body)
+  finishRequest cnx
+
+-- | Retrieve a list of copr projects for an individual user.
+--
+--   This makes use of the @\/api\/coprs/[username]\/@ endpoint.
+--
+--   > withConfig c $ coprs "codeblock"
+coprs :: Username   -- ^ The username of the person whose projects we want to list.
+      -> CoprConfig -- ^ The configuration to use.
+      -> IO Coprs
+coprs u = apiGet ("/api/coprs/" `mappend` u `mappend` "/")
+
+-- | Create a new copr project.
+--
+--   This makes use of the @\/api\/coprs/[username]\/new\/@ endpoint.
+--
+--   > withConfig c $ new "codeblock" (CoprProject "testproject" [] [] (NEL.fromList ["fedora-20-x86_64"]))
+new :: Username       -- ^ The username of the person whose project should be created.
+    -> CoprProject    -- ^ The copr project to be created.
+    -> CoprConfig     -- ^ The configuration to use.
+    -> IO NewCoprResponse
+new u = apiPost ("/api/coprs/" `mappend` u `mappend` "/new/")
+
+-- | Add a build to a copr project.
+--
+--   This makes use of the @\/api\/coprs/[username]\/[project]\/new_build\/@ endpoint.
+--
+--   > withConfig c $ addBuild "codeblock" "testproject" (CoprBuild (NEL.fromList ["http://example.com/foo-1.0.0.src.rpm"]) 2048 3600)
+addBuild :: Username    -- ^ The username of the person who owns the copr project.
+         -> ProjectName -- ^ The project to add the build to.
+         -> CoprBuild   -- ^ A representation of the build to add.
+         -> CoprConfig  -- ^ The configuration to use.
+         -> IO CoprBuildResponse
+addBuild u p = apiPost ("/api/coprs/" `mappend` u `mappend` "/" `mappend` p `mappend` "/new_build/")
+
+-- | Check the status of a copr build
+--
+--   This makes use of the @\/api\/coprs\/build_status/[build_id]\/@ endpoint.
+--
+--   > withConfig c $ buildStatus 1033
+buildStatus :: Int        -- ^ The build ID number to check.
+            -> CoprConfig -- ^ The configuration to use.
+            -> IO CoprStatusResponse
+buildStatus i = apiGet ("/api/coprs/build_status/" `mappend` C8.pack (show i) `mappend` "/")
diff --git a/src/Fedora/Copr/CoprBuild.hs b/src/Fedora/Copr/CoprBuild.hs
new file mode 100644
--- /dev/null
+++ b/src/Fedora/Copr/CoprBuild.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Fedora.Copr.CoprBuild (
+    CoprBuild (..)
+  , CoprBuildResponse (..)) where
+
+import Control.Applicative
+import Control.Monad (mzero)
+import Data.Aeson
+import qualified Data.ByteString as S
+import qualified Data.List.NonEmpty as NEL
+
+data CoprBuildResponse = CoprBuildResponse {
+    output  :: String
+  , buildId :: Maybe Int
+  , message :: Maybe String
+  , error   :: Maybe String
+} deriving (Eq, Show)
+
+instance FromJSON CoprBuildResponse where
+  parseJSON (Object v) = CoprBuildResponse <$>
+                             v .:  "output"
+                         <*> v .:? "id"
+                         <*> v .:? "message"
+                         <*> v .:? "error"
+  parseJSON _          = mzero
+
+data CoprBuild = CoprBuild {
+    packages :: NEL.NonEmpty S.ByteString
+  , memory   :: Int
+  , timeout  :: Int
+} deriving (Eq, Show)
+
+instance ToJSON CoprBuild where
+  toJSON (CoprBuild p m t) = object [ "pkgs" .= S.intercalate " " (NEL.toList p)
+                                    , "memory" .= m
+                                    , "timeout" .= t
+                                    ]
diff --git a/src/Fedora/Copr/CoprProject.hs b/src/Fedora/Copr/CoprProject.hs
new file mode 100644
--- /dev/null
+++ b/src/Fedora/Copr/CoprProject.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Fedora.Copr.CoprProject (
+    CoprProject (..)
+  , NewCoprResponse (..)) where
+
+import Control.Applicative
+import Control.Monad (mzero)
+import Data.Aeson
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Char8 as C8
+import qualified Data.List.NonEmpty as NEL
+import qualified Data.Text as T
+
+data NewCoprResponse = NewCoprResponse {
+    output  :: String
+  , message :: Maybe String
+  , error   :: Maybe String
+} deriving (Eq, Show)
+
+instance FromJSON NewCoprResponse where
+  parseJSON (Object v) = NewCoprResponse <$>
+                             v .:  "output"
+                         <*> v .:? "message"
+                         <*> v .:? "error"
+  parseJSON _          = mzero
+
+data CoprProject = CoprProject {
+    name            :: S.ByteString
+  , repos           :: [S.ByteString]
+  , initialPackages :: [S.ByteString]
+  , chroots         :: NEL.NonEmpty S.ByteString
+  , description     :: Maybe S.ByteString
+  , instructions    :: Maybe S.ByteString
+} deriving (Eq, Show)
+
+instance ToJSON CoprProject where
+  toJSON (CoprProject n r p c d i) = object $ [ "name" .= n
+                                          , "repos" .= S.intercalate " " r
+                                          , "initial_pkgs" .= S.intercalate " " p
+                                          , "description" .= d
+                                          , "instructions" .= i
+                                          ] ++ map (\x -> T.pack (C8.unpack x) .= C8.pack "y") (NEL.toList c)
diff --git a/src/Fedora/Copr/CoprStatus.hs b/src/Fedora/Copr/CoprStatus.hs
new file mode 100644
--- /dev/null
+++ b/src/Fedora/Copr/CoprStatus.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Fedora.Copr.CoprStatus (
+    CoprStatusResponse (..)) where
+
+import Control.Applicative
+import Control.Monad (mzero)
+import Data.Aeson
+
+data CoprStatusResponse = CoprStatusResponse {
+    output :: String
+  , status :: Maybe String
+  , error  :: Maybe String
+} deriving (Eq, Show)
+
+instance FromJSON CoprStatusResponse where
+  parseJSON (Object v) = CoprStatusResponse <$>
+                             v .:  "output"
+                         <*> v .:? "status"
+                         <*> v .:? "error"
+  parseJSON _          = mzero
diff --git a/src/Fedora/Copr/ListCoprs.hs b/src/Fedora/Copr/ListCoprs.hs
new file mode 100644
--- /dev/null
+++ b/src/Fedora/Copr/ListCoprs.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Fedora.Copr.ListCoprs (
+    Coprs (..)
+  , Repo (..)) where
+
+import Control.Applicative
+import Control.Monad (mzero)
+import Data.Aeson
+import qualified Data.ByteString as S
+import Data.Map (Map)
+
+data Coprs = Coprs {
+    output :: String
+  , repos  :: Maybe [Repo]
+  , error  :: Maybe String
+} deriving (Eq, Show)
+
+data Repo = Repo {
+    yumRepos        :: Map S.ByteString S.ByteString
+  , additionalRepos :: S.ByteString
+  , instructions    :: S.ByteString
+  , name            :: S.ByteString
+  , description     :: S.ByteString
+} deriving (Eq, Show)
+
+instance FromJSON Repo where
+  parseJSON (Object v) = Repo <$>
+                             v .: "yum_repos"
+                         <*> v .: "additional_repos"
+                         <*> v .: "instructions"
+                         <*> v .: "name"
+                         <*> v .: "description"
+  parseJSON _          = mzero
+
+instance FromJSON Coprs where
+  parseJSON (Object v) = Coprs <$>
+                             v .: "output"
+                         <*> v .:? "repos"
+                         <*> v .:? "error"
+  parseJSON _          = mzero
diff --git a/tests/hlint.hs b/tests/hlint.hs
new file mode 100644
--- /dev/null
+++ b/tests/hlint.hs
@@ -0,0 +1,12 @@
+module Main where
+
+import Control.Monad
+import Language.Haskell.HLint
+import System.Environment
+import System.Exit
+
+main :: IO ()
+main = do
+  args <- getArgs
+  hints <- hlint $ ["src", "--cpp-define=HLINT", "--cpp-ansi"] ++ args
+  unless (null hints) exitFailure
