Skip to main content

Write metadata filters

This guide designs the metadata of a pipeline for filtering and writes the filters that common retrieval tasks need: one value, a list of values, a date or number range, excluded values, alternative conditions and taxonomy expansion. The guide also applies a filter to the document list and describes the role of metadata indexes. Integrators who scope the search results of a client application need this guide. Filter operators defines every operator, and Metadata, filters and taxonomies explains schemas and taxonomies.

Metadata design​

A filter compares only the metadata fields that the pipeline schema declares, so the metadata of a pipeline is designed together with the filters that the client application sends:

  • Declared fields. Every field that a filter references is declared in the properties of the schema, with the type that the filter compares. A filter on an undeclared field returns HTTP 400 with the message Schema error.
  • Top-level scalar fields. Filters compare top-level fields of type string, integer, number or boolean. A value that the client application filters on is stored as a top-level field, such as author_id, rather than inside an object. A condition on a field of type object returns Schema error, and a dotted name such as author.name does not reach the nested field: the request fails with HTTP 500 and a database error. Fields of type array cannot be declared, so no filter tests whether a list contains a value.
  • Required fields. A comparison on a missing field is never satisfied, whatever the operator, and the exclusion forms of Excluded values depend on this rule. The schema lists every filtered field in required, so that every document carries a value for each field.
  • One date format. Dates are strings and compare as text. Every value of a date field uses one format, such as Coordinated Universal Time (UTC) with milliseconds and the suffix Z, and a pattern in the schema rejects other formats.
  • Numbers as numbers. Counts and scores are declared as integer or number and sent as JSON numbers, so that range filters compare the values numerically. Whole numbers are sent without a decimal point, such as 5 rather than 5.0. An integer field accepts 5.0, but while a document stores such a value, every filter on that field returns HTTP 500 with the database error invalid input syntax for type bigint, for every document of the pipeline, until the document is deleted.

Example pipeline​

The examples use a new pipeline named support-articles, with the classifications of First search and the following metadata fields:

FieldTypeUse
productStringThe product that an article describes
audienceStringcustomer or staff
publishedString, UTC with millisecondsThe publication date of the article
helpful_votesIntegerThe number of readers who marked the article as helpful
areaStringThe starting field of taxonomy filters; no document sets this field

The commands use the shell variables from Authenticate and the EMBEDDING_MODEL_ID and TEXT_SPLITTER_ID variables from First search. Create the pipeline:

curl -X POST "$FOUNDATION4_URL/pipelines" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "support-articles",
"description": "Support articles with filterable metadata",
"embedding_model_id": "'"$EMBEDDING_MODEL_ID"'",
"default_text_splitter_id": "'"$TEXT_SPLITTER_ID"'",
"classifications": ["public", ["internal", "public"]],
"has_full_text_search": true,
"schema": {
"type": "object",
"properties": {
"product": {"type": "string"},
"audience": {"type": "string", "enum": ["customer", "staff"]},
"published": {"type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3}Z$"},
"helpful_votes": {"type": "integer", "minimum": 0},
"area": {"type": "string"}
},
"required": ["product", "audience", "published", "helpful_votes"]
}
}'

Expected result: status 201 and the new pipeline. The following response omits the fields other than id, name and schema:

{
"id": "79c70816-65e8-4f88-8fb5-1fe0c9e928e2",
"name": "support-articles",
"schema": {
"type": "object",
"properties": {
"product": {"type": "string"},
"audience": {"type": "string", "enum": ["customer", "staff"]},
"published": {"type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3}Z$"},
"helpful_votes": {"type": "integer", "minimum": 0},
"area": {"type": "string"}
},
"required": ["product", "audience", "published", "helpful_votes"]
}
}

Set the variable from the id field:

export PIPELINE_ID=<id from the response>

Documents​

Add four documents:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/documents" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"classification": "public",
"external_identifier": "kb-password-reset",
"metadata": {"product": "accounts", "audience": "customer", "published": "2026-03-02T09:00:00.000Z", "helpful_votes": 42},
"contents": "To reset a password, open Settings, choose Security and select Reset password. A reset link is sent to the registered email address and expires after 30 minutes."
}'

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/documents" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"classification": "internal",
"external_identifier": "kb-invoice-export",
"metadata": {"product": "billing", "audience": "staff", "published": "2026-06-15T09:00:00.000Z", "helpful_votes": 17},
"contents": "Invoices are generated on the first day of each month. Finance staff can export invoices as PDF or CSV files from the Billing page."
}'

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/documents" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"classification": "internal",
"external_identifier": "kb-refund-policy",
"metadata": {"product": "billing", "audience": "staff", "published": "2026-08-20T09:00:00.000Z", "helpful_votes": 8},
"contents": "Refunds for annual plans are prorated. Support agents must record the refund reason in the ticket before approving a refund."
}'

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/documents" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"classification": "public",
"external_identifier": "kb-payroll-cutoff",
"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3},
"contents": "Payroll changes submitted after the 25th of the month apply to the next pay period. Employees can see the cutoff date on the Payroll page."
}'

Expected result: status 201 for each request. The response to the last request is the following:

{
"id": "01a0fd23-c4db-7640-a27a-b14a76aab61f",
"created_at": "2026-10-02T15:02:54.938555Z",
"updated_at": "2026-10-02T15:02:54.938555Z",
"expired_at": null,
"external_identifier": "kb-payroll-cutoff",
"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3},
"classification": "public",
"pipeline_id": "79c70816-65e8-4f88-8fb5-1fe0c9e928e2",
"text_splitter_id": null,
"status": "pending",
"message": null,
"version": 1790953374938555
}

Search results include a document after processing completes, as described in First search. The four documents carry the following metadata:

DocumentClassificationproductaudiencepublishedhelpful_votes
kb-password-resetpublicaccountscustomer2026-03-0242
kb-invoice-exportinternalbillingstaff2026-06-1517
kb-refund-policyinternalbillingstaff2026-08-208
kb-payroll-cutoffpublicpayrollcustomer2026-09-103

Metadata validation​

The schema rejects metadata that a filter could not compare correctly. Add a document whose helpful_votes value is a string:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/documents" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"classification": "public",
"external_identifier": "kb-sso-setup",
"metadata": {"product": "accounts", "audience": "customer", "published": "2026-09-20T09:00:00.000Z", "helpful_votes": "5"},
"contents": "Single sign-on is configured on the Security page by an account owner."
}'

Expected result: status 400, and Foundation4 stores no document:

{
"error": {
"message": "Invalid metadata",
"details": {"helpful_votes": "\"5\" is not of type \"integer\""}
}
}

A date in another format returns the same message, with the field and the reason in details. A missing required field also returns Invalid metadata, but details reports it under an empty key, and only one missing field is reported per request:

{
"error": {
"message": "Invalid metadata",
"details": {"": "\"helpful_votes\" is a required property"}
}
}

Single value​

The examples in the following sections run one similarity search and change only the filter. The classification internal makes the fragments of all four documents candidates. k is 10, larger than the number of fragments, so every fragment that satisfies the filter is returned. Restrict the search to one product:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {"product": {"$eq": "billing"}}
}'

Expected result: status 200 and the fragments of kb-invoice-export and kb-refund-policy. The following response omits the fields other than metadata:

[
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-08-20T09:00:00.000Z", "helpful_votes": 8}},
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-06-15T09:00:00.000Z", "helpful_votes": 17}}
]

Every field takes a comparison object with one operator. The shorthand {"product": "billing"} is not valid.

List of values​

Match any of several products with $in:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {"product": {"$in": ["accounts", "payroll"]}}
}'

Expected result: status 200 and the fragments of kb-password-reset and kb-payroll-cutoff. The following response omits the fields other than metadata:

[
{"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3}},
{"metadata": {"product": "accounts", "audience": "customer", "published": "2026-03-02T09:00:00.000Z", "helpful_votes": 42}}
]

Every value in the list has the JSON type that the schema declares for the field. A list of one value is equivalent to $eq.

Date range​

A range on one field combines two conditions with $and, because each field in a comparison object takes one operator. Restrict the search to articles published in August and September 2026:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {
"$and": [
{"published": {"$ge": "2026-08-01T00:00:00.000Z"}},
{"published": {"$lt": "2026-10-01T00:00:00.000Z"}}
]
}
}'

Expected result: status 200 and the fragments of kb-refund-policy and kb-payroll-cutoff. The following response omits the fields other than metadata:

[
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-08-20T09:00:00.000Z", "helpful_votes": 8}},
{"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3}}
]

The filter values use the same format as the stored values, so the text comparison is chronological. The upper bound uses $lt with the first instant after the range, so the range covers the whole of the last day without an end value such as 23:59:59.999Z.

Number range​

Restrict the search to articles with 10 to 50 helpful votes:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {
"$and": [
{"helpful_votes": {"$ge": 10}},
{"helpful_votes": {"$le": 50}}
]
}
}'

Expected result: status 200 and the fragments of kb-password-reset and kb-invoice-export. The following response omits the fields other than metadata:

[
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-06-15T09:00:00.000Z", "helpful_votes": 17}},
{"metadata": {"product": "accounts", "audience": "customer", "published": "2026-03-02T09:00:00.000Z", "helpful_votes": 42}}
]

The schema declares helpful_votes as an integer, so PostgreSQL compares the values as numbers. The filter values are therefore JSON numbers, and a string value, such as "10", returns HTTP 400 with the message Schema error.

Excluded values​

Exclude one product with $not-in:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {"product": {"$not-in": ["billing"]}}
}'

Expected result: status 200 and the fragments of kb-password-reset and kb-payroll-cutoff. The following response omits the fields other than metadata:

[
{"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3}},
{"metadata": {"product": "accounts", "audience": "customer", "published": "2026-03-02T09:00:00.000Z", "helpful_votes": 42}}
]

The filter language offers three forms of exclusion:

  • One value. $neq, such as {"product": {"$neq": "billing"}}.
  • Several values. $not-in with a list, as in the example.
  • A combined condition. $not around any filter, such as {"$not": {"$and": [{"audience": {"$eq": "staff"}}, {"product": {"$eq": "billing"}}]}}.

The first two forms also exclude documents that lack the field, because a comparison on a missing field is never satisfied, and so does $not around a single comparison. $not around an $and can include such a document: when another condition of the $and fails, the $and fails, and $not is satisfied whatever the missing field. The required list of the schema guarantees that every document in this pipeline carries each filtered field, so the three forms return the same documents here.

Alternative conditions​

$or matches fragments that satisfy at least one filter in an array, and each filter in the array can combine conditions with $and. Find the articles for customers and the staff articles published since August 2026:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {
"$or": [
{"audience": {"$eq": "customer"}},
{"$and": [
{"audience": {"$eq": "staff"}},
{"published": {"$ge": "2026-08-01T00:00:00.000Z"}}
]}
]
}
}'

Expected result: status 200 and the fragments of kb-password-reset, kb-refund-policy and kb-payroll-cutoff. The following response omits the fields other than metadata:

[
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-08-20T09:00:00.000Z", "helpful_votes": 8}},
{"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3}},
{"metadata": {"product": "accounts", "audience": "customer", "published": "2026-03-02T09:00:00.000Z", "helpful_votes": 42}}
]

kb-invoice-export is a staff article published in June, so the fragment of the article satisfies neither filter in the array. A logical operator and field conditions cannot share one object, so conditions that apply to every alternative go into an outer $and array together with the $or filter.

Taxonomy expansion​

A taxonomy groups metadata values into a hierarchy, and the $teq operator matches a value together with every value beneath the value. In this example, a taxonomy groups the products into the areas finance and identity. The taxonomy requests require write permission on the taxonomies object type, which the getting-started key from Authenticate does not hold. An administrator grants the permission, as described in Access control, or runs the two taxonomy requests with a key that holds the permission. Create the taxonomy:

curl -X POST "$FOUNDATION4_URL/taxonomies" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "product-areas",
"description": "Product areas and the products in each area"
}'

Expected result: status 201 and the new taxonomy:

{
"id": "683b4335-831f-475b-a26f-7519e699444c",
"created_at": "2026-10-02T15:03:39.512644Z",
"updated_at": "2026-10-02T15:03:39.512644Z",
"name": "product-areas",
"description": "Product areas and the products in each area"
}

Set the variable from the id field, then add one entry for each link from an area to a product:

export TAXONOMY_ID=<id from the response>

curl -X POST "$FOUNDATION4_URL/taxonomies/$TAXONOMY_ID/entries" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '[
{"parent": "area", "parent_value": "finance", "child": "product", "child_value": "billing"},
{"parent": "area", "parent_value": "finance", "child": "product", "child_value": "payroll"},
{"parent": "area", "parent_value": "identity", "child": "product", "child_value": "accounts"}
]'

Expected result: status 201 and an array of the added entries. An entry that already exists, in this taxonomy or in another one, is not added again and does not appear in the array. The following response shows the first entry and omits the other two:

[
{
"id": "6fd4c4bc-1a18-4b36-ba82-b2c0491fd87d",
"created_at": "2026-10-02T15:03:39.640217Z",
"updated_at": "2026-10-02T15:03:39.640217Z",
"taxonomy_id": "683b4335-831f-475b-a26f-7519e699444c",
"parent": "area",
"parent_value": "finance",
"child": "product",
"child_value": "billing"
}
]

Search for the articles of the finance area:

curl -X POST "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/search" \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "How do support teams handle customer requests?",
"type": "similarity",
"classification": "internal",
"params": {"k": 10},
"filters": {"area": {"$teq": ["finance", "'"$TAXONOMY_ID"'"]}}
}'

Expected result: status 200 and the fragments of kb-invoice-export, kb-refund-policy and kb-payroll-cutoff. The following response omits the fields other than metadata:

[
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-08-20T09:00:00.000Z", "helpful_votes": 8}},
{"metadata": {"product": "billing", "audience": "staff", "published": "2026-06-15T09:00:00.000Z", "helpful_votes": 17}},
{"metadata": {"product": "payroll", "audience": "customer", "published": "2026-09-10T09:00:00.000Z", "helpful_votes": 3}}
]

Foundation4 expands the filter to documents where area is finance or product is billing or payroll. Taxonomy filters obey these rules:

  • Declared fields. The starting field and every field that the expansion reaches are declared in the schema as strings. area is declared for this reason, although no document sets the field.
  • Current entries. The entries are read at search time, so an entry added for a new product applies to the next search without reprocessing any document.
  • One copy of each link. Each link from a parent value to a child value is stored once across all taxonomies. A second taxonomy that sends a link that product-areas already holds does not receive the link, and a filter that names the second taxonomy does not expand through the link. Each taxonomy therefore holds links that no other taxonomy holds.
  • Taxonomy identifier. An identifier that names no taxonomy returns no error, and the filter then matches the starting value only. A client application reads the taxonomy identifier from configuration rather than from user input.

Taxonomy filters describes the expansion in full.

Document list filters​

The document list accepts a filter in the same language, in the metadata query parameter. The filter is URL-encoded JSON, and curl -G with --data-urlencode encodes the filter. List the staff articles:

curl -G "$FOUNDATION4_URL/pipelines/$PIPELINE_ID/documents" \
--data-urlencode 'metadata={"audience": {"$eq": "staff"}}' \
-H "x-api-key: $FOUNDATION4_API_KEY" \
-H "x-api-key-secret: $FOUNDATION4_API_SECRET"

Expected result: status 200 and the two staff documents. The following response omits the document fields other than id, external_identifier, metadata and status:

{
"data": [
{
"id": "01a0fd23-c4b6-7450-921f-0e320a8013f1",
"external_identifier": "kb-invoice-export",
"metadata": {"product": "billing", "audience": "staff", "published": "2026-06-15T09:00:00.000Z", "helpful_votes": 17},
"status": "success"
},
{
"id": "01a0fd23-c4c8-7913-a549-4679c6ee3518",
"external_identifier": "kb-refund-policy",
"metadata": {"product": "billing", "audience": "staff", "published": "2026-08-20T09:00:00.000Z", "helpful_votes": 8},
"status": "success"
}
],
"page_info": {
"after": "WyIwMWEwZmQyMy1jNGM4LTc5MTMtYTU0OS00Njc5YzZlZTM1MTgiXQ",
"before": "WyIwMWEwZmQyMy1jNGI2LTc0NTAtOTIxZi0wZTMyMGE4MDEzZjEiXQ",
"has_next": false,
"has_prev": false,
"order_by": null
},
"query_info": {
"metadata": {"audience": {"$eq": "staff"}}
}
}

The document list differs from a search in the following ways:

  • Scope. The list returns the current version of each matching document, with no query and no k. The list pages through the matches, as described in Paginate and filter lists.
  • Confirmation. query_info.metadata repeats the filter that the API server applied. A filter that is missing from query_info was not applied.
  • Grammar errors. A metadata value that does not follow the grammar returns HTTP 400 with a plain-text body, rather than the HTTP 422 of a search request: Failed to deserialize query string: data did not match any variant of untagged enum ExprOrValueExpr.

The document list therefore tests a filter independently of a query and of the vector index.

Filters built at query time​

A client application that builds filters from user input or from optional settings applies the following rules:

  • Empty lists. A $in or $not-in condition with an empty list is left out of the filter. An empty list returns HTTP 400 with the message Schema error on string, integer and number fields. On a boolean field, an empty $in list matches no document, and an empty $not-in list matches every document, including documents that lack the field.
  • Empty conditions. An $and or $or array contains only non-empty filters. The client application removes an empty object, {}, from the array rather than sending the object. Inside $and, an empty object applies no restriction. Inside $or, the result depends on its position: an empty object in the first position makes the $or match every fragment, and in any later position it matches nothing.
  • Value types. Each value is converted to the type that the schema declares before the filter is sent, such as a number for helpful_votes.
  • Date bounds. Date values are formatted in the same format as the stored dates, including the milliseconds and the suffix Z.

Metadata indexes​

A metadata filter needs no index. PostgreSQL evaluates the filter on any declared field, in the same query that selects the fragments. The indexes that POST /pipelines/{id}/indexes creates on declared metadata fields do not accelerate metadata filters, so a pipeline does not create a metadata index to enable or to speed up a filter. Create a metadata index describes the operation.

Filter errors​

A filter that Foundation4 cannot apply returns one of the following errors:

  • Schema error. HTTP 400 with no details. The filter references an undeclared field or compares a value of another type than the field. The same error follows $like or $ilike on a field that is not a string, and a $teq expansion that reaches a field not declared as a string.
  • Grammar error. HTTP 422 with a plain-text body in a search request, such as Failed to deserialize the JSON body into the target type: filters: data did not match any variant of untagged enum ExprOrValueExpr at line 1 column 167. The filter uses an unknown operator, puts two operators on one field or combines a logical operator with field conditions in one object. A value without an operator returns the same error.
  • Database error. HTTP 500 with a Query Error message. The filter names a nested field with a dotted name, or a stored value cannot be converted to the type that the schema declares, such as 5.0 in an integer field or text stored before the schema changed the field to a number.

Schema error names no field, so the client application checks each condition against the schema. Filter operators lists every cause.