Skip to content
LIVOI

Dynamic Query

The /api/v1/dynamic/query namespace provides endpoints for defining flexible SQL-like queries in JSON. The engine translates request payloads into SQLAlchemy queries and supports:

  • SELECT
  • WHERE
  • JOIN
  • GROUP BY
  • ORDER BY
  • Subqueries in filters

Available endpoints:

MethodPath
GET/api/v1/dynamic/query/tables
GET/api/v1/dynamic/query/attributes
POST/api/v1/dynamic/query
POST/api/v1/dynamic/query/bulk
POST/api/v1/dynamic/query/default-configuration

All Dynamic Query endpoints require authentication. An effective tenant context is required by default. Tables and attributes are filtered by READ permissions.


query_model and target_model must be keys returned by GET /api/v1/dynamic/query/tables. The current registry uses table names from __tablename__; do not assume that singular, plural, or class-name variants also work.

Examples of table-based keys:

  • users
  • agents
  • agent_tools

A Dynamic Query request uses this schema:

FieldTypeDescription
query_modelStringRequired. A key from /tables.
query_attributesListOptional. List of strings or [expression, alias] pairs.
query_filterObjectOptional. Filter tree with operators.
order_byList of stringsOptional. -field sorts in descending order.
group_byList of stringsOptional. Grouping fields.
joinsListOptional. Join definitions.
pageObjectRequired for POST /dynamic/query and for each entry in POST /dynamic/query/bulk.

Join definitions use this format:

{
"target_model": "agent_tools",
"on": "id=agent_id",
"isouter": false
}

on can be a string or null. isouter is a boolean.

Terminal window
curl https://api.livoi.de/api/v1/dynamic/query \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_TOKEN' \
--data '{
"query_model": "users",
"query_attributes": ["id", "email"],
"page": { "number": 1, "size": 10 }
}'

Terminal window
curl 'https://api.livoi.de/api/v1/dynamic/query/tables?include_attributes=true' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'

Example response:

{
"tables": {
"users": ["id", "email", "created_at"],
"agents": ["id", "name", "tenant_id"],
"agent_tools": ["id", "agent_id", "tool_id"]
}
}
Terminal window
curl 'https://api.livoi.de/api/v1/dynamic/query/attributes?query_model=users' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'

Example response:

{
"attributes": [
"content_type",
"created_at",
"file_name",
"id",
"size",
"tags",
"title",
"updated_at",
"uri"
]
}

{
"query_model": "users",
"joins": [
{ "target_model": "agents", "on": "id=user_id", "isouter": true }
],
"query_attributes": [
"id",
"email",
"agents.name"
],
"page": { "number": 1, "size": 10 }
}

Filter fields use this format:

field__operator

Fields without an operator are invalid. For example, use permission_level__eq instead of permission_level.

Supported operators:

SuffixSQLValue
__eq=Single value
__ne!=Single value
__lt<Single value
__lte<=Single value
__gt>Single value
__gte>=Single value
__likeLIKEString
__ilikeILIKEString
__inINList
__not_inNOT INList
__isnullIS NULL / IS NOT NULLBoolean

in and not_in require lists. isnull expects a boolean. Filters can be nested using and and or.

{
"query_model": "users",
"query_filter": {
"and": [
{ "permission_level__in": ["ADMIN", "OWNER"] },
{ "last_login__gte": "2024-01-01T00:00:00Z" },
{ "deleted_at__isnull": true }
]
},
"page": { "number": 1, "size": 10 }
}

{
"id__in": {
"subquery": {
"query_model": "agent_tools",
"query_attributes": ["agent_id"],
"query_filter": {
"tool_id__eq": "tool_123"
}
}
}
}

For ordinary operators, subquery comparisons are scalar and use limit(1). For in and not_in, the subquery is used as a set comparison.


  1. target_model

    The target key must come from /api/v1/dynamic/query/tables .

  2. ON clause

    The format is left_field=right_field, for example id=agent_id.

  3. Join type

    ”isouter”: true produces a LEFT OUTER JOIN.

{
"query_model": "agents",
"joins": [
{
"target_model": "agent_tools",
"on": "id=agent_id",
"isouter": true
}
],
"query_attributes": [
"id",
"name",
["ARRAY_AGG(agent_tools.tool_id)", "tool_ids"]
],
"group_by": ["id", "name"],
"page": { "number": 1, "size": 10 }
}

{
"query_model": "users",
"order_by": [
"permission_level",
"-last_login"
],
"page": { "number": 1, "size": 10 }
}
  • field: ascending
  • -field: descending

{
"query_model": "agents",
"query_attributes": [
"tenant_id",
["COUNT(id)", "agent_count"]
],
"group_by": ["tenant_id"],
"page": { "number": 1, "size": 10 }
}

Terminal window
curl https://api.livoi.de/api/v1/dynamic/query/bulk \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN'

Example body:

{
"queries": [
{
"query_model": "users",
"query_attributes": ["id", "email"],
"page": { "number": 1, "size": 10 }
},
{
"query_model": "agents",
"query_attributes": [
"tenant_id",
["COUNT(id)", "agent_count"]
],
"group_by": ["tenant_id"],
"page": { "number": 1, "size": 10 }
}
]
}

POST /api/v1/dynamic/query/default-configuration

Filters:

  • reference
  • function

AreaLimit
query_attributes500
Bulk queries100
Filter branches250
Filter depth25
Filter nodes100
Filter values for in / not_in100
group_by100
joins50
order_by100
page.size1000

{
"status_code": "403",
"status_message": "Forbidden",
"errors": [
{
"info": "Missing permission for agents.read."
}
]
}