diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
 # Changelog
 
-## [0.1.0.0] - 2025-07-14
+## [0.2.0.0] - 2025-12-17
+
+### Added
+
+* Support for sample caching in the metrics endpoint middleware
+
+### Changed
+
+* Simplify the metrics endpoint API even further
+
+## [0.1.0.0] - 2025-12-17
 
 ### Changed
 
diff --git a/prometheus-wai.cabal b/prometheus-wai.cabal
--- a/prometheus-wai.cabal
+++ b/prometheus-wai.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           prometheus-wai
-version:        0.1.0.0
+version:        0.2.0.0
 description:    Prometheus metrics for WAI applications
 homepage:       https://github.com/NorfairKing/prometheus-wai#readme
 bug-reports:    https://github.com/NorfairKing/prometheus-wai/issues
diff --git a/src/System/Metrics/Prometheus/Wai/Middleware.hs b/src/System/Metrics/Prometheus/Wai/Middleware.hs
--- a/src/System/Metrics/Prometheus/Wai/Middleware.hs
+++ b/src/System/Metrics/Prometheus/Wai/Middleware.hs
@@ -9,15 +9,20 @@
     combineRegistrySample,
     MetricsEndpoint (..),
     defaultMetricsEndpoint,
+    withLastSecondSamples,
+    setSampleCache,
     metricsEndpointMiddleware,
   )
 where
 
-import Control.Monad (forM)
+import Control.Concurrent.MVar
+import Control.Monad
+import Control.Monad.IO.Class
 import Data.ByteString (ByteString)
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as M
 import qualified Data.Text as T
+import Data.Word
 import GHC.Clock (getMonotonicTimeNSec)
 import qualified Network.HTTP.Types as HTTP
 import Network.Wai as Wai (Middleware)
@@ -110,35 +115,73 @@
 data MetricsEndpoint = MetricsEndpoint
   { -- | Path to serve metrics on, e.g. @"/metrics"@
     metricsEndpointPath :: ByteString,
-    -- | Registry to sample from
-    metricsEndpointRegistry :: Registry,
-    -- | Extra samples to be collected 'at the last second', to be combined
-    -- with the main registry samples
-    metricsEndpointLastSecond :: Maybe (IO Prometheus.RegistrySample)
+    -- | Samples to collect.
+    --
+    -- Typically obtained via 'Registry.sample' but you can add others that you collect at the last second.
+    metricsEndpointSample :: IO Prometheus.RegistrySample,
+    -- | Cache duration in nanoseconds for the metrics samples.
+    --
+    -- You can set this to use multiple prometheus fetchers for redundancy
+    -- without resampling every time.
+    --
+    -- Nothing means no caching
+    metricsEndpointCacheDurationNanos :: Maybe Word64
   }
 
+-- | Create a default 'MetricsEndpoint' serving at @"/metrics"@ with samples
+-- from the given registry.
 defaultMetricsEndpoint :: Registry -> MetricsEndpoint
 defaultMetricsEndpoint registry =
   MetricsEndpoint
     { metricsEndpointPath = "/metrics",
-      metricsEndpointRegistry = registry,
-      metricsEndpointLastSecond = Nothing
+      metricsEndpointSample = Registry.sample registry,
+      metricsEndpointCacheDurationNanos = Nothing
     }
 
-metricsEndpointMiddleware :: MetricsEndpoint -> Wai.Middleware
-metricsEndpointMiddleware MetricsEndpoint {..} application request sendResponse =
-  if Request.rawPathInfo request == metricsEndpointPath
-    then do
-      let sampleRegistry = Registry.sample metricsEndpointRegistry
-      finalSamples <- case metricsEndpointLastSecond of
-        Nothing -> sampleRegistry
-        Just lastSecondSample ->
-          combineRegistrySample
-            <$> sampleRegistry
-            <*> lastSecondSample
-      sendResponse
-        $ Wai.responseBuilder
-          HTTP.ok200
-          [(HTTP.hContentType, "text/plain; version=0.0.4; charset=utf-8")]
-        $ Prometheus.encodeMetrics finalSamples
-    else application request sendResponse
+-- | Combine the given last second samples with the existing samples of the
+-- endpoint.
+withLastSecondSamples :: IO Prometheus.RegistrySample -> MetricsEndpoint -> MetricsEndpoint
+withLastSecondSamples lastSecondSamples endpoint =
+  endpoint
+    { metricsEndpointSample =
+        combineRegistrySample
+          <$> metricsEndpointSample endpoint
+          <*> lastSecondSamples
+    }
+
+setSampleCache :: Word64 -> MetricsEndpoint -> MetricsEndpoint
+setSampleCache cacheDurationNanos endpoint = endpoint {metricsEndpointCacheDurationNanos = Just cacheDurationNanos}
+
+metricsEndpointMiddleware :: (MonadIO m) => MetricsEndpoint -> m Wai.Middleware
+metricsEndpointMiddleware MetricsEndpoint {..} =
+  -- This function is almost always called in something like LoggingT IO, so we
+  -- might as well lift already.
+  liftIO $ do
+    mCache <- forM metricsEndpointCacheDurationNanos $ \cacheDurationNanos -> do
+      begin <- getMonotonicTimeNSec
+      firstSample <- metricsEndpointSample
+      var <- newMVar (begin, firstSample)
+      pure (cacheDurationNanos, var)
+
+    let sampleWithCache :: IO Prometheus.RegistrySample
+        sampleWithCache = case mCache of
+          Nothing -> metricsEndpointSample
+          Just (cacheDurationNanos, var) -> do
+            modifyMVar var $ \lastTup@(lastSampleTime, lastSample) -> do
+              now <- getMonotonicTimeNSec
+              if now - lastSampleTime < cacheDurationNanos
+                then pure (lastTup, lastSample)
+                else do
+                  newSample <- metricsEndpointSample
+                  let combinedSample = combineRegistrySample lastSample newSample
+                  let newTup = (now, combinedSample)
+                  pure (newTup, combinedSample)
+
+    pure $ \application request sendResponse ->
+      if Request.rawPathInfo request == metricsEndpointPath
+        then do
+          samples <- sampleWithCache
+          sendResponse $
+            Wai.responseBuilder HTTP.ok200 [(HTTP.hContentType, "text/plain; version=0.0.4; charset=utf-8")] $
+              Prometheus.encodeMetrics samples
+        else application request sendResponse
