diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.7.0.0] - 2021-06-22
+
+### Added
+
+ - Account: manageInstallationURL, installationIsSelection
+ - AccountInstallationStatus
+ - signOut
+ - Agent labels
+ - By name variations of account and state endpoints
+
 ## [0.6.0.1] - 2020-04-21
 
 ### Fixed
@@ -76,7 +86,9 @@
 
 Initial release
 
-[0.6.0.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.4.0.0...hercules-ci-api-0.6.0.0
+[0.6.0.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.6.0.1...hercules-ci-api-0.7.0.0
+[0.6.0.1]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.6.0.0...hercules-ci-api-0.6.0.1
+[0.6.0.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.5.0.0...hercules-ci-api-0.6.0.0
 [0.5.0.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.4.0.0...hercules-ci-api-0.5.0.0
 [0.4.0.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.3.0.0...hercules-ci-api-0.4.0.0
 [0.3.0.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.1.0.0...hercules-ci-api-0.3.0.0
diff --git a/hercules-ci-api.cabal b/hercules-ci-api.cabal
--- a/hercules-ci-api.cabal
+++ b/hercules-ci-api.cabal
@@ -1,6 +1,6 @@
 cabal-version:      1.12
 name:               hercules-ci-api
-version:            0.6.0.1
+version:            0.7.0.0
 synopsis:           Hercules CI API definition with Servant
 homepage:           https://github.com/hercules-ci/hercules-ci-agent#readme
 bug-reports:        https://github.com/hercules-ci/hercules-ci-agent/issues
@@ -20,16 +20,18 @@
     Hercules.API
     Hercules.API.Accounts
     Hercules.API.Accounts.Account
+    Hercules.API.Accounts.AccountInstallationStatus
     Hercules.API.Accounts.AccountSettings
     Hercules.API.Accounts.AccountSettingsPatch
     Hercules.API.Accounts.CLIAuthorizationRequest
     Hercules.API.Accounts.CLIAuthorizationRequestCreate
     Hercules.API.Accounts.CLIAuthorizationRequestCreateResponse
     Hercules.API.Accounts.CLIAuthorizationRequestStatus
+    Hercules.API.Accounts.CLIToken
+    Hercules.API.Accounts.CLITokensResponse
     Hercules.API.Agents
     Hercules.API.Agents.AgentInfo
     Hercules.API.Agents.AgentSession
-    Hercules.API.Agents.AgentSessionCreated
     Hercules.API.Agents.ClusterJoinToken
     Hercules.API.Agents.CreateClusterJoinToken
     Hercules.API.Agents.FullClusterJoinToken
@@ -55,6 +57,7 @@
     Hercules.API.Evaluation.AttributeError
     Hercules.API.Evaluation.Evaluation
     Hercules.API.Health
+    Hercules.API.Labels
     Hercules.API.Message
     Hercules.API.Organizations
     Hercules.API.Organizations.BillingInfo
@@ -155,7 +158,7 @@
 
   build-depends:
       aeson
-    , base
+    , base                     >=4.7    && <5
     , bytestring
     , containers
     , cookie
@@ -168,7 +171,7 @@
     , memory
     , network-uri
     , profunctors
-    , servant
+    , servant                  >=0.14.1
     , servant-auth
     , servant-auth-swagger
     , servant-swagger
diff --git a/src/Hercules/API.hs b/src/Hercules/API.hs
--- a/src/Hercules/API.hs
+++ b/src/Hercules/API.hs
@@ -8,6 +8,7 @@
     servantClientApi,
     swagger,
     useApi,
+    enterApiE,
     API,
     ClientAuth,
     HerculesAPI (..),
@@ -37,13 +38,13 @@
   )
 import Hercules.API.Effects (EffectsAPI)
 import Hercules.API.Health (HealthAPI)
-import Hercules.API.Id (Id)
-import Hercules.API.Name (Name)
 import Hercules.API.Organizations (OrganizationsAPI)
 import Hercules.API.Orphans ()
+import Hercules.API.Prelude
 import Hercules.API.Projects (ProjectsAPI)
 import Hercules.API.Repos (ReposAPI)
 import Hercules.API.Result (Result (..))
+import Hercules.API.Servant (useApi)
 import Hercules.API.State (StateAPI)
 import Servant.API
 import Servant.API.Generic
@@ -51,7 +52,6 @@
 import Servant.Auth.Swagger ()
 import Servant.Swagger
 import Servant.Swagger.UI.Core (SwaggerSchemaUI)
-import Prelude
 
 -- TODO remove health so we get clientapi
 data HerculesAPI auth f = HerculesAPI
@@ -94,7 +94,7 @@
 servantClientApi = Proxy
 
 type API auth =
-  (HerculesServantAPI auth)
+  HerculesServantAPI auth
     :<|> "api"
     :> SwaggerSchemaUI "v1" "swagger.json"
 
@@ -113,18 +113,6 @@
     & info
       . description
     ?~ "You have reached the Hercules Continuous Integration Application Programming Interface. This user interface provides human friendly access to the various endpoints. To get started with Hercules CI, see hercules-ci.com. Happy building! —the Hercules team"
-
--- | Postcomposes 'Servant.API.Generic.fromServant' to an accessor,
--- preserving the mode parameter, because otherwise the mode parameter
--- can not be inferred.
---
--- Ideally, this functionality would be built into a new combinator.
-useApi ::
-  (GenericServant f mode, GenericServant g mode) =>
-  (f mode -> ToServant g mode) ->
-  f mode ->
-  g mode
-useApi = (Servant.API.Generic.fromServant .)
 
 -- | 'Control.Monad.void' specialised to 'NoContent' to soothe the
 -- compiler that rightfully warns about throwing away a do notation
diff --git a/src/Hercules/API/Accounts.hs b/src/Hercules/API/Accounts.hs
--- a/src/Hercules/API/Accounts.hs
+++ b/src/Hercules/API/Accounts.hs
@@ -1,15 +1,19 @@
 {-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
 
 module Hercules.API.Accounts where
 
 import Hercules.API.Accounts.Account (Account)
+import Hercules.API.Accounts.AccountInstallationStatus (AccountInstallationStatus)
 import Hercules.API.Accounts.AccountSettings (AccountSettings)
 import Hercules.API.Accounts.AccountSettingsPatch (AccountSettingsPatch)
 import Hercules.API.Accounts.CLIAuthorizationRequest (CLIAuthorizationRequest)
 import Hercules.API.Accounts.CLIAuthorizationRequestCreate (CLIAuthorizationRequestCreate)
 import Hercules.API.Accounts.CLIAuthorizationRequestCreateResponse (CLIAuthorizationRequestCreateResponse)
 import Hercules.API.Accounts.CLIAuthorizationRequestStatus (CLIAuthorizationRequestStatus)
-import Hercules.API.Prelude
+import Hercules.API.Accounts.CLITokensResponse (CLITokensResponse)
+import Hercules.API.Prelude hiding (id)
 import Hercules.API.SourceHostingSite.SourceHostingSite
   ( SourceHostingSite,
   )
@@ -17,37 +21,60 @@
 import Servant.API.Generic
 import Servant.Auth ()
 
-data AccountsAPI auth f = AccountsAPI
-  { myAccount ::
-      f
-        :- Summary "The account of the authenticated user."
-        :> "accounts"
-        :> "me"
+data AccountResourceGroup auth f = AccountResourceGroup
+  { get ::
+      f :- Summary ("Get the account.")
         :> auth
         :> Get '[JSON] Account,
-    findAccounts ::
-      f
-        :- Summary "Accounts that the authenticated user owns, admins or collaborates with."
-        :> "accounts"
-        :> QueryParam "site" (Name SourceHostingSite)
-        :> QueryParam "name" (Name Account)
-        :> auth
-        :> Get '[JSON] [Account],
-    getAccountSettings ::
-      f :- Summary "Retrieve the account settings"
-        :> "accounts"
-        :> Capture "accountId" (Id Account)
+    getSettings ::
+      f :- Summary ("Get the account settings.")
         :> "settings"
         :> auth
         :> Get '[JSON] AccountSettings,
-    patchAccountSettings ::
-      f :- Summary "Update the account settings"
-        :> "accounts"
-        :> Capture "accountId" (Id Account)
+    patchSettings ::
+      f :- Summary ("Update the account settings.")
         :> "settings"
         :> ReqBody '[JSON] AccountSettingsPatch
         :> auth
         :> Patch '[JSON] AccountSettings,
+    postDisableAllProjects ::
+      f :- Summary ("Disable all projects in the account.")
+        :> "disable-all-projects"
+        :> auth
+        :> Post '[JSON] Int
+  }
+  deriving (Generic)
+
+accountById' id client = client `enterApiE` \api -> accountById api id
+
+accountByAuth' client = client `enterApiE` accountByAuth
+
+data AccountsAPI auth f = AccountsAPI
+  { accountByAuth ::
+      f
+        :- Substitute ("accounts" :> "me" :> Placeholder) (ToServantApi (AccountResourceGroup auth)),
+    accountById ::
+      f
+        :- Substitute
+             ("accounts" :> Capture "accountId" (Id Account) :> Placeholder)
+             (ToServantApi (AccountResourceGroup auth)),
+    accountByName ::
+      f
+        :- Substitute
+             ( "site"
+                 :> Capture "site" (Name SourceHostingSite)
+                 :> "account"
+                 :> Capture "account" (Name Account)
+                 :> Placeholder
+             )
+             (ToServantApi (AccountResourceGroup auth)),
+    findAccounts ::
+      f :- Summary "Accounts that the authenticated user owns, admins or collaborates with."
+        :> "accounts"
+        :> QueryParam "site" (Name SourceHostingSite)
+        :> QueryParam "name" (Name Account)
+        :> auth
+        :> Get '[JSON] [Account],
     postCLIAuthorizationRequest ::
       f :- Summary "Create a request to authorize the CLI."
         :> "auth"
@@ -84,12 +111,30 @@
         :> "confirm"
         :> auth
         :> Post '[JSON] NoContent,
-    postDisableAllProjects ::
-      f :- Summary "Disable all projects in the account."
-        :> "accounts"
-        :> Capture "accountId" (Id Account)
-        :> "disable-all-projects"
+    getCLITokens ::
+      f :- Summary "List the CLI tokens associated with the current account."
+        :> "auth"
+        :> "cli"
+        :> "tokens"
         :> auth
-        :> Post '[JSON] Int
+        :> Get '[JSON] CLITokensResponse,
+    revokeCLIToken ::
+      f :- Summary "Permanently disallow the use of a CLI token."
+        :> "auth"
+        :> "cli"
+        :> "tokens"
+        :> Capture "cliTokenId" (Id "CLIToken")
+        :> "revoke"
+        :> auth
+        :> Post '[JSON] NoContent,
+    installationStatus ::
+      f :- Summary "Retrieve installation status after redirect from external source site settings."
+        :> "sites"
+        :> Capture "siteId" (Id SourceHostingSite)
+        :> "installation"
+        :> Capture "installationId" Int
+        :> "status"
+        :> auth
+        :> Get '[JSON] AccountInstallationStatus
   }
   deriving (Generic)
diff --git a/src/Hercules/API/Accounts/Account.hs b/src/Hercules/API/Accounts/Account.hs
--- a/src/Hercules/API/Accounts/Account.hs
+++ b/src/Hercules/API/Accounts/Account.hs
@@ -35,6 +35,8 @@
     -- Hercules CI on this account.
     isInstallable :: Bool,
     membershipRole :: Maybe MembershipRole,
-    sourceHostingSites :: Maybe (Map Text SourceHostingSite)
+    sourceHostingSites :: Maybe (Map Text SourceHostingSite),
+    manageInstallationURL :: Maybe Text,
+    installationIsSelection :: Maybe Bool
   }
   deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Accounts/AccountInstallationStatus.hs b/src/Hercules/API/Accounts/AccountInstallationStatus.hs
new file mode 100644
--- /dev/null
+++ b/src/Hercules/API/Accounts/AccountInstallationStatus.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE DeriveAnyClass #-}
+
+module Hercules.API.Accounts.AccountInstallationStatus where
+
+import Hercules.API.Accounts.Account (Account)
+import Hercules.API.Prelude
+import Hercules.API.SourceHostingSite.SourceHostingSite (SourceHostingSite)
+
+data AccountInstallationStatus = AccountInstallationStatus
+  { site :: SourceHostingSite,
+    account :: Maybe Account,
+    isProcessingInstallationWebHook :: Bool,
+    secondsSinceInstallationWebHookComplete :: Maybe Int
+  }
+  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Accounts/CLIToken.hs b/src/Hercules/API/Accounts/CLIToken.hs
new file mode 100644
--- /dev/null
+++ b/src/Hercules/API/Accounts/CLIToken.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+
+module Hercules.API.Accounts.CLIToken where
+
+import Hercules.API.Accounts.Account (Account)
+import Hercules.API.Prelude
+
+data CLIToken = CLIToken
+  { id :: Id "CLIToken",
+    description :: Text,
+    creationTime :: UTCTime,
+    userId :: Id Account
+  }
+  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Accounts/CLITokensResponse.hs b/src/Hercules/API/Accounts/CLITokensResponse.hs
new file mode 100644
--- /dev/null
+++ b/src/Hercules/API/Accounts/CLITokensResponse.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE DeriveAnyClass #-}
+
+module Hercules.API.Accounts.CLITokensResponse where
+
+import Hercules.API.Accounts.CLIToken (CLIToken)
+import Hercules.API.Prelude
+
+data CLITokensResponse = CLITokensResponse
+  { cliTokens :: [CLIToken]
+  }
+  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Agents.hs b/src/Hercules/API/Agents.hs
--- a/src/Hercules/API/Agents.hs
+++ b/src/Hercules/API/Agents.hs
@@ -22,16 +22,14 @@
 
 data AgentsAPI auth f = AgentsAPI
   { clusterJoinTokensByAccount ::
-      f
-        :- Summary "List all cluster join tokens in an account." -- TODO rename
+      f :- Summary "List all cluster join tokens in an account."
         :> "accounts"
         :> Capture' '[Required, Strict] "accountId" (Id Account)
         :> "clusterJoinTokens"
         :> auth
         :> Get '[JSON] [ClusterJoinToken],
     clusterJoinTokenCreate ::
-      f
-        :- Summary "Generate a new cluster join token for agents to be added to this account."
+      f :- Summary "Generate a new cluster join token for agents to be added to this account."
         :> "accounts"
         :> Capture' '[Required, Strict] "accountId" (Id Account)
         :> "clusterJoinTokens"
@@ -39,8 +37,7 @@
         :> auth
         :> Post '[JSON] FullClusterJoinToken,
     clusterJoinTokenDelete ::
-      f
-        :- Summary "Delete an cluster join token in the account. No new agents will be able to join this account with the specified token."
+      f :- Summary "Delete an cluster join token in the account. No new agents will be able to join this account with the specified token."
         :> "accounts"
         :> Capture' '[Required, Strict] "accountId" (Id Account)
         :> "clusterJoinTokens"
@@ -48,8 +45,7 @@
         :> auth
         :> Delete '[JSON] NoContent,
     agentSessionsByAccount ::
-      f
-        :- Summary "Show the agents sessions owned by the account."
+      f :- Summary "Show the agents sessions owned by the account."
         :> "accounts"
         :> Capture' '[Required, Strict] "accountId" (Id Account)
         :> "agentSessions"
diff --git a/src/Hercules/API/Agents/AgentSession.hs b/src/Hercules/API/Agents/AgentSession.hs
--- a/src/Hercules/API/Agents/AgentSession.hs
+++ b/src/Hercules/API/Agents/AgentSession.hs
@@ -1,10 +1,13 @@
 {-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 module Hercules.API.Agents.AgentSession where
 
 import Hercules.API.Agents.ClusterJoinToken
   ( ClusterJoinToken,
   )
+import Hercules.API.Labels
 import Hercules.API.Prelude
 
 data AgentSession = AgentSession
@@ -20,6 +23,7 @@
     substituters :: [Text],
     creation :: UTCTime,
     lastSeen :: Maybe UTCTime,
-    available :: Bool
+    available :: Bool,
+    labels :: Labels
   }
   deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Agents/AgentSessionCreated.hs b/src/Hercules/API/Agents/AgentSessionCreated.hs
deleted file mode 100644
--- a/src/Hercules/API/Agents/AgentSessionCreated.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-{-# LANGUAGE DeriveAnyClass #-}
-
-module Hercules.API.Agents.AgentSessionCreated where
-
-import Hercules.API.Agents.AgentSession
-  ( AgentSession,
-  )
-import Hercules.API.Agents.ClusterJoinToken
-  ( ClusterJoinToken,
-  )
-import Hercules.API.Prelude
-
-data AgentSessionCreated = AgentSessionCreated
-  { hostname :: Text,
-    id :: Id AgentSession,
-    tokenId :: Id ClusterJoinToken,
-    description :: Text,
-    agentVersion :: Text,
-    nixVersion :: Text,
-    architectures :: [Text],
-    creation :: UTCTime
-  }
-  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Auth.hs b/src/Hercules/API/Auth.hs
--- a/src/Hercules/API/Auth.hs
+++ b/src/Hercules/API/Auth.hs
@@ -6,15 +6,21 @@
 import Hercules.API.Servant.Status
 import Servant.API
 import Servant.API.Generic
+import Web.Cookie (SetCookie)
 
 -- | Endpoints for authentication
-data AuthAPI f = AuthAPI
+data AuthAPI auth f = AuthAPI
   { initiateGitHubLogin ::
-      f
-        :- "api"
+      f :- "api"
         :> "auth"
         :> "github"
         :> QueryParam' '[Optional, Strict] "redirect" Text
-        :> Get302 '[PlainText, JSON] '[]
+        :> Get302 '[PlainText, JSON] '[],
+    signOut ::
+      f :- "api"
+        :> "auth"
+        :> "sign-out"
+        :> auth
+        :> Post '[JSON] (Headers '[Header "Set-Cookie" SetCookie, Header "Set-Cookie" SetCookie] NoContent)
   }
   deriving (Generic)
diff --git a/src/Hercules/API/Build.hs b/src/Hercules/API/Build.hs
--- a/src/Hercules/API/Build.hs
+++ b/src/Hercules/API/Build.hs
@@ -12,8 +12,7 @@
 
 data BuildAPI auth f = BuildAPI
   { restartDerivation ::
-      f
-        :- Summary "Restart a failed derivation"
+      f :- Summary "Restart a derivation"
         :> "accounts"
         :> Capture "accountId" (Id Account)
         :> "derivations"
@@ -22,8 +21,7 @@
         :> auth
         :> Post '[PlainText, JSON] NoContent,
     readDerivationLogText ::
-      f
-        :- Summary "Read a derivation build log"
+      f :- Summary "Read a derivation build log"
         :> Description "This interface may change."
         :> "accounts"
         :> Capture "accountId" (Id Account)
diff --git a/src/Hercules/API/Build/DerivationEvent.hs b/src/Hercules/API/Build/DerivationEvent.hs
--- a/src/Hercules/API/Build/DerivationEvent.hs
+++ b/src/Hercules/API/Build/DerivationEvent.hs
@@ -19,6 +19,8 @@
   | Succeeded DerivationEventSucceeded
   | Cancelled DerivationEventCancelled
   | Built DerivationEventBuilt
+  | HasCancelled DerivationEventHasCancelled
+  | HasCancelledForReset DerivationEventHasCancelledForReset
   deriving (Generic, Show, Eq, NFData, ToSchema)
 
 instance FromJSON DerivationEvent where
@@ -38,6 +40,8 @@
 eventTime (Succeeded (DerivationEventSucceeded {time = t})) = t
 eventTime (Cancelled (DerivationEventCancelled {time = t})) = t
 eventTime (Built (DerivationEventBuilt {time = t})) = t
+eventTime (HasCancelled (DerivationEventHasCancelled {time = t})) = t
+eventTime (HasCancelledForReset (DerivationEventHasCancelledForReset {time = t})) = t
 
 data DerivationEventQueued = DerivationEventQueued
   { time :: UTCTime,
@@ -83,5 +87,15 @@
 data DerivationEventBuilt = DerivationEventBuilt
   { time :: UTCTime,
     outputs :: [BuiltOutput]
+  }
+  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
+
+data DerivationEventHasCancelledForReset = DerivationEventHasCancelledForReset
+  { time :: UTCTime
+  }
+  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
+
+data DerivationEventHasCancelled = DerivationEventHasCancelled
+  { time :: UTCTime
   }
   deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
diff --git a/src/Hercules/API/Health.hs b/src/Hercules/API/Health.hs
--- a/src/Hercules/API/Health.hs
+++ b/src/Hercules/API/Health.hs
@@ -7,20 +7,17 @@
 
 data HealthAPI auth f = HealthAPI
   { db ::
-      f
-        :- Summary "Health check for the database"
+      f :- Summary "Health check for the database"
         :> "health"
         :> "db"
         :> Get '[JSON] NoContent,
     queue ::
-      f
-        :- Summary "Health check for the queue"
+      f :- Summary "Health check for the queue"
         :> "health"
         :> "queue"
         :> Get '[JSON] NoContent,
     github ::
-      f
-        :- Summary "Health check for the github"
+      f :- Summary "Health check for the github"
         :> "health"
         :> "github"
         :> Get '[JSON] NoContent
diff --git a/src/Hercules/API/Labels.hs b/src/Hercules/API/Labels.hs
new file mode 100644
--- /dev/null
+++ b/src/Hercules/API/Labels.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Hercules.API.Labels where
+
+import Control.Lens ((?~))
+import Data.Aeson (Value)
+import Data.Function ((&))
+import Data.Swagger
+import Hercules.API.Prelude
+
+newtype Labels = Labels {fromLabels :: Map Text Value}
+  deriving newtype (Show, Eq, NFData, ToJSON, FromJSON)
+
+instance ToSchema Labels where
+  declareNamedSchema _p = do
+    return $
+      NamedSchema (Just "Labels") $
+        mempty
+          & type_ ?~ SwaggerObject
+          & additionalProperties ?~ (AdditionalPropertiesAllowed True)
diff --git a/src/Hercules/API/Organizations.hs b/src/Hercules/API/Organizations.hs
--- a/src/Hercules/API/Organizations.hs
+++ b/src/Hercules/API/Organizations.hs
@@ -17,23 +17,20 @@
 
 data OrganizationsAPI auth f = OrganizationsAPI
   { findOrganizations ::
-      f
-        :- Summary "Get all organizations user has admin access to"
+      f :- Summary "Get all organizations user has admin access to"
         :> auth
         :> "api"
         :> "organizations"
         :> Get '[JSON] [Organization],
     createOrganization ::
-      f
-        :- Summary "Create a new organization"
+      f :- Summary "Create a new organization"
         :> auth
         :> "api"
         :> "organizations"
         :> ReqBody '[JSON] CreateOrganization
         :> Post '[JSON] Organization,
     connectAccountToOrganization ::
-      f
-        :- Summary "Connect an account to an organization"
+      f :- Summary "Connect an account to an organization"
         :> auth
         :> "api"
         :> "organizations"
@@ -42,8 +39,7 @@
         :> Capture "accountId" (Id Account)
         :> Post '[JSON] NoContent,
     paymentLinkForOrganization ::
-      f
-        :- Summary "Generate payment link for an organization"
+      f :- Summary "Generate payment link for an organization"
         :> auth
         :> "api"
         :> "organizations"
diff --git a/src/Hercules/API/Projects.hs b/src/Hercules/API/Projects.hs
--- a/src/Hercules/API/Projects.hs
+++ b/src/Hercules/API/Projects.hs
@@ -29,16 +29,14 @@
 
 data ProjectsAPI auth f = ProjectsAPI
   { projectsByOwner ::
-      f
-        :- Summary "List all projects owned by an account."
+      f :- Summary "List all projects owned by an account."
         :> "accounts"
         :> Capture' '[Required, Strict] "accountId" (Id Account)
         :> "projects"
         :> auth
         :> Get '[JSON] [Project],
     findProjects ::
-      f
-        :- Summary "Find projects"
+      f :- Summary "Find projects"
         :> "projects"
         :> QueryParam' '[Optional] "site" (Name SourceHostingSite)
         :> QueryParam' '[Optional] "account" (Name Account)
@@ -46,31 +44,27 @@
         :> auth
         :> Get '[JSON] [Project],
     createProject ::
-      f
-        :- Summary "Create a new project."
+      f :- Summary "Create a new project."
         :> "projects"
         :> auth
         :> ReqBody '[JSON] CreateProject
         :> Post '[JSON] (Id Project),
     patchProject ::
-      f
-        :- Summary "Modify a project"
+      f :- Summary "Modify a project"
         :> "projects"
         :> Capture' '[Required, Strict] "projectId" (Id Project)
         :> ReqBody '[JSON] PatchProject
         :> auth
         :> Patch '[JSON] Project,
     createUserEffectToken ::
-      f
-        :- Summary "Create a token for local effect execution"
+      f :- Summary "Create a token for local effect execution"
         :> "projects"
         :> Capture' '[Required, Strict] "projectId" (Id Project)
         :> auth
         :> "create-user-effect-token"
         :> Post '[JSON] CreateUserEffectTokenResponse,
     projectJobs ::
-      f
-        :- Summary "List all jobs in a project"
+      f :- Summary "List all jobs in a project"
         :> Description "A list of a project's revisions and their details and status."
         :> "projects"
         :> Capture' '[Required, Strict] "project" (Id Project)
@@ -78,8 +72,7 @@
         :> auth
         :> Get '[JSON] [Job],
     findJobs ::
-      f
-        :- Summary "Find jobs"
+      f :- Summary "Find jobs"
         :> "jobs"
         :> QueryParam' '[Optional, Description "Currently only \"github\" or omit entirely"] "site" (Name SourceHostingSite)
         :> QueryParam' '[Optional, Description "Account name filter"] "account" (Name Account)
@@ -89,8 +82,7 @@
         :> auth
         :> Get '[JSON] [ProjectAndJobs],
     projectJobEvaluation ::
-      f
-        :- Summary "List all attributes in a job"
+      f :- Summary "List all attributes in a job"
         :> Description "A list of all attributes that have been produced as part of the evaluation of a job."
         :> "jobs"
         :> Capture' '[Required, Strict] "jobId" (Id Job)
@@ -98,8 +90,7 @@
         :> auth
         :> Get '[JSON] EvaluationDetail,
     jobDerivationFailureGraph ::
-      f
-        :- Summary "Find all failures in an evaluation's derivations"
+      f :- Summary "Find all failures in an evaluation's derivations"
         :> Description
              "Returns all derivations that have failures in their dependency closures."
         :> "jobs"
diff --git a/src/Hercules/API/Repos.hs b/src/Hercules/API/Repos.hs
--- a/src/Hercules/API/Repos.hs
+++ b/src/Hercules/API/Repos.hs
@@ -11,16 +11,14 @@
 
 data ReposAPI auth f = ReposAPI
   { reposByOwner ::
-      f
-        :- Summary "Repositories that the account owns or has explicit access to."
+      f :- Summary "Repositories that the account owns or has explicit access to."
         :> "accounts"
         :> Capture' '[Required, Strict] "accountId" (Id Account)
         :> "repos"
         :> auth
         :> Get '[JSON] [Repo],
     parseGitURL ::
-      f
-        :- Summary "Parse a git remote URL into site, owner and repo. Returns 400 if invalid, 404 if the site can not be determined. Does provide any guarantee that the repository exists."
+      f :- Summary "Parse a git remote URL into site, owner and repo. Returns 400 if invalid, 404 if the site can not be determined. Does provide any guarantee that the repository exists."
         :> "parse-git-url"
         :> QueryParam' '[Required, Strict] "gitURL" Text
         :> auth
diff --git a/src/Hercules/API/State.hs b/src/Hercules/API/State.hs
--- a/src/Hercules/API/State.hs
+++ b/src/Hercules/API/State.hs
@@ -7,8 +7,10 @@
 import Data.ByteString (ByteString)
 import Data.Swagger (NamedSchema (NamedSchema), binarySchema)
 import Data.Swagger.Schema (ToSchema (..))
+import Hercules.API.Accounts.Account (Account)
 import Hercules.API.Prelude
 import Hercules.API.Projects.Project (Project)
+import Hercules.API.SourceHostingSite.SourceHostingSite (SourceHostingSite)
 import Hercules.API.State.ProjectState (ProjectState)
 import Servant.API
 import Servant.API.Generic
@@ -24,35 +26,51 @@
 
 type ContentDisposition = Header "Content-Disposition" Text
 
-data StateAPI auth f = StateAPI
-  { putProjectStateData ::
-      f
-        :- Summary "Upload a state file"
-        :> "projects"
-        :> Capture' '[Required, Strict] "projectId" (Id Project)
+data ProjectStateResourceGroup auth f = ProjectStateResourceGroup
+  { putStateData ::
+      f :- Summary "Upload a state file"
         :> "state"
         :> Capture' '[Required, Strict] "stateName" Text
         :> "data"
         :> StreamBody NoFraming OctetStream (SourceIO RawBytes)
         :> auth
         :> Put '[JSON] NoContent,
-    getProjectStates ::
-      f
-        :- Summary "List all state files"
-        :> "projects"
-        :> Capture' '[Required, Strict] "projectId" (Id Project)
+    getStates ::
+      f :- Summary "List all state files"
         :> "states"
         :> auth
         :> Get '[JSON] ProjectState,
-    getProjectStateData ::
-      f
-        :- Summary "Download a state file"
-        :> "projects"
-        :> Capture' '[Required, Strict] "projectId" (Id Project)
+    getStateData ::
+      f :- Summary "Download a state file"
         :> "state"
         :> Capture' '[Required, Strict] "stateName" Text
         :> "data"
+        :> QueryParam' '[Optional, Strict] "version" Int
         :> auth
         :> StreamGet NoFraming OctetStream (Headers '[ContentLength, ContentDisposition] (SourceIO RawBytes))
+  }
+  deriving (Generic)
+
+data StateAPI auth f = StateAPI
+  { byProjectId ::
+      f
+        :- Substitute
+             ( "projects"
+                 :> Capture' '[Required, Strict] "projectId" (Id Project)
+                 :> Placeholder
+             )
+             (ToServantApi (ProjectStateResourceGroup auth)),
+    byProjectName ::
+      f
+        :- Substitute
+             ( "site"
+                 :> Capture' '[Required, Strict] "site" (Name SourceHostingSite)
+                 :> "account"
+                 :> Capture' '[Required, Strict] "account" (Name Account)
+                 :> "project"
+                 :> Capture' '[Required, Strict] "project" (Name Project)
+                 :> Placeholder
+             )
+             (ToServantApi (ProjectStateResourceGroup auth))
   }
   deriving (Generic)
diff --git a/src/Hercules/Frontend.hs b/src/Hercules/Frontend.hs
--- a/src/Hercules/Frontend.hs
+++ b/src/Hercules/Frontend.hs
@@ -22,8 +22,7 @@
       f
         :- view,
     account ::
-      f
-        :- Capture' [Required, Strict] "site" (Name SourceHostingSite)
+      f :- Capture' [Required, Strict] "site" (Name SourceHostingSite)
         :> Capture' [Required, Strict] "account" (Name Account)
         :> view,
     cliAuthorize ::
@@ -31,14 +30,12 @@
         :> QueryParam' [Required, Strict] "token" Text
         :> view,
     project ::
-      f
-        :- Capture' [Required, Strict] "site" (Name SourceHostingSite)
+      f :- Capture' [Required, Strict] "site" (Name SourceHostingSite)
         :> Capture' [Required, Strict] "account" (Name Account)
         :> Capture' [Required, Strict] "project" (Name Project)
         :> view,
     job ::
-      f
-        :- Capture' [Required, Strict] "site" (Name SourceHostingSite)
+      f :- Capture' [Required, Strict] "site" (Name SourceHostingSite)
         :> Capture' [Required, Strict] "account" (Name Account)
         :> Capture' [Required, Strict] "project" (Name Project)
         :> "jobs"
