diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,31 @@
+0.10
+------
+
+### Breaking changes
+
+* Use `NT` from `natural-transformation` for `Enter`
+  ([#616](https://github.com/haskell-servant/servant/issues/616))
+
+* Change to `MkLink (Verb ...) = Link` (previously `URI`). To consume `Link`
+  use its `ToHttpApiData` instance or `linkURI`.
+  ([#527](https://github.com/haskell-servant/servant/issues/527))
+
+### Other changes
+
+* Add `Servant.API.TypeLevel` module with type families to work with API types.
+  ([#345](https://github.com/haskell-servant/servant/pull/345)
+  , [#305](https://github.com/haskell-servant/servant/issues/305))
+
+* Default JSON content type change to `application/json;charset=utf-8`.
+  ([#263](https://github.com/haskell-servant/servant/issues/263))
+  Related browser bugs:
+  [Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=438464) and
+  [Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=918742)
+
+* `Accept` class may accept multiple content-types. `MimeUnrender` adopted as well.
+  ([#613](https://github.com/haskell-servant/servant/pull/614)
+  , [#615](https://github.com/haskell-servant/servant/pull/615))
+
 0.9.1
 ------
 
@@ -14,6 +42,8 @@
 ----
 
 * Add `CaptureAll` combinator. Captures all of the remaining segments in a URL.
+* Add `Servant.API.TypeLevel` module, with frequently used type-level
+functionaliy.
 
 0.8
 ---
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import           Distribution.Simple
-main = defaultMain
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,165 @@
+\begin{code}
+{-# LANGUAGE CPP #-}
+#ifndef MIN_VERSION_Cabal
+#define MIN_VERSION_Cabal(x,y,z) 0
+#endif
+#ifndef MIN_VERSION_directory
+#define MIN_VERSION_directory(x,y,z) 0
+#endif
+#if MIN_VERSION_Cabal(1,24,0)
+#define InstalledPackageId UnitId
+#endif
+module Main (main) where
+
+import Control.Monad ( when )
+import Data.List ( nub )
+import Distribution.Package ( InstalledPackageId )
+import Distribution.Package ( PackageId, Package (..), packageVersion )
+import Distribution.PackageDescription ( PackageDescription(), TestSuite(..) , Library (..), BuildInfo (..))
+import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )
+import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose )
+import Distribution.Simple.BuildPaths ( autogenModulesDir )
+import Distribution.Simple.Setup ( BuildFlags(buildDistPref, buildVerbosity), fromFlag)
+import Distribution.Simple.LocalBuildInfo ( withPackageDB, withLibLBI, withTestLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps), compiler )
+import Distribution.Simple.Compiler ( showCompilerId , PackageDB (..))
+import Distribution.Text ( display , simpleParse )
+import System.FilePath ( (</>) )
+
+#if MIN_VERSION_Cabal(1,25,0)
+import Distribution.Simple.BuildPaths ( autogenComponentModulesDir )
+#endif
+
+#if MIN_VERSION_directory(1,2,2)
+import System.Directory (makeAbsolute)
+#else
+import System.Directory (getCurrentDirectory)
+import System.FilePath (isAbsolute)
+
+makeAbsolute :: FilePath -> IO FilePath
+makeAbsolute p | isAbsolute p = return p
+               | otherwise    = do
+    cwd <- getCurrentDirectory
+    return $ cwd </> p
+#endif
+
+main :: IO ()
+main = defaultMainWithHooks simpleUserHooks
+  { buildHook = \pkg lbi hooks flags -> do
+     generateBuildModule flags pkg lbi
+     buildHook simpleUserHooks pkg lbi hooks flags
+  }
+
+generateBuildModule :: BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
+generateBuildModule flags pkg lbi = do
+  let verbosity = fromFlag (buildVerbosity flags)
+  let distPref = fromFlag (buildDistPref flags)
+
+  -- Package DBs
+  let dbStack = withPackageDB lbi ++ [ SpecificPackageDB $ distPref </> "package.conf.inplace" ]
+  let dbFlags = "-hide-all-packages" : packageDbArgs dbStack
+
+  withLibLBI pkg lbi $ \lib libcfg -> do
+    let libBI = libBuildInfo lib
+
+    -- modules
+    let modules = exposedModules lib ++ otherModules libBI
+    -- it seems that doctest is happy to take in module names, not actual files!
+    let module_sources = modules
+
+    -- We need the directory with library's cabal_macros.h!
+#if MIN_VERSION_Cabal(1,25,0)
+    let libAutogenDir = autogenComponentModulesDir lbi libcfg
+#else
+    let libAutogenDir = autogenModulesDir lbi
+#endif
+
+    -- Lib sources and includes
+    iArgs <- mapM (fmap ("-i"++) . makeAbsolute) $ "test" : libAutogenDir : hsSourceDirs libBI
+    includeArgs <- mapM (fmap ("-I"++) . makeAbsolute) $ includeDirs libBI
+
+    -- CPP includes, i.e. include cabal_macros.h
+    let cppFlags = map ("-optP"++) $
+            [ "-include", libAutogenDir ++ "/cabal_macros.h" ]
+            ++ cppOptions libBI
+
+    -- Actually we need to check whether testName suite == "doctests"
+    -- pending https://github.com/haskell/cabal/pull/4229 getting into GHC HEAD tree
+    withTestLBI pkg lbi $ \suite suitecfg -> when (testName suite == "doctests") $ do
+
+      -- get and create autogen dir
+#if MIN_VERSION_Cabal(1,25,0)
+      let testAutogenDir = autogenComponentModulesDir lbi suitecfg
+#else
+      let testAutogenDir = autogenModulesDir lbi
+#endif
+      createDirectoryIfMissingVerbose verbosity True testAutogenDir
+
+      -- write autogen'd file
+      rewriteFile (testAutogenDir </> "Build_doctests.hs") $ unlines
+        [ "module Build_doctests where"
+        , ""
+        -- -package-id etc. flags
+        , "pkgs :: [String]"
+        , "pkgs = " ++ (show $ formatDeps $ testDeps libcfg suitecfg)
+        , ""
+        , "flags :: [String]"
+        , "flags = " ++ show (iArgs ++ includeArgs ++ dbFlags ++ cppFlags)
+        , ""
+        , "module_sources :: [String]"
+        , "module_sources = " ++ show ("Servant.Utils.LinksSpec" : map display module_sources)
+        ]
+  where
+    -- we do this check in Setup, as then doctests don't need to depend on Cabal
+    isOldCompiler = maybe False id $ do
+      a <- simpleParse $ showCompilerId $ compiler lbi
+      b <- simpleParse "7.5"
+      return $ packageVersion (a :: PackageId) < b
+
+    formatDeps = map formatOne
+    formatOne (installedPkgId, pkgId)
+      -- The problem is how different cabal executables handle package databases
+      -- when doctests depend on the library
+      | packageId pkg == pkgId = "-package=" ++ display pkgId
+      | otherwise              = "-package-id=" ++ display installedPkgId
+
+    -- From Distribution.Simple.Program.GHC
+    packageDbArgs :: [PackageDB] -> [String]
+    packageDbArgs | isOldCompiler = packageDbArgsConf
+                  | otherwise     = packageDbArgsDb
+
+    -- GHC <7.6 uses '-package-conf' instead of '-package-db'.
+    packageDbArgsConf :: [PackageDB] -> [String]
+    packageDbArgsConf dbstack = case dbstack of
+      (GlobalPackageDB:UserPackageDB:dbs) -> concatMap specific dbs
+      (GlobalPackageDB:dbs)               -> ("-no-user-package-conf")
+                                           : concatMap specific dbs
+      _ -> ierror
+      where
+        specific (SpecificPackageDB db) = [ "-package-conf=" ++ db ]
+        specific _                      = ierror
+        ierror = error $ "internal error: unexpected package db stack: "
+                      ++ show dbstack
+
+    -- GHC >= 7.6 uses the '-package-db' flag. See
+    -- https://ghc.haskell.org/trac/ghc/ticket/5977.
+    packageDbArgsDb :: [PackageDB] -> [String]
+    -- special cases to make arguments prettier in common scenarios
+    packageDbArgsDb dbstack = case dbstack of
+      (GlobalPackageDB:UserPackageDB:dbs)
+        | all isSpecific dbs              -> concatMap single dbs
+      (GlobalPackageDB:dbs)
+        | all isSpecific dbs              -> "-no-user-package-db"
+                                           : concatMap single dbs
+      dbs                                 -> "-clear-package-db"
+                                           : concatMap single dbs
+     where
+       single (SpecificPackageDB db) = [ "-package-db=" ++ db ]
+       single GlobalPackageDB        = [ "-global-package-db" ]
+       single UserPackageDB          = [ "-user-package-db" ]
+       isSpecific (SpecificPackageDB _) = True
+       isSpecific _                     = False
+
+testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]
+testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys
+
+\end{code}
diff --git a/servant.cabal b/servant.cabal
--- a/servant.cabal
+++ b/servant.cabal
@@ -1,5 +1,5 @@
 name:                servant
-version:             0.9.1.1
+version:             0.10
 synopsis:            A family of combinators for defining webservices APIs
 description:
   A family of combinators for defining webservices APIs and serving them
@@ -14,8 +14,8 @@
 author:              Servant Contributors
 maintainer:          haskell-servant-maintainers@googlegroups.com
 copyright:           2014-2016 Zalora South East Asia Pte Ltd, Servant Contributors
-category:            Web
-build-type:          Simple
+category:            Servant Web
+build-type:          Custom
 cabal-version:       >=1.10
 tested-with:         GHC >= 7.8
 extra-source-files:
@@ -25,6 +25,10 @@
   type: git
   location: http://github.com/haskell-servant/servant.git
 
+custom-setup
+  setup-depends:
+     Cabal >=1.14, base, filepath, directory
+
 library
   exposed-modules:
     Servant.API
@@ -43,6 +47,7 @@
     Servant.API.ReqBody
     Servant.API.ResponseHeaders
     Servant.API.Sub
+    Servant.API.TypeLevel
     Servant.API.Vault
     Servant.API.Verbs
     Servant.API.WithNamedContext
@@ -51,19 +56,25 @@
   build-depends:
       base                  >= 4.7  && < 4.10
     , base-compat           >= 0.9  && < 0.10
-    , aeson                 >= 0.7  && < 1.1
+    , aeson                 >= 0.7  && < 1.2
     , attoparsec            >= 0.12 && < 0.14
     , bytestring            >= 0.10 && < 0.11
     , case-insensitive      >= 1.2  && < 1.3
     , http-api-data         >= 0.3  && < 0.4
     , http-media            >= 0.4  && < 0.7
     , http-types            >= 0.8  && < 0.10
+    , natural-transformation >= 0.4 && < 0.5
     , mtl                   >= 2.0  && < 2.3
     , mmorph                >= 1    && < 1.1
     , text                  >= 1    && < 1.3
     , string-conversions    >= 0.3  && < 0.5
     , network-uri           >= 2.6  && < 2.7
     , vault                 >= 0.3  && < 0.4
+
+  if !impl(ghc >= 8.0)
+    build-depends:
+      semigroups            >= 0.16 && < 0.19
+
   hs-source-dirs: src
   default-language: Haskell2010
   other-extensions: CPP
@@ -105,6 +116,7 @@
       base == 4.*
     , base-compat
     , aeson
+    , aeson-compat >=0.3.3 && <0.4
     , attoparsec
     , bytestring
     , hspec == 2.*
@@ -115,6 +127,10 @@
     , text
     , url
 
+  if !impl(ghc >= 8.0)
+    build-depends:
+      semigroups            >= 0.16 && < 0.19
+
 test-suite doctests
  build-depends: base
               , servant
@@ -122,9 +138,11 @@
               , filemanip
               , directory
               , filepath
+              , hspec
  type: exitcode-stdio-1.0
- main-is: test/Doctests.hs
+ main-is: test/doctests.hs
  buildable: True
  default-language: Haskell2010
  ghc-options: -Wall -threaded
+ build-tools: hsc2hs
  include-dirs: include
diff --git a/src/Servant/API.hs b/src/Servant/API.hs
--- a/src/Servant/API.hs
+++ b/src/Servant/API.hs
@@ -101,7 +101,7 @@
                                               ReflectMethod (reflectMethod),
                                               Verb, StdMethod(..))
 import           Servant.API.WithNamedContext (WithNamedContext)
-import           Servant.Utils.Links         (HasLink (..), IsElem, IsElem',
+import           Servant.Utils.Links         (HasLink (..), Link, IsElem, IsElem',
                                               URI (..), safeLink)
 import           Web.HttpApiData             (FromHttpApiData (..),
                                               ToHttpApiData (..))
diff --git a/src/Servant/API/Alternative.hs b/src/Servant/API/Alternative.hs
--- a/src/Servant/API/Alternative.hs
+++ b/src/Servant/API/Alternative.hs
@@ -7,6 +7,7 @@
 {-# OPTIONS_HADDOCK not-home    #-}
 module Servant.API.Alternative ((:<|>)(..)) where
 
+import           Data.Semigroup   (Semigroup (..))
 import           Data.Typeable    (Typeable)
 import           Prelude ()
 import           Prelude.Compat
@@ -22,6 +23,9 @@
 data a :<|> b = a :<|> b
     deriving (Typeable, Eq, Show, Functor, Traversable, Foldable, Bounded)
 infixr 8 :<|>
+
+instance (Semigroup a, Semigroup b) => Semigroup (a :<|> b) where
+    (a :<|> b) <> (a' :<|> b') = (a <> a') :<|> (b <> b')
 
 instance (Monoid a, Monoid b) => Monoid (a :<|> b) where
     mempty = mempty :<|> mempty
diff --git a/src/Servant/API/ContentTypes.hs b/src/Servant/API/ContentTypes.hs
--- a/src/Servant/API/ContentTypes.hs
+++ b/src/Servant/API/ContentTypes.hs
@@ -8,6 +8,7 @@
 {-# LANGUAGE OverloadedStrings     #-}
 {-# LANGUAGE PolyKinds             #-}
 {-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TupleSections         #-}
 {-# LANGUAGE TypeFamilies          #-}
 {-# LANGUAGE TypeOperators         #-}
 {-# LANGUAGE UndecidableInstances  #-}
@@ -81,6 +82,7 @@
 import           Data.ByteString.Lazy             (ByteString, fromStrict,
                                                    toStrict)
 import qualified Data.ByteString.Lazy.Char8       as BC
+import qualified Data.List.NonEmpty               as NE
 import           Data.Maybe                       (isJust)
 import           Data.String.Conversions          (cs)
 import qualified Data.Text                        as TextS
@@ -119,10 +121,18 @@
 --
 class Accept ctype where
     contentType   :: Proxy ctype -> M.MediaType
+    contentType = NE.head . contentTypes
 
+    contentTypes  :: Proxy ctype -> NE.NonEmpty M.MediaType
+    contentTypes  =  (NE.:| []) . contentType
+
+    {-# MINIMAL contentType | contentTypes #-}
+
 -- | @application/json@
 instance Accept JSON where
-    contentType _ = "application" M.// "json"
+    contentTypes _ =
+      "application" M.// "json" M./: ("charset", "utf-8") NE.:|
+      [ "application" M.// "json" ]
 
 -- | @application/x-www-form-urlencoded@
 instance Accept FormUrlEncoded where
@@ -198,16 +208,32 @@
 --
 class Accept ctype => MimeUnrender ctype a where
     mimeUnrender :: Proxy ctype -> ByteString -> Either String a
+    mimeUnrender p = mimeUnrenderWithType p (contentType p)
 
+    -- | Variant which is given the actual 'M.MediaType' provided by the other party.
+    --
+    -- In the most cases you don't want to branch based on the 'M.MediaType'.
+    -- See <https://github.com/haskell-servant/servant/pull/552 pr552> for a motivating example.
+    mimeUnrenderWithType :: Proxy ctype -> M.MediaType -> ByteString -> Either String a
+    mimeUnrenderWithType p _ = mimeUnrender p
+
+    {-# MINIMAL mimeUnrender | mimeUnrenderWithType #-}
+
 class AllCTUnrender (list :: [*]) a where
+    canHandleCTypeH
+        :: Proxy list
+        -> ByteString  -- Content-Type header
+        -> Maybe (ByteString -> Either String a)
+
     handleCTypeH :: Proxy list
                  -> ByteString     -- Content-Type header
                  -> ByteString     -- Request body
                  -> Maybe (Either String a)
+    handleCTypeH p ctypeH body = ($ body) `fmap` canHandleCTypeH p ctypeH
 
 instance ( AllMimeUnrender ctyps a ) => AllCTUnrender ctyps a where
-    handleCTypeH _ ctypeH body = M.mapContentMedia lkup (cs ctypeH)
-      where lkup = allMimeUnrender (Proxy :: Proxy ctyps) body
+    canHandleCTypeH p ctypeH =
+        M.mapContentMedia (allMimeUnrender p) (cs ctypeH)
 
 --------------------------------------------------------------------------
 -- * Utils (Internal)
@@ -219,9 +245,10 @@
     allMime _ = []
 
 instance (Accept ctyp, AllMime ctyps) => AllMime (ctyp ': ctyps) where
-    allMime _ = (contentType pctyp):allMime pctyps
-      where pctyp  = Proxy :: Proxy ctyp
-            pctyps = Proxy :: Proxy ctyps
+    allMime _ = NE.toList (contentTypes pctyp) ++ allMime pctyps
+      where
+        pctyp  = Proxy :: Proxy ctyp
+        pctyps = Proxy :: Proxy ctyps
 
 canHandleAcceptH :: AllMime list => Proxy list -> AcceptHeader -> Bool
 canHandleAcceptH p (AcceptHeader h ) = isJust $ M.matchAccept (allMime p) h
@@ -235,25 +262,31 @@
                   -> [(M.MediaType, ByteString)]    -- content-types/response pairs
 
 instance OVERLAPPABLE_ ( MimeRender ctyp a ) => AllMimeRender '[ctyp] a where
-    allMimeRender _ a = [(contentType pctyp, mimeRender pctyp a)]
-        where pctyp = Proxy :: Proxy ctyp
+    allMimeRender _ a = map (, bs) $ NE.toList $ contentTypes pctyp
+      where
+        bs    = mimeRender pctyp a
+        pctyp = Proxy :: Proxy ctyp
 
 instance OVERLAPPABLE_
          ( MimeRender ctyp a
          , AllMimeRender (ctyp' ': ctyps) a
          ) => AllMimeRender (ctyp ': ctyp' ': ctyps) a where
-    allMimeRender _ a = (contentType pctyp, mimeRender pctyp a)
-                       :(allMimeRender pctyps a)
-        where pctyp = Proxy :: Proxy ctyp
-              pctyps = Proxy :: Proxy (ctyp' ': ctyps)
+    allMimeRender _ a =
+        (map (, bs) $ NE.toList $ contentTypes pctyp)
+        ++ allMimeRender pctyps a
+      where
+        bs     = mimeRender pctyp a
+        pctyp  = Proxy :: Proxy ctyp
+        pctyps = Proxy :: Proxy (ctyp' ': ctyps)
 
 
 -- Ideally we would like to declare a 'MimeRender a NoContent' instance, and
 -- then this would be taken care of. However there is no more specific instance
 -- between that and 'MimeRender JSON a', so we do this instead
 instance OVERLAPPING_ ( Accept ctyp ) => AllMimeRender '[ctyp] NoContent where
-    allMimeRender _ _ = [(contentType pctyp, "")]
-      where pctyp = Proxy :: Proxy ctyp
+    allMimeRender _ _ = map (, "") $ NE.toList $ contentTypes pctyp
+      where
+        pctyp = Proxy :: Proxy ctyp
 
 instance OVERLAPPING_
          ( AllMime (ctyp ': ctyp' ': ctyps)
@@ -265,19 +298,21 @@
 --------------------------------------------------------------------------
 class (AllMime list) => AllMimeUnrender (list :: [*]) a where
     allMimeUnrender :: Proxy list
-                    -> ByteString
-                    -> [(M.MediaType, Either String a)]
+                    -> [(M.MediaType, ByteString -> Either String a)]
 
 instance AllMimeUnrender '[] a where
-    allMimeUnrender _ _ = []
+    allMimeUnrender _ = []
 
 instance ( MimeUnrender ctyp a
          , AllMimeUnrender ctyps a
          ) => AllMimeUnrender (ctyp ': ctyps) a where
-    allMimeUnrender _ val = (contentType pctyp, mimeUnrender pctyp val)
-                           :(allMimeUnrender pctyps val)
-        where pctyp = Proxy :: Proxy ctyp
-              pctyps = Proxy :: Proxy ctyps
+    allMimeUnrender _ =
+        (map mk $ NE.toList $ contentTypes pctyp)
+        ++ allMimeUnrender pctyps
+      where
+        mk ct   = (ct, \bs -> mimeUnrenderWithType pctyp ct bs)
+        pctyp  = Proxy :: Proxy ctyp
+        pctyps = Proxy :: Proxy ctyps
 
 --------------------------------------------------------------------------
 -- * MimeRender Instances
diff --git a/src/Servant/API/TypeLevel.hs b/src/Servant/API/TypeLevel.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/API/TypeLevel.hs
@@ -0,0 +1,264 @@
+{-# LANGUAGE CPP                   #-}
+{-# LANGUAGE ConstraintKinds       #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE KindSignatures        #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE UndecidableInstances  #-}
+
+{-|
+This module collects utilities for manipulating @servant@ API types. The
+functionality in this module is for advanced usage.
+
+The code samples in this module use the following type synonym:
+
+> type SampleAPI = "hello" :> Get '[JSON] Int
+>             :<|> "bye" :> Capture "name" String :> Post '[JSON, PlainText] Bool
+
+-}
+module Servant.API.TypeLevel (
+    -- $setup
+    -- * API predicates
+    Endpoints,
+    -- ** Lax inclusion
+    IsElem',
+    IsElem,
+    IsSubAPI,
+    AllIsElem,
+    -- ** Strict inclusion
+    IsIn,
+    IsStrictSubAPI,
+    AllIsIn,
+    -- * Helpers
+    -- ** Lists
+    MapSub,
+    AppendList,
+    IsSubList,
+    Elem,
+    ElemGo,
+    -- ** Logic
+    Or,
+    And,
+    -- * Custom type errors
+    -- | Before @base-4.9.0.0@ we use non-exported 'ElemNotFoundIn' class,
+    -- which cannot be instantiated.
+    ) where
+
+
+import           GHC.Exts                (Constraint)
+import           Servant.API.Alternative (type (:<|>))
+import           Servant.API.Capture     (Capture, CaptureAll)
+import           Servant.API.Header      (Header)
+import           Servant.API.QueryParam  (QueryFlag, QueryParam, QueryParams)
+import           Servant.API.ReqBody     (ReqBody)
+import           Servant.API.Sub         (type (:>))
+import           Servant.API.Verbs       (Verb)
+#if MIN_VERSION_base(4,9,0)
+import           GHC.TypeLits            (TypeError, ErrorMessage(..))
+#endif
+
+
+
+-- * API predicates
+
+-- | Flatten API into a list of endpoints.
+--
+-- >>> Refl :: Endpoints SampleAPI :~: '["hello" :> Verb 'GET 200 '[JSON] Int, "bye" :> (Capture "name" String :> Verb 'POST 200 '[JSON, PlainText] Bool)]
+-- Refl
+type family Endpoints api where
+  Endpoints (a :<|> b) = AppendList (Endpoints a) (Endpoints b)
+  Endpoints (e :> a)   = MapSub e (Endpoints a)
+  Endpoints a = '[a]
+
+-- ** Lax inclusion
+
+-- | You may use this type family to tell the type checker that your custom
+-- type may be skipped as part of a link. This is useful for things like
+-- @'QueryParam'@ that are optional in a URI and do not affect them if they are
+-- omitted.
+--
+-- >>> data CustomThing
+-- >>> type instance IsElem' e (CustomThing :> s) = IsElem e s
+--
+-- Note that @'IsElem'@ is called, which will mutually recurse back to @'IsElem''@
+-- if it exhausts all other options again.
+--
+-- Once you have written a @HasLink@ instance for @CustomThing@ you are ready to go.
+type family IsElem' a s :: Constraint
+
+-- | Closed type family, check if @endpoint@ is within @api@.
+-- Uses @'IsElem''@ if it exhausts all other options.
+--
+-- >>> ok (Proxy :: Proxy (IsElem ("hello" :> Get '[JSON] Int) SampleAPI))
+-- OK
+--
+-- >>> ok (Proxy :: Proxy (IsElem ("bye" :> Get '[JSON] Int) SampleAPI))
+-- ...
+-- ... Could not deduce...
+-- ...
+--
+-- An endpoint is considered within an api even if it is missing combinators
+-- that don't affect the URL:
+--
+-- >>> ok (Proxy :: Proxy (IsElem (Get '[JSON] Int) (Header "h" Bool :> Get '[JSON] Int)))
+-- OK
+--
+-- >>> ok (Proxy :: Proxy (IsElem (Get '[JSON] Int) (ReqBody '[JSON] Bool :> Get '[JSON] Int)))
+-- OK
+--
+-- *N.B.:* @IsElem a b@ can be seen as capturing the notion of whether the URL
+-- represented by @a@ would match the URL represented by @b@, *not* whether a
+-- request represented by @a@ matches the endpoints serving @b@ (for the
+-- latter, use 'IsIn').
+type family IsElem endpoint api :: Constraint where
+  IsElem e (sa :<|> sb)                   = Or (IsElem e sa) (IsElem e sb)
+  IsElem (e :> sa) (e :> sb)              = IsElem sa sb
+  IsElem sa (Header sym x :> sb)          = IsElem sa sb
+  IsElem sa (ReqBody y x :> sb)           = IsElem sa sb
+  IsElem (CaptureAll z y :> sa) (CaptureAll x y :> sb)
+                                          = IsElem sa sb
+  IsElem (Capture z y :> sa) (Capture x y :> sb)
+                                          = IsElem sa sb
+  IsElem sa (QueryParam x y :> sb)        = IsElem sa sb
+  IsElem sa (QueryParams x y :> sb)       = IsElem sa sb
+  IsElem sa (QueryFlag x :> sb)           = IsElem sa sb
+  IsElem (Verb m s ct typ) (Verb m s ct' typ)
+                                          = IsSubList ct ct'
+  IsElem e e                              = ()
+  IsElem e a                              = IsElem' e a
+
+-- | Check whether @sub@ is a sub-API of @api@.
+--
+-- >>> ok (Proxy :: Proxy (IsSubAPI SampleAPI (SampleAPI :<|> Get '[JSON] Int)))
+-- OK
+--
+-- >>> ok (Proxy :: Proxy (IsSubAPI (SampleAPI :<|> Get '[JSON] Int) SampleAPI))
+-- ...
+-- ... Could not deduce...
+-- ...
+--
+-- This uses @IsElem@ for checking; thus the note there applies here.
+type family IsSubAPI sub api :: Constraint where
+  IsSubAPI sub api = AllIsElem (Endpoints sub) api
+
+-- | Check that every element of @xs@ is an endpoint of @api@ (using @'IsElem'@).
+type family AllIsElem xs api :: Constraint where
+  AllIsElem '[] api = ()
+  AllIsElem (x ': xs) api = (IsElem x api, AllIsElem xs api)
+
+-- ** Strict inclusion
+
+-- | Closed type family, check if @endpoint@ is exactly within @api@.
+--
+-- >>> ok (Proxy :: Proxy (IsIn ("hello" :> Get '[JSON] Int) SampleAPI))
+-- OK
+--
+-- Unlike 'IsElem', this requires an *exact* match.
+--
+-- >>> ok (Proxy :: Proxy (IsIn (Get '[JSON] Int) (Header "h" Bool :> Get '[JSON] Int)))
+-- ...
+-- ... Could not deduce...
+-- ...
+type family IsIn (endpoint :: *) (api :: *) :: Constraint where
+  IsIn e (sa :<|> sb)                = Or (IsIn e sa) (IsIn e sb)
+  IsIn (e :> sa) (e :> sb)           = IsIn sa sb
+  IsIn e e                           = ()
+
+-- | Check whether @sub@ is a sub API of @api@.
+--
+-- Like 'IsSubAPI', but uses 'IsIn' rather than 'IsElem'.
+type family IsStrictSubAPI sub api :: Constraint where
+  IsStrictSubAPI sub api = AllIsIn (Endpoints sub) api
+
+-- | Check that every element of @xs@ is an endpoint of @api@ (using @'IsIn'@).
+--
+-- ok (Proxy :: Proxy (AllIsIn (Endpoints SampleAPI) SampleAPI))
+-- OK
+type family AllIsIn xs api :: Constraint where
+  AllIsIn '[] api = ()
+  AllIsIn (x ': xs) api = (IsIn x api, AllIsIn xs api)
+
+-- * Helpers
+
+-- ** Lists
+
+-- | Apply @(e :>)@ to every API in @xs@.
+type family MapSub e xs where
+  MapSub e '[] = '[]
+  MapSub e (x ': xs) = (e :> x) ': MapSub e xs
+
+-- | Append two type-level lists.
+type family AppendList xs ys where
+  AppendList '[]       ys = ys
+  AppendList (x ': xs) ys = x ': AppendList xs ys
+
+type family IsSubList a b :: Constraint where
+  IsSubList '[] b          = ()
+  IsSubList (x ': xs) y    = Elem x y `And` IsSubList xs y
+
+-- | Check that a value is an element of a list:
+--
+-- >>> ok (Proxy :: Proxy (Elem Bool '[Int, Bool]))
+-- OK
+--
+-- >>> ok (Proxy :: Proxy (Elem String '[Int, Bool]))
+-- ...
+-- ... [Char]...'[Int, Bool...
+-- ...
+type Elem e es = ElemGo e es es
+
+-- 'orig' is used to store original list for better error messages
+type family ElemGo e es orig :: Constraint where
+  ElemGo x (x ': xs) orig = ()
+  ElemGo y (x ': xs) orig = ElemGo y xs orig
+#if MIN_VERSION_base(4,9,0)
+  -- Note [Custom Errors]
+  ElemGo x '[] orig       = TypeError ('ShowType x
+                                 ':<>: 'Text " expected in list "
+                                 ':<>: 'ShowType orig)
+#else
+  ElemGo x '[] orig       = ElemNotFoundIn x orig
+#endif
+
+-- ** Logic
+
+-- | If either a or b produce an empty constraint, produce an empty constraint.
+type family Or (a :: Constraint) (b :: Constraint) :: Constraint where
+    -- This works because of:
+    -- https://ghc.haskell.org/trac/ghc/wiki/NewAxioms/CoincidentOverlap
+  Or () b       = ()
+  Or a ()       = ()
+
+-- | If both a or b produce an empty constraint, produce an empty constraint.
+type family And (a :: Constraint) (b :: Constraint) :: Constraint where
+  And () ()     = ()
+
+-- * Custom type errors
+
+#if !MIN_VERSION_base(4,9,0)
+class ElemNotFoundIn val list
+#endif
+
+{- Note [Custom Errors]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+We might try to factor these our more cleanly, but the type synonyms and type
+families are not evaluated (see https://ghc.haskell.org/trac/ghc/ticket/12048).
+-}
+
+
+-- $setup
+--
+-- The doctests in this module are run with following preamble:
+--
+-- >>> :set -XPolyKinds
+-- >>> :set -XGADTs
+-- >>> import Data.Proxy
+-- >>> import Data.Type.Equality
+-- >>> import Servant.API
+-- >>> data OK ctx where OK :: ctx => OK ctx
+-- >>> instance Show (OK ctx) where show _ = "OK"
+-- >>> let ok :: ctx => Proxy ctx -> OK ctx; ok _ = OK
+-- >>> type SampleAPI = "hello" :> Get '[JSON] Int :<|> "bye" :> Capture "name" String :> Post '[JSON, PlainText] Bool
+-- >>> let sampleAPI = Proxy :: Proxy SampleAPI
diff --git a/src/Servant/Utils/Enter.hs b/src/Servant/Utils/Enter.hs
--- a/src/Servant/Utils/Enter.hs
+++ b/src/Servant/Utils/Enter.hs
@@ -8,9 +8,13 @@
 {-# LANGUAGE TypeFamilies           #-}
 {-# LANGUAGE TypeOperators          #-}
 {-# LANGUAGE UndecidableInstances   #-}
-module Servant.Utils.Enter where
+module Servant.Utils.Enter (
+    module Servant.Utils.Enter,
+    -- * natural-transformation re-exports
+    (:~>)(..),
+    ) where
 
-import qualified Control.Category            as C
+import           Control.Natural
 import           Control.Monad.Identity
 import           Control.Monad.Morph
 import           Control.Monad.Reader
@@ -18,7 +22,6 @@
 import qualified Control.Monad.State.Strict  as SState
 import qualified Control.Monad.Writer.Lazy   as LWriter
 import qualified Control.Monad.Writer.Strict as SWriter
-import           Data.Typeable
 import           Prelude                     ()
 import           Prelude.Compat
 
@@ -38,57 +41,49 @@
 
 -- ** Useful instances
 
--- | A natural transformation from @m@ to @n@. Used to `enter` particular
--- datatypes.
-newtype m :~> n = Nat { unNat :: forall a. m a -> n a} deriving Typeable
-
-instance C.Category (:~>) where
-    id = Nat id
-    Nat f . Nat g = Nat (f . g)
-
 instance Enter (m a) (m :~> n) (n a) where
-    enter (Nat f) = f
+    enter (NT f) = f
 
 -- | Like `lift`.
 liftNat :: (Control.Monad.Morph.MonadTrans t, Monad m) => m :~> t m
-liftNat = Nat Control.Monad.Morph.lift
+liftNat = NT Control.Monad.Morph.lift
 
 runReaderTNat :: r -> (ReaderT r m :~> m)
-runReaderTNat a = Nat (`runReaderT` a)
+runReaderTNat a = NT (`runReaderT` a)
 
 evalStateTLNat :: Monad m => s -> (LState.StateT s m :~> m)
-evalStateTLNat a = Nat (`LState.evalStateT` a)
+evalStateTLNat a = NT (`LState.evalStateT` a)
 
 evalStateTSNat :: Monad m => s -> (SState.StateT s m :~> m)
-evalStateTSNat a = Nat (`SState.evalStateT` a)
+evalStateTSNat a = NT (`SState.evalStateT` a)
 
 -- | Log the contents of `SWriter.WriterT` with the function provided as the
 -- first argument, and return the value of the @WriterT@ computation
 logWriterTSNat :: MonadIO m => (w -> IO ()) -> (SWriter.WriterT w m :~> m)
-logWriterTSNat logger = Nat $ \x -> do
+logWriterTSNat logger = NT $ \x -> do
     (a, w) <- SWriter.runWriterT x
     liftIO $ logger w
     return a
 
 -- | Like `logWriterTSNat`, but for strict @WriterT@.
 logWriterTLNat :: MonadIO m => (w -> IO ()) -> (LWriter.WriterT w m :~> m)
-logWriterTLNat logger = Nat $ \x -> do
+logWriterTLNat logger = NT $ \x -> do
     (a, w) <- LWriter.runWriterT x
     liftIO $ logger w
     return a
 
 -- | Like @mmorph@'s `hoist`.
 hoistNat :: (MFunctor t, Monad m) => (m :~> n) ->  (t m :~> t n)
-hoistNat (Nat n) = Nat $ hoist n
+hoistNat (NT n) = NT $ hoist n
 
 -- | Like @mmorph@'s `embed`.
 embedNat :: (MMonad t, Monad n) => (m :~> t n) -> (t m :~> t n)
-embedNat (Nat n) = Nat $ embed n
+embedNat (NT n) = NT $ embed n
 
 -- | Like @mmorph@'s `squash`.
 squashNat :: (Monad m, MMonad t) => t (t m) :~> t m
-squashNat = Nat squash
+squashNat = NT squash
 
 -- | Like @mmorph@'s `generalize`.
 generalizeNat :: Applicative m => Identity :~> m
-generalizeNat = Nat (pure . runIdentity)
+generalizeNat = NT (pure . runIdentity)
diff --git a/src/Servant/Utils/Links.hs b/src/Servant/Utils/Links.hs
--- a/src/Servant/Utils/Links.hs
+++ b/src/Servant/Utils/Links.hs
@@ -6,7 +6,6 @@
 {-# LANGUAGE ScopedTypeVariables    #-}
 {-# LANGUAGE TypeFamilies           #-}
 {-# LANGUAGE TypeOperators          #-}
-{-# LANGUAGE UndecidableInstances   #-}
 {-# OPTIONS_HADDOCK not-home        #-}
 
 -- | Type safe generation of internal links.
@@ -30,7 +29,7 @@
 -- you would like to restrict links to. The second argument is the destination
 -- endpoint you would like the link to point to, this will need to end with a
 -- verb like GET or POST. Further arguments may be required depending on the
--- type of the endpoint. If everything lines up you will get a 'URI' out the
+-- type of the endpoint. If everything lines up you will get a 'Link' out the
 -- other end.
 --
 -- You may omit 'QueryParam's and the like should you not want to provide them,
@@ -41,19 +40,19 @@
 -- with an example. Here, a link is generated with no parameters:
 --
 -- >>> let hello = Proxy :: Proxy ("hello" :> Get '[JSON] Int)
--- >>> print (safeLink api hello :: URI)
--- hello
+-- >>> toUrlPiece (safeLink api hello :: Link)
+-- "hello"
 --
 -- If the API has an endpoint with parameters then we can generate links with
 -- or without those:
 --
 -- >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete '[JSON] NoContent)
--- >>> print $ safeLink api with (Just "Hubert")
--- bye?name=Hubert
+-- >>> toUrlPiece $ safeLink api with (Just "Hubert")
+-- "bye?name=Hubert"
 --
 -- >>> let without = Proxy :: Proxy ("bye" :> Delete '[JSON] NoContent)
--- >>> print $ safeLink api without
--- bye
+-- >>> toUrlPiece $ safeLink api without
+-- "bye"
 --
 -- If you would like create a helper for generating links only within that API,
 -- you can partially apply safeLink if you specify a correct type signature
@@ -79,27 +78,24 @@
 --  bad_link under api after trying the open (but empty) type family
 --  `IsElem'` as a last resort.
 module Servant.Utils.Links (
+  module Servant.API.TypeLevel,
+
   -- * Building and using safe links
   --
-  -- | Note that 'URI' is Network.URI.URI from the network-uri package.
+  -- | Note that 'URI' is from the "Network.URI" module in the @network-uri@ package.
     safeLink
   , URI(..)
   -- * Adding custom types
   , HasLink(..)
   , linkURI
   , Link
-  , IsElem'
-  -- * Illustrative exports
-  , IsElem
-  , Or
 ) where
 
-import qualified Data.ByteString.Char8 as BSC
 import           Data.List
 import           Data.Monoid.Compat    ( (<>) )
 import           Data.Proxy            ( Proxy(..) )
 import qualified Data.Text             as Text
-import           GHC.Exts              (Constraint)
+import qualified Data.Text.Encoding    as TE
 import           GHC.TypeLits          ( KnownSymbol, symbolVal )
 import           Network.URI           ( URI(..), escapeURIString, isUnreserved )
 import           Prelude               ()
@@ -115,7 +111,7 @@
 import Servant.API.Verbs ( Verb )
 import Servant.API.Sub ( type (:>) )
 import Servant.API.Raw ( Raw )
-import Servant.API.Alternative ( type (:<|>) )
+import Servant.API.TypeLevel
 
 -- | A safe link datatype.
 -- The only way of constructing a 'Link' is using 'safeLink', which means any
@@ -126,60 +122,10 @@
   } deriving Show
 
 instance ToHttpApiData Link where
-    toUrlPiece = Text.pack . show
-    toHeader   = BSC.pack . show
-
--- | If either a or b produce an empty constraint, produce an empty constraint.
-type family Or (a :: Constraint) (b :: Constraint) :: Constraint where
-    -- This works because of:
-    -- https://ghc.haskell.org/trac/ghc/wiki/NewAxioms/CoincidentOverlap
-    Or () b       = ()
-    Or a ()       = ()
-
--- | If both a or b produce an empty constraint, produce an empty constraint.
-type family And (a :: Constraint) (b :: Constraint) :: Constraint where
-    And () ()     = ()
-
--- | You may use this type family to tell the type checker that your custom
--- type may be skipped as part of a link. This is useful for things like
--- 'QueryParam' that are optional in a URI and do not affect them if they are
--- omitted.
---
--- >>> data CustomThing
--- >>> type instance IsElem' e (CustomThing :> s) = IsElem e s
---
--- Note that 'IsElem' is called, which will mutually recurse back to `IsElem'`
--- if it exhausts all other options again.
---
--- Once you have written a HasLink instance for CustomThing you are ready to
--- go.
-type family IsElem' a s :: Constraint
-
--- | Closed type family, check if endpoint is within api
-type family IsElem endpoint api :: Constraint where
-    IsElem e (sa :<|> sb)                   = Or (IsElem e sa) (IsElem e sb)
-    IsElem (e :> sa) (e :> sb)              = IsElem sa sb
-    IsElem sa (Header sym x :> sb)          = IsElem sa sb
-    IsElem sa (ReqBody y x :> sb)           = IsElem sa sb
-    IsElem (Capture z y :> sa) (Capture x y :> sb)
-                                            = IsElem sa sb
-    IsElem (CaptureAll z y :> sa) (CaptureAll x y :> sb)
-                                            = IsElem sa sb
-    IsElem sa (QueryParam x y :> sb)        = IsElem sa sb
-    IsElem sa (QueryParams x y :> sb)       = IsElem sa sb
-    IsElem sa (QueryFlag x :> sb)           = IsElem sa sb
-    IsElem (Verb m s ct typ) (Verb m s ct' typ)
-                                            = IsSubList ct ct'
-    IsElem e e                              = ()
-    IsElem e a                              = IsElem' e a
-
-type family IsSubList a b :: Constraint where
-    IsSubList '[] b          = ()
-    IsSubList (x ': xs) y    = Elem x y `And` IsSubList xs y
-
-type family Elem e es :: Constraint where
-    Elem x (x ': xs) = ()
-    Elem y (x ': xs) = Elem y xs
+    toHeader   = TE.encodeUtf8 . toUrlPiece
+    toUrlPiece l =
+        let uri = linkURI l
+        in Text.pack $ uriPath uri ++ uriQuery uri
 
 -- Phantom types for Param
 data Query
@@ -307,9 +253,9 @@
 
 -- Verb (terminal) instances
 instance HasLink (Verb m s ct a) where
-    type MkLink (Verb m s ct a) = URI
-    toLink _ = linkURI
+    type MkLink (Verb m s ct a) = Link
+    toLink _ = id 
 
 instance HasLink Raw where
-    type MkLink Raw = URI
-    toLink _ = linkURI
+    type MkLink Raw = Link
+    toLink _ = id
diff --git a/test/Doctests.hs b/test/Doctests.hs
deleted file mode 100644
--- a/test/Doctests.hs
+++ /dev/null
@@ -1,40 +0,0 @@
-module Main where
-
-import           Data.List            (isPrefixOf)
-import           System.Directory
-import           System.FilePath
-import           System.FilePath.Find
-import           Test.DocTest
-
-main :: IO ()
-main = do
-    files <- find always (extension ==? ".hs") "src"
-    tfiles <- find always (extension ==? ".hs") "test/Servant"
-    mCabalMacrosFile <- getCabalMacrosFile
-    doctest $ "-isrc" : "-Iinclude" :
-              (maybe [] (\ f -> ["-optP-include", "-optP" ++ f]) mCabalMacrosFile) ++
-              "-XOverloadedStrings" :
-              "-XFlexibleInstances" :
-              "-XMultiParamTypeClasses" :
-              (files ++ tfiles)
-
-getCabalMacrosFile :: IO (Maybe FilePath)
-getCabalMacrosFile = do
-  exists <- doesDirectoryExist "dist"
-  if exists
-    then do
-      contents <- getDirectoryContents "dist"
-      let rest = "build" </> "autogen" </> "cabal_macros.h"
-      whenExists $ case filter ("dist-sandbox-" `isPrefixOf`) contents of
-        [x] -> "dist" </> x </> rest
-        [] -> "dist" </> rest
-        xs -> error $ "ran doctests with multiple dist/dist-sandbox-xxxxx's: \n"
-                    ++ show xs ++ "\nTry cabal clean"
-    else return Nothing
- where
-  whenExists :: FilePath -> IO (Maybe FilePath)
-  whenExists file = do
-    exists <- doesFileExist file
-    return $ if exists
-      then Just file
-      else Nothing
diff --git a/test/Servant/API/ContentTypesSpec.hs b/test/Servant/API/ContentTypesSpec.hs
--- a/test/Servant/API/ContentTypesSpec.hs
+++ b/test/Servant/API/ContentTypesSpec.hs
@@ -11,21 +11,25 @@
 import           Prelude ()
 import           Prelude.Compat
 
-import           Data.Aeson
+import           Data.Aeson.Compat
 import           Data.ByteString.Char8     (ByteString, append, pack)
 import qualified Data.ByteString.Lazy      as BSL
+import qualified Data.ByteString.Lazy.Char8 as BSL8
 import           Data.Either
 import           Data.Function             (on)
 import           Data.List                 (maximumBy)
+import qualified Data.List.NonEmpty        as NE
 import           Data.Maybe                (fromJust, isJust, isNothing)
 import           Data.Proxy
 import           Data.String               (IsString (..))
 import           Data.String.Conversions   (cs)
 import qualified Data.Text                 as TextS
+import qualified Data.Text.Encoding        as TextSE
 import qualified Data.Text.Lazy            as TextL
 import           GHC.Generics
 import           Test.Hspec
 import           Test.QuickCheck
+import           Text.Read                 (readMaybe)
 import "quickcheck-instances" Test.QuickCheck.Instances ()
 
 import           Servant.API.ContentTypes
@@ -101,23 +105,31 @@
                 "application/octet-stream" ("content" :: ByteString)
                 `shouldSatisfy` isJust
 
+        it "returns Just if the 'Accept' header matches, with multiple mime types" $ do
+            handleAcceptH (Proxy :: Proxy '[JSONorText]) "application/json" (3 :: Int)
+                `shouldSatisfy` isJust
+            handleAcceptH (Proxy :: Proxy '[JSONorText]) "text/plain" (3 :: Int)
+                `shouldSatisfy` isJust
+            handleAcceptH (Proxy :: Proxy '[JSONorText]) "image/jpeg" (3 :: Int)
+                `shouldBe` Nothing
+
         it "returns the Content-Type as the first element of the tuple" $ do
             handleAcceptH (Proxy :: Proxy '[JSON]) "*/*" (3 :: Int)
-                `shouldSatisfy` ((== "application/json") . fst . fromJust)
+                `shouldSatisfy` ((== "application/json;charset=utf-8") . fst . fromJust)
             handleAcceptH (Proxy :: Proxy '[PlainText, JSON]) "application/json" (3 :: Int)
-                `shouldSatisfy` ((== "application/json") . fst . fromJust)
+                `shouldSatisfy` ((== "application/json;charset=utf-8") . fst . fromJust)
             handleAcceptH (Proxy :: Proxy '[PlainText, JSON, OctetStream])
                 "application/octet-stream" ("content" :: ByteString)
                 `shouldSatisfy` ((== "application/octet-stream") . fst . fromJust)
 
         it "returns the appropriately serialized representation" $ do
             property $ \x -> handleAcceptH (Proxy :: Proxy '[JSON]) "*/*" (x :: SomeData)
-                == Just ("application/json", encode x)
+                == Just ("application/json;charset=utf-8", encode x)
 
         it "respects the Accept spec ordering" $ do
             let highest a b c = maximumBy (compare `on` snd)
                         [ ("application/octet-stream", a)
-                        , ("application/json", b)
+                        , ("application/json;charset=utf-8", b)
                         , ("text/plain;charset=utf-8", c)
                         ]
             let acceptH a b c = addToAccept (Proxy :: Proxy OctetStream) a $
@@ -158,6 +170,24 @@
                     (encode val)
                     `shouldBe` Just (Right val)
 
+            it "returns Just (Right val) if the decoding succeeds for either of multiple mime-types" $ do
+                let val = 42 :: Int
+                handleCTypeH (Proxy :: Proxy '[JSONorText]) "application/json"
+                    "42" `shouldBe` Just (Right val)
+                handleCTypeH (Proxy :: Proxy '[JSONorText]) "text/plain"
+                    "42" `shouldBe` Just (Right val)
+                handleCTypeH (Proxy :: Proxy '[JSONorText]) "image/jpeg"
+                    "42" `shouldBe` (Nothing :: Maybe (Either String Int))
+
+            it "passes content-type to mimeUnrenderWithType" $ do
+                let val = "foobar" :: TextS.Text
+                handleCTypeH (Proxy :: Proxy '[JSONorText]) "application/json"
+                    "\"foobar\"" `shouldBe` Just (Right val)
+                handleCTypeH (Proxy :: Proxy '[JSONorText]) "text/plain"
+                    "foobar" `shouldBe` Just (Right val)
+                handleCTypeH (Proxy :: Proxy '[JSONorText]) "image/jpeg"
+                    "foobar" `shouldBe` (Nothing :: Maybe (Either String Int))
+
 #if MIN_VERSION_aeson(0,9,0)
     -- aeson >= 0.9 decodes top-level strings
     describe "eitherDecodeLenient" $ do
@@ -200,6 +230,23 @@
 
 instance IsString AcceptHeader where
     fromString = AcceptHeader . fromString
+
+-- To test multiple content types
+data JSONorText
+
+instance Accept JSONorText where
+    contentTypes _ = "text/plain" NE.:| [ "application/json" ]
+
+instance MimeRender JSONorText Int  where
+    mimeRender _ = cs . show
+
+instance MimeUnrender JSONorText Int where
+    mimeUnrender _ = maybe (Left "") Right . readMaybe . BSL8.unpack
+
+instance MimeUnrender JSONorText TextS.Text where
+    mimeUnrenderWithType _ mt
+        | mt == "application/json" = maybe (Left "") Right . decode
+        | otherwise                = Right . TextSE.decodeUtf8 . BSL.toStrict
 
 addToAccept :: Accept a => Proxy a -> ZeroToOne -> AcceptHeader -> AcceptHeader
 addToAccept p (ZeroToOne f) (AcceptHeader h) = AcceptHeader (cont h)
diff --git a/test/Servant/Utils/LinksSpec.hs b/test/Servant/Utils/LinksSpec.hs
--- a/test/Servant/Utils/LinksSpec.hs
+++ b/test/Servant/Utils/LinksSpec.hs
@@ -7,6 +7,7 @@
 import           Data.Proxy              (Proxy (..))
 import           Test.Hspec              (Expectation, Spec, describe, it,
                                           shouldBe)
+import           Data.String             (fromString)
 
 import           Servant.API
 
@@ -32,38 +33,38 @@
 
 -- | Convert a link to a URI and ensure that this maps to the given string
 -- given string
-shouldBeURI :: URI -> String -> Expectation
-shouldBeURI link expected =
-    show link `shouldBe` expected
+shouldBeLink :: Link -> String -> Expectation
+shouldBeLink link expected =
+    toUrlPiece link `shouldBe` fromString expected
 
 spec :: Spec
 spec = describe "Servant.Utils.Links" $ do
     it "generates correct links for capture query params" $ do
         let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete '[JSON] NoContent)
-        apiLink l1 "hi" `shouldBeURI` "hello/hi"
+        apiLink l1 "hi" `shouldBeLink` "hello/hi"
 
         let l2 = Proxy :: Proxy ("hello" :> Capture "name" String
                                          :> QueryParam "capital" Bool
                                          :> Delete '[JSON] NoContent)
-        apiLink l2 "bye" (Just True) `shouldBeURI` "hello/bye?capital=true"
+        apiLink l2 "bye" (Just True) `shouldBeLink` "hello/bye?capital=true"
 
     it "generates correct links for CaptureAll" $ do
         apiLink (Proxy :: Proxy ("all" :> CaptureAll "names" String :> Get '[JSON] NoContent))
           ["roads", "lead", "to", "rome"]
-          `shouldBeURI` "all/roads/lead/to/rome"
+          `shouldBeLink` "all/roads/lead/to/rome"
 
     it "generates correct links for query flags" $ do
         let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"
                                          :> QueryFlag "fast" :> Delete '[JSON] NoContent)
-        apiLink l1 True True `shouldBeURI` "balls?bouncy&fast"
-        apiLink l1 False True `shouldBeURI` "balls?fast"
+        apiLink l1 True True `shouldBeLink` "balls?bouncy&fast"
+        apiLink l1 False True `shouldBeLink` "balls?fast"
 
     it "generates correct links for all of the verbs" $ do
-        apiLink (Proxy :: Proxy ("get" :> Get '[JSON] NoContent)) `shouldBeURI` "get"
-        apiLink (Proxy :: Proxy ("put" :> Put '[JSON] NoContent)) `shouldBeURI` "put"
-        apiLink (Proxy :: Proxy ("post" :> Post '[JSON] NoContent)) `shouldBeURI` "post"
-        apiLink (Proxy :: Proxy ("delete" :> Delete '[JSON] NoContent)) `shouldBeURI` "delete"
-        apiLink (Proxy :: Proxy ("raw" :> Raw)) `shouldBeURI` "raw"
+        apiLink (Proxy :: Proxy ("get" :> Get '[JSON] NoContent)) `shouldBeLink` "get"
+        apiLink (Proxy :: Proxy ("put" :> Put '[JSON] NoContent)) `shouldBeLink` "put"
+        apiLink (Proxy :: Proxy ("post" :> Post '[JSON] NoContent)) `shouldBeLink` "post"
+        apiLink (Proxy :: Proxy ("delete" :> Delete '[JSON] NoContent)) `shouldBeLink` "delete"
+        apiLink (Proxy :: Proxy ("raw" :> Raw)) `shouldBeLink` "raw"
 
 
 -- |
@@ -96,8 +97,8 @@
 -- ...
 --
 -- sanity check
--- >>> apiLink (Proxy :: Proxy AllGood)
--- get
+-- >>> toUrlPiece $ apiLink (Proxy :: Proxy AllGood)
+-- "get"
 type WrongPath = "getTypo" :> Get '[JSON] NoContent
 type WrongReturnType = "get" :> Get '[JSON] Bool
 type WrongContentType = "get" :> Get '[OctetStream] NoContent
diff --git a/test/doctests.hsc b/test/doctests.hsc
new file mode 100644
--- /dev/null
+++ b/test/doctests.hsc
@@ -0,0 +1,59 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Main (doctests)
+-- Copyright   :  (C) 2012-14 Edward Kmett
+-- License     :  BSD-style (see the file LICENSE)
+-- Maintainer  :  Edward Kmett <ekmett@gmail.com>
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-- This module provides doctests for a project based on the actual versions
+-- of the packages it was built with. It requires a corresponding Setup.lhs
+-- to be added to the project
+-----------------------------------------------------------------------------
+module Main where
+
+import Build_doctests (flags, pkgs, module_sources)
+import Data.Foldable (traverse_)
+import Test.DocTest
+
+##if defined(mingw32_HOST_OS)
+##if defined(i386_HOST_ARCH)
+##define USE_CP
+import Control.Applicative
+import Control.Exception
+import Foreign.C.Types
+foreign import stdcall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool
+foreign import stdcall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt
+##elif defined(x86_64_HOST_ARCH)
+##define USE_CP
+import Control.Applicative
+import Control.Exception
+import Foreign.C.Types
+foreign import ccall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool
+foreign import ccall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt
+##endif
+##endif
+
+-- | Run in a modified codepage where we can print UTF-8 values on Windows.
+withUnicode :: IO a -> IO a
+##ifdef USE_CP
+withUnicode m = do
+  cp <- c_GetConsoleCP
+  (c_SetConsoleCP 65001 >> m) `finally` c_SetConsoleCP cp
+##else
+withUnicode m = m
+##endif
+
+main :: IO ()
+main = withUnicode $ do
+    traverse_ putStrLn args
+    doctest args
+  where
+    args = 
+      "-XOverloadedStrings" :
+      "-XFlexibleInstances" :
+      "-XMultiParamTypeClasses" :
+      flags ++ pkgs ++ module_sources
