Skip to main content

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:

  1. OData filtering on the GetFiltered* list endpoints — quick, URL-based $filter/$orderby/paging over a single entity's columns.
  2. 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.
  3. 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 URLOData on the entity's GetFiltered* endpoint
Query with nested AND/OR conditions, or across related entitiesAdvanced Search
Drive a physical "find these items" scan on a handheldItem find-it search
Export a saved query to CSV/PDFsee generatereport — covered in the reporting reference

Prerequisites

  • A token — see Getting started. The scheme is Authorization: Token <jwt>, not Bearer.
  • Advanced Search and OData both enforce the caller's module Read permission for the entity being queried (e.g. Item Read 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.

Escaping $ in curl

On 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 matchName eq 'Nike'
Substringcontains(Name,'jack')
Enum value (by name)LifecycleState eq 'Active'
Date rangeDateCreated ge 2026-01-01T00:00:00Z and DateCreated lt 2026-02-01T00:00:00Z
Combinecontains(Name,'jack') and BrandName eq 'Nike'

Page by following NextPageLink until it is null, or drive it yourself with $top/$skip.

Unsupported functions return 500, not 400

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.

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:

EndpointModule
advancedsearch/itemsItem
advancedsearch/skusSku
advancedsearch/placesPlace
advancedsearch/personsPerson
advancedsearch/collectionsCollection
advancedsearch/tasksTask
advancedsearch/orders · orderlinesOrder
advancedsearch/itemmovements · itemstatehistoryItem
advancedsearch/servicerecords · servicerecorditemsServiceRecord

The rule tree

The request body is an AdvancedSearchDto with three parts:

  • CustomQuery — a CustomDetailedQueryDto node, the query itself.
  • CustomPagingSorting — a SortingPagingDto (PageNumber, NumberRecords, and a SortOrders list).
  • TimeZoneOffset — optional, for date-boundary correctness.

Each CustomQuery node is either a group or a leaf:

  • A group has a non-empty Rules list of child nodes, combined by LogicalConditionAnd (0, every child must match) or Or (1, any child). Groups nest arbitrarily.
  • A leaf has an empty Rules and instead carries Property, Comparator, and Value — 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:

ComparatorMeaning
Equal / NotEqual= /
Contains / NotContainssubstring
In / NotInvalue is a list
LessThan / LessThanOrEqual / GreaterThan / GreaterThanOrEqualordering
IsNullproperty 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.

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.

  1. Create the search with the query defining its expected item set — POST api/itemsearch/create (AddItemSearchDto). The response carries the new search id.
  2. Start it — POST api/itemsearch/startsearch/{searchId} (also auto-starts on the first found report).
  3. Report found items as the handheld reads them — POST api/itemsearch/itemsfound (ItemSearchFoundDto). Returns the updated ItemSearchDetailDto with running TotalFound vs TotalItems.
  4. 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:

SituationResponse
OData with an EF6-untranslatable function (e.g. matchesPattern)500 (platform limitation)
Advanced Search with an unrecognised Property400 — property not in the entity's allow-list
Advanced Search / OData without the entity's module Read permission403
Malformed OData $filter400
find-it action on an unknown search id400 error array