Reten Docs

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

ParameterTypeDescription
idUUIDEvent filter id.

Query Parameters

ParameterTypeRequiredDescription
formatstringNoSend 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:

FieldTypeDescription
idstring (UUID)Filter id.
namestringFilter name.
conditionsIComputeCondition[]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

FieldTypeDescription
fieldstringPath of the column within source.
operatorOperator from the subsetAny except CONTAINS_RECORD.
valuescalar | arrayAbsent —not null— for IS NULL and IS NOT NULL. Array for IN and NOT IN. Scalar for the rest.
sourcestringTable the condition comes from.

Record condition

FieldTypeDescription
fieldstringRECORD REPEATED column within source.
operator"CONTAINS_RECORD"Only exists in this shape.
sourcestringTable the condition comes from.
match_allbooleanHow the sub_filters are combined within an element (see below).
sub_filtersarrayConditions 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"] }
      ]
    }
  ]
}

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:

  1. match_all combines the sub_filters within the element, not over the elements. match_all: true means that there is one element of the array that satisfies all the sub_filters. An element that matches only on key or only on value does not satisfy it.
  2. match_all: false is the same existential with OR inside: there is one element that satisfies at least one of the sub_filters. It never means "all the elements".
  3. With a single sub_filter, match_all: true always travels. With fewer than two sub-filters the connective does not change the result, and true is the canonical shape.
  4. CONTAINS_RECORD is existential with negative operators too. items with category NOT IN ["devolucion"] and match_all: true means "there is an item that is not a return", not "no item is a return". This representation cannot express universal quantification.
  5. An empty array or NULL never satisfies a CONTAINS_RECORD, and a sub-field that is NULL does 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 appearsWhat it containsExample
Top-level scalar conditionPath of the column within source.pricing.final_amount
Record conditionName of the RECORD REPEATED column within source.attributes
Inside sub_filtersPath relative to the record, without its prefix.key — not attributes.key
Condition from a related tableFull 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 definitionoperator in this response
EQUALS=
NOT_EQUALS!=
GREATER_THAN>
GREATER_THAN_OR_EQUAL>=
LESS_THAN<
LESS_THAN_OR_EQUAL<=
ININ
NOT_INNOT IN
IS_NULLIS NULL
IS_NOT_NULLIS 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 an IN, 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 typeJSON type of value
STRINGstring
TIMESTAMPstring
BOOLEANboolean
FLOAT64number
INT64number
NUMERICstring
BIGNUMERICstring

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"
}
FieldTypeDescription
codestringOne of the twelve codes below.
pathstringPath to the tree node that has no projection, in accessor notation (queryBlocks[0].where.children[1].subWhere). It is what has to change.
messagestringHuman-readable description of the rejection.

The twelve codes

codeWhat triggers itpath
MULTIPLE_QUERY_BLOCKSThe filter has more than one query block. This representation is of a single block.queryBlocks
AGGREGATION_UNSUPPORTEDThe block aggregates: non-empty groupBy, an aggregate in the select, or a having with conditions.queryBlocks[0]
TIME_WINDOW_UNSUPPORTEDThe block declares a time window other than ALL_TIME.queryBlocks[0].timeWindow
JOIN_UNSUPPORTEDThe block declares joins.queryBlocks[0].from.joins
TOP_LEVEL_OR_UNSUPPORTEDThe top-level filter tree is combined with OR. The list is joined with AND.queryBlocks[0].where
MIXED_LOGICAL_OPERATORS_UNSUPPORTEDThe 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_UNSUPPORTEDAn operator outside the subset (CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH, BETWEEN).The leaf that uses it
NESTED_RECORD_UNSUPPORTEDA record filter nested inside another. The sub_filters are flat and scalar.The nested leaf
SEMI_JOIN_OR_UNSUPPORTEDThe 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_SOURCETwo 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_GROUPA relative sub-tree with no conditions: it would not restrict anything.The empty sub-tree
VALUE_NOT_REPRESENTABLEAn 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

StatusDescription
400The 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.
401Missing or invalid Authorization: Bearer.
403The user lacks the event-criteria:view permission, or the x-tenant-id is not authorized.
404No event filter with that id exists in the tenant (Event criteria not found).
422The filter is valid but has no projection to this representation (see the twelve codes).