Skip to main content
API reference sections

Schema

AttrFilter

One typed attribute filter.

`kinds` is not optional and not a hint. A stored value is compared only against a filter that names its kind, which is what keeps the three temporal types three: a date due and a datetime_zoned due are different questions, and a window naming one does not answer the other ([ADR-206](../../../docs/decisions/ADR-201-225.md#adr-206-timemodel--koru-has-three-temporal-types-not-one-with-a-nullable-timezone)). Name every kind the question covers - a due window usually means ["date", "datetime_zoned"] - or the answer silently omits the rest.

Comparison follows the kind: number and datetime_zoned compare as numbers, everything else as its canonical text, which is ISO for the remaining temporal kinds and therefore ordered correctly within one kind.

Type object

Shape

FieldTypeDescription
keyrequiredstring

The attribute key: koru.due, koru.priority, or a workspace's own.

oprequiredAttrOp

How an [AttrFilter] compares.

Ranges are half-open, matching the windows the attribute service already uses: Before is < value, After is >= value, and Between is [first, second). A person asking for "before the 10th" does not mean the 10th, and one asking for "after the 10th" does.

valuesarray of string

The operand(s). Empty for Exists/Missing, one for Is/Before/ After, two for Between, any number for OneOf.

Default []

numeric_valuesarray of string

The same operands again, for the kinds that compare as numbers - epoch milliseconds for a datetime_zoned, the plain number for a number. Positionally identical to [Self::values]: same count, same order, same meaning.

This exists because one filter names kinds that do not share a column. A due window is ["date", "datetime_zoned"], and there is deliberately no column those two could be compared through ([ADR-217](../../../docs/decisions/ADR-201-225.md#adr-217-typedsearch--search-keeps-every-attribute-in-its-declared-type-and-a-filter-names-the-kinds-it-compares-against)): the date half needs 2026-09-10 and the instant half needs 1789.... Without a second operand list the instant half would silently drop out - an ISO date does not parse as a number - and the filter would answer only for all-day deadlines while looking as though it answered for both.

Empty means [Self::values] applies to every named kind, which is right whenever they all compare the same way (/effort >3).

The caller computes it, because the caller knows the zone. Turning a calendar day into an instant is a zone question, and the store has no answer to it - the same rule DueFilter already states.

Default []

kindsarray of string

Which stored kinds this applies to. See the type docs - an empty list matches nothing, deliberately, rather than guessing.

Default []

Example
{
  "key": "string",
  "op": "is",
  "values": [],
  "numeric_values": [],
  "kinds": []
}

The contract has no example values, so example bodies are generated from each schema: every field is shown with a placeholder of its type.

Generated from contract version 1. Last updated .

Privacy choices

Choose which optional tools Koru may use on this site. Your choice is saved in a cookie in this browser for 12 months. Privacy policy

Privacy choices

Keep you signed in, protect forms and remember your language and this choice.

Always on

PostHog and Google Analytics 4 measure page views and actions such as downloads, using cookies or local storage.

PostHog records how pages are used, with form inputs masked and private pages excluded. Needs Analytics.

Ahrefs Web Analytics measures visits and where they come from, and Google Ads measures whether our ads led to a visit or download.