Documentation

Pagination and sorting

The page/pageSize and sort conventions used everywhere in AppEngine.

Every list endpoint in AppEngine uses the same pagination and sort conventions. Learn them once.

Query-string pagination (p, ps)

GET endpoints — find-page-data, find-by-attribute, search, marketing/campaigns, client-data/orders, etc. — accept the same four params:

FieldTypeDescription
pnumber

Page number. 1-based. Default 1.

psnumber

Page size. Default 50. Most controllers cap at 200.

sstring

Sort field. Default modifydate. Use the wrapper field for system fields (createdate, modifydate, version); prefix with data. for payload fields (data.price).

st'asc' | 'desc'

Sort direction. Default desc.

curl 'https://appengine.appmint.io/repository/find-page-data/product?p=2&ps=20&s=data.price&st=asc' \
  -H "orgid: my-org"

Body pagination (options)

POST endpoints — find, search, aggregate, dynamic-query/find — take the same fields inside an options object:

{
  "query": { "data.status": "active" },
  "options": {
    "page": 2,
    "pageSize": 20,
    "sort": "data.createdAt",
    "sortType": "desc"
  }
}

Some endpoints also accept the MongoDB-driver shape { limit, skip, sort: { 'data.x': -1 } }. The dynamic-query controller passes these straight through:

{
  "collection": "lead",
  "filter": { "data.score": { "$gte": 70 } },
  "options": { "limit": 50, "skip": 100, "sort": { "data.score": -1 } }
}

Response envelope

Paged endpoints wrap the results in:

{
  data: BaseModel\<T\>[],   // or an array of payloads, depending on endpoint
  total: number,          // total count matching the query (across all pages)
  hasNext: boolean,       // convenience; true when (p * ps) < total
  page?: number,
  pageSize?: number,
  totalPages?: number,
}

total is exact, not estimated. It's computed from a count against the same filter. For very large collections, this adds a small overhead — pass ?count=false (where supported) to skip it.

Total count behavior

The platform returns exact totals because most UIs (admin tables, customer order history, lead lists) expect them. There is no "fast count" mode that returns an estimate.

If you only need the next page and don't care about totals, use the hasNext flag in the response and ignore total — the count step still runs but you save the round-trip you'd otherwise spend on count separately.

Cursor vs offset

AppEngine list endpoints are offset-paginated. There is no opaque cursor token. This is fine for typical UI tables (sorted by modifydate desc, page sizes <= 100). Two consequences:

  • Deep pages are slower than shallow ones — ?p=500 reads 500 pages of records to skip them. Avoid it.
  • Concurrent inserts can cause skips/dupes between pages. If you need consistency across a long iteration, snapshot a modifydate upper bound at the start and add it to your filter.

The find-page-data endpoint also supports a lastItem (?l=...) parameter for keyset-style pagination on indexed fields. Pass the sk of the last record on the previous page; the server returns records strictly after it, in the current sort order. This is faster than offset for streaming through big collections.

# first page
curl 'https://appengine.appmint.io/repository/find-page-data/contact?ps=100&s=createdate&st=asc'

# next page using last sk
curl 'https://appengine.appmint.io/repository/find-page-data/contact?ps=100&s=createdate&st=asc&l=contact-abc-123'

Sort fields

Common sort fields are indexed:

  • modifydate, createdate — every collection
  • version — every collection
  • state — every collection
  • data.name, data.title — most content collections
  • data.price, data.sku — products
  • data.score — leads, opportunities

Sorting on an unindexed data.* field works but scans the collection. For large collections, talk to platform support about adding an index, or pre-compute a sort key into data.

Multi-field sort

The query-string convention only supports one field. For multi-field sorts, use the body shape (options.sort) with a Mongo-style document:

{
  "options": {
    "sort": { "data.priority": -1, "createdate": -1 }
  }
}

-1 for descending, 1 for ascending — matches the MongoDB driver.

Examples by domain

# storefront product list, cheapest first
curl 'https://appengine.appmint.io/storefront/products?p=1&ps=24&s=data.price&st=asc' \
  -H "orgid: my-org"

# customer order history, newest first
curl 'https://appengine.appmint.io/client-data/orders?p=1&ps=10' \
  -H "orgid: my-org" -H "Authorization: Bearer $JWT"

# CRM leads, highest score first
curl 'https://appengine.appmint.io/crm/leads?p=1&ps=20&s=data.score&st=desc' \
  -H "orgid: my-org" -H "Authorization: Bearer $JWT"

The convention is the same across /repository/*, /storefront/*, /crm/*, /client-data/*, /marketing/*, and /finance/*. Once you've paginated one, you've paginated them all.