RSQL

Public API — RSQL Filtering

Every major list resource has a companion rsql-list endpoint that accepts one expressive filter string instead of dozens of query parameters — with and/or, parentheses, nested relation filters, sorting, and (for infrastructure resources) asynchronous CSV export.

Endpoints

All are POST, paginated with the standard ?page=&limit= envelope (see General guide):

ResourceEndpointPermissionCSV export
Sessions/api/v1/sessions/rsql-listsession:read
CDRs/api/v1/cdrs/rsql-listcdr:read
Charging pools/api/v1/charging-pools/rsql-listcharging-pool:readyes
Charging stations/api/v1/charging-stations/rsql-listcharging-station:readyes
Charging points/api/v1/charging-points/rsql-listcharging-point:readyes
Connectors/api/v1/connectors/rsql-listconnector:readyes
Station OCPP logs/api/v1/charging-stations/{id}/ocpp-logs/rsql-listcharging-station:read-logs

Request body — both fields optional:

curl -s -X POST -H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
  -d '{
        "filter": "status == '\''COMPLETED'\'' and startDate =gte= '\''2026-01-01T00:00:00Z'\''",
        "sort": "startDate desc"
      }' \
  "https://<host>/api/v1/sessions/rsql-list?page=1&limit=50"
  • filter — RSQL expression (syntax below). Omitted → no filtering.
  • sort — comma-separated field direction pairs, direction asc or desc (e.g. "name asc, createdAt desc"). Any scalar filterable field is sortable. Omitted → endpoint default order.

Filter syntax

-- comparison
status == 'ACTIVE'
name != 'Test'
totalEnergy =gt= 10
totalEnergy > 10                      -- shorthand: > >= < <= map to =gt= =gte= =lt= =lte=
startDate =gte= '2026-01-01T00:00:00Z'

-- logic: keyword operators, case-insensitive; and binds tighter than or
status == 'ACTIVE' and currency == 'EUR'
(status == 'ACTIVE' or status == 'COMPLETED') and currency == 'EUR'

-- value lists
status =in= ('ACTIVE', 'COMPLETED')
status =out= ('RESERVATION')

-- text (strings only)
name =contains= 'Park'
name =starts= 'EV'

-- null checks
stopDate =is= null
stopDate =isnot= null

-- ranges (value is a 2-element list)
totalEnergy =between= (5, 50)

-- nested to-one relation: dot notation
connector.powerType == 'DC'

-- nested to-many relation: dot notation = "at least one related record matches"
chargingPoints.status == 'AVAILABLE'

-- scoped sub-filter: all conditions must hold on the SAME related record
chargingPoints =any= (status == 'AVAILABLE' and connectors =any= (standard == 'CHADEMO'))

-- =all= : every related record matches
connectors =all= (status == 'AVAILABLE')

Operators

OperatorsMeaningField types
== !=equal / not equalall
=gt= =gte= =lt= =lte= (or > >= < <=)comparisonnumber, date
=between= =notbetween=inclusive range, value (low, high)number, date
=in= =out=membership in value liststring, enum, ID, number
=contains= =starts= =ends= (+ =notcontains= =notstarts= =notends=)text matchstring
=is= =isnot=null check, value must be nullnullable fields
=anyin= =allin=array field contains some / all of the valuesscalar arrays (e.g. tags)
=any= =all=scoped sub-filter on a relationlist relations
=anyeq= =anygt= =anylte==alleq= =allgt==allof= =anyof=quantified comparison over a related collectionrelation paths

Values

TypeFormat
String / enum'single' or "double" quotes; case-sensitive
Numberbare literal, negative allowed: =gt= -5
Booleantrue / false (bare)
DateISO 8601, quoted — unquoted : breaks the parser
Nullbare null with =is= / =isnot=
Value listparenthesized, comma-separated: ('A', 'B')

Discovering filterable fields

Each endpoint publishes its own filter spec — fields, types, allowed operators, enum values:

curl -s -H "x-api-key: $API_KEY" \
  "https://<host>/api/v1/sessions/rsql-list/configuration"

Some string/ID fields additionally offer GET .../configuration/{fieldPath}/autocomplete?query=... (typeahead values) and GET .../configuration/{fieldPath}/dictionary (ID → label pairs) — the configuration response marks which. The Swagger UI also embeds every endpoint's field list in its operation description.

CSV export

The four infrastructure endpoints export matching rows asynchronously:

# 1. Schedule — same filter/sort body, returns 201 with an export id
curl -s -X POST -H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
  -d '{ "filter": "status == '\''ONLINE'\''", "sort": "createdAt desc" }' \
  "https://<host>/api/v1/charging-stations/rsql-list/export-csv"

# 2. Poll until finished
curl -s -H "x-api-key: $API_KEY" "https://<host>/api/v1/rsql-csv-exports/<id>"

Status response: { id, finished, failed, errorMessage, downloadUrl, createdAt }downloadUrl is a signed URL, null until finished is true. Export status is only visible to the API key that scheduled it.

Errors

Invalid filters return 400 with a precise message:

{ "statusCode": 400, "error": "Bad Request", "message": "Unknown field: xyz" }
{ "statusCode": 400, "error": "Bad Request", "message": "Operator '=contains=' is not allowed for 'status'. Allowed: ==, !=, =in=, =out=" }

Gotchas

  • ; and , are NOT logical operators (unlike standard RSQL) — use the and / or keywords. A bare , only separates values inside (...) lists.
  • Quote datetime valuesstartDate =gte= "2026-01-01T00:00:00Z"; the unquoted : is a parse error.
  • String matching is case-sensitive: status == 'active' does not match ACTIVE.
  • Dot notation on a to-many relation is an implicit =any= — and two dot-notation conditions on the same relation may be satisfied by different related records. Use relation =any= (cond1 and cond2) to force same-record matching.
  • Numbers against string fields are coerced (address =contains= 27 works); booleans and dates in quotes are coerced too.

Related



Did this page help you?