Kosha docs
Documentation menu

Getting started

Quickstart

Start a server, index a handful of documents, flush them into a segment, and get a BM25-scored result back. Everything below runs against a local Docker container with no S3 and no Postgres.

Run a server

Multi-arch images are published to GHCR on every merge to main. The container listens on 8080 and needs nothing else to start.

shell
docker run --rm -p 8080:8080 ghcr.io/decover-tech/kosha:latest

curl localhost:8080/v1/healthz
# {"status":"ok"}

To bring up MinIO alongside the server — an S3-compatible endpoint on :9000 with the dev bucket pre-created — clone the repo and use the compose file instead:

shell
git clone https://github.com/decover-tech/kosha
cd kosha
docker compose up --build

Point a client at it

Both the CLI and the Python client read KOSHA_HOST and KOSHA_API_KEY, so setting them once covers every command on this page.

shell
export KOSHA_HOST=http://localhost:8080
export KOSHA_API_KEY=sk-kosha-dev

cargo install --path crates/kosha-cli   # or: cargo run -p kosha-cli -- …
kosha health

The key resolves to a tenant, and that tenant id is prefixed onto every namespace you name. Nothing else in this guide has to think about it.

Index documents

A document is an id and a list of typed fields. Values are always sent as strings — the field_type is what tells Kosha how to index and compare them.

POST /v1/namespaces/quickstart-demo/documents
{
  "documents": [
    {
      "id": "d1",
      "fields": [
        {"name": "title",  "field_type": "Text",    "value": "Breach notification policy"},
        {"name": "body",   "field_type": "Text",    "value": "Report any breach within 72 hours."},
        {"name": "status", "field_type": "Keyword", "value": "published"},
        {"name": "year",   "field_type": "Integer", "value": "2026"}
      ]
    }
  ]
}
response
{"indexed_count": 1, "namespace": "acme-corp/quickstart-demo"}

The namespace does not need to be created first — indexing into a name that does not exist registers it. A namespace with no documents costs a metadata row, which is why creating one per customer, per matter, or per tenant is a normal thing to do here.

From the CLI, the same thing from a JSONL file:

shell
kosha index -n quickstart-demo --file crates/kosha-cli/examples/docs.jsonl

The CLI also accepts a shorthand line format and infers types from JSON values — strings become Text, integers Integer, floats Float, booleans Boolean:

docs.jsonl
{"id":"d1","title":"Breach notification policy","year":2026}

Flush

Indexed documents land in a write buffer first. They become visible to search when that buffer is written out as a segment — which happens automatically once it crosses the flush threshold, or immediately when you ask:

POST /v1/namespaces/quickstart-demo/flush
{}

# → {"status": "flushed"}

This is the one place Kosha's consistency model shows up in day-to-day use: read-your-writes is bounded by the flush, not immediate. In a test, flush explicitly. In production, let the threshold do it and treat freshness as eventual — the same trade OpenSearch makes with its refresh interval.

One endpoint covers the whole read path. The smallest useful body is a query string:

POST /v1/namespaces/quickstart-demo/search
{
  "query_text": "breach notification",
  "max_results": 10
}
response
{
  "results": [
    {
      "doc_id": "d1",
      "score": 2.4079,
      "fields": [
        {"name": "title", "field_type": "Text", "value": "Breach notification policy"}
      ]
    }
  ],
  "total_hits": 1,
  "total_hits_relation": "eq"
}

Every other feature is another key in that same body: filter, knn, aggs, sort, highlight, wildcard, match_phrase. They combine in one round trip.

shell
kosha search -n quickstart-demo "breach"
kosha search -n quickstart-demo --body @query.json --json

Check the state

Stats tell you how many documents are live, how many segments they are spread across, and which manifest version is current. Segment count is the number that matters for query fan-out.

GET /v1/namespaces/quickstart-demo/stats
{
  "namespace": "quickstart-demo",
  "documents": 1,
  "segments": 1,
  "version": 2
}

From Python

The Python client is OpenSearch-shaped on purpose — it exists so an existing OpenSearch integration can be pointed at Kosha without rewriting its call sites.

python
from kosha_client import KoshaClient

client = KoshaClient(
    hosts="http://localhost:8080",
    api_key="sk-kosha-dev",
)
client.ping()  # True

Next

Read Schema & field types to decide how your documents should be shaped, then Full-text search for what the scorer actually does with them.