Skip to content

Pagination, Sorting, Filtering & Consistency

This page standardizes pagination and query behavior across services. When a service has exceptions, it is documented on that service’s surface page.

Pagination (baseline)

  • Default limit: 8
  • Clamp: 1–256 inclusive
  • Cursor: next_token (opaque, per-service format)
  • Behavior: if more data exists, next_token is returned; if not, it is null or omitted.

Clients must treat next_token as opaque and echo it back unchanged.

Sorting (baseline)

Stable sorting guarantees are per-endpoint. If a service does not document the order:

  • Assume a deterministic order per cursor, but do not rely on a specific sort key.
  • Do not mix filters without re-reading the first page.

Filtering & scoping (baseline)

  • High-cardinality lists must include a scoping filter (org, facility, channel, parent ID, status).
  • If a list endpoint allows status filtering and includes a terminal state (for example doomed/archived), the default is the non-terminal state (active/current) unless the service documents otherwise.
  • include_doomed defaults to false unless explicitly set; status=all (where supported) or include_doomed=true returns terminal records.

Search endpoints (exact vs partial)

Search behavior varies by service:

  • Exact-match: identifiers, codes, and IDs are typically exact-match and tenant-scoped.
  • Partial-match: supported only where documented (e.g., PMC product search, PVM list text filters).
  • Indexing latency: search planes can be eventually consistent. Expect transient staleness after writes (tokenized index or search-backed lists).

Target posture (UNCONFIRMED):

  • Indexing latency p95 under 2 minutes for search planes.
  • Explicit stale flag returned when a query may be stale (future).

Tie-breakers (baseline)

When two results are equal on the primary sort key:

  • Use a deterministic tie-breaker (e.g., record ID) to keep stable pagination.
  • If not documented, treat ordering as stable but opaque.

Consistency expectations

  • Read-after-write: strong consistency is not guaranteed across all services.
  • List/search endpoints: may be eventually consistent under load.
  • Search plane: always eventually consistent; use exact-match lookups for immediate consistency when needed.

Pagination Walkthrough

This walkthrough uses PVM GET /division as the example list endpoint. The same pattern applies to every list endpoint across all services.

curl (page-by-page)

First page (no next_token):

sh
curl -sS -G "https://api.g3nretailstack.com/pvm/division" \
  -H "x-orgcode: $ORGCODE" \
  -H "x-session-guid: $SESSION_GUID" \
  --data-urlencode "limit=2"

Response:

json
{
  "success": true,
  "data": {
    "items": [
      { "division_id": "d1", "code": "APPAREL", "caption": "Apparel", "status": "active" },
      { "division_id": "d2", "code": "FOOTWEAR", "caption": "Footwear", "status": "active" }
    ],
    "next_token": { "GSI1PK": "ORG#ACME", "GSI1SK": "DIV#d2" }
  }
}

Second page — echo next_token exactly as received:

sh
curl -sS -G "https://api.g3nretailstack.com/pvm/division" \
  -H "x-orgcode: $ORGCODE" \
  -H "x-session-guid: $SESSION_GUID" \
  --data-urlencode "limit=2" \
  --data-urlencode 'next_token={"GSI1PK":"ORG#ACME","GSI1SK":"DIV#d2"}'

Response (last page — next_token is null):

json
{
  "success": true,
  "data": {
    "items": [
      { "division_id": "d3", "code": "ACCESSORIES", "caption": "Accessories", "status": "active" }
    ],
    "next_token": null
  }
}

Node.js (drain all pages)

js
async function listAll(path, params, headers) {
  const items = [];
  let nextToken = null;
  do {
    const url = new URL(`https://api.g3nretailstack.com${path}`);
    Object.entries(params).forEach(([k, v]) => {
      if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
    });
    if (nextToken) url.searchParams.set('next_token', JSON.stringify(nextToken));

    const res = await fetch(url, { headers });
    const json = await res.json();
    if (!json.success) throw new Error(JSON.stringify(json.error));

    items.push(...(json.data.items || []));
    nextToken = json.data.next_token ?? null;
  } while (nextToken);
  return items;
}

// Usage
const divisions = await listAll('/pvm/division', { limit: 50, status: 'active' }, {
  'x-orgcode': ORGCODE,
  'x-session-guid': SESSION_GUID,
});

Python (drain all pages)

python
import json
import requests

def list_all(path, params, headers):
    items = []
    next_token = None
    while True:
        query = {**params}
        if next_token is not None:
            query['next_token'] = json.dumps(next_token)
        resp = requests.get(f"https://api.g3nretailstack.com{path}",
                            params=query, headers=headers)
        data = resp.json()
        if not data.get('success'):
            raise RuntimeError(data.get('error'))
        items.extend(data['data'].get('items', []))
        next_token = data['data'].get('next_token')
        if not next_token:
            break
    return items

# Usage
divisions = list_all('/pvm/division', {'limit': 50, 'status': 'active'}, {
    'x-orgcode': ORGCODE,
    'x-session-guid': SESSION_GUID,
})

Best practices

  • Prefer get by ID for immediate consistency.
  • For lists, handle empty pages and retry with the same next_token.
  • When in doubt, cross-check with a get call before acting on list/search results.