Skip to content

Base API pagination

Base API endpoints that can return large record collections use a shared page-number pagination contract. This bounds response size and serialization work while preserving each endpoint's authorization, company and cooling-unit scope, filters, and search behavior.

!!! warning "Breaking response change"

Paginated endpoints return an object containing `results`; they no longer
return a top-level JSON array or an endpoint-specific pagination envelope.
Mobile and web clients must be updated before deploying the corresponding
backend release.

Request parameters

Parameter Default Limits Description
page 1 Positive base-10 integer One-based page number.
page_size 25 Positive base-10 integer, maximum 200 Number of records requested.

Each parameter may appear at most once. Values such as 0, negative numbers, decimals, non-numeric text, repeated parameters, and a page_size greater than 200 return 400 Bad Request with an error keyed by the invalid field.

{
  "page_size": [
    "page_size must be less than or equal to 200."
  ]
}

Response contract

{
  "count": 250,
  "page": 1,
  "page_size": 25,
  "total_pages": 10,
  "next": true,
  "previous": false,
  "results": []
}

count is the number of records remaining after authorization, tenant or cooling-unit scope, filtering, search, and duplicate removal. Those operations and deterministic ordering are applied in the database before the page is sliced and serialized.

A valid page beyond the available range returns 200 OK with an empty results array. An empty collection uses count: 0, total_pages: 0, and sets both navigation flags to false.

Ordering

Pagination requires deterministic ordering so adjacent pages do not duplicate or omit records under normal use:

  • Historical and event records sort by descending event date or timestamp, followed by descending id.
  • Named reference records normally sort by ascending name, followed by ascending id.
  • Other collections use their documented endpoint ordering with id as a tie-breaker, or descending id as the fallback.

Offset-based pages can still shift when records are inserted concurrently. Clients that need a frozen export should use a stable date range or another endpoint-specific snapshot filter where available.

Affected endpoint groups

The shared contract applies to high-volume or query-intensive lists in these Base API areas:

Area Paginated collections
Users Users, active-check-in users, archived users, companies, cooling users, operators, service providers, invitations, cooling-user surveys, and notifications
Storage Cooling units, locations, crates, checked-in produce, next checkouts, and pricing-plan resources
Operations Check-ins, checkouts, movements, usage, revenue events, storage payments, refunds, and market surveys
Marketplace Buyer listings and orders; seller listings, coupons, and orders; company orders and delivery contacts
Audit Audit logs
Prediction Data-table result collections

Small, naturally bounded configuration or aggregate payloads remain unpaginated. Examples include crop and crop-type taxonomies, cooling-unit crop configuration, current cooling-unit specifications, map topology, current cart resources, and fixed-window prediction graphs.

Marketplace order ordering

GET /marketplace/buyer/orders/ and GET /marketplace/seller/orders/ accept an ordering parameter of created_at or -created_at (default -created_at), with id/-id as a deterministic tiebreak. Any other value returns a field-specific 400 response.

Marketplace compatibility alias

GET /marketplace/buyer/available-listings/ temporarily accepts the legacy items_per_page parameter. It has the same maximum of 200 and is deprecated. If both parameters are provided, page_size takes precedence. The response always uses the shared envelope above.

Analytics API exception

The token-authenticated endpoints under /api/v1/analytics/* and /api/v1/sensor-data retain their public data and meta.pagination contract, with a default page size of 100 and maximum of 500. Do not apply the Base API response parser to those endpoints. See Analytics API examples.

Movement search and ordering

GET /operation/movements/, GET /operation/movements/usage/, and GET /operation/movements/revenue/ share the same search and ordering query parameters, applied before pagination:

Parameter Format Behavior
search Free text Case-insensitive match against movement code, cooling-user name, company name, and crop name, across both check-in and checkout data.
ordering One of crop_type, movement_date, movement_date_reverse, check_in_first, check_out_first, cooling_user_name Selects a deterministic sort. Defaults to movement_date_reverse. An unsupported value returns a field-specific 400 response.

Movement usage endpoint

GET /operation/movements/usage/ uses the shared pagination response and the search/ordering parameters above, plus the existing cooling_units scope and these optional filters:

Parameter Format Behavior
start_date YYYY-MM-DD Includes movements from the start of this date.
end_date YYYY-MM-DD Includes movements through the end of this date.

The date range is applied in SQL before counting and slicing. Invalid dates, or a start_date after end_date, return a field-specific 400 response.

The endpoint remains check-in driven: it selects movements whose checked-in produce used one of the authorized cooling units. A matching movement can also contain checkout data. Existing serialized item fields were preserved.

Movement revenue endpoint

GET /operation/movements/revenue/ derives one revenue event per matching payment and paginates the resulting event list, not the underlying movements. It accepts the existing cooling_units and payment_methods scope plus the shared search/ordering parameters above.

Ordering is applied differently here because events do not map one-to-one onto movements:

  • If ordering is provided, events are sorted by their movement's position in the ordered movement queryset, with descending event id as a tiebreaker.
  • If ordering is omitted, events sort by descending occurred_at (payment date), not movement date.

Client migration

Replace code that treats an affected response as an array:

// Before
const records = await response.json();

// After
const page = await response.json();
const records = page.results;

Fetch subsequent pages while next is true:

async function fetchAllPages(url, options) {
  const records = [];
  let pageNumber = 1;

  while (true) {
    const requestUrl = new URL(url);
    requestUrl.searchParams.set("page", String(pageNumber));
    requestUrl.searchParams.set("page_size", "200");

    const response = await fetch(requestUrl, options);
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    const page = await response.json();
    records.push(...page.results);
    if (!page.next) return records;
    pageNumber += 1;
  }
}

Prefer rendering one page at a time instead of immediately reconstructing the old unbounded response in memory.

Performance characteristics

Filtering, authorization scope, ordering, counting, and slicing happen before response serialization. Related records needed by collection serializers are loaded in page-level batches to avoid issuing the same lookup once per result.

Pagination bounds the number of top-level records, but it does not necessarily bound nested arrays inside each record. For example, /storage/v1/cooling-units/{id}/produces/ can return a page of produce records where each produce still contains multiple crates. Consumers should avoid requesting the maximum page size when they do not need it and should render pages incrementally.

Migration checklist

When updating an API consumer:

  1. Read records from results and retain the pagination metadata needed by the interface.
  2. Remove assumptions that affected responses are top-level arrays or use the legacy marketplace nodes/pagination fields.
  3. Preserve existing filters and authorization context when requesting the next page.
  4. Test empty, first, intermediate, final, and out-of-range pages.