Skip to main content

Queries

Queries join multiple collections through lookup references, creating read-only datasets with selected properties from each.

URL Structure​

Queries share the same namespace as collections:

/api/{query-name}

For external clients, replace /api with https://<region>.restapi.com/<api-name>. See Virtual Paths.

Fetching a Single Item​

To fetch a single item from a query, append the ID to the URL:

/api/{query-name}/{id}

The ID refers to the primary (root) collection's item ID. This returns the query result for that specific item with all joined data included.

Creating Queries​

Use the Query Builder in the Developer Portal:

  1. Navigate to your API's schema
  2. Create a new query
  3. Select collections to join
  4. Choose properties to include
  5. Test with the Query Explorer

Property Aliasing​

Properties must have unique aliases within the query. This is necessary because multiple collections may have properties with the same name.

customers.name → customerName
orders.name → orderName

Simple Query Example​

Join orders with customer information:

Query: orders-with-customers

orders
└── customer (lookup) → customers
└── name, state

Selected properties:

  • orders.orderDate → orderDate
  • orders.total → total
  • customers.name → customerName
  • customers.state → customerState

Result:

{
"data": [
{
"orderDate": "2024-01-15",
"total": 299.99,
"customerName": "Acme Corp",
"customerState": "California"
}
]
}

Property Behaviour​

Each property in a query can have a behaviour that determines how it's processed:

BehaviourDescription
NoneInclude the property value as-is (default)
SUMTotal of numeric values
AVGAverage of numeric values
MINMinimum value
MAXMaximum value
COUNTNumber of items
FILTERUse for filtering only, not included in results

None (Default)​

Properties with no behaviour are included directly in results. When joining collections, this creates a flat result set.

Aggregation Functions​

Use SUM, AVG, MIN, MAX, or COUNT to aggregate values. When using aggregation, non-aggregated properties become grouping columns.

Filter Only​

The FILTER behaviour lets you use a property in the query's filter without including it in the results. Useful for filtering on fields you don't need to display.

Aggregation Example​

Query: Customer Order Totals

customers
└── orders (via customer lookup)
└── orderLines (via order lookup)
└── lineTotal

Property configuration:

  • customers.name → behaviour: None (grouping column)
  • orderLines.lineTotal → behaviour: SUM

Result:

{
"data": [
{ "customerName": "Acme Corp", "totalOrders": 15420.5 },
{ "customerName": "Tech Inc", "totalOrders": 8750.0 }
]
}

Sorting Query Results​

Use the sortBy parameter:

?sortBy=totalOrders-    # Descending
?sortBy=customerName # Ascending

Filtering Queries​

Apply filters to query results:

?filter=totalOrders gt 10000

Filter Operators​

OperatorDescriptionExample
eqEqual tostatus eq "active"
neNot equal tostatus ne "deleted"
gtGreater thantotal gt 100
ltLess thanquantity lt 10
geGreater than or equalrating ge 4
leLess than or equalpriority le 3
conContainscustomerName con "Corp"
swStarts withorderName sw "ORD-"
ewEnds withemail ew "@company.com"

Case-insensitive: Append ~ to operators (e.g., eq~, con~)

Dynamic Expressions​

Use exp() for dynamic values. The expression must be wrapped in single or double quotes.

tip

When using + in URLs, encode it as %2B (e.g., exp('now() %2B 7D')).

?filter=orderDate gt exp('now() - 30D')     # Orders from last 30 days
?filter=dueDate lt exp('now()') # Overdue orders
?filter=total gt exp('100 + 50') # Total > 150

Date/time tokens:

TokenDescriptionExample
now()Current UTC date-timenow()
DDaysnow() + 7D
HHoursnow() + 24H
MMinutesnow() + 30M
SSecondsnow() + 60S

Tokens are case-insensitive. All date/time values are handled in UTC.

Current user: userId() resolves to the id of the authenticated caller. As a request parameter it is a convenience; as the query's built-in filter it is what scopes a query per user, since _CREATOR does not (Access and Security).

Math functions: Abs, Ceiling, Floor, Round, Max, Min, Pow, Sqrt

Access and Security​

A query is read-only — only GET is accepted — and is authorized through its own access rules like any other entity. Security policies apply to a query's rows as they do everywhere else.

_CREATOR does not scope a query

Unlike a collection or a view, a query is not owner-filtered. Naming _CREATOR in a query's access rule still lets the caller through the gate, but it adds no narrowing of its own — so a query whose GET rule names only _CREATOR serves every row it would otherwise return to any authenticated caller. Only the query's built-in filter and the security policies on the collections it joins narrow it.

When a query has to be per-user, put that in the configuration, not in the request: give the query a built-in filter on userId(), or a security policy on the collections it joins. A filter passed as a request parameter is chosen by the caller and can simply be left out, so it is never an access control.

Query vs View​

FeatureQueryView
Joins collectionsYesNo
AggregationYesNo
Write operationsNo (read-only)Configurable
Filter built-inOptionalOptional
Owner filtering (_CREATOR)NoYes
PerformanceComputed on requestComputed on request

Best Practices​

  • Alias clearly — Use descriptive names for aliased properties
  • Limit joins — Complex queries with many joins may be slower
  • Add filters — Reduce result size with built-in filters
  • Use pagination — For large datasets, use pageNo and pageSize; for exporting a whole collection, prefer keyset pagination with lastKey