the document's current location
API reference sections
HTTP API reference
Documents and search
Document location and outline, search, and read-model queries over attributes and tasks.
6 operations. The contract has no example values, so example bodies are generated from each schema: every field is shown with a placeholder of its type.
GET/v1/documents/{id}/location
- Authorization
- Bearer token with
read_documents
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| idrequired | path | string |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | See above. | DocumentLocation |
| 401 | no bearer token, or one that is not recognised | Failure |
| 403 | the credential does not hold the required scope | Failure |
{
"profile": "00000000-0000-4000-8000-000000000000",
"workspace": "00000000-0000-4000-8000-000000000000",
"document": "00000000-0000-4000-8000-000000000000"
}curl "http://127.0.0.1:$PORT/v1/documents/$ID/location" \
-H "Authorization: Bearer $KORU_TOKEN"GET/v1/documents/{id}/outline
headings in reading order, and whether the document is indexed yet
- Authorization
- Bearer token with
read_documents
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| idrequired | path | string |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | See above. | DocumentOutline |
| 401 | no bearer token, or one that is not recognised | Failure |
| 403 | the credential does not hold the required scope | Failure |
{
"document": "00000000-0000-4000-8000-000000000000",
"headings": [
{
"node": "string",
"level": 0,
"text": "string"
}
],
"projected": false
}curl "http://127.0.0.1:$PORT/v1/documents/$ID/outline" \
-H "Authorization: Bearer $KORU_TOKEN"POST/v1/attributes/query
a page of rows - each carrying workspace, document and node together, every attribute the node holds, an excerpt, and the source document's title and inherited trashed flag - plus the attribute definitions and tag tables in scope, so a filter UI builds from types. A POST because the query travels in the body: a filter can name an attribute key or a tag, and a URL is written to the access log. group_by: "tag" adds groups - a count per tag over the whole matching corpus rather than the page, with merged tags folded into the tag they were merged into and binned rows counted and flagged. updating_workspaces names any workspace the answer may be behind on rather than waiting for it
- Authorization
- Bearer token with
read_documents
Request body (required)
application/json AttributeQuery
| Field | Type | Description |
|---|---|---|
| workspaces | array of WorkspaceId | Which workspaces to search. Empty means every workspace this store holds. Default |
| has | string or null | Only nodes carrying this attribute key ( Default |
| missing | string or null | Only nodes not carrying this key. Combined with the base rule this reads "has some attribute, but not this one". Default |
| tags | array of string | Only nodes carrying any of these tags, each a Default |
| priority | string or null | Only nodes whose Default |
| min_priority | string or null | Only nodes whose Default |
| due | DueFilter or null | Which Default |
| sort | string or null |
Default |
| group_by | string or null | Ask for corpus-wide counts beside the page. It is a separate question from paging, deliberately: a client can count a page itself, and what it cannot do is count the rows it did not fetch. So the counts come from their own statement over the same filter, and a group's count is the whole matching corpus rather than the page above it. Default |
| limit | integer (uint, >= 0) or null | Rows per page. Absent means the server default; the server also caps it, so a huge number is a full page rather than a full table. Default |
| offset | integer (uint, >= 0) or null | Rows to skip. Page by increasing this until a short page comes back. Default |
{
"workspaces": [],
"has": "string",
"missing": "string",
"tags": [],
"priority": "string",
"min_priority": "string",
"due": {
"dates": {
"after": "string",
"before": "string"
},
"instants": {
"after_ms": 0,
"before_ms": 0
}
},
"sort": "string",
"group_by": "string",
"limit": 0,
"offset": 0
}Responses
| Status | Description | Body |
|---|---|---|
| 200 | See above. | AttributeQueryResponse |
| 401 | no bearer token, or one that is not recognised | Failure |
| 403 | the credential does not hold the required scope | Failure |
{
"rows": [
{
"workspace": "00000000-0000-4000-8000-000000000000",
"document": "00000000-0000-4000-8000-000000000000",
"node": "string",
"node_kind": "string",
"excerpt": "string",
"document_title": "string",
"trashed": false,
"attributes": [
{
"id": "string",
"role": "string",
"kind": "string",
"value": "string",
"zone": "string",
"label": "string"
}
]
}
],
"definitions": [
{
"id": "string",
"key": "string",
"label": "string",
"value_type": "string",
"cardinality": "string",
"scope": "string",
"system": false,
"options": [],
"filterable": false,
"sortable": false,
"groupable": false
}
],
"tags": [
{
"id": "string",
"name": "string",
"color": "string",
"workspace": {}
}
],
"groups": [],
"projected": false,
"updating_workspaces": [],
"limit": 0,
"offset": 0
}curl -X POST "http://127.0.0.1:$PORT/v1/attributes/query" \
-H "Authorization: Bearer $KORU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workspaces": [],
"has": "string",
"missing": "string",
"tags": [],
"priority": "string",
"min_priority": "string",
"due": {
"dates": {
"after": "string",
"before": "string"
},
"instants": {
"after_ms": 0,
"before_ms": 0
}
},
"sort": "string",
"group_by": "string",
"limit": 0,
"offset": 0
}'POST/v1/tasks/query
a page of tasks - each carrying workspace, document and node together, the task: reference that is both its identity and the page cursor, its lifecycle, its title, the breadcrumb that says where it lives, both trash answers under their own names, and every attribute the node holds - plus the attribute definitions and tag tables in scope, so a row draws without a second request. A POST for the same reason the attribute query is one: the filter travels in the body rather than in a URL that is written to the access log. Paging is a keyset on the task reference, so next_after continues and restarted says the cursor named a row the filter no longer matches. updating_workspaces names any workspace the answer may be behind on rather than waiting for it
- Authorization
- Bearer token with
read_documents
Request body (required)
application/json TaskQuery
| Field | Type | Description |
|---|---|---|
| workspaces | array of WorkspaceId | Which workspaces to search. Empty means every workspace this store holds. Default |
| has | string or null | Only tasks carrying this attribute key ( Default |
| missing | string or null | Only tasks not carrying this key. Default |
| tags | array of string | Only tasks carrying any of these tags, each a Default |
| priority | string or null | Only tasks whose Default |
| min_priority | string or null | Only tasks whose Default |
| due | DueFilter or null | Which Default |
| bucket | string or null | Only tasks in one agenda bucket - 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). Default |
| bucket_windows | BucketWindows or null | The reader's zone, already resolved into days and instants - required by [ Default |
| completed_on_or_after | string or null | Hide tasks completed before this day - a canonical 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 |
| recently_completed_since | string or null | Keep tasks completed on or after this day where they were, rather than only in the Completed queue: a canonical 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 |
| list | string or null | Only tasks in this list - a A list is a filter, not a place (SPEC §8): membership is one Default |
| group_by | string or null | Ask for corpus-wide counts beside the page. 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.
Default |
| overdue | DueFilter or null | Which dues count as overdue, stated per temporal type - used only to split 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 Default |
| lifecycle | LifecycleFilter | Open, completed, or both. Absent means open - a task list answers what should I do next, and absence of Default |
| include_trashed | boolean | Also return tasks sitting in their document's own bin, each flagged with [ It does not govern binned documents: those rows are always returned, flagged with [ Default |
| sort | TaskSort |
Default |
| limit | integer (uint, >= 0) | Rows per page, clamped by the store; see [ Default |
| after | string or null | The 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 [ Default |
| covering | array 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 [ Optional and additive. Absent means the caller is not waiting on anything; an older client never sends it. At most [ Default |
{
"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": []
}Responses
| Status | Description | Body |
|---|---|---|
| 200 | See above. | TaskQueryResponse |
| 401 | no bearer token, or one that is not recognised | Failure |
| 403 | the credential does not hold the required scope | Failure |
{
"rows": [
{
"workspace": "00000000-0000-4000-8000-000000000000",
"document": "00000000-0000-4000-8000-000000000000",
"node": "string",
"task": "string",
"node_kind": "string",
"lifecycle": "string",
"document_trashed": false,
"in_trash": false,
"breadcrumb": "string",
"title": "string",
"attributes": [
{
"id": "string",
"role": "string",
"kind": "string",
"value": "string",
"zone": "string",
"label": "string"
}
]
}
],
"definitions": [
{
"id": "string",
"key": "string",
"label": "string",
"value_type": "string",
"cardinality": "string",
"scope": "string",
"system": false,
"options": [],
"filterable": false,
"sortable": false,
"groupable": false
}
],
"tags": [
{
"id": "string",
"name": "string",
"color": "string",
"workspace": {}
}
],
"groups": [],
"projected": false,
"updating_workspaces": [],
"limit": 0,
"next_after": "string",
"restarted": false,
"covered": false
}curl -X POST "http://127.0.0.1:$PORT/v1/tasks/query" \
-H "Authorization: Bearer $KORU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"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": []
}'POST/v1/search
Queries eligible sources concurrently, preserves partial results and source-labelled failures, and returns a deterministic merged page.
- Authorization
- Bearer token with
store.read_documents - Operation id
- federated_search
Request body (required)
application/json FederatedSearchRequest
| Field | Type | Description |
|---|---|---|
| textrequired | string | |
| entity_picker | boolean | Restrict results to document/file titles and live task nodes for an @ picker. This is a query shape over the existing projection, not a stored index. Default |
| workspaces | array of WorkspaceId | Empty means every workspace the index knows. The caller resolves a profile to this; the index has never heard of one. Default |
| excluded_workspaces | array of WorkspaceId | Default |
| kinds | array of string |
Default |
| tags | array of string | Tag names, not A surface may fill this directly; a person fills it by typing Default |
| attributes | array of AttrFilter | Typed attribute filters. A hit's node must satisfy every one. One general filter rather than a field per attribute, because a workspace defines its own attributes and declares their types: a wire naming Default |
| sort | SearchSort | Default |
| lifecycle | LifecycleFilter | Default |
| page_size | integer (uint, >= 0) | Clamped by the store; see [ Default |
| offset | integer (uint, >= 0) | Default |
| request_seq | integer (uint64, >= 0) | Copied to the response, so a surface typing quickly can drop an answer to a keystroke it has already moved past. Default |
| include_facets | boolean | Default |
| recent_queries | integer (uint, >= 0) or null | Default |
| include_ai | boolean | Default |
| cursor | string or null | Default |
| profile | ProfileId or null | Only this profile's chats among the AI results (AI schema 13): a window searches its own profile's. Absent searches every profile's, each hit naming its own. The Store results are scoped by |
{
"text": "string",
"entity_picker": false,
"workspaces": [],
"excluded_workspaces": [],
"kinds": [],
"tags": [],
"attributes": [],
"sort": "relevance",
"lifecycle": "live",
"page_size": 50,
"offset": 0,
"request_seq": 0,
"include_facets": false,
"recent_queries": 0,
"include_ai": true,
"cursor": "string",
"profile": "00000000-0000-4000-8000-000000000000"
}Responses
| Status | Description | Body |
|---|---|---|
| 200 | A merged page plus per-source status | FederatedSearchResponse |
| 400 | The request or opaque cursor is invalid | |
| 401 | The gateway bearer is absent or invalid |
{
"page": {
"hits": [
{
"hit": {
"workspace": "string",
"workspace_name": "string",
"document": "string",
"node": "string",
"document_kind": "",
"mime": "string",
"kind": "string",
"field": "string",
"title": "string",
"breadcrumb": "string",
"breadcrumb_spans": [
{
"start": 0,
"end": 0
}
],
"snippet": "string",
"spans": [
{
"start": 0,
"end": 0
}
],
"lifecycle": "live",
"trashed_at": 0,
"trashed_by": "string",
"score": 0,
"observed_modified_at_ms": 0,
"workspace_last_used_at_ms": 0
},
"source": "store"
}
],
"next_cursor": "string",
"updating_workspaces": [
"string"
],
"request_seq": 0,
"corrected_query": "string"
},
"facets": {
"workspaces": [
{
"workspace": "string",
"workspace_name": "string"
}
]
},
"recent_queries": [
{
"text": "string",
"used_at_ms": 0
}
],
"sources": [
{
"source": "store",
"state": "complete",
"error_code": "string"
}
]
}curl -X POST "http://127.0.0.1:$PORT/v1/search" \
-H "Authorization: Bearer $KORU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "string",
"entity_picker": false,
"workspaces": [],
"excluded_workspaces": [],
"kinds": [],
"tags": [],
"attributes": [],
"sort": "relevance",
"lifecycle": "live",
"page_size": 50,
"offset": 0,
"request_seq": 0,
"include_facets": false,
"recent_queries": 0,
"include_ai": true,
"cursor": "string",
"profile": "00000000-0000-4000-8000-000000000000"
}'POST/v1/search/navigated
nothing. Machine-local and derived: it feeds recent queries and a workspace's recency, and reaches no document and no shared profile. A query earns a place in the history only by leading somewhere, which is why the text arrives here rather than on the search
- Authorization
- Bearer token with
edit_documents
Request body (required)
application/json Navigation
| Field | Type | Description |
|---|---|---|
| workspacerequired | WorkspaceId | |
| document | string or null | Default |
| node | string or null | Default |
| query | string | What was typed to get here. Empty is allowed and records nothing. Default |
{
"workspace": "00000000-0000-4000-8000-000000000000",
"document": "string",
"node": "string",
"query": ""
}Responses
curl -X POST "http://127.0.0.1:$PORT/v1/search/navigated" \
-H "Authorization: Bearer $KORU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workspace": "00000000-0000-4000-8000-000000000000",
"document": "string",
"node": "string",
"query": ""
}'Generated from contract version 1. Last updated .
