How AI is applied across API Evangelist and APIs.io. Read my AI disclosure →
API Evangelist API Evangelist
Discovery
Learnings
Guidance
Toolbox
Alignment
API Evangelist LLC

OpenPredicate (formerly JSON Query Language)

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.

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 /…/search on OpenAPI 3.1 and the HTTP QUERY method on 3.2, where additionalOperations can finally describe a method outside the fixed set.
  • One file, two integration paths - $ref it from an OpenAPI document, or bundle it inline as an MCP tool’s inputSchema — 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, refs and text. Implement core in full and take the rest whole, so a clause you cannot execute is rejected rather than silently dropped.
  • Three-valued logic, stated out loud - $ne and $not exclude nulls the way SQL does, and $in is 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/FieldPath definition in a bundled copy, and the narrowing propagates to every nesting level including $field references.

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.