Skip to content
Last updated

Filtering

This page explains how to use the filter query parameter to retrieve only the records you need from list endpoints in the Kanbert REST API. It’s written for external developers integrating with the API and is safe to use with Redocly.

TL;DR

  • Add a filter query parameter to list endpoints (e.g., GET /api/v1/projects).
  • Two formats are supported: JSON and a human‑readable string DSL.
  • You can combine multiple conditions, nest groups with and / or, and use rich operators (e.g., contains, in, gte).
  • Fields and operators are validated per endpoint. Unknown fields/operators return a 400 error.

Supported endpoints

Filtering is supported on list endpoints (for example, GET /api/v1/projects). Consult each endpoint’s description to see a list of filterable fields and their types. In general, you can expect common fields like id, created_at, and domain‑specific fields to be available.

Important note

Field availability is endpoint‑specific. When in doubt, try the examples below or check the endpoint’s filter Query parameter.


Filter formats

You can pass the filter parameter in one of two formats:

1) JSON format

Simple array (logical AND by default):

[
  {"field": "title", "op": "contains", "value": "Acme"},
  {"field": "created_at", "op": "gte", "value": "2024-01-01"}
]

Explicit boolean grouping with and/or:

{
  "and": [
    {"field": "client.name", "op": "contains", "value": "Acme"},
    {"or": [
      {"field": "shortcode", "op": "eq", "value": "P-42"},
      {"field": "shortcode", "op": "startswith", "value": "P-"}
    ]}
  ]
}

Form‑encoded equivalent is also supported (useful for HTML forms):

filter[0][field]=title&filter[0][op]=contains&filter[0][value]=Acme

2) String DSL format

Pass a readable string instead of JSON:

filter=title contains "Acme" and (created_at >= "2024-01-01" or client.name contains 'Corp')

Examples:

filter=shortcode = "P-42"
filter=client.name contains 'Acme' or title startsWith 'P-'
filter=status in ['ACTIVE','PENDING'] and budget >= 1000
filter=primary_contact_id is null

Operators

Unless restricted by the endpoint, the following operators are generally available:

  • Comparison: eq, ne/neq, lt, lte/le, gt, gte/ge
  • Strings: contains, like, startsWith, endsWith
  • Sets: in, nin/notin (arrays like [1,2,3] or ['A','B'])
  • Null checks: is null, is not null

Operator names are case‑insensitive in the DSL format. Symbol equivalents are supported in DSL too: =, !=, <, <=, >, >=.


Field types and values

Filters are type‑aware. Common types include:

  • string — free‑text fields; use string operators like contains or equality.
  • number — numeric fields; use comparison operators.
  • booleantrue / false values.
  • datetime / date — ISO‑8601 timestamps or dates, e.g., 2025-01-31T12:34:56Z or 2025-01-31.
  • enum — limited set of string values; combine with eq or in.
  • id — Entity ID. Usually Schema of entity is mentioned at filter definition.
  • relation - Can be used to filter a N+1 relation. See Section "Relation filters" below

If a field maps to a related model, dot‑notation may be available (e.g., client.name).


Relation filters: any, all, none, and .count

You can filter by conditions on relations using relation operators and counts.

  • any: at least one related record matches the sub‑filter. If no sub‑filter is provided, it means “has at least one related record”.
  • none: no related records match the sub‑filter. Without a sub‑filter, it means “has no related records”.
  • all: every related record matches the sub‑filter (use when all associated rows must meet a condition).
  • .count: compare the number of related records using standard comparison operators (=, !=, <, <=, >, >=).

Examples (DSL):

# Any related member with first_name = 'Neil'
filter=members any (first_name = 'Neil')

# Has at least one member (existence)
filter=members any

# No related members with role = 'EXTERNAL'
filter=members none (role = 'EXTERNAL')

# All related tasks are completed
filter=tasks all (status = 'DONE')

# Projects with more than 0 tasks
filter=tasks.count > 0

# Projects with at least 3 active tasks
filter=tasks.count >= 3 and tasks any (status = 'ACTIVE')

Examples (JSON):

[
  {"field":"members","op":"any","value":{"field":"first_name","op":"eq","value":"Neil"}},
  {"field":"members","op":"none","value":{"field":"role","op":"eq","value":"EXTERNAL"}},
  {"field":"tasks","op":"all","value":{"field":"status","op":"eq","value":"DONE"}},
  {"field":"tasks.count","op":"gt","value":0},
  {"field":"tasks.count","op":"gte","value":3}
]

Notes:

  • The sub‑filter for any, none, and all uses the same field/operator rules as top‑level filters, but fields are relative to the related model (e.g., members.first_name).
  • You can also combine relation filters with other conditions and boolean groups.

Practical examples

Below are example requests using cURL. Replace the base URL and endpoint as needed.

Filter by title substring and minimum creation date (JSON):

curl -G \
  --data-urlencode 'filter=[{"field":"title","op":"contains","value":"Acme"},{"field":"created_at","op":"gte","value":"2024-01-01"}]' \
  'https://api.kanbert.com/api/v1/projects'

Find by related client name or matching shortcode prefix (JSON with grouping):

curl -G \
  --data-urlencode 'filter={"and":[{"field":"client.name","op":"contains","value":"Acme"},{"or":[{"field":"shortcode","op":"eq","value":"P-42"},{"field":"shortcode","op":"startsWith","value":"P-"}]}]}' \
  'https://api.kanbert.com/api/v1/projects'

Same logic using the DSL string:

curl -G \
  --data-urlencode "filter=client.name contains 'Acme' and (shortcode = 'P-42' or shortcode startsWith 'P-')" \
  'https://api.kanbert.com/api/v1/projects'

Filter on sets and numeric comparisons:

curl -G \
  --data-urlencode "filter=status in ['ACTIVE','PENDING'] and budget >= 1000" \
  'https://api.kanbert.com/api/v1/projects'

Null checks:

curl -G \
  --data-urlencode "filter=primary_contact_id is null" \
  'https://api.kanbert.com/api/v1/projects'

Sorting, pagination, and includes

Filtering works independently of sorting and pagination:

  • Sorting: continue using the documented sort parameter for each endpoint.
  • Pagination: use the default pagination options for the endpoint (cursor or page‑based) as documented.
  • Includes: if the endpoint supports include/fields (sparse fieldsets), you can combine them with filtering.

Validation and errors

If the filter payload contains an unknown field, an unsupported operator for that field, or an invalid value type, the API returns a 400 Bad Request with details about the problem. Common cases:

  • Unknown field name.
  • Operator not allowed for the field type (e.g., contains on a numeric field).
  • Malformed JSON or DSL expression.

Example error body:

{
  "message": "Invalid filter",
  "errors": [
    {"field": "budget", "issue": "Operator 'contains' is not allowed for type 'number'"}
  ]
}

Tips

  • Prefer the JSON format for complex, programmatically built filters.
  • Use the DSL for quick ad‑hoc queries and debugging.
  • Use dot‑notation for related fields when the endpoint documents them (e.g., client.name).
  • For consistent dates, pass ISO‑8601 strings (e.g., 2025-12-04T12:28:00Z).

FAQ

Q: Can I mix JSON and DSL in one request?

A: No. Choose either JSON or DSL per request.

Q: Are and/or required?

A: In JSON array form, items are combined with AND by default. To mix AND/OR, use the object form with explicit groups, or the DSL with parentheses.

Q: Can I filter by nested relations?

A: Yes, when documented. Use dot‑notation like client.name.