Field Filters
Filter list results by attribute using attribute__filtertype=value.
The Platform API lets you filter GET list results on most object attributes. Filters are added as query parameters and can be combined freely.
Filter syntax
For every filter except an exact match, the format is:
<attribute>__<filtertype>=<value>
A bare <attribute>=<value> performs an exact match. Examples:
id__in=6865acf980bcac000f4ca5ec,6865acef80bcac000f4ca5dbtags__contains=pharmacreatedAt__date__gt=2026-01-01
There is no__exactsuffixUse the bare form.
label__exact=temperatureis not a recognised filter, so it is ignored and the endpoint returns every object in the collection — 215 variables instead of the 15 you asked for. Onlylabel=temperatureperforms an exact match. The same applies toid__exact,unit__exactandcreatedAt__exact.
Negating a filter
Any filter can be negated by using != instead of = — handy for excluding results.
| Filter | Returns |
|---|---|
tags__contains=pharma | Devices tagged pharma |
tags__contains!=pharma | Devices not tagged pharma |
lastActivity__gt=1700000000000 | Active since that time |
lastActivity__gt!=1700000000000 | Not active since that time |
Filter types by attribute
The filters available depend on the attribute's data type.
String — label, name, description, unit
label, name, description, unitiexact (case-insensitive) · contains · icontains · startswith · istartswith · endswith · iendswith · in · isnull
# Temperature variables whose label contains "temp" (case-insensitive)
curl -X GET 'https://api.surfact.com/api/v2.0/variables/?label__icontains=temp' \
-H 'X-Auth-Token: your_token_here'Enum — type
typeBare form only: type=raw or type=synthetic. type takes no __ suffix — type__iexact, type__icontains and type__in are all ignored and return every variable.
# Synthetic (derived) variables only
curl -X GET 'https://api.surfact.com/api/v2.0/variables/?type=synthetic' \
-H 'X-Auth-Token: your_token_here'Number — e.g. lastActivity, lastTriggered
lastActivity, lastTriggeredrange · gt (>) · gte (≥) · lt (<) · lte (≤) · isnull
# Events triggered at or after a given POSIX timestamp (ms)
curl -X GET 'https://api.surfact.com/api/v2.0/events/?lastTriggered__gte=1583248038207' \
-H 'X-Auth-Token: your_token_here'
# lastActivity within a range
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?lastActivity__range=1700000000000,1700600000000' \
-H 'X-Auth-Token: your_token_here'isnull is not recognised on lastTriggered — both lastTriggered__isnull=true and lastTriggered__isnull=false return every event. Use lastTriggered__gt=0 for alerts that have fired, and negate it (lastTriggered__gt!=0) for those that never have. isnull does work on lastActivity.
Date — createdAt
createdAtdate · year · quarter · month · week · day · hour · minute · second · isnull
createdAt is an ISO 8601 string (for example 2026-08-12T13:39:08.353792Z), so a Number filter applied to it directly is not recognised — createdAt__gt=1700000000000 is ignored and returns every object. Extract a date component first, then chain a filter onto it, because the extracted component is numeric:
# Devices created after May 20, 2026
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?createdAt__date__gt=2026-05-20' \
-H 'X-Auth-Token: your_token_here'
# Devices created in Q1
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?createdAt__quarter=1' \
-H 'X-Auth-Token: your_token_here'
Use the singular formTime-frame filters are singular:
day__rangeis correct,days__rangeis not.
ID — id
idin — plus the bare id=<value> form for a single object
# Fetch two specific trackers by id
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?id__in=6865acf980bcac000f4ca5ec,6865acef80bcac000f4ca5db' \
-H 'X-Auth-Token: your_token_here'Tags (string list) — tags
tagscontains · contained_by · overlap · len · isnull
# All devices tagged for a customer's pharma cold chain
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?tags__contains=pharma' \
-H 'X-Auth-Token: your_token_here'Properties (object) — properties
propertiescontains · contained_by · has_key · has_any_keys · has_keys · isnull
Every properties__* filter except isnull takes a JSON-encoded value, URL-encoded into the query string: has_key a JSON string, has_any_keys and has_keys a JSON array, contains and contained_by a JSON object. isnull takes a plain true or false. A bare word or a comma-separated list is rejected with 400 and {"code": 400001, "message": "Validation Error.", "detail": {"properties__has_key": ["Enter a valid JSON."]}}.
# Devices that carry a calibration certificate property
# The value is the JSON string "certificate", URL-encoded as %22certificate%22
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?properties__has_key=%22certificate%22' \
-H 'X-Auth-Token: your_token_here'
# The list operators take a JSON array — ["certificate"] URL-encoded
curl -X GET 'https://api.surfact.com/api/v2.0/devices/?properties__has_keys=%5B%22certificate%22%5D' \
-H 'X-Auth-Token: your_token_here'
Unrecognised filters are ignored — unparseable values are notAn unrecognised filter name or suffix is silently dropped and the endpoint returns all objects, so a filter that "isn't working" is usually a typo in the attribute name, the
__separator, or a suffix the attribute does not support.A recognised filter carrying a value it cannot parse fails loudly instead:
400with{"code": 400001, "message": "Validation Error."}and adetailnaming the parameter — for examplelastActivity__gt=notanumberreturns"Enter a number.",createdAt__date__gt=notadatereturns"Enter a valid date.", andlastActivity__range=1,2,3returns"Range query expects two values.". See the FAQ.
Combine filters with Sorting, Pagination, and Dynamic Fields to build precise queries.
Updated 7 days ago