diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Alexander Thiemann <mail@thiemann.at> (c) 2020
+
+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 Author name here 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# stripe-servant
+
+Unofficial and incomplete description of the Stripe API using servant types. Contributions are welcome!
+
+For usage, see the `stripe-hs` package.
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/src/Stripe/Api.hs b/src/Stripe/Api.hs
new file mode 100644
--- /dev/null
+++ b/src/Stripe/Api.hs
@@ -0,0 +1,47 @@
+-- | The API
+
+module Stripe.Api where
+
+import Stripe.Resources
+
+import Servant.API
+import qualified Data.Text as T
+
+type StripeAuth = BasicAuth "Stripe API" ()
+
+type StripeApi
+  = "v1" :> StripeApiInternal
+
+type StripeApiInternal
+  = "customers" :> CustomerApi
+  :<|> "products" :> ProductApi
+  :<|> "prices" :> PriceApi
+  :<|> "checkout" :> "sessions" :> CheckoutApi
+  :<|> "billing_portal" :> "sessions" :> CustomerPortalApi
+  :<|> "events" :> EventApi
+
+type CustomerApi
+  = StripeAuth :> ReqBody '[FormUrlEncoded] CustomerCreate :> Post '[JSON] Customer
+  :<|> StripeAuth :> Capture ":customer_id" CustomerId :> Get '[JSON] Customer
+  :<|> StripeAuth :> Capture ":customer_id" CustomerId :> ReqBody '[FormUrlEncoded] CustomerUpdate :> Post '[JSON] Customer
+  :<|> StripeAuth :> QueryParam "starting_after" CustomerId :> Get '[JSON] (StripeList Customer)
+
+type EventApi
+  = StripeAuth :> Capture ":event_id" EventId :> Get '[JSON] Event
+  :<|> StripeAuth :> QueryParam "starting_after" EventId :> Get '[JSON] (StripeList Event)
+
+type ProductApi
+  = StripeAuth :> ReqBody '[FormUrlEncoded] ProductCreate :> Post '[JSON] Product
+  :<|> StripeAuth :> Capture ":product_id" ProductId :> Get '[JSON] Product
+
+type PriceApi
+  = StripeAuth :> ReqBody '[FormUrlEncoded] PriceCreate :> Post '[JSON] Price
+  :<|> StripeAuth :> Capture ":product_id" PriceId :> Get '[JSON] Price
+  :<|> StripeAuth :> QueryParam "lookup_keys[]" T.Text :> Get '[JSON] (StripeList Price)
+
+type CheckoutApi
+  = StripeAuth :> ReqBody '[FormUrlEncoded] CheckoutSessionCreate :> Post '[JSON] CheckoutSession
+  :<|> StripeAuth :> Capture ":session_id" CheckoutSessionId :> Get '[JSON] CheckoutSession
+
+type CustomerPortalApi
+  = StripeAuth :> ReqBody '[FormUrlEncoded] CustomerPortalCreate :> Post '[JSON] CustomerPortal
diff --git a/src/Stripe/Resources.hs b/src/Stripe/Resources.hs
new file mode 100644
--- /dev/null
+++ b/src/Stripe/Resources.hs
@@ -0,0 +1,278 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+module Stripe.Resources
+  ( -- * Core Types
+    TimeStamp(..), StripeList(..)
+    -- * Customers
+  , CustomerId(..), Customer(..), CustomerCreate(..), CustomerUpdate(..)
+    -- * Product catalog
+  , ProductId(..), PriceId(..)
+  , Product(..), ProductCreate(..)
+  , Price(..), PriceRecurring(..), PriceCreate(..), PriceCreateRecurring(..)
+    -- * Subscriptions
+  , SubscriptionId(..)
+    -- * Customer Portal
+  , CustomerPortalId(..), CustomerPortal(..), CustomerPortalCreate(..)
+    -- * Checkout
+  , CheckoutSessionId(..), CheckoutSession(..), CheckoutSessionCreate(..), CheckoutSessionCreateLineItem(..)
+    -- * Events
+  , EventId(..), Event(..), EventData(..)
+  )
+where
+
+import Stripe.Util.Aeson
+
+import Data.Maybe
+import Data.Time
+import Data.Time.Clock.POSIX
+import GHC.Generics
+import Servant.API
+import Text.Casing (quietSnake)
+import Web.FormUrlEncoded
+import qualified Data.Aeson as A
+import qualified Data.HashMap.Strict as HM
+import qualified Data.Text as T
+import qualified Data.Vector as V
+
+formOptions :: Int -> FormOptions
+formOptions x =
+  FormOptions
+  { fieldLabelModifier = quietSnake . drop x }
+
+-- | A 'UTCTime' wrapper that has unix timestamp JSON representation
+newtype TimeStamp
+  = TimeStamp { unTimeStamp :: UTCTime }
+  deriving (Show, Eq)
+
+instance A.ToJSON TimeStamp where
+  toJSON = A.Number . fromRational . toRational . utcTimeToPOSIXSeconds . unTimeStamp
+
+instance A.FromJSON TimeStamp where
+  parseJSON =
+    A.withScientific "unix timestamp" $ \sci ->
+    pure $ TimeStamp $ posixSecondsToUTCTime (fromRational $ toRational sci)
+
+-- | A 'V.Vector' wrapper with an indication is there are more items available through pagination.
+data StripeList a
+  = StripeList
+  { slHasMore :: Bool
+  , slData :: V.Vector a
+  } deriving (Show, Eq)
+
+newtype CustomerId
+  = CustomerId { unCustomerId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+data Customer
+  = Customer
+  { cId :: CustomerId
+  , cLivemode :: Bool
+  , cCreated :: TimeStamp
+  , cName :: Maybe T.Text
+  , cEmail :: Maybe T.Text
+  } deriving (Show, Eq)
+
+data CustomerCreate
+  = CustomerCreate
+  { ccName :: Maybe T.Text
+  , ccEmail :: Maybe T.Text
+  } deriving (Show, Eq, Generic)
+
+data CustomerUpdate
+  = CustomerUpdate
+  { cuName :: Maybe T.Text
+  , cuEmail :: Maybe T.Text
+  } deriving (Show, Eq, Generic)
+
+newtype EventId
+  = EventId { unEventId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+data Event
+  = Event
+  { eId :: EventId
+  , eCreated :: TimeStamp
+  , eLivemode :: Bool
+  , eType :: T.Text
+  , eApiVersion :: T.Text
+  , eData :: EventData
+  } deriving (Show, Eq)
+
+data EventData
+  = EventData
+  { edObject :: A.Value
+  } deriving (Show, Eq)
+
+newtype PriceId
+  = PriceId { unPriceId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+data Price
+  = Price
+  { pId :: PriceId
+  , pActive :: Bool
+  , pCurrency :: T.Text
+  , pNickname :: Maybe T.Text
+  , pType :: T.Text -- TODO: make enum
+  , pRecurring :: Maybe PriceRecurring
+  , pUnitAmount :: Maybe Int
+  , pProduct :: ProductId
+  , pLookupKey :: Maybe T.Text
+  } deriving (Show, Eq)
+
+data PriceRecurring
+  = PriceRecurring
+  { prInterval :: T.Text -- TODO: make enum
+  , prIntervalCount :: Int
+  } deriving (Show, Eq)
+
+data PriceCreate
+  = PriceCreate
+  { pcCurrency :: T.Text
+  , pcUnitAmount :: Maybe Int
+  , pcProduct :: ProductId
+  , pcLookupKey :: Maybe T.Text
+  , pcTransferLookupKey :: Bool
+  , pcRecurring :: Maybe PriceCreateRecurring
+  } deriving (Show, Eq, Generic)
+
+data PriceCreateRecurring
+  = PriceCreateRecurring
+  { prcInterval :: T.Text -- TODO: make enum
+  , prcIntervalCount :: Maybe Int
+  } deriving (Show, Eq)
+
+newtype ProductId
+  = ProductId { unProductId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+data Product
+  = Product
+  { prId :: ProductId
+  , prActive :: Bool
+  , prName :: T.Text
+  , prDescription :: Maybe T.Text
+  } deriving (Show, Eq)
+
+data ProductCreate
+  = ProductCreate
+  { prcName :: T.Text
+  , prcDescription :: Maybe T.Text
+  } deriving (Show, Eq, Generic)
+
+newtype SubscriptionId
+  = SubscriptionId { unSubscriptionId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+newtype CheckoutSessionId
+  = CheckoutSessionId { unCheckoutSessionId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+data CheckoutSession
+  = CheckoutSession
+  { csId :: CheckoutSessionId
+  , csLivemode :: Bool
+  , csClientReferenceId :: Maybe T.Text
+  , csCancelUrl :: T.Text
+  , csSuccessUrl :: T.Text
+  , csPaymentMethodTypes :: V.Vector T.Text  -- TODO: make enum
+  , csSubscription :: Maybe SubscriptionId
+  } deriving (Show, Eq)
+
+data CheckoutSessionCreate
+  = CheckoutSessionCreate
+  { cscCancelUrl :: T.Text
+  , cscMode :: T.Text  -- TODO: make enum
+  , cscPaymentMethodTypes :: [T.Text]  -- TODO: make enum
+  , cscSuccessUrl :: T.Text
+  , cscClientReferenceId :: Maybe T.Text
+  , cscCustomer :: Maybe CustomerId
+  , cscLineItems :: [CheckoutSessionCreateLineItem]
+  } deriving (Show, Eq, Generic)
+
+data CheckoutSessionCreateLineItem
+  = CheckoutSessionCreateLineItem
+  { cscliPrice :: PriceId
+  , cscliQuantity :: Integer
+  } deriving (Show, Eq, Generic)
+
+newtype CustomerPortalId
+  = CustomerPortalId { unCustomerPortalId :: T.Text }
+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData)
+
+data CustomerPortal
+  = CustomerPortal
+  { cpId :: CustomerPortalId
+  , cpLivemode :: Bool
+  , cpCreated :: TimeStamp
+  , cpCustomer :: CustomerId
+  , cpReturnUrl :: Maybe T.Text
+  , cpUrl :: T.Text
+  } deriving (Show, Eq)
+
+data CustomerPortalCreate
+  = CustomerPortalCreate
+  { cpcCustomer :: CustomerId
+  , cpcReturnUrl :: Maybe T.Text
+  } deriving (Show, Eq, Generic)
+
+$(deriveJSON (jsonOpts 2) ''StripeList)
+$(deriveJSON (jsonOpts 1) ''Customer)
+$(deriveJSON (jsonOpts 1) ''Event)
+$(deriveJSON (jsonOpts 2) ''EventData)
+$(deriveJSON (jsonOpts 2) ''CheckoutSession)
+$(deriveJSON (jsonOpts 1) ''Price)
+$(deriveJSON (jsonOpts 2) ''PriceRecurring)
+$(deriveJSON (jsonOpts 2) ''Product)
+$(deriveJSON (jsonOpts 2) ''CustomerPortal)
+
+instance ToForm CustomerCreate where
+  toForm = genericToForm (formOptions 2)
+
+instance ToForm CustomerUpdate where
+  toForm = genericToForm (formOptions 2)
+
+instance ToForm CustomerPortalCreate where
+  toForm = genericToForm (formOptions 3)
+
+instance ToForm ProductCreate where
+  toForm = genericToForm (formOptions 3)
+
+instance ToForm PriceCreate where
+  toForm pc =
+    let recurringPiece =
+          case pcRecurring pc of
+            Nothing -> []
+            Just x ->
+              [ ("recurring[interval]", [prcInterval x])
+              , ("recurring[interval_count]", maybeToList $ fmap toUrlPiece $ prcIntervalCount x)
+              ]
+    in Form $ HM.fromList $
+       [ ("currency", [pcCurrency pc])
+       , ("product", [toUrlPiece $ pcProduct pc])
+       , ("unit_amount", maybeToList $ fmap toUrlPiece $ pcUnitAmount pc)
+       , ("lookup_key", maybeToList $ pcLookupKey pc)
+       , ("transfer_lookup_key", [toUrlPiece $ pcTransferLookupKey pc])
+       ] <> recurringPiece
+
+instance ToForm CheckoutSessionCreate where
+  toForm csc =
+    let convertItem (idx, itm) =
+          [ ("line_items[" <> toUrlPiece idx <> "][price]", [toUrlPiece $ cscliPrice itm])
+          , ("line_items[" <> toUrlPiece idx <> "][quantity]", [toUrlPiece $ cscliQuantity itm])
+          ]
+        lineItems =
+          concatMap convertItem (zip ([0..] :: [Int]) (cscLineItems csc))
+        convertPmt (idx, pm) =
+          ( "payment_method_types[" <> toUrlPiece idx <> "]"
+          , [pm]
+          )
+        pmt =
+          map convertPmt (zip ([0..] :: [Int]) (cscPaymentMethodTypes csc))
+    in Form $ HM.fromList $
+       [ ("cancel_url", [cscCancelUrl csc])
+       , ("success_url", [cscSuccessUrl csc])
+       , ("mode", [cscMode csc])
+       , ("client_reference_id", maybeToList $ cscClientReferenceId csc)
+       , ("customer", maybeToList $ fmap toUrlPiece $ cscCustomer csc)
+       ] <> lineItems <> pmt
diff --git a/src/Stripe/Util/Aeson.hs b/src/Stripe/Util/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/Stripe/Util/Aeson.hs
@@ -0,0 +1,16 @@
+-- |
+
+module Stripe.Util.Aeson
+  ( jsonOpts, deriveJSON, ToJSON, FromJSON )
+where
+
+import Data.Aeson
+import Data.Aeson.TH
+import Text.Casing (quietSnake)
+
+jsonOpts :: Int -> Options
+jsonOpts x =
+  defaultOptions
+  { fieldLabelModifier = quietSnake . drop x
+  , constructorTagModifier = quietSnake
+  }
diff --git a/stripe-servant.cabal b/stripe-servant.cabal
new file mode 100644
--- /dev/null
+++ b/stripe-servant.cabal
@@ -0,0 +1,49 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 44759c17c1b913f08638e1ae86cf9a71e9a090db0772cb4d9da196ae9447d461
+
+name:           stripe-servant
+version:        0.1.0.0
+synopsis:       Unofficial Stripe servant types
+description:    Unofficial description of the Stripe API using servant types
+category:       Web
+homepage:       https://github.com/agrafix/stripe-hs#readme
+bug-reports:    https://github.com/agrafix/stripe-hs/issues
+author:         Alexander Thiemann <mail@thiemann.at>
+maintainer:     Alexander Thiemann <mail@thiemann.at>
+copyright:      2020 Alexander Thiemann <mail@thiemann.at>
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/agrafix/stripe-hs
+
+library
+  exposed-modules:
+      Stripe.Api
+      Stripe.Resources
+      Stripe.Util.Aeson
+  other-modules:
+      Paths_stripe_servant
+  hs-source-dirs:
+      src
+  default-extensions: OverloadedStrings DataKinds TypeOperators TypeFamilies GADTs FlexibleInstances FlexibleContexts MultiParamTypeClasses StrictData ScopedTypeVariables DeriveGeneric DeriveFunctor
+  build-depends:
+      aeson
+    , base >=4.7 && <5
+    , casing
+    , http-api-data
+    , servant
+    , text
+    , time
+    , unordered-containers
+    , vector
+  default-language: Haskell2010
