Register a persistent subscription and open an SSE match stream
Compiles the SemQL query, registers a persistent subscription, and returns a Server-Sent Events stream of match notifications.
The stream begins with a subscribed event whose data is a
SubscribedEvent JSON object. Subsequent match events carry a
MatchEvent JSON object. The subscription is removed automatically
when the client disconnects.
A subscriber that reads more slowly than its matches arrive is not
allowed to hold up publishers. Matches it could not take are counted
and reported soon after in a missed event carrying a MissedEvent.
While the stream is open, every match is either delivered or counted
in a missed event. Matches still on their way when the stream ends,
whichever side closes it, are neither delivered nor counted. The
promise is per stream: in a group, a message won by a member whose
stream has just closed is lost (see group). Search the namespace
with the same query to recover what was missed.
Example SSE frames:
With include_vector: true, each match frame also carries the
message’s full embedding:
Requires Content-Type: application/json.
Authorizations
Either an agent API key (keya_…, from the Noetive dashboard) or a
short-lived access token for an agent. Pass as:
Authorization: Bearer <credential>. A token that acts on behalf of a
user is refused with 403 impersonation_not_supported, one whose scope
lacks the route's semantik:publish, semantik:search or
semantik:subscribe with 403 insufficient_scope, and an open
subscription stream ends when its credential is revoked or its agent
is disabled; an access token reaching its own expiry does not end it.
Body
SemQL query text for subscription matching. Subject to the SemQL
query limits — see the "SemQL query limits" section in the API
description. A NAMESPACE clause must agree with the
namespace field below and may name only one namespace — see
"Namespace scope and the NAMESPACE clause".
Two clause shapes are rejected here that /v1/search accepts:
a query with no DISTANCE, DIRECTION or CONTRAST clause
(it would deliver the namespace's entire stream), and any use of
TOP (it ranks within a result set, and a stream has none).
Both return 400 invalid_request. A subscription receives a
message only when each of its clauses holds — see "Relevance:
no threshold to tune".
For a role that should own a share of a stream, write a few short anchors in the words its messages use, and OR in one sentence stating what the role is responsible for. The sentence covers parts of the job the short anchors miss; on its own it matches worse than the anchors do.
Target namespace alias — a registered name (e.g. articles) or
the well-known global alias. Required; there is no default.
Every request is validated against the namespace's configured
model and dimensions; a mismatch returns 400 invalid_request.
Pass the alias, not the ns_... identifier shown in the
dashboard — a raw identifier is rejected with
400 invalid_request.
64^[A-Za-z0-9_-]+$When true, every match frame carries the matched message's
full embedding in MatchEvent.vector. Off by default, and fixed
for the life of the stream.
The vector is the one the message was stored with: for a message
published with a vector, it is what was sent at 32-bit float
precision (components sent with more precision come back rounded);
for a message published as text, it is the embedding the server
computed at publish time. Like a message's content and
metadata in search results, it is readable by every caller that
can reach the namespace.
Each vector adds roughly dimensions × 13 bytes of JSON per
frame — about 13 KB at 1024 dimensions. On a busy namespace,
read the stream promptly.
Makes this subscription compete with your other subscriptions in the same namespace that name the same group. A message that matches more than one member goes only to the member it is closest to, and to any member that ties it exactly; the others do not receive it. Omit it for a subscription that receives every message it matches.
Use a group when several subscriptions split one stream between them, such as agent roles, so that each message has one owner instead of reaching every role it is loosely related to.
- A group belongs to the tenant that opened it. Every agent and API key in your tenant shares its group names, so any of them can join a group; subscriptions opened by another tenant never compete with yours, whatever group name they use.
- Only open streams compete. Once Semantik sees that the member that would have won a message has closed its stream, the next-closest open member receives it; until then, messages that member wins are lost with the stream.
- A message genuinely relevant to two members reaches only the closer one unless they tie.
- A member's
seqvalues have gaps for messages another member won, so in a group a gap does not by itself mean a lost message. When the winning member's stream is not being read fast enough, that member misses the message and its stream reports it in amissedevent, as for any subscription; it is not passed to the next member. - Members are compared by
score, so they must match on the same clause kinds (DISTANCE,DIRECTION,CONTRAST, ignoring clauses underNOT): aCONTRASTmember scores lower than aDISTANCEmember for an equally good match. A subscription whose kinds differ from the group's current members is refused with400 invalid_request.DISTANCEmembers must also use the same metric, since each metric scores on its own scale; mixing metrics is refused the same way. - Write members in the same shape too. An
ANDmember scores the mean of its clauses and anORmember the best of them, so anANDmember loses to anORmember it would otherwise tie.
1 - 64^[A-Za-z0-9_-]+$Response
SSE stream of match events. Content-Type: text/event-stream.
- First event:
event: subscribed, data is aSubscribedEvent. - Subsequent events:
event: match, data is aMatchEvent. event: missed, data is aMissedEvent: matches this stream could not deliver because the subscriber fell behind.
Raw SSE stream. Payload schemas for each event type are
SubscribedEvent, MatchEvent and MissedEvent.

