diff --git a/application/src/main/scala/com/azavea/franklin/datamodel/PaginationToken.scala b/application/src/main/scala/com/azavea/franklin/datamodel/PaginationToken.scala
index 0d80e01cc..7199cfc0d 100644
--- a/application/src/main/scala/com/azavea/franklin/datamodel/PaginationToken.scala
+++ b/application/src/main/scala/com/azavea/franklin/datamodel/PaginationToken.scala
@@ -1,7 +1,9 @@
 package com.azavea.franklin.datamodel
 
+import cats.syntax.either._
 import com.azavea.stac4s.meta._
 import eu.timepit.refined.types.numeric.PosInt
+import io.circe.Error
 import io.circe.generic.semiauto._
 import io.circe.parser._
 import io.circe.refined._
@@ -31,23 +33,30 @@ object PaginationToken {
     }
   }
 
-  implicit val dec: Decoder[PaginationToken] = deriveDecoder
-  implicit val enc: Encoder[PaginationToken] = deriveEncoder
+  val defaultDecoder: Decoder[PaginationToken] = deriveDecoder
+  val defaultEncoder: Encoder[PaginationToken] = deriveEncoder
 
-  val b64Encoder = Base64.getEncoder()
-  val b64Decoder = Base64.getDecoder()
+  val b64Encoder: Base64.Encoder = Base64.getEncoder
+  val b64Decoder: Base64.Decoder = Base64.getDecoder
 
   def encPaginationToken(token: PaginationToken): String = b64Encoder.encodeToString(
-    token.asJson.noSpaces.getBytes
+    token.asJson(defaultEncoder).noSpaces.getBytes
   )
 
-  def decPaginationToken(encoded: String): DecodeResult[PaginationToken] = {
-    val jsonString: String = new String(b64Decoder.decode(encoded))
-    val circeResult = for {
+  def decPaginationTokenEither(encoded: String): Either[Error, PaginationToken] = {
+    val jsonString = new String(b64Decoder.decode(encoded))
+    for {
       js      <- parse(jsonString)
-      decoded <- js.as[PaginationToken]
+      decoded <- js.as[PaginationToken](defaultDecoder)
     } yield decoded
-    circeResult.toDecodeResult
   }
 
+  def decPaginationToken(encoded: String): DecodeResult[PaginationToken] =
+    decPaginationTokenEither(encoded).toDecodeResult
+
+  implicit val paginationTokenDecoder: Decoder[PaginationToken] =
+    Decoder.decodeString.emap(str => decPaginationTokenEither(str).leftMap(_.getMessage))
+
+  implicit val paginationTokenEncoder: Encoder[PaginationToken] = { encPaginationToken(_).asJson }
+
 }
diff --git a/application/src/test/scala/com/azavea/franklin/api/TestClient.scala b/application/src/test/scala/com/azavea/franklin/api/TestClient.scala
index 3e470b7b0..03564a7e5 100644
--- a/application/src/test/scala/com/azavea/franklin/api/TestClient.scala
+++ b/application/src/test/scala/com/azavea/franklin/api/TestClient.scala
@@ -80,4 +80,10 @@ class TestClient[F[_]: Sync](
       collection: StacCollection
   ): Resource[F, (StacCollection, StacItem)] =
     (getCollectionResource(collection), getItemResource(collection, item)).tupled
+
+  def getCollectionItemsResource(
+      items: List[StacItem],
+      collection: StacCollection
+  ): Resource[F, (StacCollection, List[StacItem])] =
+    (getCollectionResource(collection), items.traverse(getItemResource(collection, _))).tupled
 }
diff --git a/application/src/test/scala/com/azavea/franklin/api/services/FiltersFor.scala b/application/src/test/scala/com/azavea/franklin/api/services/FiltersFor.scala
index 72790a970..c77adca86 100644
--- a/application/src/test/scala/com/azavea/franklin/api/services/FiltersFor.scala
+++ b/application/src/test/scala/com/azavea/franklin/api/services/FiltersFor.scala
@@ -189,4 +189,13 @@ object FiltersFor {
     // just to cooperate with timeFilterFor
     concatenated.get
   }
+
+  def inclusiveFilters(collection: StacCollection): SearchFilters = {
+    val filters: NonEmptyList[Option[SearchFilters]] =
+      NonEmptyList.of(collectionFilterFor(collection).some)
+    val concatenated = filters.combineAll
+    // guaranteed to succeed, since most of the filters are being converted into options
+    // just to cooperate with timeFilterFor
+    concatenated.get
+  }
 }
diff --git a/application/src/test/scala/com/azavea/franklin/api/services/SearchServiceSpec.scala b/application/src/test/scala/com/azavea/franklin/api/services/SearchServiceSpec.scala
index d68afcb7f..39a5216f0 100644
--- a/application/src/test/scala/com/azavea/franklin/api/services/SearchServiceSpec.scala
+++ b/application/src/test/scala/com/azavea/franklin/api/services/SearchServiceSpec.scala
@@ -6,10 +6,12 @@ import cats.syntax.all._
 import com.azavea.franklin.Generators
 import com.azavea.franklin.api.{TestClient, TestServices}
 import com.azavea.franklin.database.{SearchFilters, TestDatabaseSpec}
-import com.azavea.franklin.datamodel.StacSearchCollection
+import com.azavea.franklin.datamodel.{PaginationToken, StacSearchCollection}
 import com.azavea.stac4s.testing.JvmInstances._
 import com.azavea.stac4s.testing._
-import com.azavea.stac4s.{StacCollection, StacItem}
+import com.azavea.stac4s.{StacCollection, StacItem, StacLinkType}
+import eu.timepit.refined.types.numeric.NonNegInt
+import io.circe.syntax._
 import org.http4s.circe.CirceEntityDecoder._
 import org.http4s.circe.CirceEntityEncoder._
 import org.http4s.{Method, Request, Uri}
@@ -24,14 +26,15 @@ class SearchServiceSpec
   This specification verifies that the Search Service sensibly finds and excludes items
 
   The search service should:
-    - search with POST search filters               $postSearchFiltersExpectation
-    - search with GET search filters                $getSearchFiltersExpectation
-    - find an item with filters designed to find it $findItemWhenExpected
-    - not find items when excluded by time          $dontFindTimeFilters
-    - not find items when excluded by bbox          $dontFindBboxFilters
-    - not find items when excluded by intersection  $dontFindGeomFilters
-    - not find items when excluded by collection    $dontFindCollectionFilters
-    - not find items when excluded by item          $dontFindItemFilters
+    - search with POST search filters                       $postSearchFiltersExpectation
+    - search with GET search filters                        $getSearchFiltersExpectation
+    - find an item with filters designed to find it         $findItemWhenExpected
+    - find two items with filters designed to find it       $find2ItemsWhenExpected
+    - not find items when excluded by time                  $dontFindTimeFilters
+    - not find items when excluded by bbox                  $dontFindBboxFilters
+    - not find items when excluded by intersection          $dontFindGeomFilters
+    - not find items when excluded by collection            $dontFindCollectionFilters
+    - not find items when excluded by item                  $dontFindItemFilters
 """
   val testServices = new TestServices[IO](transactor)
 
@@ -117,6 +120,50 @@ class SearchServiceSpec
     result.features.head.id should beEqualTo(stacItem.id)
   }
 
+  def find2ItemsWhenExpected = prop {
+    (stacItem1: StacItem, stacItem2: StacItem, stacCollection: StacCollection) =>
+      val resourceIO = testClient map {
+        _.getCollectionItemsResource(stacItem1 :: stacItem2 :: Nil, stacCollection)
+      }
+      val requestIO = resourceIO flatMap { resource =>
+        def getSearchCollection(searchFilters: SearchFilters): IO[Option[StacSearchCollection]] = {
+          val request =
+            Request[IO](method = Method.POST, uri = Uri.unsafeFromString(s"/search"))
+              .withEntity(searchFilters.asJson)
+          (for {
+            resp    <- testServices.searchService.routes.run(request)
+            decoded <- OptionT.liftF { resp.as[StacSearchCollection] }
+          } yield decoded).value
+        }
+
+        resource.use {
+          case (collection, _) =>
+            val inclusiveParams =
+              FiltersFor.inclusiveFilters(collection).copy(limit = NonNegInt.from(1).toOption)
+            val result1 = getSearchCollection(inclusiveParams)
+
+            val result2 = result1
+              .flatMap {
+                _.flatTraverse { r =>
+                  /** This line intentionally decodes next token [[String]] into [[PaginationToken]] */
+                  val next = r.links.collectFirst {
+                    case l if l.rel == StacLinkType.Next =>
+                      l.href.split("next=").last.asJson.as[PaginationToken].toOption
+                  }.flatten
+                  getSearchCollection(inclusiveParams.copy(next = next))
+                }
+              }
+
+            (result1, result2).tupled
+        }
+      }
+
+      val (Some(result1), Some(result2)) = requestIO.unsafeRunSync()
+
+      result1.features.head.id should beEqualTo(stacItem1.id)
+      result2.features.head.id should beEqualTo(stacItem2.id)
+  }
+
   def dontFindTimeFilters =
     getExclusionTest("temporal extent")(_ => item => FiltersFor.timeFilterExcluding(item))