diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -95,29 +95,7 @@
 Submit a PR if you'd like a language to be added. There will eventually be
 support for this to be user configurable
 
-### Installing
-
-The easiest way to get toodles is via [stack](https://docs.haskellstack.org).
-Just a `stack install --resolver=lts-12.14 toodles` and you're done! Alternatively, with GHC 8.4.3
-you can use [cabal](https://www.haskell.org/cabal/download.html). If there is
-desire for it I can look into precompiled distribution.
-
-### Running
-
-Invoking `toodles` with no arguments will treat the current directory as the
-project root and will start a server on port 9001. You can set these with the
-`-d` and `-p` flags, respectively.
-
-
-```bash
-# $ toodles -d <root directory of your project> -p <port to run server>
-# for more info run:
-# $ toodles --help
-$ toodles -d /path/to/your/project -p 9001
-# or simply
-$ toodles
-```
-#### Running with Docker
+### Running with Docker
 
 You can run a pre-built toodles for your current directory via docker:
 
@@ -141,23 +119,40 @@
 
 ```
 
-### Background
+### Installing manually
 
-I work at a small startup called DotDashPay and over time the TODOs in our code
-base continued building up to the point where it was difficult to use them
-holistically. While the information in the TODOs was actually very useful and
-methodically written, the fact that were couldn't easily organize them started
-to weigh on us as mounting tech debt.
+The best way to install manually is with [stack](https://docs.haskellstack.org).
+Just a `stack install --resolver=lts-12.14 toodles` and you're done! Alternatively, with GHC 8.4.3
+you can use [cabal](https://www.haskell.org/cabal/download.html).
 
-While not our main product focus, we try hard to find opportunities to build
-tools that make use of the organization schemes we already have in place, since
-doing so is a big win for us. Toodles became a nights and weekends side
-project to use the pre-existing TODO scheme we had spent years using, but had
-never effectively capitalized on.
+You'll also need to PyCrypto. If you have pip, you can run:
 
-A quick plug if you also like building great tools, like working in a fast paced
-startup environment, and are located in the SF Bay Area: Reach out at
-careers@dotdashpay.com and come work with us!
+```bash
+$ pip install pycrypto
+```
+
+If you've closed the toodles repo directly, you can also run
+
+```bash
+$ cd path/to/toodles
+$ pip install -r requirements.txt
+```
+
+### Running
+
+Invoking `toodles` with no arguments will treat the current directory as the
+project root and will start a server on port 9001. You can set these with the
+`-d` and `-p` flags, respectively.
+
+
+```bash
+# $ toodles -d <root directory of your project> -p <port to run server>
+# for more info run:
+# $ toodles --help
+$ toodles -d /path/to/your/project -p 9001
+# or simply
+$ toodles
+```
 
 ### Contributing
 
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -3,6 +3,7 @@
 module Main where
 
 import           Config
+import           License
 import           Paths_toodles
 import           Server
 import           Types
@@ -15,6 +16,9 @@
 
 main :: IO ()
 main = do
+  dataDir <- getDataDir
+  licenseRead <- readLicense (dataDir ++ "/toodles-license-public-key.pem") "/etc/toodles/license.json"
+  let license = (either (BadLicense) (id) licenseRead)
   userArgs <- toodlesArgs >>= setAbsolutePath
   case userArgs of
     (ToodlesArgs _ _ _ _ True _) -> do
@@ -23,9 +27,9 @@
     _ -> do
       let webPort = fromMaybe 9001 $ port userArgs
       ref <- newIORef Nothing
-      dataDir <- (++ "/web") <$> getDataDir
       putStrLn $ "serving on " ++ show webPort
-      run webPort $ app $ ToodlesState ref dataDir
+      tierRef <- newIORef license
+      run webPort $ app $ ToodlesState ref (dataDir ++ "/web") tierRef
 
 prettyFormat :: TodoEntry -> String
 prettyFormat (TodoEntryHead _ l a p n entryPriority f _ _ _ _ _ _) =
diff --git a/src/License.hs b/src/License.hs
new file mode 100644
--- /dev/null
+++ b/src/License.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE DeriveAnyClass      #-}
+{-# LANGUAGE DeriveGeneric       #-}
+{-# LANGUAGE GADTs               #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+
+module License
+  (
+    UserTier(..),
+    ToodlesLicense(..),
+    readLicense
+  ) where
+
+import           Paths_toodles
+
+import           Data.Aeson
+import qualified Data.ByteString.Base64.Lazy as B64
+import qualified Data.ByteString.Lazy.Char8  as LB
+import           Data.Text                   (Text)
+import qualified Data.Text                   as T
+import           Data.Time.Clock.POSIX
+import           GHC.Generics
+import           System.Directory
+import           System.Exit
+import           System.Process
+
+data UserTier
+  = BadLicense String
+  | NoLiscense
+  | Individual
+  | Commercial
+  deriving (Show, Eq, Ord, Generic, ToJSON, FromJSON)
+
+data License = License {
+  payload          :: ToodlesLicense,
+  encoded          :: Text,
+  payloadSignature :: Text
+                       } deriving (Generic, FromJSON, ToJSON, Show)
+
+data ToodlesLicense = ToodlesLicense
+  { validStart :: Integer
+  , validEnd   :: Integer
+  , email      :: Text
+  , reference  :: Text
+  , product    :: Text
+  } deriving (Generic, FromJSON, ToJSON, Show)
+
+readLicense :: FilePath -> FilePath  -> IO (Either String UserTier)
+readLicense publicKeyPath licensePath  = do
+  licenseExists <- doesFileExist licensePath
+  if not licenseExists then
+    return $ Right NoLiscense
+  else do
+    parsedContents <- eitherDecodeFileStrict licensePath
+    either (return . Left) (isLicenseValid publicKeyPath) parsedContents
+
+isLicenseValid :: FilePath -> License -> IO (Either String UserTier)
+isLicenseValid publicKeyPath (License _ encodedPayload sig) = do
+  dataDir <- getDataDir
+  now <- ((* 1000) . round) `fmap` getPOSIXTime
+  -- dependencies for license verification. for now, python
+  -- TODO(#techdebt) - license verification should be done in haskell, this removed
+  let args =
+        [ dataDir ++ "/verify.py"
+        , publicKeyPath
+        , T.unpack sig
+        , T.unpack encodedPayload
+        ]
+      decodedPayload =
+        decode (B64.decodeLenient . LB.pack $ T.unpack encodedPayload)
+  (exitcode, stdout, stderr) <- readProcessWithExitCode "python" args ""
+  putStrLn stderr
+  return $
+    let validated = ("True" == T.strip (T.pack stdout))
+    in if (exitcode == ExitSuccess) &&
+          validated && (maybe 0 validEnd decodedPayload >= now)
+         then Right Commercial
+         else Left "Invalid license file"
diff --git a/src/Server.hs b/src/Server.hs
--- a/src/Server.hs
+++ b/src/Server.hs
@@ -7,7 +7,9 @@
 module Server where
 
 import           Config
+import           License
 import           Parse
+import           Paths_toodles
 import           ToodlesApi
 import           Types
 
@@ -34,6 +36,9 @@
 import           Text.Printf
 import           Text.Regex.Posix
 
+freeResultsLimit :: Int
+freeResultsLimit = 100
+
 data ToodlesConfig = ToodlesConfig
   { ignore :: [FilePath]
   , flags  :: [UserFlag]
@@ -54,18 +59,31 @@
     server = liftIO . getFullSearchResults s
         :<|> deleteTodos s
         :<|> editTodos s
+        :<|> getLicense s
         :<|> serveDirectoryFileServer (dataPath s)
         :<|> showRawFile s
         :<|> root s
 
 root :: ToodlesState -> [Text] -> Handler Html
-root (ToodlesState _ dPath) path =
+root (ToodlesState _ dPath _) path =
     if null path then
         liftIO $ BZ.preEscapedToHtml <$> readFile (dPath ++ "/html/index.html")
     else throwError $ err404 { errBody = "Not found" }
 
+getLicense :: ToodlesState -> Handler GetLicenseResponse
+getLicense (ToodlesState _ _ tierRef) = do
+  license <- liftIO readUserTier
+  _ <- liftIO $ atomicModifyIORef' tierRef (const (license, license))
+  return $ GetLicenseResponse license
+
+readUserTier :: IO UserTier
+readUserTier = do
+  dataDir <- getDataDir
+  licenseRead <- readLicense (dataDir ++ "/toodles-license-public-key.pem") "/etc/toodles/license.json"
+  return $ either BadLicense id licenseRead
+
 showRawFile :: ToodlesState -> Integer -> Handler Html
-showRawFile (ToodlesState ref _) eId = do
+showRawFile (ToodlesState ref _ _) eId = do
     storedResults <- liftIO $ readIORef ref
     case storedResults of
       (Just (TodoListResult r _)) -> do
@@ -88,7 +106,7 @@
                 codeLines)
 
 editTodos :: ToodlesState -> EditTodoRequest -> Handler Text
-editTodos s@(ToodlesState ref _) req = do
+editTodos s@(ToodlesState ref _ _) req = do
   storedResults <- liftIO $ readIORef ref
   case storedResults of
     (Just (TodoListResult r _)) -> do
@@ -133,10 +151,10 @@
 data UpdateType = UpdateTypeEdit | UpdateTypeDelete deriving (Eq)
 
 updateCache :: MonadIO m => ToodlesState -> [TodoEntry] -> m ()
-updateCache (ToodlesState ref _) entries = do
+updateCache (ToodlesState ref _ _) entries = do
   storedResults <- liftIO $ readIORef ref
   case storedResults of
-    (Just (TodoListResult currentCache _)) -> do
+    (Just (TodoListResult currentCache resultLimit)) -> do
       let idsToUpdate = map entryId entries
           newCache =
             TodoListResult
@@ -144,7 +162,7 @@
                  (filter
                     (\item -> entryId item `notElem` idsToUpdate)
                     currentCache))
-              "edits applied"
+              resultLimit
       _ <-
         liftIO $ atomicModifyIORef' ref (const (Just newCache, Just newCache))
       return ()
@@ -214,7 +232,7 @@
     slice a b xs = take (b - a + 1) (drop a xs)
 
 deleteTodos :: ToodlesState -> DeleteTodoRequest -> Handler Text
-deleteTodos (ToodlesState ref _) req = do
+deleteTodos (ToodlesState ref _ _) req = do
     storedResults <- liftIO $ readIORef ref
     case storedResults of
       (Just refVal@(TodoListResult r _)) -> do
@@ -268,16 +286,16 @@
     return $ args {directory = absolute}
 
 getFullSearchResults :: ToodlesState -> Bool -> IO TodoListResult
-getFullSearchResults (ToodlesState ref _) recompute = do
+getFullSearchResults (ToodlesState ref _ tierRef) recompute = do
   result <- readIORef ref
+  userLicense <- readIORef tierRef
   if recompute || isNothing result
     then do
       putStrLn "refreshing todo's"
       userArgs <- toodlesArgs >>= setAbsolutePath
-      sResults <- runFullSearch userArgs
+      sResults <- runFullSearch (userArgs { limit_results = if userLicense == Commercial then 0 else freeResultsLimit})
       atomicModifyIORef' ref (const (Just sResults, sResults))
-    else do
-      putStrLn "cached read"
+    else
       return $ fromMaybe (error "tried to read from the cache when there wasn't anything there") result
 
 runFullSearch :: ToodlesArgs -> IO TodoListResult
@@ -295,7 +313,8 @@
     let filteredTodos = filter (filterSearch (assignee_search userArgs)) parsedTodos
         resultList = limitSearch filteredTodos $ limit_results userArgs
         indexedResults = map (\(i, r) -> r {entryId = i}) $ zip [1 ..] resultList
-    return $ TodoListResult indexedResults ""
+        limit = limit_results userArgs
+    return $ TodoListResult indexedResults (limit /= 0 && (length indexedResults >= limit))
 
     where
     filterSearch :: Maybe SearchFilter -> TodoEntry -> Bool
@@ -343,7 +362,7 @@
     fileHasValidExtension path = any (\ext -> ext `T.isSuffixOf` T.pack path) (map extension fileTypeToComment)
 
     isValidFile :: FilePath -> Bool
-    isValidFile path = (not $ ignorePath path) && fileHasValidExtension path
+    isValidFile path = not (ignorePath path) && fileHasValidExtension path
 
 
 mapHead :: (a -> a) -> [a] -> [a]
diff --git a/src/ToodlesApi.hs b/src/ToodlesApi.hs
--- a/src/ToodlesApi.hs
+++ b/src/ToodlesApi.hs
@@ -1,10 +1,10 @@
-{-# LANGUAGE DataKinds,
-             ScopedTypeVariables,
-             TypeOperators #-}
+{-# LANGUAGE DataKinds           #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeOperators       #-}
 
 module ToodlesApi where
 
-import Types
+import           Types
 
 import           Data.Proxy         (Proxy)
 import           Data.Text          (Text)
@@ -17,6 +17,8 @@
              :<|> "todos" :> "delete" :> ReqBody '[JSON] DeleteTodoRequest :> Post '[JSON] Text
 
              :<|> "todos" :> "edit" :> ReqBody '[JSON] EditTodoRequest :> Post '[JSON] Text
+
+             :<|> "license" :> Post '[JSON] GetLicenseResponse
 
              :<|> "static" :> Raw
 
diff --git a/src/Types.hs b/src/Types.hs
--- a/src/Types.hs
+++ b/src/Types.hs
@@ -5,6 +5,8 @@
 
 module Types where
 
+import           License
+
 import           Data.Aeson       (FromJSON, ToJSON, Value (String), parseJSON,
                                    toJSON)
 import           Data.Aeson.Types (typeMismatch)
@@ -24,7 +26,8 @@
 
 data ToodlesState = ToodlesState
   { results  :: IORef (Maybe TodoListResult),
-    dataPath :: FilePath
+    dataPath :: FilePath,
+    userTier :: IORef UserTier
   }
 
 data CommentType = SingleLine | MultiLine deriving (Show, Eq, Generic)
@@ -58,7 +61,7 @@
 
 data TodoListResult = TodoListResult
   { todos   :: [TodoEntry]
-  , message :: Text
+  , limited :: Bool
   } deriving (Show, Generic)
 instance ToJSON TodoListResult
 
@@ -75,6 +78,11 @@
   , setPriority :: Maybe Integer
   } deriving (Show, Generic)
 instance FromJSON EditTodoRequest
+
+data GetLicenseResponse = GetLicenseResponse
+  { toodlesTier:: UserTier
+  } deriving (Show, Generic)
+instance ToJSON GetLicenseResponse
 
 data Flag = TODO | FIXME | XXX | UF UserFlag
   deriving (Generic)
diff --git a/toodles-license-public-key.pem b/toodles-license-public-key.pem
new file mode 100644
--- /dev/null
+++ b/toodles-license-public-key.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvrVba8B99vGOUvHUSNSv
+T6idquIYp3CdhRww2VckyrVwqCeFNePm6EDhHAsG4SVd94HQDpZdJd/Oohrqlt1I
+UccaPse4W3J9nMJx82H9BK91VuFWLKvPAEbeQn+uStRsSeAPeIBVo+y4i5cYPqdV
+eN6me2r79/cNwD21auTGTsTLJzY+RaiRA+pnmJuXvxhMjyER/GUn6hikJWrSF+6e
+9txokiJR9r2hCv2JQqYHWp/moF/nd/25aTrGEgiWz+RGY15P4UUk1Ju1DmNUQA4/
+RmOe1XnXw0X8aoKhuhTjZw5COsq5uHTq7tqC/CemQpldsETqj19XYO3TNi7ojmfa
+EQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/toodles.cabal b/toodles.cabal
--- a/toodles.cabal
+++ b/toodles.cabal
@@ -1,6 +1,6 @@
 cabal-version: 1.12
 name: toodles
-version: 1.0.3
+version: 1.1.0
 license: MIT
 license-file: LICENSE
 copyright: 2018 Avi Press
@@ -24,6 +24,8 @@
     web/fonts/fontawesome-webfont.woff
     web/fonts/fontawesome-webfont.woff2
     web/img/favicon.png
+    verify.py
+    toodles-license-public-key.pem
 extra-source-files:
     README.md
 
@@ -38,6 +40,7 @@
         Config
         ToodlesApi
         Server
+        License
     hs-source-dirs: src
     other-modules:
         Paths_toodles
@@ -45,21 +48,24 @@
     ghc-options: -Wall -Wcompat
     build-depends:
         MissingH >=1.4.0.1 && <1.5,
+        RSA >=2.3.0 && <2.4,
         aeson ==1.3.1.1,
-        base >=4.0 && <5,
+        base >=4.4.0 && <5,
+        base64-bytestring ==1.0.0.1,
         blaze-html ==0.9.1.1,
+        bytestring >=0.10.8.2 && <0.11,
         cmdargs ==0.10.20,
         directory ==1.3.1.5,
         extra ==1.6.13,
-        hspec >=2.4.4 && <2.6,
-        hspec-expectations >=0.8.2 && <0.9,
         megaparsec ==6.5.0,
+        process >=1.6.3.0 && <1.7,
         regex-posix ==0.95.2,
         servant ==0.14.1,
         servant-blaze ==0.8,
         servant-server ==0.14.1,
         strict ==0.3.2,
         text ==1.2.3.1,
+        time >=1.8.0.2 && <1.9,
         wai ==3.2.1.2,
         warp ==3.2.25,
         yaml ==0.8.32
@@ -69,6 +75,7 @@
     hs-source-dirs: app src
     other-modules:
         Config
+        License
         Parse
         Server
         ToodlesApi
@@ -79,21 +86,24 @@
                  -with-rtsopts=-N
     build-depends:
         MissingH >=1.4.0.1 && <1.5,
+        RSA >=2.3.0 && <2.4,
         aeson ==1.3.1.1,
-        base >=4.0 && <5,
+        base >=4.4.0 && <5,
+        base64-bytestring ==1.0.0.1,
         blaze-html ==0.9.1.1,
+        bytestring >=0.10.8.2 && <0.11,
         cmdargs ==0.10.20,
         directory ==1.3.1.5,
         extra ==1.6.13,
-        hspec >=2.4.4 && <2.6,
-        hspec-expectations >=0.8.2 && <0.9,
         megaparsec ==6.5.0,
+        process >=1.6.3.0 && <1.7,
         regex-posix ==0.95.2,
         servant ==0.14.1,
         servant-blaze ==0.8,
         servant-server ==0.14.1,
         strict ==0.3.2,
         text ==1.2.3.1,
+        time >=1.8.0.2 && <1.9,
         wai ==3.2.1.2,
         warp ==3.2.25,
         yaml ==0.8.32
@@ -104,6 +114,7 @@
     hs-source-dirs: test src
     other-modules:
         Config
+        License
         Parse
         Server
         ToodlesApi
@@ -114,20 +125,24 @@
     build-depends:
         MissingH >=1.4.0.1 && <1.5,
         aeson ==1.3.1.1,
-        base >=4.0 && <5,
+        base >=4.4.0 && <5,
+        base64-bytestring ==1.0.0.1,
         blaze-html ==0.9.1.1,
+        bytestring >=0.10.8.2 && <0.11,
         cmdargs ==0.10.20,
         directory ==1.3.1.5,
         extra ==1.6.13,
         hspec >=2.4.4 && <2.6,
         hspec-expectations >=0.8.2 && <0.9,
         megaparsec ==6.5.0,
+        process >=1.6.3.0 && <1.7,
         regex-posix ==0.95.2,
         servant ==0.14.1,
         servant-blaze ==0.8,
         servant-server ==0.14.1,
         strict ==0.3.2,
         text ==1.2.3.1,
+        time >=1.8.0.2 && <1.9,
         toodles -any,
         wai ==3.2.1.2,
         warp ==3.2.25,
diff --git a/verify.py b/verify.py
new file mode 100644
--- /dev/null
+++ b/verify.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+import sys
+from Crypto.PublicKey import RSA
+from Crypto.Signature import PKCS1_v1_5
+from Crypto.Hash import SHA512
+from base64 import b64decode, b64encode
+
+def verify_sign(public_key_loc, signature, data):
+    with open(public_key_loc, "r") as pub_key_file:
+        pub_key = pub_key_file.read()
+        rsakey = RSA.importKey(pub_key)
+        signer = PKCS1_v1_5.new(rsakey)
+        digest = SHA512.new()
+        digest.update(data)
+        if signer.verify(digest, b64decode(signature)):
+            return True
+        return False
+
+if __name__ == "__main__":
+    print(verify_sign(sys.argv[1], sys.argv[2], sys.argv[3]))
+
diff --git a/web/css/toodles.css b/web/css/toodles.css
--- a/web/css/toodles.css
+++ b/web/css/toodles.css
@@ -146,3 +146,19 @@
 .input-error {
     color: red;
 }
+
+.bad-license-banner {
+    background-color: #d1345b;
+    color: white;
+    text-align: center;
+}
+
+.bad-license-banner a {
+    text-decoration: underline;
+    color: white;
+}
+
+.warning-banner {
+    background-color: #FFBE0B;
+}
+
diff --git a/web/html/index.html b/web/html/index.html
--- a/web/html/index.html
+++ b/web/html/index.html
@@ -15,7 +15,7 @@
             <div class="navbar-brand">
                 <div class="navbar-item" href="#">
                     <span><img src="/static/img/favicon.png" alt="favicon" height="25" width="25"></span>
-                    <span class="toodles-nav-title-text">Toodles</span>
+                    <span class="toodles-nav-title-text">Toodles <span v-show="license == 'Commercial'">| Pro</span></span>
                 </div>
                 <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" v-on:click="toggleMenuBurger">
                     <span aria-hidden="true"></span>
@@ -58,6 +58,19 @@
                 </div>
             </div>
         </nav>
+
+        <section v-show="license == 'BadLicense'" class="bad-license-banner is-fullwidth" >
+            <div class="container">
+                Your license file is invalid. <a v-on:click="getLicense">Try again</a>
+            </div>
+        </section>
+
+        <section v-show="limited" class="warning-banner is-fullwidth" >
+            <div class="container">
+                The free edition of Toodles lets you manage up to 100 TODO's, so your results here are truncated. Please <a href="http://toodles.avi.press/#pricing">upgrade to Toodles Pro</a> in order to manage all of your code's TODO's.
+            </div>
+        </section>
+
         <div class="modal">
             <div class="modal-background" @click="closeModal"></div>
             <div class="modal-content">
diff --git a/web/js/app.js b/web/js/app.js
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -17,12 +17,17 @@
         addKeyVals: "",
         addKeyValParseError: false,
         nothingFilledError: false,
+        license: null,
+        loadingLicense: false,
+        limited: false,
       }
     },
     created: function() {
       this.refresh()()
+      return this.getLicense()
     },
     methods: {
+      // higher order for ease of calling from vue templates
       refresh: function(recompute) {
         return function() {
           this.loading = true
@@ -49,7 +54,7 @@
                   selected: false
                 }
               })
-              console.log("todos", this.todos)
+              this.limited = data.limited
               this.loading = false
 
               if (!recompute) {
@@ -120,7 +125,7 @@
           console.log(name)
         }
       },
-      
+
       toggleTodo: function(todo) {
         todo.selected = !todo.selected
       },
@@ -182,6 +187,24 @@
       hideDropdown: function(ev) {
         $(".navbar-menu").removeClass("is-active")
         $(".navbar-burger").removeClass("is-active")
+      },
+
+      getLicense: function() {
+        const self = this
+        self.loadingLicense = true
+        $.ajax({
+          url: "/license",
+          type: "POST",
+          dataType: "json",
+          contentType: 'application/json',
+          success: function(data){
+            self.license = data.toodlesTier.tag
+            self.loadingLicense = false
+          },
+          error: function(err){
+            console.error(err)
+          }
+        })
       },
 
       submitTodoEdits: function(){
