Skip to main content
API reference sections

HTTP API reference

Store and system

The Store's status, its own contracts, backup, import, settings, shutdown, and the sync WebSocket.

13 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.

What this store is and what it can do

GET/v1/status

api_version, capabilities, channel, started_at

Authorization
Bearer token, any scope

Responses

StatusDescriptionBody
200See above.GatewayStatus
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example 200 response
{
  "api_version": 0,
  "capabilities": [
    "string"
  ],
  "channel": "string",
  "started_at": 0,
  "gateway": "starting",
  "store": "starting",
  "ai": "starting"
}
Example request
curl "http://127.0.0.1:$PORT/v1/status" \
  -H "Authorization: Bearer $KORU_TOKEN"

This document

GET/v1/openapi.json

an OpenAPI 3.1 document

Authorization
None: a public discovery page.

Responses

StatusDescriptionBody
200See above.
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example request
curl "http://127.0.0.1:$PORT/v1/openapi.json"

Ask the store to stop; ?only_if_idle=true stops only when nothing is connected

POST/v1/shutdown

stopping: whether it is going down

Authorization
Bearer token with manage_workspaces

Responses

StatusDescriptionBody
200See above.Stopping
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example 200 response
{
  "stopping": false
}
Example request
curl -X POST "http://127.0.0.1:$PORT/v1/shutdown" \
  -H "Authorization: Bearer $KORU_TOKEN"

Write a consistent whole-Store backup

POST/v1/backup

the destination after the backup is complete

Authorization
Bearer token with manage_workspaces

Request body (required)

application/json StoreBackup

FieldTypeDescription
destinationrequiredstring
Example request body
{
  "destination": "string"
}

Responses

StatusDescriptionBody
200See above.StoreBackupResult
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example 200 response
{
  "destination": "string"
}
Example request
curl -X POST "http://127.0.0.1:$PORT/v1/backup" \
  -H "Authorization: Bearer $KORU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "destination": "string"
}'

WebSocket carrying the standard Loro Protocol

GET/v1/sync

a WebSocket; the token travels on the upgrade, never in the protocol

Authorization
Bearer token with read_documents

Responses

StatusDescriptionBody
200See above.
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example request
curl "http://127.0.0.1:$PORT/v1/sync" \
  -H "Authorization: Bearer $KORU_TOKEN"

Take a .koru bundle in. ?mode=restore or ?mode=duplicate, and ?owner=<profile>

POST/v1/import

the workspace. The complete bundle is buffered in memory, with no HTTP size ceiling beyond the bundle format itself. There is no default mode, deliberately: restore keeps the ids so the result can converge with its origin, duplicate mints new ones so it never will, and guessing wrong is quiet either way

Authorization
Bearer token with manage_workspaces

Responses

StatusDescriptionBody
200See above.Workspace
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example 200 response
{
  "id": "00000000-0000-4000-8000-000000000000",
  "owner": {},
  "name": "string",
  "created_at": 0,
  "last_opened_at": 0,
  "icon": "string",
  "color": "string",
  "pinned": false,
  "archived": false,
  "deleted_at": 0,
  "position": 0
}
Example request
curl -X POST "http://127.0.0.1:$PORT/v1/import" \
  -H "Authorization: Bearer $KORU_TOKEN"

What this store is and what it can do

GET/v1/store/status

api_version, capabilities, channel, started_at

Authorization
Bearer token, any scope
Operation id
store_status

Responses

StatusDescriptionBody
200See above.Status
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example 200 response
{
  "api_version": 0,
  "capabilities": [
    "string"
  ],
  "channel": "string",
  "started_at": 0
}
Example request
curl "http://127.0.0.1:$PORT/v1/store/status" \
  -H "Authorization: Bearer $KORU_TOKEN"

POST/v1/store/search

a page of hits, and optionally the workspace list and recent queries. A POST because the query travels in the body: a URL is written to the access log, and a search is not something to leave in a file that rotates for a week. updating_workspaces names any workspace the answer may be behind on rather than waiting for it

Authorization
Bearer token with read_documents
Operation id
store_search

Request body (required)

application/json SearchRequest

FieldTypeDescription
textrequiredstring
entity_pickerboolean

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 false

workspacesarray 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_workspacesarray of WorkspaceId

Default []

kindsarray of string

Kind::as_str() values, as documents store them.

Default []

tagsarray of string

Tag names, not TagIds - this is a text-search surface and a person types the word they read. A hit's node must carry every one of them, and a name here matches its own descendants too (fun matches fun/home), so this is node-scoped filtering rather than a term: nothing here is searched for, ranked, or spelled back in [SearchPage::corrected_query] ([ADR-207](../../../docs/decisions/ADR-201-225.md#adr-207-tagfilter--a-tag-in-a-query-is-a-node-scoped-filter-not-a-term-and-it-lives-in-candidates-alone)).

A surface may fill this directly; a person fills it by typing #tag into [Self::text], which the store lifts out before it searches.

Default []

attributesarray 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 priority and due explicitly would need a new field for every attribute anybody invents, while key + kinds serves a custom one on the day it exists.

Default []

sortSearchSort

Default "relevance"

lifecycleLifecycleFilter2

Default "live"

page_sizeinteger (uint, >= 0)

Clamped by the store; see [MAX_PAGE_SIZE].

Default 50

offsetinteger (uint, >= 0)

Default 0

request_seqinteger (uint64, >= 0)

Copied to the response, so a surface typing quickly can drop an answer to a keystroke it has already moved past.

Default 0

include_facetsboolean

Also return the workspaces the index knows, for a scope picker.

Default false

recent_queriesinteger (uint, >= 0) or null

Also return this many recent queries. Absent means none.

Default null

Example request body
{
  "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
}

Responses

StatusDescriptionBody
200See above.SearchResponse
401

no bearer token, or one that is not recognised

Failure
403

the credential does not hold the required scope

Failure
Example 200 response
{
  "page": {
    "hits": [
      {
        "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
      }
    ],
    "next_offset": 0,
    "updating_workspaces": [
      "string"
    ],
    "request_seq": 0,
    "corrected_query": "string"
  },
  "facets": {
    "workspaces": [
      {
        "workspace": "string",
        "workspace_name": "string"
      }
    ]
  },
  "recent_queries": [
    {
      "text": "string",
      "used_at_ms": 0
    }
  ]
}
Example request
curl -X POST "http://127.0.0.1:$PORT/v1/store/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
}'

Read one settings section

GET/v1/settings/{section}

The typed section: global, ai, or terminal. Secrets are never returned.

Authorization
Bearer token with settings.read
Operation id
settings_read_section

Parameters

NameInTypeDescription
sectionrequiredpath"global" | "ai" | "terminal"

Responses

StatusDescriptionBody
200See above.GlobalSettings or AiSettings or TerminalSettings
400

The patch names a field that is not a setting, or a value the registry refuses

401

The gateway bearer is absent or invalid

403

The session does not hold the scope this operation needs

404

There is no such settings section

409

A required system side effect refused, so nothing was written

500

The write did not land; the body carries the authoritative section

503

The settings authority or the AI worker is unavailable

Example 200 response
{
  "theme": "system",
  "icon_pack": "phosphor",
  "caret_color": "default",
  "caret_width": "thin",
  "caret_height": "short",
  "ui_font": "follow_default",
  "body_font": "follow_default",
  "headings_font": "follow_default",
  "code_font": "follow_default",
  "chat_font": "follow_default",
  "document_text_size": "follow_default",
  "base_color_light": "string",
  "base_color_dark": "string",
  "accent_color_light": "string",
  "accent_color_dark": "string",
  "density": "normal",
  "scroll_speed": "2x",
  "zoom": 0,
  "default_view_mode": "page",
  "default_canvas_background": "auto",
  "pinned_show_title": false,
  "tabs_per_workspace": false,
  "popout_close": "ask",
  "emoji_skin_tone": "string",
  "dictation_gesture": "push_to_talk",
  "dictation_language": "automatic",
  "dictation_tier": "compact",
  "dictation_input": "system_default",
  "spellcheck_enabled": false,
  "spellcheck_language": "automatic",
  "favorite_models": [],
  "close_behavior": "ask",
  "global_shortcut": "string",
  "global_dictation_enabled": false,
  "global_dictation_shortcut": {},
  "start_on_login": false,
  "reminder_start_on_login_explained": false,
  "automatic_update_checks": false,
  "delegate_temporary_root": "string",
  "recording": {
    "preset": "",
    "custom_width": 0,
    "custom_height": 0,
    "frame_rate": 0,
    "cursor": false,
    "folder": "string"
  },
  "disabled_agents": [],
  "default_permission": "manual",
  "default_permission_other": "manual",
  "default_agent": "string",
  "agent_order": []
}
Example request
curl "http://127.0.0.1:$PORT/v1/settings/$SECTION" \
  -H "Authorization: Bearer $KORU_TOKEN"

Change named fields of one settings section

PATCH/v1/settings/{section}

The section as it stands after the change, read back from the authority.

Authorization
Bearer token with settings.write
Operation id
settings_patch_section

Parameters

NameInTypeDescription
sectionrequiredpath"global" | "ai" | "terminal"

Request body (required)

application/json GlobalPatch or AiPatch or TerminalPatch

One of 3 shapes:

  1. GlobalPatch

    One patch of [GlobalSettings]. Absent fields are left alone.

    FieldTypeDescription
    themeThemeChoice or null
    icon_packIconPackChoice or null
    caret_colorCaretColorChoice or null
    caret_widthCaretWidthChoice or null
    caret_heightCaretHeightChoice or null
    ui_fontFontSetting or null
    body_fontFontSetting or null
    headings_fontFontSetting or null
    code_fontFontSetting or null
    chat_fontFontSetting or null

    follow_default puts the chat back on the app font.

    document_text_sizeDocTextSizeSetting or null
    base_color_lightNullable_RgbColor

    #rrggbb sets the light theme's base color; null puts the theme's own back. Settings offers that as its first swatch, so unlike the text size this choice can be taken back - which is why this is a [Field] and not an Option.

    base_color_darkNullable_RgbColor

    The same for the dark theme.

    accent_color_lightNullable_RgbColor

    #rrggbb sets the light theme's accent; null puts the theme's own back, which Settings offers as the row's first swatch.

    accent_color_darkNullable_RgbColor

    The same for the dark theme.

    densityDensityChoice or null
    scroll_speedScrollSpeedChoice or null
    zoomnumber (float) or null
    default_view_modeViewModeChoice or null
    default_canvas_backgroundCanvasBackground or null
    pinned_show_titleboolean or null
    tabs_per_workspaceboolean or null
    popout_closePopoutCloseChoice or null
    emoji_skin_tonestring or null
    dictation_gestureDictationGesture or null
    dictation_languageLanguageChoice or null
    dictation_tierDictationTier or null
    dictation_inputDictationInput or null
    spellcheck_enabledboolean or null
    spellcheck_languageLanguageChoice or null
    favorite_modelsarray of FavoriteModel or null

    The whole favorites list, written whole on every star: an empty list clears them. Absent leaves the stored list alone.

    close_behaviorCloseBehaviorChoice or null
    global_shortcutGlobalShortcut or null
    global_dictation_enabledboolean or null
    global_dictation_shortcutDictationShortcut or null
    start_on_loginboolean or null
    reminder_start_on_login_explainedboolean or null
    automatic_update_checksboolean or null
    delegate_temporary_rootNullable_string

    null puts it back to the platform's temporary directory, which is why this is a [Field] and not an Option: absent, null and a path are three different instructions and only two of them fit in an Option.

    recordingRecordingPrefs or null

    The recorder's prefill, written whole on every Record. Absent leaves the stored set alone.

    disabled_agentsarray of string or null

    The whole off list, written whole on every switch: an empty list is Restore defaults. Absent leaves the stored list alone.

    default_permissionAgentPermission or null

    The permission a new chat with a supported agent starts in.

    default_permission_otherAgentPermission or null

    The permission a new chat with any other agent starts in: Manual or Yolo - an agent with no auto mode has no Auto to start in.

    default_agentNullable_string

    The agent a new chat starts with; null is Auto.

    agent_orderarray of string or null

    The whole order, written whole on every move: an empty list is the built-in order. Absent leaves the stored order alone.

  2. AiPatch

    One patch of [AiSettings].

    The nullable fields use [Field] so that clearing a model is expressible: "openai_model": null clears it, and omitting the key leaves it alone.

    FieldTypeDescription
    selected_providerNullable_AiProvider
    openai_modelNullable_string
    openai_reasoningReasoningChoice or null
    ollama_endpointstring or null
    ollama_modelNullable_string
    ollama_reasoningReasoningChoice or null
  3. TerminalPatch

    One patch of [TerminalSettings].

    FieldTypeDescription
    themeThemeChoice or null
    fontFontSetting or null
    zoomnumber (float) or null
    cursor_colorCaretColorChoice or null
    cursor_widthTerminalCursorWidth or null
    cursor_heightTerminalCursorHeight or null
    cursor_blinkTerminalCursorBlink or null
    composer_defaultboolean or null
Example request body
{
  "theme": "system",
  "icon_pack": "phosphor",
  "caret_color": "default",
  "caret_width": "thin",
  "caret_height": "short",
  "ui_font": "follow_default",
  "body_font": "follow_default",
  "headings_font": "follow_default",
  "code_font": "follow_default",
  "chat_font": "follow_default",
  "document_text_size": "follow_default",
  "base_color_light": {},
  "base_color_dark": {},
  "accent_color_light": {},
  "accent_color_dark": {},
  "density": "normal",
  "scroll_speed": "1x",
  "zoom": 0,
  "default_view_mode": "page",
  "default_canvas_background": "auto",
  "pinned_show_title": false,
  "tabs_per_workspace": false,
  "popout_close": "ask",
  "emoji_skin_tone": "string",
  "dictation_gesture": "push_to_talk",
  "dictation_language": "automatic",
  "dictation_tier": "compact",
  "dictation_input": "system_default",
  "spellcheck_enabled": false,
  "spellcheck_language": "automatic",
  "favorite_models": [
    {
      "agent": "string",
      "model": "string"
    }
  ],
  "close_behavior": "ask",
  "global_shortcut": "string",
  "global_dictation_enabled": false,
  "global_dictation_shortcut": "string",
  "start_on_login": false,
  "reminder_start_on_login_explained": false,
  "automatic_update_checks": false,
  "delegate_temporary_root": {},
  "recording": {
    "preset": "",
    "custom_width": 0,
    "custom_height": 0,
    "frame_rate": 0,
    "cursor": false,
    "folder": "string"
  },
  "disabled_agents": [
    "string"
  ],
  "default_permission": "manual",
  "default_permission_other": "manual",
  "default_agent": {},
  "agent_order": [
    "string"
  ]
}

Responses

StatusDescriptionBody
200See above.GlobalSettings or AiSettings or TerminalSettings
400

The patch names a field that is not a setting, or a value the registry refuses

401

The gateway bearer is absent or invalid

403

The session does not hold the scope this operation needs

404

There is no such settings section

409

A required system side effect refused, so nothing was written

500

The write did not land; the body carries the authoritative section

503

The settings authority or the AI worker is unavailable

Example 200 response
{
  "theme": "system",
  "icon_pack": "phosphor",
  "caret_color": "default",
  "caret_width": "thin",
  "caret_height": "short",
  "ui_font": "follow_default",
  "body_font": "follow_default",
  "headings_font": "follow_default",
  "code_font": "follow_default",
  "chat_font": "follow_default",
  "document_text_size": "follow_default",
  "base_color_light": "string",
  "base_color_dark": "string",
  "accent_color_light": "string",
  "accent_color_dark": "string",
  "density": "normal",
  "scroll_speed": "2x",
  "zoom": 0,
  "default_view_mode": "page",
  "default_canvas_background": "auto",
  "pinned_show_title": false,
  "tabs_per_workspace": false,
  "popout_close": "ask",
  "emoji_skin_tone": "string",
  "dictation_gesture": "push_to_talk",
  "dictation_language": "automatic",
  "dictation_tier": "compact",
  "dictation_input": "system_default",
  "spellcheck_enabled": false,
  "spellcheck_language": "automatic",
  "favorite_models": [],
  "close_behavior": "ask",
  "global_shortcut": "string",
  "global_dictation_enabled": false,
  "global_dictation_shortcut": {},
  "start_on_login": false,
  "reminder_start_on_login_explained": false,
  "automatic_update_checks": false,
  "delegate_temporary_root": "string",
  "recording": {
    "preset": "",
    "custom_width": 0,
    "custom_height": 0,
    "frame_rate": 0,
    "cursor": false,
    "folder": "string"
  },
  "disabled_agents": [],
  "default_permission": "manual",
  "default_permission_other": "manual",
  "default_agent": "string",
  "agent_order": []
}
Example request
curl -X PATCH "http://127.0.0.1:$PORT/v1/settings/$SECTION" \
  -H "Authorization: Bearer $KORU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "theme": "system",
  "icon_pack": "phosphor",
  "caret_color": "default",
  "caret_width": "thin",
  "caret_height": "short",
  "ui_font": "follow_default",
  "body_font": "follow_default",
  "headings_font": "follow_default",
  "code_font": "follow_default",
  "chat_font": "follow_default",
  "document_text_size": "follow_default",
  "base_color_light": {},
  "base_color_dark": {},
  "accent_color_light": {},
  "accent_color_dark": {},
  "density": "normal",
  "scroll_speed": "1x",
  "zoom": 0,
  "default_view_mode": "page",
  "default_canvas_background": "auto",
  "pinned_show_title": false,
  "tabs_per_workspace": false,
  "popout_close": "ask",
  "emoji_skin_tone": "string",
  "dictation_gesture": "push_to_talk",
  "dictation_language": "automatic",
  "dictation_tier": "compact",
  "dictation_input": "system_default",
  "spellcheck_enabled": false,
  "spellcheck_language": "automatic",
  "favorite_models": [
    {
      "agent": "string",
      "model": "string"
    }
  ],
  "close_behavior": "ask",
  "global_shortcut": "string",
  "global_dictation_enabled": false,
  "global_dictation_shortcut": "string",
  "start_on_login": false,
  "reminder_start_on_login_explained": false,
  "automatic_update_checks": false,
  "delegate_temporary_root": {},
  "recording": {
    "preset": "",
    "custom_width": 0,
    "custom_height": 0,
    "frame_rate": 0,
    "cursor": false,
    "folder": "string"
  },
  "disabled_agents": [
    "string"
  ],
  "default_permission": "manual",
  "default_permission_other": "manual",
  "default_agent": {},
  "agent_order": [
    "string"
  ]
}'

Offline HTTP API documentation

GET/docs

Authorization
None: a public discovery page.

Responses

StatusDescriptionBody
200

Offline HTTP API documentation

Example request
curl "http://127.0.0.1:$PORT/docs"

Offline streaming API documentation

GET/docs/asyncapi

Authorization
None: a public discovery page.

Responses

StatusDescriptionBody
200

Offline streaming API documentation

Example request
curl "http://127.0.0.1:$PORT/docs/asyncapi"

The generated public AsyncAPI contract

GET/v1/asyncapi.json

Authorization
None: a public discovery page.

Responses

StatusDescriptionBody
200

The generated public AsyncAPI contract

Example request
curl "http://127.0.0.1:$PORT/v1/asyncapi.json"

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.