> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vainona.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> A saved query that tells you when a document starts or stops matching it.

A subscription is a saved query. You give it a filter, in the same grammar a query takes (see [querying answers](/concepts/answers#querying-answers)). When a document starts matching the filter you get a `subscription.entered` event, and when it stops matching you get `subscription.exited`. Events are pushed to a [webhook endpoint](/guides/webhooks), and every event is also in the [events feed](/guides/events-feed).

```json theme={null}
{
  "name": "urgent-enterprise",
  "filters": ["And", [
    ["answers.urgency.p", "Gt", 0.8],
    ["attributes.plan", "Eq", "enterprise"]
  ]],
  "events": ["entered", "exited"],
  "include": {"attributes": ["account_id"]},
  "bulk": "summary"
}
```

A filter means exactly what it means in a query, including the rule for missing fields: a comparison on a missing field is false, and `NotEq` and `NotIn` are true. So `["answers.a.p", "NotEq", 0.5]` matches documents with no answer yet. Add `["answers.a.p", "Exists", true]` to leave those out.

**Filters are fixed.** To change one, create a new subscription and delete the old. A changed filter would have no honest edges: every document would have to be judged against it again, which is what a new subscription does, and a new name makes that plain.

## Create, list and query

<CodeGroup>
  ```python Python theme={null}
  from vainona import And

  ns = db.namespace("acme/prod")
  ns.subscriptions.create(
      name="urgent-enterprise",
      filters=And(("answers.urgency.p", "Gt", 0.8), ("attributes.plan", "Eq", "enterprise")),
      events=["entered", "exited"],
      include={"attributes": ["account_id"]},
      endpoint=endpoint["id"],  # or leave it out for the events feed only
  )

  for sub in ns.subscriptions.list()["subscriptions"]:
      print(sub["name"], sub["status"], sub.get("template"))

  # The documents that match now, as a query.
  page = ns.subscriptions.query("urgent-enterprise", top_k=100)
  ```

  ```ts TypeScript theme={null}
  const ns = db.namespace("acme/prod");
  await ns.subscriptions.create({
    name: "urgent-enterprise",
    filters: ["And", [["answers.urgency.p", "Gt", 0.8], ["attributes.plan", "Eq", "enterprise"]]],
    events: ["entered", "exited"],
    include: { attributes: ["account_id"] },
    endpoint: endpoint.id, // or leave it out for the events feed only
  });

  const { subscriptions } = await ns.subscriptions.list();
  for (const sub of subscriptions) console.log(sub.name, sub.status, sub.template);

  // The documents that match now, as a query.
  const page = await ns.subscriptions.query("urgent-enterprise", { top_k: 100 });
  ```
</CodeGroup>

* **`events`** defaults to `["entered"]`. Add `exited` if you keep a list of the matching documents, or you will never hear that one left.
* **`endpoint`** defaults to none: the events go to the [events feed](/guides/events-feed) only. An endpoint's `namespace_prefix` must cover the namespace, or every namespace of a template.
* **`include`** names the attributes and answers to send with each event. The attributes and answers the filter reads are always sent. `state` never is.
* **`bulk`** is `summary` (the default) or `deliver`. See [bulk changes](#bulk-changes).
* **`name`** is 1 to 128 letters, digits, `_` and `-`. Creating a subscription again with the same name and the same settings returns the existing one, so a retried create is safe.
* **`status`** is `syncing` until the subscription's first evaluation in the namespace, then `live`, with `live_at`.
* **`lag_ms`** is how long the oldest change the subscription hasn't evaluated has waited. It is accurate only to about a minute: a quiet subscription records its progress about once a minute, so `lag_ms` reads 0 until a change has waited longer than that. It tells you the subscription is stuck, not that it is a few seconds behind. It is null while the subscription is `syncing`.
* **`warnings`** lists problems such as `judgment_inactive`: a judgment the subscription reads or includes was deactivated or deleted. Its answers stop changing, so the subscription sees nothing new from it. The subscription is never paused or deleted for that.
* **`update`** changes `events`, `include`, `endpoint` and `bulk` for the changes evaluated after it. It can't change `filters`.
* **`delete`** removes the definition, and the next evaluation drops the subscription's matching set. Events recorded before then, and deliveries already queued, still go out.
* **The query** runs the saved filter with your own `rank_by`, `top_k`, `cursor` and `include`, and is billed like any query. It takes a namespace, never a template prefix, and its body can't have `filters`. Use it to seed a receiver, and after `subscription.synced`.

### What is refused

* **`invalid_request`:** a filter on `freshness`, or on `state`; a judgment that doesn't exist or is computed on read (`on_read`), in the filter or in `include`; `state` in `include`; empty or repeated `events`; more than 24 fields, counting those the filter reads; a name the namespace already has or inherits; an endpoint that doesn't exist or doesn't cover the namespace; a 101st subscription in a namespace, counting those it inherits; and an `update` that sends `filters`. See [limits](/limits).
* **`plan_required`:** a subscription on a template prefix below the Team plan, as for [templates](/guides/templates).
* **`unavailable`** (`503`, with `Retry-After`): a create or `update` that names an endpoint while the endpoint can't be checked. Nothing was changed, so retry it.

## Events are edges, not states

A subscription sends an event when a document's match changes, never while it stays the same. As long as every change is live, the events for one document alternate: `entered`, `exited`, `entered`, and so on. A document that joined or left silently, in the [first evaluation](#a-new-subscription-starts-from-now) or in a [bulk change](#bulk-changes), breaks that chain: it can send `exited` without an `entered` before it. Those changes are always announced by `subscription.synced`, and re-reading the members then puts a receiver right.

An `entered` or `exited` event carries:

* **`subscription`**, with its `id`, `name` and `defined_on`, the namespace or template prefix it was created on;
* **`namespace`**, the namespace the document is in;
* **`document`**, with its `id`, `revision`, `incarnation` and `updated_at`;
* **`attributes` and `answers`**: those the filter reads and those named in `include`, as a get returns them;
* **`cause`**: `change` for a write or a new answer, `fanout`, `periodic`, or, for bulk changes you asked to receive, `backfill` (with the backfill's `job_id`) or `resync`;
* **`sequence`**, below;
* **`url`**, the document's get, to fetch the rest with your own key;
* **`truncated`**: a request body is at most 64 KB. When the whole event, with `id`, `type` and `timestamp`, would pass 63 KB, `attributes` and `answers` are left out, `truncated` is `true`, and `url` has them.

`exited` also has a `reason`: `no_longer_matches`, with the current values, which show why the document left; or `deleted`, with only the document's `id` and `incarnation`. A document created again with the same id is a new life, with a new `incarnation`, and enters like any new document.

**`sequence`** is the number of the evaluation that recorded the event in the namespace. Every event of one evaluation has the same number, and a later evaluation has a higher one, with gaps. For one subscription and document, a higher `sequence` is newer, so a receiver drops an event older than what it has. A document deleted and created again within one evaluation sends `exited` and `entered` with the same `sequence`; the `incarnation` tells them apart. The numbers start again when a namespace is deleted and created again, so forget what you kept for a namespace when you delete it.

## Only settled answers count

A document is evaluated only once every answer its filter reads is up to date with its current revision. So a write never makes a document drop out on a `pending` answer and come back when the answer lands.

| The answer the filter reads is | What happens |
| - | - |
| `fresh` | The document is evaluated. |
| `pending` | Nothing yet. The document is evaluated when its answer lands. |
| `stale` because judging is paused | Nothing yet. The document is evaluated when judging resumes and its answer lands. A filter that reads only attributes keeps working while judging is paused. |
| `failed` | Nothing changes. The document keeps its current membership until an evaluation succeeds. A failure is not evidence that the answer changed. |
| `unavailable` | The document is evaluated with the answer missing, as a query would. |
| any other `stale` | The document is evaluated with the stored answer, as a query would. Nothing else is coming for it: a `manual` or `periodic` judgment, for example. |

A filter over two judgments waits for both. The `freshness` field can't be used in a subscription's filter, since only settled answers are seen. Only the answers the filter reads are waited for: an answer named only in `include` is sent as it stands, and can be `pending`. See [freshness](/concepts/freshness#freshness-and-subscriptions) for how this fits with the freshness states.

Events follow answer commits by a few seconds.

## Bulk changes

A backfill, or an edit to a threshold your filter reads, can move thousands of documents at once, without anything happening to any of them. So by default, `"bulk": "summary"`, those moves update the subscription without sending an event for each document. You get one `subscription.synced` event instead:

```json theme={null}
{"subscription": {...}, "namespace": "acme/prod", "reason": "backfill",
 "job_ids": ["job_01j9..."], "entered": 1204, "exited": 37}
```

`reason` is one of:

* `created`, once the subscription's [first evaluation](#a-new-subscription-starts-from-now) in the namespace is done;
* `backfill`, sent once an evaluation finds no more of the backfill's answers, or 10 minutes after the first one, whichever comes first. Backfills that overlap share one `synced`, with all their `job_ids`;
* `thresholds_changed`, when a threshold the filter reads is edited, or a new version changes it;
* `weights_changed`, when a composite judgment the filter reads gets new calibration fits.

`thresholds_changed` and `weights_changed` come within seconds of the change, whether or not anything else happens in the namespace. For a template's judgment they come from every tenant where the subscription has started; a tenant where it hasn't starts under the new settings.

On `synced`, read the current members again with the subscription's query.

A subscription whose receiver must see every change sets `"bulk": "deliver"`. It gets an event for every document a backfill or a definition change moves, with `cause: "backfill"` and the `job_id`, or `cause: "resync"`. The first evaluation is silent either way.

Answers from fan-outs and periodic runs are live changes, never bulk. When a document has both a live change and a bulk one at the same time, the live change wins.

## A new subscription starts from now

A subscription is edge-triggered from when you create it. Its first evaluation in a namespace is silent, whatever `bulk` says: the documents that already match join without an event. Then one `subscription.synced` with `reason: "created"` says how many joined, even when none did, which tells you the subscription is live there. A document written or answered after the subscription was created is the exception: its change sends an event as usual, so the first change after creation is never lost.

Suppose a document matched when you created the subscription and stopped matching before that first evaluation. It sends no `exited`, and it never sent an `entered` either. A receiver built from events and the `synced` re-reads stays consistent. A receiver that listed the matches with a query at creation time does not.

## Templates

A subscription created on a template prefix, such as `acme/prod/*`, applies to every namespace under it, including tenants created later. See [templates](/guides/templates#subscriptions-for-every-tenant).

* **Per tenant.** Each tenant has its own matching set, and its events name the tenant in `namespace`. `subscription.defined_on` names the prefix the subscription was created on, so a platform routes events without a lookup.
* **Lazy start.** A tenant starts evaluating a template's subscription at its next change after the subscription exists, so idle tenants cost nothing. Until then the subscription reads `syncing` in that tenant.
* **No opting out.** Tenants can't change, delete or detach a template's subscription. To leave one out, add a condition on `id` or an attribute to the filter.
* **Names.** A tenant can't create a subscription with the name of one it inherits. A template can create a name a tenant already has; both run, and the tenant's own is listed first.

See [multi-tenant platforms](/guides/multi-tenant-platforms#get-told-when-any-tenants-documents-change) for setting up templates.

## Limits

A namespace has at most 100 subscriptions, counting those it inherits from a template, on every plan. A subscription sends at most 24 attributes and answers, counting those its filter reads. Subscriptions and their events are never billed; the query is billed like any query. See [limits](/limits) and [pricing](/pricing).
