Skip to content

Filtering strategy

The CommonGrants protocol needs a standardized approach to filtering across endpoints that balances consistency, flexibility, and ease of implementation.

  • Should filters be passed in the request body or as query parameters?
  • If the request body accepts a filters parameter, should it be an object or an array?
  • Which filter operators (e.g. eq, neq, gt, etc.) should be supported?
  • How should the protocol support implementation-defined filters?
  • Consistency: Filters should be consistent across endpoints.
  • Flexibility: Filters should support complex queries while keeping implementation straightforward.
  • Compatibility: Filters should be easy to combine with pagination and sorting.
  • Extensibility: Filters should provide a way to support implementation-defined filters.

API endpoints that support filtering should be POST operations that accept a filters parameter in the request body. Filters will be defined using a standard schema to ensure consistency across different endpoints.

  • The filters parameter MUST be included at the root of the request body.
  • Each filter MUST conform to the Filter schema, which contains:
Property Type Required Description
operation enum Yes The operation to perform on the value
value any Yes The data to use for the filter operation
  • Supported operations:
Operation Description Supported value types
eq Equal to string, number, boolean, date
neq Not equal to string, number, boolean, date
gt Greater than number, date
gte Greater than or equal to number, date
lt Less than number, date
lte Less than or equal to number, date
like Contains string
in In list array
not_in Not in list array
between Between two values range object with min and max properties
  • If a route supports implementation-defined filters, it MUST include a customFilters property.
  • This property must be an object whose values match the Filter schema.
  • Unsupported custom filters should be ignored and reported in the filterInfo.errors property.

An example of a request body that includes a standard filter:

{
"filters": {
"title": {
"value": "example",
"operation": "like"
},
"closedDateRange": {
"value": {
"min": "2024-01-01",
"max": "2024-01-31"
},
"operation": "between"
}
}
}

An example of a request body that includes both standard and custom filters:

{
"filters": {
"title": {
"value": "example",
"operation": "like"
},
"customFilters": {
"agency": {
"value": ["Department of Transportation"],
"operation": "in"
}
}
}
}
  • Positive
    • Establishes a consistent pattern for filtering across endpoints.
    • Supports more complex filters than query parameters.
    • Provide a consistent way to extend the protocol with implementation-defined filters.
  • Negative
    • Passing filters in the request body makes it harder to cache requests.
    • Using an object to represent filters makes it harder to support complex queries that include multiple filters with the same property name.

These are the other options we considered but did not choose.

  • Pros
    • Easy to implement for simple filters.
    • Fully cacheable in HTTP caches.
  • Cons
    • Difficult to support complex filtering.
    • Doesn’t provide a way to support implementation-defined filters.

A filter request body parameter of type array

Section titled “A filter request body parameter of type array”
  • Pros
    • Easy to understand.
    • Supports complex filtering with multiple filters for the same property.
    • Could support index-based filter logic (e.g. (1 AND 2) OR (3 AND 4)).
  • Cons
    • Doesn’t provide a way to support implementation-defined filters.
    • Makes it harder to define standard and custom filters in the OpenAPI document.
    • Makes it harder for API servers to parse request bodies in a deterministic way.