diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+Copyright (C) 2015 Fergus Noble
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+RAML
+====
+
+RESTful API Modeling Language (RAML) library for Haskell
+
+TODO
+----
+
+Curently the `raml` library doesn't implement the full RAML specification. The
+following features still need to be implemented:
+
+ - [ ] Reading and verifying RAML header line `#%RAML 0.8`
+ - [ ] `!include` links (markdown for docs, yaml anywhere)
+ - [ ] Resource Types
+ - [ ] Traits
+ - [ ] Security
+ - [ ] Top level schemas
+
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/raml.cabal b/raml.cabal
new file mode 100644
--- /dev/null
+++ b/raml.cabal
@@ -0,0 +1,36 @@
+name:                raml
+version:             0.1.0
+synopsis:            RESTful API Modeling Language (RAML) library for Haskell
+homepage:            https://github.com/fnoble/raml
+license:             MIT
+license-file:        LICENSE
+author:              Fergus Noble
+maintainer:          Fergus Noble <fergusnoble@gmail.com>
+copyright:           Copyright (C) 2015 Fergus Noble
+category:            Web
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >= 1.10
+
+description:
+  RESTful API Modeling Language (RAML) library for Haskell
+
+source-repository head
+  type:              git
+  location:          git@github.com:fnoble/raml.git
+
+library
+  default-language:    Haskell2010
+  hs-source-dirs:      src
+  ghc-options:         -Wall
+  exposed-modules:
+      Web.Raml
+    , Web.Raml.Types
+  build-depends:
+      base == 4.*
+    , bytestring >= 0.10 && < 0.11
+    , text >= 1.2 && < 1.3
+    , aeson >= 0.9 && < 1.0
+    , yaml >= 0.8 && < 0.9
+    , unordered-containers >= 0.2 && < 0.3
+
diff --git a/src/Web/Raml.hs b/src/Web/Raml.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Raml.hs
@@ -0,0 +1,147 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Web.Raml
+  (
+  decode
+  ) where
+
+import Control.Applicative
+import Data.Aeson hiding (decode)
+import Data.Aeson.Types (Parser)
+import Data.Yaml (decodeEither)
+import Data.List (nub)
+import qualified Data.ByteString as BS
+import qualified Data.Text as T
+import qualified Data.HashMap.Strict as H
+import Data.Traversable (mapM)
+
+import Prelude hiding (mapM)
+
+import Web.Raml.Types
+
+toParameterType :: H.HashMap T.Text Value -> Maybe T.Text
+                -> Parser RamlParameterType
+toParameterType v Nothing = toParameterType v (Just "string")
+toParameterType v (Just "string") = RamlParameterString
+  <$> v .:? "enum"
+  <*> v .:? "pattern"
+  <*> v .:? "maxLength"
+  <*> v .:? "minLength"
+  <*> v .:? "example"
+  <*> v .:? "default"
+toParameterType v (Just "integer") = RamlParameterInteger
+  <$> v .:? "maximum"
+  <*> v .:? "minimum"
+  <*> v .:? "example"
+  <*> v .:? "default"
+toParameterType v (Just "number") = RamlParameterNumber
+  <$> v .:? "maximum"
+  <*> v .:? "minimum"
+  <*> v .:? "example"
+  <*> v .:? "default"
+toParameterType v (Just "date") = RamlParameterDate
+  <$> v .:? "example"
+  <*> v .:? "default"
+toParameterType v (Just "boolean") = RamlParameterBoolean
+  <$> v .:? "example"
+  <*> v .:? "default"
+toParameterType v (Just "file") = RamlParameterFile
+  <$> v .:? "example"
+  <*> v .:? "default"
+toParameterType _ (Just s) = fail $ "Not a valid parameter type: " ++ show s
+
+instance FromJSON RamlParameter where
+    parseJSON (Object v) = do
+      t <- v .:? "type"
+      RamlParameter
+        <$> v .:? "displayName"
+        <*> v .:? "description"
+        <*> v .:? "repeat"
+        <*> v .:? "required"
+        <*> (toParameterType v t)
+    parseJSON m = fail $ "Not a valid body: " ++ show m
+
+instance FromJSON RamlTrait where
+    parseJSON _ = fail "Traits not yet supported"
+
+instance FromJSON RamlSecurityScheme where
+    parseJSON _ = fail "Security schemes not yet supported"
+
+instance FromJSON RamlBody where
+    parseJSON (Object v) = RamlBody
+        <$> v .:? "schema"
+        <*> v .:? "example"
+        <*> v .:? "formParameters"
+    parseJSON Null = pure $ RamlBody Nothing Nothing Nothing
+    parseJSON m = fail $ "Not a valid body: " ++ show m
+
+instance FromJSON RamlResponse where
+    parseJSON (Object v) = RamlResponse
+        <$> v .:? "body"
+        <*> v .:? "description"
+        <*> v .:? "headers"
+    parseJSON m = fail $ "Not a valid response: " ++ show m
+
+instance FromJSON RamlMethod where
+    parseJSON (Object v) = RamlMethod
+        <$> v .:? "description"
+        <*> v .:? "headers"
+        <*> nub' (v .:? "protocols")
+        <*> v .:? "queryParameters"
+        <*> v .:? "body"
+        <*> v .:? "responses"
+        <*> v .:? "is"
+      where nub' = (fmap . fmap) nub
+    parseJSON m = fail $ "Not a valid method: " ++ show m
+
+instance FromJSON RamlResource where
+    parseJSON (Object v) = RamlResource
+        <$> v .:? "displayName"
+        <*> v .:? "description"
+        <*> v .:? "uriParameters"
+        <*> v .:? "baseUriParameters"
+        -- TODO: Make methods case insensitive
+        <*> v .:? "get"
+        <*> v .:? "post"
+        <*> v .:? "put"
+        <*> v .:? "delete"
+        <*> toResource v
+        <*> v .:? "is"
+    parseJSON m = fail $ "Not a valid resource: " ++ show m
+
+instance FromJSON RamlProtocol where
+    parseJSON (String "HTTP") = pure HTTP
+    parseJSON (String "HTTPS") = pure HTTPS
+    parseJSON m = fail $
+      "Protocol must be HTTP or HTTPS, got: " ++ show m
+
+instance FromJSON RamlDocumentation where
+    parseJSON (Object v) = RamlDocumentation
+        <$> v .: "title"
+        <*> v .: "content"
+    parseJSON m = fail $ "Not a valid documentation entry: " ++ show m
+
+toResource :: H.HashMap T.Text Value -> Parser (H.HashMap T.Text RamlResource)
+toResource m = mapM (parseJSON :: Value -> Parser RamlResource) $
+  H.filterWithKey (\k _ -> T.head k == '/') m
+
+instance FromJSON Raml where
+    parseJSON (Object v) = Raml
+        <$> v .: "title"
+        <*> v .:? "version"
+        <*> v .:? "baseUri"
+        <*> v .:? "baseUriParameters"
+        <*> nub' (v .:? "protocols")
+        <*> v .:? "mediaType"
+        <*> v .:? "documentation"
+        <*> toResource v
+      where nub' = (fmap . fmap) nub
+    parseJSON m = fail $ "Not a valid RAML file: " ++ show m
+
+-- | Deserialize a RAML document from a 'BS.ByteString'.
+-- If this fails due to incomplete or invalid input, 'Left e' is
+-- returned, containing an error message.
+decode :: BS.ByteString -> Either Error Raml
+decode = decodeEither
+
diff --git a/src/Web/Raml/Types.hs b/src/Web/Raml/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Raml/Types.hs
@@ -0,0 +1,124 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Web.Raml.Types where
+
+import Data.Text (Text)
+import qualified Data.HashMap.Strict as H
+
+type Error = String
+
+type Uri = Text
+type Path = Text
+
+data RamlProtocol = HTTP | HTTPS
+  deriving (Show, Eq, Ord)
+
+data RamlParameterType =
+   RamlParameterString
+    { paramEnum :: Maybe [Text]
+    , paramPattern :: Maybe Text
+    , paramMaxLength :: Maybe Int
+    , paramMinLength :: Maybe Int
+    , paramExampleS :: Maybe Text
+    , paramDefaultS :: Maybe Text }
+  | RamlParameterInteger
+    { paramMaximumI :: Maybe Int
+    , paramMinimumI :: Maybe Int
+    , paramExampleI :: Maybe Int
+    , paramDefaultI :: Maybe Int }
+  | RamlParameterNumber
+    { paramMaximumN :: Maybe Double
+    , paramMinimumN :: Maybe Double
+    , paramExampleN :: Maybe Double
+    , paramDefaultN :: Maybe Double }
+  | RamlParameterDate
+    -- TODO: Implement using date type
+    { paramExampleD :: Maybe Text
+    , paramDefaultD :: Maybe Text }
+  | RamlParameterBoolean
+    { paramExampleB :: Maybe Bool
+    , paramDefaultB :: Maybe Bool }
+  | RamlParameterFile
+    -- TODO: Do example and default make sense for files?
+    -- Ask RAML org?
+    { paramExample :: Maybe Text
+    , paramDefault :: Maybe Text
+  } deriving (Show, Eq)
+
+data RamlParameter = RamlParameter
+  { paramDisplayName :: Maybe Text
+  , paramDescription :: Maybe Text
+  , paramRepeat :: Maybe Bool
+  , paramRequired :: Maybe Bool
+  , paramType :: RamlParameterType
+  } deriving (Show, Eq)
+
+data RamlTrait = RamlTrait
+  deriving (Show, Eq)
+
+data RamlSecurityScheme = RamlSecurityScheme
+  deriving (Show, Eq)
+
+data RamlBody = RamlBody
+  { bodySchema :: Maybe Text
+  , bodyExample :: Maybe Text
+  , bodyFormParameters :: Maybe (H.HashMap Text RamlParameter)
+  } deriving (Show, Eq)
+
+data RamlResponse = RamlResponse
+  { respBody :: Maybe (H.HashMap Text RamlBody)
+  , respDescription :: Maybe Text
+  , respHeaders :: Maybe (H.HashMap Text RamlParameter)
+  } deriving (Show, Eq)
+
+data RamlMethod = RamlMethod
+  { methDescription :: Maybe Text
+  , methHeaders :: Maybe (H.HashMap Text RamlParameter)
+  , methProtocols :: Maybe [RamlProtocol]
+  , methQueryParameters :: Maybe (H.HashMap Text RamlParameter)
+  , methBody :: Maybe (H.HashMap Text RamlBody)
+  , methResponses :: Maybe (H.HashMap Text RamlResponse)
+  , methIs :: Maybe [RamlTrait]
+  --, methSecuredBy :: Maybe [RamlSecurityScheme]
+  } deriving (Show, Eq)
+
+data RamlResource = RamlResource
+  { resDisplayName :: Maybe Text
+  , resDescription :: Maybe Text
+  , resUriParameters :: Maybe (H.HashMap Text RamlParameter)
+  , resBaseUriParameters :: Maybe (H.HashMap Text RamlParameter)
+  -- TODO: Generalize or support all HTTP std. methods?
+  , resPost :: Maybe RamlMethod
+  , resGet :: Maybe RamlMethod
+  , resPut :: Maybe RamlMethod
+  , resDelete :: Maybe RamlMethod
+  , resResources :: H.HashMap Path RamlResource
+  , resIs :: Maybe [RamlTrait]
+  } deriving (Show, Eq)
+
+data RamlDocumentation = RamlDocumentation
+  { docTitle :: Text
+  , docContent :: Text
+  } deriving (Show, Eq, Ord)
+
+data Raml = Raml
+  { ramlTitle :: Text
+  , ramlVersion :: Maybe Text
+  -- TODO: Type for parameterized paths?
+  , ramlBaseUri :: Maybe Uri
+  -- TODO: Type for parameterized paths?
+  , baseUriParameters :: Maybe (H.HashMap Text RamlParameter)
+  -- TODO: Remember to parse from baseUri if present
+  , ramlProtocols :: Maybe [RamlProtocol]
+  -- TODO: Check against valid types in spec, better type than plain Text?
+  , ramlMediaType :: Maybe Text
+  , ramlDocumentation :: Maybe [RamlDocumentation]
+  , ramlResources :: H.HashMap Path RamlResource
+  --, ramlSchemas :: Maybe (H.HashMap Text Text)
+  --, ramlResourceTypes :: Maybe [RamlResourceType]
+  --, ramlTraits :: Maybe (H.HashMap Text RamlTrait)
+  --, ramlSecuritySchemes :: Maybe (H.HashMap Text RamlSecurityScheme)
+  } deriving (Show, Eq)
+
+
