Skip to content

Advanced Filtering

The advanced filtering API lets you search a collection for entities that match one or more filter criteria. You send a POST request to the filter endpoint with a JSON body that describes the criteria you want to apply, and the API returns the matching entities together with pagination and count information.

Each criterion describes a filter type (what kind of data you are matching: text, number, date, ...), a key (which field to look at), and a value (what to match). A set of matchers determines how the value is compared to the data. This document is the complete reference: the endpoint and its query parameters, every filter type and matcher, every request field, the response format, and a set of worked examples.

Endpoint

POST /entities/filter

This searches the entities collection. Other collections can be searched by substituting the collection name, for example POST /mediafiles/filter. Some installations expose versioned routes (for example /<spec>/v1/entities/filter); check the API documentation of your deployment (/api/docs) for the exact path.

The request body must be a JSON array. The Content-Type header must be application/json. The endpoint is protected by the same authentication and authorization rules as the rest of the collection API.

Query parameters

ParameterTypeDescription
skipintNumber of results to skip. Default 0.
limitintMaximum number of results to return. Default 20.
order_bystringComma-separated list of fields to sort on.
asc0/1Sort ascending (1, default) or descending (0).
fieldstringRestrict the returned fields. Repeatable, field[] also accepted.
history0/1Query the history collection instead of the current collection.
soft0/1Dry run; validates the request without executing the query.
exact_count0/1Bypass the count cap and return the true total. Default 0.

Request body

The body is always a JSON array of filter criteria:

json
[
  { "type": "type", "value": "asset" },
  {
    "type": "text",
    "key": "properties.title.value",
    "value": "Portret",
    "match_exact": true
  }
]

This returns every asset whose title property equals Portret.

A type filter is strictly required for every request. Every request body must contain at least one criterion that filters on the entity type: either a type filter (a single type) or a selection filter on the type key (one or more types). See Type.

All criteria in the array are combined with a logical AND by default. See Combining filters for how to build OR expressions.

Every example body in this document is a JSON array of filter criteria and always includes a type filter.

Filter types and matchers

The table below lists every filter type and the matchers it supports.

Filter typeMatchersvalue format
textany, none, exact, contains, contains_not, regexstring, "*", or ""
numberexact, min_included, max_included, in_betweennumber, or {min/max, included}
dateany, none, exact, min_included, max_included, in_betweendate string, or {min/max}
selectionany, none, exact, contains, contains_notarray of values, "*", or ""
booleanany, none, exacttrue / false
geogeoGeoJSON geometry
typeany, none, exactentity type string

Which matcher is applied is decided by the value and the modifier flags on the criterion (match_exact, match_not, regex, ...). The sections below describe each filter type, the matchers it supports, and how to trigger them.

Text

Filters on text values. Applies to any field that stores a string.

MatcherTriggerDescription
any"value": "*"Field has a non-empty value.
none"value": ""Field is empty or absent.
exact"value": "...", "match_exact": trueExact (case-sensitive) match.
contains"value": "..." (default)Case-insensitive substring match.
contains_not"value": "...", "match_not": trueField does not contain the substring.
regex"value": "...", "regex": true, "regex_options": "i"Regular expression match.

Contains matches are case-insensitive. The characters *, ^ and $ are treated as wildcards (. and anchors), so ^lpa1: matches any value starting with lpa1:.

json
[
  { "type": "type", "value": "IotDevice" },
  { "type": "text", "key": "properties.name.value", "value": "*" },
  { "type": "text", "key": "properties.serial_number.value", "value": "^lpa1:" },
  { "type": "text", "key": "properties.description.value", "value": "" }
]

Regular expressions are triggered with regex: true; regex_options sets the flags (for example i for a case-insensitive match):

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "text",
    "key": "properties.serial_number.value",
    "value": "^(lpa1|lpb2)-[0-9]{4}$",
    "regex": true
  }
]

Number

Filters on numeric values. Range bounds use the included flag to mark the boundary as inclusive; the flag must be present for a boundary matcher to apply.

MatcherTriggerDescription
exactscalar valueValue is exactly equal.
min_included{ "min": N, "included": true }Value is >= N.
max_included{ "max": N, "included": true }Value is <= N.
in_between{ "min": A, "max": B }Value is between A and B (inclusive).
json
[
  { "type": "type", "value": "asset" },
  {
    "type": "number",
    "key": "properties.width.value",
    "value": { "min": 4137, "included": true }
  },
  {
    "type": "number",
    "key": "properties.height.value",
    "value": { "min": 800, "max": 1200, "included": true }
  }
]

Date

Filters on date and datetime values. Values are ISO 8601 strings.

MatcherTriggerDescription
any"value": "*"Field has a non-empty value.
none"value": ""Field is empty or absent.
exactfull timestamp "2022-04-14T15:00:00"Value is exactly that instant.
min_included{ "min": "...", "included": true }Value is >= the given datetime.
max_included{ "max": "...", "included": true }Value is <= the given datetime.
in_between{ "min": "...", "max": "..." }Value is between (inclusive).

A bare date ("2022-04-14", no time component) is interpreted as the whole calendar day rather than a single instant:

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "date",
    "key": "properties.date_last_reported.value",
    "value": { "min": "2022-04-13", "max": "2022-04-14T15:00:00" }
  },
  {
    "type": "date",
    "key": "properties.created.value",
    "value": { "max": "2022-04-14T15:00:00", "included": true }
  }
]

Selection

Filters on a field against one or more pre-defined values. This is also the recommended filter type for IDs and identifiers: an array of values is treated as an OR (any value in the list matches).

MatcherTriggerDescription
exactarray (or scalar) valueField equals any of the given values.
any"value": "*"Field has a non-empty value.
none"value": ""Field is empty or absent.
contains"value": "...", "match_exact": falseField contains the substring.
contains_not"value": "...", "match_not": trueField does not contain the substring.
json
[
  { "type": "type", "value": "mediafile" },
  {
    "type": "selection",
    "key": "properties.filesize.value",
    "value": ["482978", "13525477"],
    "match_exact": true
  },
  {
    "type": "selection",
    "key": "identifiers",
    "value": ["R 53.5", "Bg9DTJPNRjLQmViHTb72qGeR"],
    "match_exact": true
  }
]

Boolean

Filters on boolean fields.

json
[
  { "type": "type", "value": "asset" },
  {
    "type": "boolean",
    "key": "properties.is_original.value",
    "value": true
  }
]

Type

Filters on the entity type. The simple form takes a single type:

json
[
  { "type": "type", "value": "asset" }
]

To match one of several types, use a selection filter on the type key:

json
[
  {
    "type": "selection",
    "key": "type",
    "value": ["asset", "asset_part"],
    "match_exact": true
  }
]

A type filter is strictly required for every request. Every request body must contain at least one criterion that filters on the entity type: either a type filter (a single type) or a selection filter on the type key (one or more types). The type filter lets the API route the request to the correct underlying collection, and every request without one is rejected. There is no restriction on where the type filter appears in the array; it simply needs to be present. Every example body in this document includes a type filter.

Geo

Filters on GeoJSON geometry fields. The value is a GeoJSON geometry (most commonly a polygon) and the criterion matches entities whose point lies inside it.

json
[
  {
    "type": "type",
    "value": "region"
  },
  {
    "type": "geo",
    "key": "properties.gps_coordinates.geojson",
    "value": {
      "type": "Polygon",
      "coordinates": [
        [
          [-74.006, 40.712],
          [-73.935, 40.712],
          [-73.935, 40.779],
          [-74.006, 40.779],
          [-74.006, 40.712]
        ]
      ]
    }
  }
]

Geo filters additionally support a bucket option. Setting "bucket": <integer> subdivides the polygon into a grid of that many cells and returns, instead of every matching entity, one representative entity per cell together with a bucket_count (the number of entities in that cell). This is useful for map visualisations that need to cluster large result sets.

Filter criteria fields

Every criterion is a JSON object. Only type, key and value are required (the key is not needed for type filters). All other fields are optional modifiers.

FieldTypeDescription
typestringFilter type: text, number, date, selection, boolean, geo, type.
keystring / arrayField to filter on. See Keys.
valuevariesValue(s) to match; format depends on the filter type.
match_exactboolSwitch between exact and contains matching. Default false.
match_notboolNegate a contains match (contains_not). Default false.
regexboolTreat the value as a regular expression. Default false.
regex_optionsstringRegex flags (e.g. i for case-insensitive). Default "".
operatorand / orCombine this criterion with OR instead of AND. Default and.
orarrayNested list of criteria combined as an OR. See Combining.
inner_exact_matchesobjectExtra equality constraints applied within an object list entry. See Inner exact matches.
lookupobjectJoin to another collection (virtual relations). See Relations.
aggregationstringApply an aggregator to an array field (e.g. size to count relations).
distinct_bystringGroup results by a field; one result per distinct value.
facetsarrayCompute facet counts alongside the results. See Facets.

Keys and document structure

A key is a dotted path into the document. Documents store structured lists of objects, and the path tells the filter engine how to traverse them. The main object lists are metadata, properties, relations and data.

properties.object_number.value   ->  the property entry with key "object_number",
                                     and its "value" field
properties.gps_coordinates.geojson -> the "geojson" field of the "gps_coordinates"
                                     property
properties.ref_words.value      ->  the id of a referenced word entity

Parts of a key that are part of an object list need no special syntax. Fields inside the object list entry are addressed as regular path segments after the object-list part.

Schema-prefixed keys

A key can be written as an array of schema:version|path strings. The engine then treats each entry as a separate schema and combines them with an OR:

json
[
  { "type": "type", "value": "asset" },
  {
    "type": "selection",
    "key": ["dams:1|relations.isAssetFor.key"],
    "value": ["9f7146e4-7aba-5993-8f58-320dc325a2f9"],
    "match_exact": true
  }
]

Optional and required keys

Prefix a key with ? to make the criterion optional: entities match if they have the value, or if the field is empty. The ! prefix can only be used in combination with ?, as !?, to force an optional criterion to apply strictly. A boolean criterion with value false is treated as optional automatically.

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "text",
    "key": "?properties.alternate_name.value",
    "value": "POA-ANTHAO-002",
    "match_exact": true
  }
]

Forcing an optional criterion to apply strictly:

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "text",
    "key": "!?properties.serial_number.value",
    "value": "^lpa1:",
    "match_exact": true
  }
]

Combining filters

Multiple criteria in the request body are combined with AND. Two criteria are combined with OR when at least one of them carries "operator": "or":

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "selection",
    "key": "properties.device_category.value",
    "value": ["tracker"],
    "match_exact": true,
    "operator": "or"
  },
  {
    "type": "selection",
    "key": "properties.alternate_name.value",
    "value": ["POA-ANTHAO-002"],
    "match_exact": true,
    "operator": "or"
  }
]

This matches IotDevices whose category is tracker or whose alternate name is POA-ANTHAO-002.

For a nested OR group inside an otherwise AND-combined request, use the or field on a criterion:

json
[
  {
    "type": "selection",
    "key": "type",
    "value": ["asset", "asset_part"],
    "match_exact": true
  },
  {
    "type": "selection",
    "key": "relations.isAssetFor.key",
    "value": ["9f7146e4-7aba-5993-8f58-320dc325a2f9"],
    "match_exact": true,
    "or": [
      {
        "type": "selection",
        "key": "relations.isAssetPartFor.key",
        "value": ["9f7146e4-7aba-5993-8f58-320dc325a2f9"],
        "match_exact": true
      }
    ]
  }
]

This matches assets (or asset parts) that are an asset for the given entity or an asset part for it.

Inner exact matches

inner_exact_matches adds equality constraints on the entries of an object list being scanned. For example, to collect keyword relations that have the lang entry set to en, add an exact match on that entry field:

json
[
  { "type": "type", "value": "media" },
  {
    "type": "text",
    "key": "relations.hasKeyword.key",
    "value": "*",
    "inner_exact_matches": { "lang": "en" }
  }
]

Relation filters

Entities are linked to each other through relations. A relation is stored as a plain property on the document that references the id of another entity. There is no dedicated relations namespace: such id references can live anywhere in the document, for example under relations.<type>.key or under a property such as properties.ref_words.value, which holds the id of a referenced word entity. For filtering purposes a relation has no special meaning: it is filtered on exactly like any other field that stores an id.

Filtering on a relation is therefore just an equality check on its id value:

json
[
  { "type": "type", "value": "asset" },
  {
    "type": "selection",
    "key": "relations.isAssetFor.key",
    "value": ["9f7146e4-7aba-5993-8f58-320dc325a2f9"],
    "match_exact": true
  }
]

This matches every entity that has a relation of type isAssetFor pointing to the given entity.

Counting relations with the aggregation modifier (see Aggregation):

json
[
  { "type": "type", "value": "inscription" },
  {
    "type": "number",
    "key": "properties.ref_words.value",
    "value": { "min": 4, "max": 6, "included": true },
    "match_exact": true,
    "aggregation": "size"
  }
]

This matches inscriptions that have between 4 and 6 referenced words.

Virtual relations (lookup)

Some relations are not stored on the document itself but must be resolved by joining to another collection. These are addressed through lookup and use keys prefixed with lookup.virtual_relations.<name>.

A lookup object has the following fields:

FieldDescription
fromThe collection to join to.
local_fieldField on the source document.
foreign_fieldField on the target collection.
asName of the virtual relation.
json
[
  { "type": "type", "value": "PoliceZone" },
  {
    "lookup": {
      "from": "devices_actual",
      "local_field": "id",
      "foreign_field": "tenants",
      "as": "lookup.virtual_relations.zonesServed"
    },
    "type": "selection",
    "key": "lookup.virtual_relations.zonesServed.identifiers",
    "value": ["urn:ngsi-ld:IotDevice:anpr:5345ANT664ADJ1"],
    "match_exact": true
  }
]

This returns every police zone that has the given IotDevice among its served devices, where the relationship lives on the device (tenants) rather than on the zone.

The same mechanism works in the reverse direction, where the joined collection references the source document:

json
[
  { "type": "type", "value": "PoliceAsset" },
  {
    "lookup": {
      "from": "assets_actual",
      "local_field": "properties.ref_asset_group.value",
      "foreign_field": "id",
      "as": "lookup.virtual_relations.ref_asset_group"
    },
    "type": "selection",
    "key": "lookup.virtual_relations.ref_asset_group.properties.name.value",
    "value": ["dev"],
    "match_exact": true
  }
]

Distinct values

distinct_by groups the result so that only one entity is returned per distinct value of the given field. It is used to populate dropdowns of attribute values.

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "text",
    "key": "properties.state.value",
    "value": "*",
    "distinct_by": "properties.state.value"
  }
]

Facets

A facets array computes counts per distinct value for one or more fields, returned alongside the results in the response facets list.

json
[
  {
    "type": "type",
    "value": "point",
    "facets": [
      { "key": "properties.record_completion_status.value" },
      {
        "lookups": [
          {
            "from": "sites_actual",
            "local_field": "properties.ref_site.value",
            "foreign_field": "id",
            "as": "lookup.virtual_relations.ref_site"
          },
          {
            "from": "areas_actual",
            "local_field": "lookup.virtual_relations.ref_site.properties.ref_area.value",
            "foreign_field": "id",
            "as": "lookup.virtual_relations.ref_area"
          }
        ],
        "key": "lookup.virtual_relations.ref_area.properties.name.value"
      }
    ]
  }
]

A facet can chain several lookups to facet on a field that lives a few relations away.

Aggregation

The aggregation modifier applies an aggregator to an array-valued field before comparing. The supported aggregator is size, which counts the elements of an array. This is how you filter on the number of relations a document has:

json
[
  { "type": "type", "value": "inscription" },
  {
    "type": "number",
    "key": "properties.ref_words.value",
    "value": { "min": 4, "max": 6, "included": true },
    "match_exact": true,
    "aggregation": "size"
  }
]

Response

A normal filter response has the following shape:

json
{
  "results": [ ... ],
  "count": 1254,
  "skip": 0,
  "limit": 20
}
FieldDescription
resultsThe matching entities.
countTotal number of matches.
skipThe skip value used for this request.
limitThe limit value used for this request.
facetsFacet counts, present when a facet was requested.
nextRelative URL to fetch the next page, when one exists.
previousRelative URL to fetch the previous page, when one exists.

Count limits

On very large result sets the total count may be capped. When the number of matches exceeds the configured cap, count reports <cap>+ (for example 1000+), meaning there are more matches than the count shown. The unfiltered total of a whole collection is always exact. See Rounded counts for details.

Pass ?exact_count=1 to bypass the cap and get the true total (for example when a user clicks through on a 1000+ count). This runs the count to completion instead of stopping early, so it is more expensive on large, filtered result sets — use it on demand, not on every request.

Response formats

The endpoint honours the Accept header. Besides plain JSON it can return CSV, text/uri-list, and RDF serialisations (application/ld+json, application/n-triples, application/rdf+xml), which are applied to the entities in results.

Errors

The endpoint returns HTTP 400 with a JSON error body when the request is invalid. Common cases:

  • The body is not a JSON array.
  • A criterion uses an unknown type value.
  • A number/date range value is missing both min and max.
  • A range's min is larger than its max.

A soft query parameter (?soft=1) runs validation without executing the query, which is useful for testing whether a request body is accepted.

Worked examples

A search combining an exact match on a category with a contains search on a serial number:

json
[
  { "type": "type", "value": "IotDevice" },
  {
    "type": "text",
    "key": "properties.category.value",
    "value": "track&trace",
    "match_exact": true
  },
  {
    "type": "text",
    "key": "properties.serial_number.value",
    "value": "^lpa1:"
  }
]

Negating a text match to find creators whose initials do not contain a value:

json
[
  { "type": "type", "value": "creator" },
  {
    "type": "text",
    "key": "properties.initials.value",
    "value": "zn",
    "match_exact": false,
    "match_not": true
  }
]

Filtering mediafiles of a single schema on a technical field:

json
[
  { "type": "type", "value": "mediafile" },
  {
    "type": "text",
    "key": "technical_origin",
    "value": "original",
    "match_exact": true
  }
]

Finding an asset group by its members, using a reverse lookup:

json
[
  { "type": "type", "value": "AssetGroup" },
  {
    "lookup": {
      "from": "assets_actual",
      "local_field": "id",
      "foreign_field": "properties.ref_asset_group.value",
      "as": "lookup.virtual_relations.assets"
    },
    "type": "selection",
    "key": "identifiers",
    "value": "*",
    "match_exact": true
  }
]

Powered by Elody - Open Source Semantic Data Platform