GET /app/{orgCode}/list/{entity} is the query layer, and it runs on DynamoDB — immediately consistent and available on every instance. Any field can be filtered with no setup; a lookup field, name, created_by, updated_by, the parent key and the owner scope use an index and stay fast on any amount of data. Only full-text search (?q=) and facets need the OpenSearch add-on (Enterprise).
Common query parameters
GET /list/order default list
GET /list/order?sortby=name&sortdir=desc sort across all records (name, created_at, updated_at, id)
GET /list/order?status=active filter by any field
GET /list/order?price=100&price_op=gt range filter (see operators below)
GET /list/order?customer_id=c42 lookup column: index-backed
GET /list/order?status=a&tier=b&filterLogic=any OR the filters (default: AND)
GET /list/order?pagesize=20&nexttoken=… cursor pagination
GET /list/order?view=compact apply a named view
GET /count/order?status=active count, same filters -> { count, capped }
GET /list/order?q=urgent full-text search (Enterprise)
GET /list/order?facets=status bucket counts (Enterprise) How a list is answered
A request picks the most selective index it can — a lookup column, then the parent key, the owner scope, name, created_by/updated_by, or a time index chosen by sortby — and reads only that slice. Every other filter is applied to what that index returns, inside DynamoDB. With no index at all, the whole entity is read. Operators: eq (default), ne, gt, gte, lt, lte, between (with field_val2), begins, contains, notcontains, exists, notexists. Values are typed by the field: a number field compares as a number. An unknown operator, a bad value, or a filter on an encrypted or hidden field returns 400 — a filter is never silently ignored.
GET /list/order -> primary, all records of the entity
?customer_id=c42 -> LookupIndex (a colType=lookup field)
?name=widget -> NameIndex, exact (?name=wid&name_op=begins for a prefix)
?created_by=alice / ?updated_by=alice -> CreatedByIndex / UpdatedByIndex
?sortby=updated_at&sortdir=desc -> UpdatedAtIndex, newest first
?{parentKey}=val -> ParentIndex
?status=active -> entity partition + filter
?q=urgent / ?facets=status -> OpenSearch (Enterprise), otherwise an empty result + note Search is an Enterprise add-on
Full-text (?q=) and facets need an OpenSearch add-on on your instance — contact nostackai to enable it. Without it they return an empty list with a note, not a guess and not a slow scan. Filters, sorting, counts and lookups work everywhere.
Scoping a list
- Owner-scoped — if the entity has an owner index, a plain
GET /list/{entity}returns only the caller's records. - `?all=true` (or
by=all) — skip the owner filter and return all of the org's records (needs a broad ACL). - `by=owner&userid=user456` — filter to a specific owner (needs a broad ACL). A bare
?owner=user456is not the scoping mechanism. - Parent-scoped —
?{parentKey}={value}returns only children of that parent. - `by=category` —
?by=category&category_id=c8x2returns every record in that category; add&subcategory_id=…(and&subsubcategory_id=…) to narrow. Uses the entity's category index; the parameter names are its category fields. - `?created_by=` / `?updated_by=` — records created / last updated by that user, newest first. On an owner-indexed entity, a caller without a broad ACL stays limited to their own records — like the parent key, the filter never widens access.
ACL applies to every query
The authorizer checks list{entity} before the handler runs, and the handler then enforces owner scope: on an entity with an owner index, a caller without a broad ACL only ever sees their own records — a lookup, parent or created_by filter narrows within that, it never widens it. Asking for another user's records with by=owner&userid= returns 403.
Pagination & counts
Pagination is by cursor: pass the nexttoken from the previous response for the next page (?pagesize= up to 500). A request reads at most about 2,000 records. If that limit stops a filtered search before the page is full, the response has scanLimited: true and a nexttoken to keep going — add an indexed filter (a lookup column, name, created_by) to narrow it. ?sortfield=<other field> sorts only the page returned and says sorted: "window". GET /count/{entity} takes the same filters and returns { count, capped }; capped: true means "at least this many". totalcount and facets appear only on an Enterprise search.