Skip to Content
ContributeBackendOData Query Guide

OData Query Guide

How to use OData-style query parameters such as $filter, $select, skip, and limit with the Rhesis backend API.

Basic Concepts

OData (Open Data Protocol) is a REST-based protocol for querying and updating data. In Rhesis, you can use OData filters with the $filter query parameter.

URL Format

url-format.txt
GET /endpoint?$filter=<odata-expression>

Authentication

All requests require a Bearer token:

authentication.sh
curl -H "Authorization: Bearer YOUR_API_KEY" "URL"

Common query parameters

Use the following parameters together to shape list responses:

ParameterPurposeExample
$filterFilter rows with OData expressions$filter=status/name eq 'Active'
$selectReturn only selected top-level fields (id is always included)$select=name,score_type,threshold
sort_byChoose sort fieldsort_by=created_at
sort_orderSet sort direction (asc or desc)sort_order=desc
skipOffset for paginationskip=20
limitMax rows returned by REST list endpointslimit=50

Comparison Operators

Equality Operators

Equal (eq)

equal-operator.sh
# Find tests with priority = 1
GET /tests?$filter=priority eq 1

# Find behaviors with specific name
GET /behaviors?$filter=name eq 'Test Behavior'

Not Equal (ne)

not-equal-operator.sh
# Find tests where priority is not null
GET /tests?$filter=priority ne null

# Find behaviors not named 'Test'
GET /behaviors?$filter=name ne 'Test'

Comparison Operators

Greater Than (gt)

greater-than.sh
# Find tests with priority > 0
GET /tests?$filter=priority gt 0

Less Than (lt)

less-than.sh
# Find tests with priority < 5
GET /tests?$filter=priority lt 5

Greater Than or Equal (ge)

greater-equal.sh
# Find tests with priority >= 1
GET /tests?$filter=priority ge 1

Less Than or Equal (le)

less-equal.sh
# Find tests with priority <= 5
GET /tests?$filter=priority le 5

String Functions

All string functions use function-style syntax: function(field, value)

Contains

contains-function.sh
# Find behaviors containing 'Test' in name
GET /behaviors?$filter=contains(name,'Test')

# Case-insensitive search
GET /behaviors?$filter=contains(tolower(name),'test')

Starts With

startswith-function.sh
# Find behaviors starting with 'Test'
GET /behaviors?$filter=startswith(name,'Test')

Ends With

endswith-function.sh
# Find behaviors ending with 'Behavior'
GET /behaviors?$filter=endswith(name,'Behavior')

Case Conversion

To Lower Case (tolower)

tolower-function.sh
# Case-insensitive search using tolower
GET /behaviors?$filter=contains(tolower(name),'test')

To Upper Case (toupper)

toupper-function.sh
# Case-insensitive search using toupper
GET /behaviors?$filter=contains(toupper(name),'TEST')

Navigate through relationships using the / operator: relationship/field

Behavior Navigation

behavior-navigation.sh
# Find tests by behavior name
GET /tests?$filter=behavior/name eq 'Test Behavior'

# Case-insensitive behavior search
GET /tests?$filter=contains(tolower(behavior/name),'rel')

Status Navigation

status-navigation.sh
# Find tests by status
GET /tests?$filter=status/name eq 'New'

# Find active behaviors
GET /behaviors?$filter=status/name eq 'Active'

Prompt Navigation

prompt-navigation.sh
# Find tests by prompt content
GET /tests?$filter=contains(prompt/content,'test')

Topic Navigation

topic-navigation.sh
# Find tests by topic
GET /tests?$filter=topic/name eq 'Security'

Category Navigation

category-navigation.sh
# Find tests by category
GET /tests?$filter=category/name eq 'Performance'

Logical Operators

AND Operator

and-operator.sh
# Multiple conditions must be true
GET /tests?$filter=status/name eq 'New' and priority ne null

OR Operator

or-operator.sh
# Either condition can be true
GET /tests?$filter=priority eq 1 or priority eq 2

NOT Operator

not-operator.sh
# Negate a condition (use parentheses)
GET /tests?$filter=not (priority eq 1)

Complex Expressions with Parentheses

complex-expressions.sh
# Group conditions with parentheses
GET /tests?$filter=(status/name eq 'New' and priority ne null) or contains(prompt/content,'test')

Advanced Features

Sorting

Use sort_by and sort_order parameters:

sorting.sh
# Sort by creation date, newest first
GET /tests?sort_by=created_at&sort_order=desc

# Sort by priority, lowest first
GET /tests?sort_by=priority&sort_order=asc

Pagination

Use skip and limit parameters:

pagination.sh
# Get 10 items starting from item 20
GET /tests?skip=20&limit=10

# Combine with filtering
GET /tests?$filter=status/name eq 'New'&skip=0&limit=50

Field Selection with $select

Use $select to return only the fields you need from list endpoints.

id is always included by the backend even when omitted from $select.

field-selection.sh
# Return only metric name and scope (id is included automatically)
GET /metrics?$select=name,metric_scope

# Return lightweight test result rows
GET /test_results?$filter=test_run_id eq 'run-uuid'&$select=status,prompt,metric_scores

$select works across all standard list endpoints generated through the backend routers (tests, behaviors, categories, topics, test_sets, test_runs, test_results, metrics, projects, sources, and related list routes).

MCP server-managed pagination

When list operations are called through MCP tools (instead of direct REST), some tools define a server-managed page_size in mcp_tools.yaml.

In those cases:

  • the MCP tool schema hides limit from the model
  • the server applies a fixed page size with peek-ahead (limit = page_size + 1)
  • list responses are wrapped with _pagination metadata
mcp-pagination-response.json
{
  "results": [
    {
      "id": "a1",
      "name": "Project A"
    },
    {
      "id": "a2",
      "name": "Project B"
    }
  ],
  "_pagination": {
    "returned": 2,
    "has_more": true,
    "next_skip": 2,
    "hint": "Showing 2 results — there are more. Use $filter to narrow or call again with skip=2 for the next page."
  }
}

For direct REST API calls, limit and skip remain fully client-controlled.

Combining Parameters

combining-parameters.sh
# Filter + Sort + Paginate
GET /tests?$filter=contains(tolower(behavior/name),'rel')&sort_by=created_at&sort_order=desc&skip=0&limit=25

Field Selection

Use $select to return only the fields a list view or tool call needs. The backend accepts a comma-separated list of serialized response fields and always includes id so each entity remains addressable.

select-fields.sh
# Return only id, name, and description for metrics
GET /metrics?$select=name,description

# Combine field selection with filtering and pagination
GET /test_sets?$filter=contains(tolower(name),'safety')&$select=name,created_at&limit=10

$select filters serialized response objects after the query runs. It reduces payload size for list responses, but it does not change which rows match $filter.

Complete Examples

example-case-insensitive.sh
# Find all tests where behavior name contains 'rel' (case-insensitive)
GET /tests?$filter=contains(tolower(behavior/name),'rel')

Example 2: Complex Multi-Condition Filter

example-multi-condition.sh
# Find tests that are either:
# - New status with non-null priority, OR
# - Have 'test' in prompt content
GET /tests?$filter=(status/name eq 'New' and priority ne null) or contains(prompt/content,'test')

Example 3: Advanced Search with Sorting

example-advanced-search.sh
# Find active behaviors, sort by name
GET /behaviors?$filter=status/name eq 'Active'&sort_by=name&sort_order=asc

Example 4: Navigation with String Functions

example-navigation.sh
# Find tests where behavior description contains 'robust' (case-insensitive)
GET /tests?$filter=contains(tolower(behavior/description),'robust')

Best Practices

1. Use Case-Insensitive Searches

For user-friendly searches, always use tolower() or toupper():

best-practice-case-insensitive.sh
# Good: Case-insensitive
GET /behaviors?$filter=contains(tolower(name),'reliability')

# Avoid: Case-sensitive (user must know exact case)
GET /behaviors?$filter=contains(name,'Reliability')

Instead of multiple API calls, use navigation:

best-practice-navigation.sh
# Good: Single request with navigation
GET /tests?$filter=behavior/name eq 'Reliability'

# Avoid: Multiple requests (less efficient)
# 1. GET /behaviors?$filter=name eq 'Reliability'
# 2. GET /tests?$filter=behavior_id eq 'uuid-from-step-1'

3. Use Parentheses for Complex Logic

Make complex expressions clear with parentheses:

best-practice-parentheses.sh
# Good: Clear precedence
GET /tests?$filter=(status/name eq 'New' or status/name eq 'Active') and priority gt 0

# Avoid: Ambiguous precedence
GET /tests?$filter=status/name eq 'New' or status/name eq 'Active' and priority gt 0

4. Combine Filtering with Pagination

Always use pagination for potentially large result sets:

best-practice-pagination.sh
# Good: Paginated results
GET /tests?$filter=status/name eq 'New'&limit=50

# Avoid: Potentially huge response
GET /tests?$filter=status/name eq 'New'

5. URL Encoding

Remember to URL-encode your queries:

best-practice-url-encoding.sh
# Space becomes %20, / becomes %2F, ' becomes %27
GET /tests?%24filter=contains(behavior%2Fname,%27Test%27)

Error Handling

Common Errors

Invalid Field Names

error-invalid-field.json
{
  "detail": "Invalid filter. Must use valid fields: field1, field2, ..."
}

Parse Errors

error-parse.json
{
  "detail": "Error processing filter: Failed to parse at: Token(...)"
}

Invalid Syntax Use function-style syntax for string operations:

error-invalid-syntax.sh
# Correct
GET /tests?$filter=contains(behavior/name,'test')

# Incorrect (will cause parse error)
GET /tests?$filter=behavior/name contains 'test'

Available Endpoints

The following endpoints support OData filtering:

  • /tests - Test entities
  • /behaviors - Behavior entities
  • /topics - Topic entities
  • /categories - Category entities
  • /test-sets - Test set entities
  • /test-runs - Test run entities
  • /test-results - Test result entities
  • /prompts - Prompt entities
  • /metrics - Metric entities
  • /projects - Project entities

Each endpoint supports the navigation properties defined in its model relationships.