diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,10 @@
+Changelog
+=========
+
+Version 0.1.0.0
+---------------
+
+<https://github.com/mstksg/servant-validate/releases/tag/v0.1.0.0>
+
+*   Initial release.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (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,28 @@
+# servant-validate
+
+A package with "test suites" to help verify that your servant APIs are valid at
+compile-time.
+
+Currently the only property tested is that there are no duplicated paths.
+
+```haskell
+data DeadlySinEnum = Lust | Gluttony | Greed | Sloth | Wrath | Envy | Pride
+
+type MathApi  = "sin" :> ReqBody '[JSON] Double        :> Post '[JSON] NoContent
+type SatanApi = "sin" :> ReqBody '[JSON] DeadlySinEnum :> Post '[JSON] NoContent
+
+type MyApi = MathApi :<|> SatanApi
+
+myApi :: Proxy MyApi
+myApi = Proxy
+
+validMyApi :: ValidApiTree MyApi
+validMyApi = validApiTree myApi
+```
+
+```haskell
+warning: [-Wdeferred-type-errors]
+    • Duplicate method in API at path /sin: "POST"
+    • In the expression: validApiTree myApi
+      In an equation for ‘validMyApi’: validMyApi = validApiTree myApi
+```
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/servant-validate.cabal b/servant-validate.cabal
new file mode 100644
--- /dev/null
+++ b/servant-validate.cabal
@@ -0,0 +1,86 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 9d03cea5ce068721828151ec4a161c3038d17f7dabe935243dfa8403378463db
+
+name:           servant-validate
+version:        0.1.0.0
+synopsis:       Chekc static properties of servant APIs
+description:    A package with "test suites" to help verify that your servant APIs are valid at
+                compile-time.
+                .
+                Currently the only property tested is that there are no duplicated paths.
+                .
+                ```haskell
+                data DeadlySinEnum = Lust | Gluttony | Greed | Sloth | Wrath | Envy | Pride
+                .
+                type MathApi  = "sin" :> ReqBody '[JSON] Double        :> Post '[JSON] NoContent
+                type SatanApi = "sin" :> ReqBody '[JSON] DeadlySinEnum :> Post '[JSON] NoContent
+                .
+                type MyApi = MathApi :<|> SatanApi
+                .
+                myApi :: Proxy MyApi
+                myApi = Proxy
+                .
+                validMyApi :: ValidApiTree MyApi
+                validMyApi = validApiTree myApi
+                ```
+                .
+                ```haskell
+                warning: [-Wdeferred-type-errors]
+                    • Duplicate method in API at path /sin: "POST"
+                    • In the expression: validApiTree myApi
+                      In an equation for ‘validMyApi’: validMyApi = validApiTree myApi
+                ```
+category:       Web
+homepage:       https://github.com/mstksg/servant-validate#readme
+bug-reports:    https://github.com/mstksg/servant-validate/issues
+author:         Justin Le
+maintainer:     justin@jle.im
+copyright:      (c) 2021 Justin Le
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/mstksg/servant-validate
+
+library
+  exposed-modules:
+      Servant.Validate
+      Servant.Validate.Internal
+  other-modules:
+      Paths_servant_validate
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , containers
+    , servant
+    , text
+  default-language: Haskell2010
+
+test-suite servant-validate-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_servant_validate
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , containers
+    , hspec
+    , servant
+    , servant-validate
+    , should-not-typecheck
+    , text
+  default-language: Haskell2010
diff --git a/src/Servant/Validate.hs b/src/Servant/Validate.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/Validate.hs
@@ -0,0 +1,196 @@
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE BangPatterns          #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE LambdaCase            #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PatternSynonyms       #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE StandaloneDeriving    #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE UndecidableInstances  #-}
+{-# LANGUAGE ViewPatterns          #-}
+
+-- |
+-- Module      : Servant.Validate
+-- Copyright   : (c) Justin Le 2021
+-- License     : BSD3
+--
+-- Maintainer  : justin@jle.im
+-- Stability   : experimental
+-- Portability : non-portable
+--
+-- A package with "test suites" to help verify that your servant APIs are
+-- valid at compile-time.  Currently the only property tested is that there
+-- are no duplicated paths.  See README for more information on usage.
+
+module Servant.Validate
+  ( HasApiTree(..), MethodString(..)
+  , validApiTree, ValidApiTree
+  , reflectApiTree, reflectApiTree_
+  , SApiTree(..), reflectSApiTree
+  ) where
+
+import           Data.Kind
+import           Data.Map                  (Map)
+import           Data.Proxy
+import           Data.Set                  (Set)
+import           Data.Text                 (Text)
+import           Data.Type.Bool
+import           Data.Type.Equality
+import           GHC.TypeLits
+import           Servant.API
+import           Servant.Validate.Internal
+import           Type.Reflection
+import           Unsafe.Coerce
+import qualified Data.Map                  as M
+import qualified Data.Set                  as S
+import qualified Data.Text                 as T
+
+-- | Has a valid well-formed API Tree
+class HasApiTree (api :: Type) where
+    type ToApiTree api :: ApiTree
+
+    -- | Useful runtime witness of the API tree; use to inspect it with
+    -- 'reflectApiTree'.  This is not used in any part of the actual
+    -- validation; is just an extra treat.
+    sApiTree :: SApiTree (ToApiTree api)
+
+instance (KnownSymbol path, HasApiTree api) => HasApiTree ((path :: Symbol) :> api) where
+    type ToApiTree (path :> api) = 'Branch '[] '[ '(path, ToApiTree api) ]
+
+    sApiTree = SBranch PNil (Tup SSym (sApiTree @api) :< PNil)
+
+instance HasApiTree api => HasApiTree (Capture' mods sym a :> api) where
+    type ToApiTree (Capture' mods sym a :> api) =
+            'Branch '[] '[ '("<capture>", ToApiTree api) ]
+
+    sApiTree = SBranch PNil (Tup SSym (sApiTree @api) :< PNil)
+
+instance HasApiTree api => HasApiTree (CaptureAll sym v :> api) where
+    type ToApiTree (CaptureAll sym v :> api) =
+            'Branch '[] '[ '("<capture>", ToApiTree api) ]
+
+    sApiTree = SBranch PNil (Tup SSym (sApiTree @api) :< PNil)
+
+instance (HasApiTree a, HasApiTree b) => HasApiTree (a :<|> b) where
+    type ToApiTree (a :<|> b) = MergeTree '[] (ToApiTree a) (ToApiTree b)
+
+    sApiTree = sMergeTree @'[] (sApiTree @a) (sApiTree @b)
+
+-- | A type-level version of 'Servant.API.Verbs.ReflectMethod'.
+class MethodString k where
+    type ToMethodString (x :: k) :: Symbol
+
+instance MethodString StdMethod where
+    type ToMethodString 'PATCH = "PATCH"
+    type ToMethodString 'OPTIONS = "OPTIONS"
+    type ToMethodString 'CONNECT = "CONNECT"
+    type ToMethodString 'TRACE = "TRACE"
+    type ToMethodString 'DELETE = "DELETE"
+    type ToMethodString 'PUT = "PUT"
+    type ToMethodString 'HEAD = "HEAD"
+    type ToMethodString 'POST = "POST"
+    type ToMethodString 'GET = "GET"
+
+instance MethodString Symbol where
+    type ToMethodString (m :: Symbol) = m
+
+instance (MethodString k, KnownSymbol (ToMethodString m)) => HasApiTree (Verb (m :: k) s t a) where
+    type ToApiTree (Verb m s t a) = 'Branch '[ToMethodString m] '[]
+    sApiTree = SBranch (SSym :< PNil) PNil
+
+instance HasApiTree api => HasApiTree (Summary s :> api) where
+    type ToApiTree (Summary s :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (Description s :> api) where
+    type ToApiTree (Description s :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (QueryFlag s :> api) where
+    type ToApiTree (QueryFlag s :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (QueryParams s a :> api) where
+    type ToApiTree (QueryParams s a :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (QueryParam' mods sym a :> api) where
+    type ToApiTree (QueryParam' mods sym a :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (Header' mods sym a :> api) where
+    type ToApiTree (Header' mods sym a :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (HttpVersion :> api) where
+    type ToApiTree (HttpVersion :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (Vault :> api) where
+    type ToApiTree (Vault :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (BasicAuth realm a :> api) where
+    type ToApiTree (BasicAuth realm a :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (AuthProtect tag :> api) where
+    type ToApiTree (AuthProtect tag :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (IsSecure :> api) where
+    type ToApiTree (IsSecure :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (RemoteHost :> api) where
+    type ToApiTree (RemoteHost :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (ReqBody' mods ct a :> api) where
+    type ToApiTree (ReqBody' mods ct a :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+instance HasApiTree api => HasApiTree (StreamBody' mods framing ct a :> api) where
+    type ToApiTree (StreamBody' mods framing ct a :> api) = ToApiTree api
+    sApiTree = sApiTree @api
+
+-- | To be used with 'validApiTree'.
+type ValidApiTree api = TypeRep (ToApiTree api)
+
+-- | The full validator.  To use:
+--
+-- @
+-- serverApi :: Proxy ServerApi
+-- serverApi = Proxy
+--
+-- validServerApi :: ValidApiTree ServerApi
+-- validServerApi = validApiTree serverApi
+-- @
+validApiTree :: forall api. (HasApiTree api, Typeable (ToApiTree api)) => Proxy api -> ValidApiTree api
+validApiTree _ = typeRep
+
+data ApiTreeMap = BranchesMap (Set Text) (Map Text ApiTreeMap)
+  deriving (Show, Eq, Ord)
+
+-- | Version of 'reflectApiTree' taking an explicit 'TypeRep'.
+reflectApiTree_ :: TypeRep (apiTree :: ApiTree) -> ApiTreeMap
+reflectApiTree_ = reflectSApiTree . toSApiTree
+
+-- | Useful utility to view the routing structure of a tree; similar to
+-- 'Servant.Server.layout'.
+reflectApiTree :: forall api. (HasApiTree api, Typeable (ToApiTree api)) => ApiTreeMap
+reflectApiTree = reflectApiTree_ (typeRep @(ToApiTree api))
+
+reflectSApiTree :: SApiTree api -> ApiTreeMap
+reflectSApiTree (SBranch ms ts) = BranchesMap
+    (S.fromAscList (reflectProd reflectSSym ms))
+    (M.fromAscList (reflectProd (reflectTup reflectSSym reflectSApiTree) ts))
diff --git a/src/Servant/Validate/Internal.hs b/src/Servant/Validate/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/Validate/Internal.hs
@@ -0,0 +1,193 @@
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE LambdaCase            #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE StandaloneDeriving    #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE UndecidableInstances  #-}
+
+-- |
+-- Module      : Servant.Validate.Internal
+-- Copyright   : (c) Justin Le 2021
+-- License     : BSD3
+--
+-- Maintainer  : justin@jle.im
+-- Stability   : experimental
+-- Portability : non-portable
+--
+-- Internal type-level tools.
+
+module Servant.Validate.Internal (
+    ApiTree(..)
+  , Compare, Cases, MergeMethods, MergePaths, MergeTree
+  , compSym, sMergeMethods, sMergePaths, sMergeTree
+  , SApiTree(..) , toSApiTree
+  , Prod(..), Tup(..), SSym(..)
+  , toProd, reflectProd, toTup, reflectTup, toSSym, reflectSSym
+  ) where
+
+import           Data.Kind
+import           Unsafe.Coerce
+import           Data.Proxy
+import           Data.Text       (Text)
+import           GHC.TypeLits
+import           Type.Reflection
+import qualified Data.Text       as T
+
+data ApiTree = Branch [Symbol] [(Symbol, ApiTree)]
+
+type family Compare (a :: k) (b :: k) :: Ordering
+type instance Compare (a :: Symbol) (b :: Symbol) = CmpSymbol a b
+
+type family Cases (c :: Ordering) (lt :: k) (eq :: k) (gt :: k) where
+    Cases 'LT lt eq gt = lt
+    Cases 'EQ lt eq gt = eq
+    Cases 'GT lt eq gt = gt
+
+type family MergeMethods err (xs :: [k]) (ys :: [k]) :: [k] where
+    MergeMethods err '[]       '[]       = '[]
+    MergeMethods err '[]       (y ': ys) = y ': ys
+    MergeMethods err (x ': xs) '[]       = x ': xs
+    MergeMethods err (x ': xs) (y ': ys) = Cases
+      (Compare x y)
+      (x ': MergeMethods err xs (y ': ys))
+      (TypeError (err ':<>: 'Text ": " ':<>: ShowType x))
+      (y ': MergeMethods err (x ': xs) ys)
+
+type family MergePaths (base :: [Symbol]) (xs :: [(Symbol, ApiTree)]) (ys :: [(Symbol, ApiTree)]) :: [(Symbol, ApiTree)] where
+    MergePaths base '[]                '[]                = '[]
+    MergePaths base '[]                ( '(b, y) ': bys ) = '(b, y) ': bys
+    MergePaths base ( '(a, x) ': axs ) '[]                = '(a, x) ': axs
+    MergePaths base ( '(a, x) ': axs ) ( '(b, y) ': bys ) = Cases
+      (Compare a b)
+      ( '(a, x) ': MergePaths base axs ( '(b, y) ': bys ) )
+      ( '(a, MergeTree (a ': base) x y) ': MergePaths base axs bys )
+      ( '(b, y) ': MergePaths base ( '(a, x) ': axs ) bys )
+
+type family MergeTree (base :: [Symbol]) (a :: ApiTree) (b :: ApiTree) :: ApiTree where
+    MergeTree base ('Branch mA pA) ('Branch mB pB) =
+        'Branch
+            (MergeMethods
+                ('Text "Duplicate method in API at path " ':<>: 'Text ("/" `AppendSymbol` ShowPath base))
+                mA mB
+            )
+            (MergePaths base pA pB)
+
+type family ShowPath (path :: [Symbol]) :: Symbol where
+    ShowPath '[] = ""
+    ShowPath '[x] = x
+    ShowPath (x ': y ': xs) = x `AppendSymbol` "/" `AppendSymbol` ShowPath (y ': xs)
+
+
+
+
+
+-- a bunch of stuff to avoid needing a singletons dep.  this isn't needed
+-- for any of the compile-stuff, it's just useful for reflection to
+-- a value-level map
+
+
+data Prod :: (k -> Type) -> [k] -> Type where
+    PNil :: Prod f '[]
+    (:<) :: f a -> Prod f as -> Prod f (a ': as)
+infixr 5 :<
+deriving instance (forall a. Show (f a)) => Show (Prod f as)
+
+toProd :: forall k (as :: [k]) f. (forall (a :: k). TypeRep a -> f a) -> TypeRep as -> Prod f as
+toProd f = go
+  where
+    go :: forall (bs :: [k]). TypeRep bs -> Prod f bs
+    go = \case
+      Con _            -> unsafeCoerce PNil
+      App (App _ x) xs -> unsafeCoerce $ f (unsafeCoerce x) :< go (unsafeCoerce xs)
+
+reflectProd :: forall k (as :: [k]) f r. (forall (a :: k). f a -> r) -> Prod f as -> [r]
+reflectProd f = go
+  where
+    go :: forall (bs :: [k]). Prod f bs -> [r]
+    go = \case
+      PNil    -> []
+      x :< xs -> f x : go xs
+
+data Tup :: (a -> Type) -> (b -> Type) -> (a, b) -> Type where
+    Tup :: f x -> g y -> Tup f g '(x, y)
+deriving instance (forall a. Show (f a), forall a. Show (g a)) => Show (Tup f g xy)
+
+toTup :: (forall x. TypeRep x -> f x) -> (forall x. TypeRep x -> g x) -> TypeRep xy -> Tup f g xy
+toTup f g (App (App _ x) y) = unsafeCoerce $ Tup (f (unsafeCoerce x)) (g (unsafeCoerce y))
+
+reflectTup :: forall j k (xy :: (j, k)) f g a b. (forall (x :: j). f x -> a) -> (forall (y :: k). g y -> b) -> Tup f g xy -> (a, b)
+reflectTup f g (Tup x y) = (f x, g y)
+
+data SSym :: Symbol -> Type where
+    SSym :: KnownSymbol s => SSym s
+deriving instance Show (SSym s)
+toSSym :: TypeRep s -> SSym s
+toSSym tr = case someSymbolVal (read (show tr) :: String) of
+  SomeSymbol (Proxy :: Proxy b) -> unsafeCoerce (SSym :: SSym b)
+reflectSSym :: forall s. SSym s -> Text
+reflectSSym s@SSym = T.pack $ symbolVal s
+
+data SApiTree :: ApiTree -> Type where
+    SBranch :: Prod SSym ms -> Prod (Tup SSym SApiTree) ts -> SApiTree ('Branch ms ts)
+deriving instance Show (SApiTree api)
+toSApiTree :: TypeRep api -> SApiTree api
+toSApiTree (App (App _ ms) ts) = unsafeCoerce $
+    SBranch (toProd toSSym (unsafeCoerce ms))
+            (toProd (toTup toSSym toSApiTree) (unsafeCoerce ts))
+
+data SOrdering :: Ordering -> Type where
+    SLT :: SOrdering 'LT
+    SEQ :: SOrdering 'EQ
+    SGT :: SOrdering 'GT
+
+compSym
+    :: forall a b. ()
+    => SSym a
+    -> SSym b
+    -> SOrdering (CmpSymbol a b)
+compSym a@SSym b@SSym = case compare (symbolVal a) (symbolVal b) of
+    LT -> unsafeCoerce SLT
+    EQ -> unsafeCoerce SEQ
+    GT -> unsafeCoerce SGT
+
+sMergeMethods
+    :: forall err xs ys. ()
+    => Prod SSym xs
+    -> Prod SSym ys
+    -> Prod SSym (MergeMethods err xs ys)
+sMergeMethods = \case
+    PNil -> \case
+      PNil    -> PNil
+      y :< ys -> y :< ys
+    x :< xs -> \case
+      PNil    -> x :< xs
+      y :< ys -> case compSym x y of
+        SLT -> x :< sMergeMethods @err xs (y :< ys)
+        SEQ -> error "sMergeMethods: forbidden by type system"
+        SGT -> y :< sMergeMethods @err (x :< xs) ys
+
+sMergePaths
+    :: forall base xs ys. ()
+    => Prod (Tup SSym SApiTree) xs
+    -> Prod (Tup SSym SApiTree) ys
+    -> Prod (Tup SSym SApiTree) (MergePaths base xs ys)
+sMergePaths = \case
+    PNil -> \case
+      PNil -> PNil
+      Tup b y :< bys -> Tup b y :< bys
+    Tup (a :: SSym a) x :< axs -> \case
+      PNil -> Tup a x :< axs
+      Tup b y :< bys -> case compSym a b of
+        SLT -> Tup a x :< sMergePaths @base axs (Tup b y :< bys)
+        SEQ -> Tup a (sMergeTree @(a ': base) x y) :< sMergePaths @base axs bys
+        SGT -> Tup b y :< sMergePaths @base (Tup a x :< axs) bys
+
+sMergeTree :: forall base a b. SApiTree a -> SApiTree b -> SApiTree (MergeTree base a b)
+sMergeTree (SBranch mA pA) (SBranch mB pB) = SBranch undefined (sMergePaths @base pA pB)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE DataKinds              #-}
+{-# LANGUAGE TypeApplications       #-}
+{-# LANGUAGE TypeOperators          #-}
+{-# OPTIONS_GHC -fdefer-type-errors #-}
+
+import Test.Hspec (hspec, describe, it)
+import Test.ShouldNotTypecheck (shouldNotTypecheck)
+
+import           Data.Proxy
+import           Servant.API
+import           Servant.Validate
+
+type TestApi = "hello" :> Get '[] ()
+          :<|> "ok" :> "bye" :> Get '[] ()
+          :<|> "ok" :> "what" :> Get '[] ()
+          :<|> "ok" :> "bye" :> Post '[] ()
+          :<|> "ok" :> "bye" :> Get '[] ()
+
+testApi :: Proxy TestApi
+testApi = Proxy
+
+validTestApi :: ValidApiTree TestApi
+validTestApi = validApiTree testApi
+
+data DeadlySinEnum = Lust | Gluttony | Greed | Sloth | Wrath | Envy | Pride
+
+type MathApi  = "sin" :> ReqBody '[JSON] Double        :> Post '[JSON] NoContent
+type SatanApi = "sin" :> ReqBody '[JSON] DeadlySinEnum :> Post '[JSON] NoContent
+
+type MyApi = MathApi :<|> SatanApi
+
+myApi :: Proxy MyApi
+myApi = Proxy
+
+validMyApi :: ValidApiTree MyApi
+validMyApi = validApiTree myApi
+
+main :: IO ()
+main = hspec $ do
+  describe "Servant" $ do
+    it "should not allow overlapping routes" $
+      shouldNotTypecheck validMyApi
+    it "should not allow overlapping routes (nested)" $
+      shouldNotTypecheck validTestApi
