Skip to main content
API reference sections

Schema

TaskQuery

POST /v1/tasks/query.

Every filter is optional and absent means "no constraint". Filters compose with AND; the values inside tags compose with OR (any of). The base set is derived, never marked in a second table: a node is a task when it carries the koru.task marker or a due date, which is the whole derivation ([ADR-285]).

[ADR-285]: ../../../docs/decisions/ADR-276-300.md#adr-285-taskmodel--a-task-is-a-marked-node-and-the-marker-or-a-due-date-is-the-whole-derivation

Type object

Shape

FieldTypeDescription
workspacesarray of WorkspaceId

Which workspaces to search. Empty means every workspace this store holds.

Default []

hasstring or null

Only tasks carrying this attribute key (koru.due, or a custom definition's key).

Default null

missingstring or null

Only tasks not carrying this key.

Default null

tagsarray of string

Only tasks carrying any of these tags, each a TagId in storage form. Ids, never names - the tag table owns names and rename must not change what a saved query matches.

Default []

prioritystring or null

Only tasks whose koru.priority equals this token (low | medium | high | urgent).

Default null

min_prioritystring or null

Only tasks whose koru.priority is this token or more important - the semantic order low < medium < high < urgent, never the alphabetic one.

Default null

dueDueFilter or null

Which koru.due values match, stated per temporal type.

Default null

bucketstring or null

Only tasks in one agenda bucket - today, next, upcoming, later or completed (SPEC §7).

Usable only with [`bucket_windows`](Self::bucket_windows), which is where the reader's zone was resolved; naming one without the other is a 400, as is a token that is not one of the five. A bucket is never persisted and never guessed - a store that invented today's date would be wrong by morning and wrong in another country.

A bucket names a lifecycle, so it supersedes [`lifecycle`](Self::lifecycle). Completed is one of the five and the other four are open by rule 1 of the bucket function, so ANDing the two fields would answer bucket: "completed" with an empty page under the default lifecycle - a silent trap on the most obvious request. When this field is present, the query's lifecycle is not applied.

Default null

bucket_windowsBucketWindows or null

The reader's zone, already resolved into days and instants - required by [bucket](Self::bucket) and by group_by: "bucket".

Default null

completed_on_or_afterstring or null

Hide tasks completed before this day - a canonical YYYY-MM-DD, inclusive, which the caller computed from its own today and [COMPLETED_WINDOW_DAYS].

It applies to completed rows and to nothing else, so it narrows the Completed queue and leaves the four open ones exactly as they were.

A task the store cannot date is kept. A completion recorded before this build carries no day, and a store that cannot tell how old something is must not call it old: hiding those would take work out of sight that nobody could ask for back. They age out as they are reopened, finished again, or taken off Tasks.

Default null

recently_completed_sincestring or null

Keep tasks completed on or after this day where they were, rather than only in the Completed queue: a canonical YYYY-MM-DD the caller computed from its own today.

What it loosens is where a completed task is listed, not what a task is. A queue places a row by its due when this covers it, so something ticked this morning stays on the page it was ticked on, struck through, instead of vanishing; a list shows it beside the open work in it. Nothing else about the row changes, and the row is still in the Completed queue as well - the two pages are two questions.

The counts do not carry it, deliberately: a queue's number is how much open work is in it, and ticking something should make that number go down rather than leave it where it was.

Default null

liststring or null

Only tasks in this list - a ListId in storage form.

A list is a filter, not a place (SPEC §8): membership is one koru.list value on the task, so this is one more predicate over one more attribute and ANDs with every filter above it. Ids, never names - the workspace's list table owns names, and renaming a list must not change what a saved query matches. An id no table names matches nothing.

Default null

group_bystring or null

Ask for corpus-wide counts beside the page. list or bucket; absent means no groups, and the response's groups is empty. An unknown token is a 400, never a silently ungrouped answer.

The attribute query's rule, applied here: a client can count a page itself, and what it cannot do is count the rows it did not fetch - so a sidebar's "14" comes from its own statement over the same filter rather than from the page above it.

bucket requires [bucket_windows](Self::bucket_windows) and answers five rows per workspace, one per bucket - the rail's Today 5 · 3 overdue, Next 12, Upcoming 40, Later 9, Completed 118. It partitions by lifecycle (Completed is one of the five), so it does not apply [lifecycle](Self::lifecycle) either, for [bucket](Self::bucket)'s reason.

Default null

overdueDueFilter or null

Which dues count as overdue, stated per temporal type - used only to split groups, never to filter rows.

Overdue is a fact about the reader's display zone (SPEC §7) and the store carries no timezone database, so the caller computes the windows and sends them: typically dates.before = today and instants.before_ms = now. Absent means every group's overdue is zero, which is the honest answer to a question nobody asked.

Default null

lifecycleLifecycleFilter

Open, completed, or both. Absent means open - a task list answers what should I do next, and absence of koru.task_state is itself open.

Default "open"

include_trashedboolean

Also return tasks sitting in their document's own bin, each flagged with [TaskRow::in_trash]. Off by default; on is how a binned task stays restorable from a Tasks surface.

It does not govern binned documents: those rows are always returned, flagged with [TaskRow::document_trashed].

Default false

sortTaskSort

due, priority or modified. Absent means due - soonest first, undated last.

Default "due"

limitinteger (uint, >= 0)

Rows per page, clamped by the store; see [MAX_LIMIT].

Default 100

afterstring or null

The task: reference the previous page ended on, exclusive.

Keyset, never an offset. A Tasks view re-queries at every local midnight and on every resume, and an offset page silently repeats or skips a row when anything inserts above it. A reference that no longer matches the filter is not an error: the answer comes from the top with [TaskQueryResponse::restarted] set.

Default null

coveringarray of RevisionRequirement

Writes this answer must include for the caller to trust it.

A surface that has just written a document and re-asks the read model can be answered from a projection that has not seen the write yet - the store projects asynchronously and coalesces. Naming the write lets the answer say so through [TaskQueryResponse::covered], and the caller can ask again rather than draw a number it knows is behind.

Optional and additive. Absent means the caller is not waiting on anything; an older client never sends it. At most [MAX_COVERING] entries; a malformed one refuses the request rather than answering uncovered, because a malformed requirement is a client bug and a quiet answer would hide it. Pagination is untouched.

Default []

Example
{
  "workspaces": [],
  "has": "string",
  "missing": "string",
  "tags": [],
  "priority": "string",
  "min_priority": "string",
  "due": {
    "dates": {
      "after": "string",
      "before": "string"
    },
    "instants": {
      "after_ms": 0,
      "before_ms": 0
    }
  },
  "bucket": "string",
  "bucket_windows": {
    "today": "string",
    "horizon": "string",
    "upcoming_horizon": "string",
    "today_ends_ms": 0,
    "horizon_ends_ms": 0,
    "upcoming_ends_ms": 0,
    "now_ms": 0,
    "min_next_priority": "string"
  },
  "completed_on_or_after": "string",
  "recently_completed_since": "string",
  "list": "string",
  "group_by": "string",
  "overdue": {
    "dates": {
      "after": "string",
      "before": "string"
    },
    "instants": {
      "after_ms": 0,
      "before_ms": 0
    }
  },
  "lifecycle": "open",
  "include_trashed": false,
  "sort": "due",
  "limit": 100,
  "after": "string",
  "covering": []
}

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

Used by

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.