Compute format
Flat representation of an event filter as a list of conditions, in snake_case, for analytics consumers.
GET /api/event-criterias/:id?format=compute
Returns an event filter as a flat list of conditions, instead of the full
tree. It is the other branch of the format query param of
GET /event-criterias/:id.
Auth: Required — event-criteria:view permission
This representation is narrower than the definition: it carries row conditions
over a single query block. A filter whose definition does not fit in it is rejected with
422 and a code that says exactly what does not fit — a degraded version
of the filter is never returned.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | UUID | Event filter id. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | Send compute to request this representation. Omitting it —or sending detail— returns the full detail. |
Example
curl "https://api.reten.ai/api/event-criterias/9f3a1c22-4b7e-4c31-9a8d-2f5e6b7c8d90?format=compute" \
-H "Authorization: Bearer <token>" \
-H "x-tenant-id: <tenant-id>" \
-H "x-project-id: <project-id>"import axios from 'axios';
const id = '9f3a1c22-4b7e-4c31-9a8d-2f5e6b7c8d90';
const response = await axios.get(
`https://api.reten.ai/api/event-criterias/${id}`,
{
headers: { Authorization: 'Bearer <token>', 'x-tenant-id': '<tenant-id>', 'x-project-id': '<project-id>' },
params: { format: 'compute' },
},
);
const computeCriteria = response.data;Response shape 200 OK
The fields of this response use snake_case (sub_filters, match_all),
unlike the rest of the resource, which uses camelCase.
IComputeEventCriteria:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Filter id. |
name | string | Filter name. |
conditions | IComputeCondition[] | Conditions to apply. They are combined with AND. [] is valid. |
Each element of conditions is one of two shapes, distinguishable by its operator:
Scalar condition
| Field | Type | Description |
|---|---|---|
field | string | Path of the column within source. |
operator | Operator from the subset | Any except CONTAINS_RECORD. |
value | scalar | array | Absent —not null— for IS NULL and IS NOT NULL. Array for IN and NOT IN. Scalar for the rest. |
source | string | Table the condition comes from. |
Record condition
| Field | Type | Description |
|---|---|---|
field | string | RECORD REPEATED column within source. |
operator | "CONTAINS_RECORD" | Only exists in this shape. |
source | string | Table the condition comes from. |
match_all | boolean | How the sub_filters are combined within an element (see below). |
sub_filters | array | Conditions over the element's sub-fields: field, operator, value — without source. |
The four cases
1 · Scalar condition
Events whose status is Facturada.
{
"id": "9f3a1c22-4b7e-4c31-9a8d-2f5e6b7c8d90",
"name": "Venta oficial — solo facturada",
"conditions": [
{
"field": "status",
"operator": "=",
"value": "Facturada",
"source": "event_transactions"
}
]
}2 · Record with two sub-filters
Events with an attribute whose key is documento_tipo and whose value is factura.
{
"id": "1c7d2e83-5a90-4f62-8b31-6d4e5f70a1b2",
"name": "Documentos tipo factura",
"conditions": [
{
"field": "attributes",
"operator": "CONTAINS_RECORD",
"source": "event_transactions",
"match_all": true,
"sub_filters": [
{ "field": "key", "operator": "=", "value": "documento_tipo" },
{ "field": "value", "operator": "=", "value": "factura" }
]
}
]
}3 · Scalar + record
Invoiced sales with at least one line that is not a return.
{
"id": "4b8e0f15-2c76-4a03-9d58-7e1f2a3b4c5d",
"name": "Ventas con ítems no-devolución",
"conditions": [
{
"field": "status",
"operator": "=",
"value": "Facturada",
"source": "event_transactions"
},
{
"field": "items",
"operator": "CONTAINS_RECORD",
"source": "event_transactions",
"match_all": true,
"sub_filters": [
{ "field": "category", "operator": "NOT IN", "value": ["devolucion"] }
]
}
]
}4 · Scalar + related table
Invoiced sales with at least one line whose product is in the Food Service or
HORECA category. The condition over the related table arrives at the same level
as the others, distinguished by its source: the path to product_properties does not travel.
{
"id": "6d2a9c41-7e35-48b0-b1c9-0f8e7d6c5b4a",
"name": "Proyecto Food Service",
"conditions": [
{
"field": "status",
"operator": "=",
"value": "Facturada",
"source": "event_transactions"
},
{
"field": "categorization.category_name",
"operator": "IN",
"value": ["Food Service", "HORECA"],
"source": "product_properties"
}
]
}A related-table filter with several sub-filters emits one condition per
sub-filter, all with the same source. It is the only leaf whose output cardinality
is not 1.
What match_all means
It is where an integration most easily goes wrong, so it is spelled out:
match_allcombines thesub_filterswithin the element, not over the elements.match_all: truemeans that there is one element of the array that satisfies all thesub_filters. An element that matches only onkeyor only onvaluedoes not satisfy it.match_all: falseis the same existential withORinside: there is one element that satisfies at least one of thesub_filters. It never means "all the elements".- With a single
sub_filter,match_all: truealways travels. With fewer than two sub-filters the connective does not change the result, andtrueis the canonical shape. CONTAINS_RECORDis existential with negative operators too.itemswithcategory NOT IN ["devolucion"]andmatch_all: truemeans "there is an item that is not a return", not "no item is a return". This representation cannot express universal quantification.- An empty array or
NULLnever satisfies aCONTAINS_RECORD, and a sub-field that isNULLdoes not match, not even with a negative operator.
What field means
The field is the full path relative to its scope, and the scope is never repeated:
| Where it appears | What it contains | Example |
|---|---|---|
| Top-level scalar condition | Path of the column within source. | pricing.final_amount |
| Record condition | Name of the RECORD REPEATED column within source. | attributes |
Inside sub_filters | Path relative to the record, without its prefix. | key — not attributes.key |
| Condition from a related table | Full path within that table, with source = the related table. | categorization.category_name |
Operator subset
The operators travel as SQL literals, not as the members of the definition's enum:
comparisonOperator in the definition | operator in this response |
|---|---|
EQUALS | = |
NOT_EQUALS | != |
GREATER_THAN | > |
GREATER_THAN_OR_EQUAL | >= |
LESS_THAN | < |
LESS_THAN_OR_EQUAL | <= |
IN | IN |
NOT_IN | NOT IN |
IS_NULL | IS NULL |
IS_NOT_NULL | IS NOT NULL |
CONTAINS_RECORD does not translate any comparison operator: it is emitted by the record leaf.
These operators are not in the subset, and a filter that uses them is rejected with 422
and code OPERATOR_UNSUPPORTED:
CONTAINS·NOT_CONTAINS·STARTS_WITH·ENDS_WITH— the text operators.BETWEEN— emitting the pair as a list would turn it into anIN, which is another filter.
JSON type of value
Which JSON type the value travels as is fixed by the column type in the catalog, not the shape in which the definition was saved:
| Column type | JSON type of value |
|---|---|
STRING | string |
TIMESTAMP | string |
BOOLEAN | boolean |
FLOAT64 | number |
INT64 | number |
NUMERIC | string |
BIGNUMERIC | string |
The definition accepts both shapes for several types —100 and "100" are equally valid
for a FLOAT64—, so emitting what was saved would make the same column travel now as a
number, now as text, depending on who wrote it, and would let an IN mix the two shapes in
a single list. NUMERIC and BIGNUMERIC travel as strings because as numbers they would
lose the exact digits that their type guarantees.
Response 422 Unprocessable Entity
The filter is valid and compiles to SQL; what does not exist is its projection to this representation. The body carries the diagnosis:
This error body travels without a wrapper: it is plain code, path and message,
without the statusCode or the error that the rest of the API errors carry.
{
"code": "TIME_WINDOW_UNSUPPORTED",
"path": "queryBlocks[0].timeWindow",
"message": "No se puede proyectar queryBlocks[0].timeWindow (TIME_WINDOW_UNSUPPORTED): el wire no transporta la ventana IN_THE_LAST"
}| Field | Type | Description |
|---|---|---|
code | string | One of the twelve codes below. |
path | string | Path to the tree node that has no projection, in accessor notation (queryBlocks[0].where.children[1].subWhere). It is what has to change. |
message | string | Human-readable description of the rejection. |
The twelve codes
code | What triggers it | path |
|---|---|---|
MULTIPLE_QUERY_BLOCKS | The filter has more than one query block. This representation is of a single block. | queryBlocks |
AGGREGATION_UNSUPPORTED | The block aggregates: non-empty groupBy, an aggregate in the select, or a having with conditions. | queryBlocks[0] |
TIME_WINDOW_UNSUPPORTED | The block declares a time window other than ALL_TIME. | queryBlocks[0].timeWindow |
JOIN_UNSUPPORTED | The block declares joins. | queryBlocks[0].from.joins |
TOP_LEVEL_OR_UNSUPPORTED | The top-level filter tree is combined with OR. The list is joined with AND. | queryBlocks[0].where |
MIXED_LOGICAL_OPERATORS_UNSUPPORTED | The same scope mixes AND and OR in nested groups. Only one connective per scope is allowed. | The shallowest group where the conflict is detected |
OPERATOR_UNSUPPORTED | An operator outside the subset (CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH, BETWEEN). | The leaf that uses it |
NESTED_RECORD_UNSUPPORTED | A record filter nested inside another. The sub_filters are flat and scalar. | The nested leaf |
SEMI_JOIN_OR_UNSUPPORTED | The sub-tree of a related-table filter is combined with OR. Flattened it would become AND: a stricter filter. | The leaf's subWhere |
MULTIPLE_SEMI_JOINS_SAME_SOURCE | Two different scopes would emit the same source. They are two independent existentials that, flattened, would apply to the same row. | The second leaf that claims that source |
EMPTY_PREDICATE_GROUP | A relative sub-tree with no conditions: it would not restrict anything. | The empty sub-tree |
VALUE_NOT_REPRESENTABLE | An operator that needs a value and does not carry one, or an INT64 outside the exact range of a JSON number. | The leaf |
These codes say what this representation cannot express, not what the API does not support: all those shapes are valid definitions that compile to SQL and work in Generate SQL, Validate and Preview.
Relationship with conditionCount
The conditionCount from the list counts the
leaves of the tree of the definition and does not necessarily match the length of
conditions. A filter with a related-table leaf of three sub-filters has
conditionCount: 1 and returns three conditions. Both numbers are correct and measure
different things.
Error Responses
| Status | Description |
|---|---|
400 | The id is not a valid UUID, format is neither detail nor compute, or the saved filter no longer compiles to SQL against the current catalog. |
401 | Missing or invalid Authorization: Bearer. |
403 | The user lacks the event-criteria:view permission, or the x-tenant-id is not authorized. |
404 | No event filter with that id exists in the tenant (Event criteria not found). |
422 | The filter is valid but has no projection to this representation (see the twelve codes). |