Skip to main content
SemQL is the query language for expressing subscriptions and searches over Semantik. Queries describe geometric regions in embedding space rather than named topics: a subscriber receives messages whose meaning falls inside the declared region, regardless of what words were used to say it. SemQL has two equivalent, losslessly interconvertible representations:
  • Text syntax — SQL-like, human-readable
  • JSON wire format — canonical format used by the Noetive API
Either format can be submitted to the broker. The JSON serializer always emits ISO 8601 durations.

Query structure

A query has one required clause and three optional modifiers:

Clauses

DISTANCE

Nearest-neighbor sphere in embedding space. Matches messages whose embedding is at least as similar to the anchor as the within floor you set.
within is a similarity floor, not a distance ceiling. A message matches when its similarity to the anchor is at least within, so a higher value is stricter. WITHIN 0.9 is near-exact; WITHIN 0.3 lets almost everything through. Values above 1.0 are rejected.
Provide either within or top_k. If neither is set, the clause acts as a scoring signal without a hard threshold.

DIRECTION

Cone search in embedding space. Matches messages aligned with one or more concept directions, regardless of distance from the origin.
When toward is an array, the direction vector is the normalized mean of all embedded concepts.

CONTRAST

Attract/repel vector arithmetic. Matches messages semantically close to the attract concepts and far from the repel concepts.
The composite vector is normalize(mean(embed(attract)) − mean(embed(repel))). With repel absent it is normalize(mean(embed(attract))).

Boolean composition

Combine clauses with AND, OR, and NOT. Use parentheses to control precedence.
NOT narrows a result set but cannot say where to look, so a query whose top level is a bare NOT is rejected. Pair it with a positive clause, as above.

What subscribe rejects

/v1/subscribe accepts a narrower query set than /v1/search: any use of TOP returns 400 invalid_request. TOP ranks a message against the others in a result set. A stream has no result set, since each message is scored on its own as it is published, so the clause has no meaning there and is refused rather than quietly ignored. Use WITHIN to set a score floor instead. The query is legal SemQL, so /v1/lint reports it as valid. This restriction belongs to the subscribe endpoint, not to the language. Subscribe also refuses a query that would match every message, since that delivers the namespace’s entire stream rather than a subscription. In practice a query that constrains nothing — an empty toward or attract list — is already rejected as invalid SemQL before it reaches that check.

Modifiers

NAMESPACE

Restates the namespace the query was written for, so the server can check it. Scope itself comes from the namespace field in the request body — that field is required, it is what your API key is authorized against, and there is no default to fall back on. See Namespaces. NAMESPACE does not change where a query runs; it declares where the query expects to run, and the request is refused with 400 invalid_request if the two disagree. That is worth having when a query outlives the call site: a query stored in a config file, generated by an agent, or copied between environments carries its intended namespace with it, and sending it against the wrong one fails immediately instead of quietly answering from somewhere else. Namespace names match exactly — wildcards are not supported.
Selecting several namespaces is not supported yet. Multiple names, NOT exclusions and ALL are valid SemQL, so /v1/lint parses them, but /v1/search and /v1/subscribe reject them with 400 invalid_request. They are refused rather than ignored: a clause written to exclude a namespace must never return the messages it was meant to leave out.To search several namespaces today, issue one request per namespace and merge the results yourself. Note that each namespace pins its own embedding model and dimensionality, so scores are only comparable across namespaces configured identically.

WINDOW

Restricts results to messages published within a time window ending now.
JSON form uses ISO 8601 durations: "P7D", "PT48H", "PT30M".

LIMIT

Caps the number of results returned. Defaults to 100 when omitted, and is clamped to a maximum of 1000. A larger value succeeds and returns 1000 rather than failing. See Limits.

Anchors

An anchor is the reference point for a clause. Two forms are accepted: Natural language string — the broker embeds it automatically:
Raw float vector — skip embedding, use the vector directly:
A query carries at most 32 anchors in total, and at most 8 anchors in a single list — the toward list of a DIRECTION clause, or either list of a CONTRAST clause. Anchor text across the whole query is capped at 4 KB, and vector anchors at 8192 floats. Over any of these, the query is rejected before anything is embedded. Full table in Limits.

Duration format

The text parser accepts both formats. The JSON serializer always emits ISO 8601.

Full examples


Reserved words

Reserved words are case-insensitive in text syntax. JSON uses lowercase keys exclusively.

Grammar reference