Searching & query recipes
Loca.fi gives you three distinct ways to find data, in increasing order of power. Pick the lightest one that answers your question:
- OData filtering on the
GetFiltered*list endpoints — quick, URL-based$filter/$orderby/paging over a single entity's columns. - Advanced Search (
POST api/advancedsearch/{entity}) — a nested AND/OR rule tree over an allow-listed set of properties, including related-entity fields; the same engine that backs saved reports. - Item find-it search (
api/itemsearch/*) — a stateful handheld workflow that snapshots a set of expected items and tracks which have been physically located.
Read Errors & conventions first for
the PageResult envelope and the platform's error shape — every search
mechanism returns results and errors the same way.
Overview
| You want to… | Use |
|---|---|
| Page/filter one entity by its own columns, from a URL | OData on the entity's GetFiltered* endpoint |
| Query with nested AND/OR conditions, or across related entities | Advanced Search |
| Drive a physical "find these items" scan on a handheld | Item find-it search |
| Export a saved query to CSV/PDF | see generatereport — covered in the reporting reference |
Prerequisites
- A token — see Getting started. The scheme is
Authorization: Token <jwt>, notBearer. - Advanced Search and OData both enforce the caller's module Read
permission for the entity being queried (e.g.
ItemRead for item search).
OData filtering
Most list endpoints named GetFiltered* (and some GetAll*) accept the OData
query options $filter, $orderby, $top, $skip, and $count. They return
a PageResult: an Items array, a
NextPageLink, and a nullable Count.
$ in curlOn the command line the $ in OData parameters must be URL-escaped as %24
(otherwise the shell eats it). Browsers and HTTP clients that send the literal
$ are fine.
# Items whose Name contains "jacket", newest first, first 20, with a total count
curl -sk "http://<host>/api/items/getfiltereditems?\
%24filter=contains(Name,'jacket')&%24orderby=DateCreated desc&%24top=20&%24count=true" \
-H "Authorization: Token $TOKEN"
Common recipes:
| Goal | $filter fragment |
|---|---|
| Exact match | Name eq 'Nike' |
| Substring | contains(Name,'jack') |
| Enum value (by name) | LifecycleState eq 'Active' |
| Date range | DateCreated ge 2026-01-01T00:00:00Z and DateCreated lt 2026-02-01T00:00:00Z |
| Combine | contains(Name,'jack') and BrandName eq 'Nike' |
Page by following NextPageLink until it is null, or drive it yourself with
$top/$skip.
The OData layer translates to Entity Framework 6. Any function EF6 cannot turn
into SQL — most notably matchesPattern — fails at materialisation and
surfaces as an HTTP 500, not a validation error. Stick to the operators in
the table above (eq, ne, gt/ge/lt/le, and/or, contains,
startswith/endswith). This is a platform-wide limitation, not a per-endpoint
one.
Advanced Search
When a URL filter isn't expressive enough — nested AND/OR logic, In lists, or
conditions on related entities — POST an AdvancedSearchDto
to the per-entity endpoint. All of these are Public-tier and each is gated
on its entity's module Read permission:
| Endpoint | Module |
|---|---|
advancedsearch/items | Item |
advancedsearch/skus | Sku |
advancedsearch/places | Place |
advancedsearch/persons | Person |
advancedsearch/collections | Collection |
advancedsearch/tasks | Task |
advancedsearch/orders · orderlines | Order |
advancedsearch/itemmovements · itemstatehistory | Item |
advancedsearch/servicerecords · servicerecorditems | ServiceRecord |
The rule tree
The request body is an AdvancedSearchDto with three parts:
CustomQuery— aCustomDetailedQueryDtonode, the query itself.CustomPagingSorting— aSortingPagingDto(PageNumber,NumberRecords, and aSortOrderslist).TimeZoneOffset— optional, for date-boundary correctness.
Each CustomQuery node is either a group or a leaf:
- A group has a non-empty
Ruleslist of child nodes, combined byLogicalCondition—And(0, every child must match) orOr(1, any child). Groups nest arbitrarily. - A leaf has an empty
Rulesand instead carriesProperty,Comparator, andValue— one condition.
Property is an allow-listed, client-facing name (including related-entity
properties like PlaceName); the engine translates it to the real EF path.
An unrecognised property is rejected, not passed through — this is the
engine's injection guard. Comparator is a ComparisonType:
| Comparator | Meaning |
|---|---|
Equal / NotEqual | = / ≠ |
Contains / NotContains | substring |
In / NotIn | value is a list |
LessThan / LessThanOrEqual / GreaterThan / GreaterThanOrEqual | ordering |
IsNull | property is null (no Value needed) |
Example
Items that are Active and either in place "Store 1" or tagged after a
date:
curl -sk -X POST "http://<host>/api/advancedsearch/items" \
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d '{
"CustomQuery": {
"LogicalCondition": "And",
"Rules": [
{ "Property": "LifecycleState", "Comparator": "Equal", "Value": "Active" },
{
"LogicalCondition": "Or",
"Rules": [
{ "Property": "PlaceName", "Comparator": "Equal", "Value": "Store 1" },
{ "Property": "DateCreated", "Comparator": "GreaterThan", "Value": "2026-06-01T00:00:00Z" }
]
}
]
},
"CustomPagingSorting": {
"PageNumber": 1,
"NumberRecords": 50,
"SortOrders": [ { "ColumnName": "Name", "ColumnOrder": "Ascending" } ]
}
}'
The same CustomQuery shape is what a saved report preference stores — see the
reporting reference for turning a query into a scheduled CSV/PDF.
Item find-it search
Unlike the stateless searches above, a find-it search is a stateful record: you snapshot the items you expect to find, then a handheld reports what it physically reads, and the platform tracks located vs missing. The lifecycle mirrors the item search states.
- Create the search with the query defining its expected item set —
POST api/itemsearch/create(AddItemSearchDto). The response carries the new search id. - Start it —
POST api/itemsearch/startsearch/{searchId}(also auto-starts on the first found report). - Report found items as the handheld reads them —
POST api/itemsearch/itemsfound(ItemSearchFoundDto). Returns the updatedItemSearchDetailDtowith runningTotalFoundvsTotalItems. - End the search —
POST api/itemsearch/endsearch.
Review progress any time with
getfilteredlist (OData) or
getsingle/{Id}.
Error handling
Standard error conventions apply — a
400 with a JSON array of { ErrorCode, Message, Field }, and ErrorCode
often collapses to InvalidOperation on the wire (read Message). Search
specifics:
| Situation | Response |
|---|---|
OData with an EF6-untranslatable function (e.g. matchesPattern) | 500 (platform limitation) |
Advanced Search with an unrecognised Property | 400 — property not in the entity's allow-list |
| Advanced Search / OData without the entity's module Read permission | 403 |
Malformed OData $filter | 400 |
| find-it action on an unknown search id | 400 error array |
Related
- Concepts: Errors & conventions, Item lifecycle.
- Reference: Search items, Search SKUs, Create item search, Generate report.