JSON Query Language is a JSON-encoded, SQL-flavoured predicate language for the request body of search operations, described entirely by a single JSON Schema draft 2020-12 document. It covers the filter and nothing else — no projection, ordering or pagination — so the same grammar can be $ref’d from every POST /…/search and QUERY /… operation in an OpenAPI document, or bundled inline as an MCP tool’s inputSchema. Operators are grouped into conformance profiles (core, strings, regex, ranges, types, collections, refs, text), semantics are three-valued like SQL, and rejected filters come back as RFC 9457 Problem Details with a JSON Pointer at the offending clause.
OpenPredicate (formerly JSON Query Language)
JSON Query Language (JQL) is a small, deliberately narrow specification: a JSON-encoded, SQL-flavoured predicate language, defined by one JSON Schema draft 2020-12 file. It answers a problem every catalog of APIs runs into — search endpoints attract a bespoke query syntax each, arriving as an opaque string parameter that no schema can validate, no generator can type, and no client can safely build. JQL structures the filter as JSON so it can be described, and a description is the one thing both of today’s API description layers already speak.
- The predicate and nothing else - No projection, ordering or pagination. Those differ per API; the filter does not, which is what makes one grammar reusable across every search operation.
- Built for the search request body - It targets
POST /…/searchon OpenAPI 3.1 and the HTTP QUERY method on 3.2, whereadditionalOperationscan finally describe a method outside the fixed set. - One file, two integration paths -
$refit from an OpenAPI document, or bundle it inline as an MCP tool’sinputSchema— the second path cannot resolve a remote$ref, so the copy is the only option there. - Conformance profiles - Operators are grouped into
core,strings,regex,ranges,types,collections,refsandtext. Implementcorein full and take the rest whole, so a clause you cannot execute is rejected rather than silently dropped. - Three-valued logic, stated out loud -
$neand$notexclude nulls the way SQL does, and$inis value equality rather than array membership. Both are documented as traps rather than left to be discovered. - Errors that locate themselves - A rejected filter returns Problem Details (RFC 9457) with a JSON Pointer at the offending clause —
malformed-query,unknown-field,unsupported-operator,invalid-operand,query-too-complex. - One override point - The queryable field set is narrowed by replacing a single
$defs/FieldPathdefinition in a bundled copy, and the narrowing propagates to every nesting level including$fieldreferences.
It is worth being precise about where this sits, because the JSON query space is crowded and JQL is not
doing what its neighbors do. JSONPath and JMESPath are selection
languages — they address and reshape nodes inside a document you already hold. JQL is a predicate
language: it never selects, it only decides whether a record matches, and it is designed to travel over
the wire as a request body rather than to be evaluated client-side. OData and
GraphQL both carry filtering, but each does so inside a whole protocol you have to
adopt — OData’s $filter string is still a string, and GraphQL replaces your HTTP surface entirely. JQL
asks for neither; it is a schema you point at from the API you already have.
The part that matters most for agentic operations is the MCP path. A
model handed a tool definition sees only that definition — it does not fetch a remote schema and it does
not read a specification. Today the near-universal pattern is a query: string parameter with prose
telling the model how to assemble a search string it can never be checked against. Bundling a real grammar
into inputSchema moves that contract from prose into something a validator enforces, with the operator
descriptions carried along as the instructions. The specification is unusually honest about the limit
of this: syntax is now checkable, but a filter that is valid and wrong still fails as an empty result
set an agent cannot distinguish from “no such records,” which is why it pushes value domains into a
separate capability document rather than pretending the grammar can carry them.
Two caveats belonged on the record when this was first catalogued on 27 August 2026, and both have since
been answered by the project rather than by argument. The schema’s $id returned a 404 then; it resolves
now. Neither documented npm package name resolved then; @open-predicate/open-predicate publishes 0.6.2
today. In the same window the project left a personal repository for its own GitHub organization, renamed
itself OpenPredicate, and added governance, contributing, security and release documents. That is a
meaningful amount of seriousness for a project that had eight commits and no stars a month ago.
What has not changed is the adoption question. It is pre-1.0, effectively single-author, sitting at three
stars, and I still cannot identify anyone using it in production. There is also a small inconsistency worth
watching: the $id still declares v0.4.0 while the repository and npm are both at v0.6.2, so the canonical
URL a consumer is told to $ref does not name the current version. It remains catalogued here for the
design work, which is careful and reads like someone who has operated search endpoints — not for adoption
it has not yet earned.