What does compatibility have to cover?
The first thing an official client does is refuse to talk to a server it does not recognize. The Elasticsearch clients look for an X-Elastic-Product: Elasticsearch header on every successful response, and the Python client raises UnsupportedProductError when the header is missing. An engine that wants to sit behind those clients has to answer the root endpoint with that header and a version block, and it has to echo request headers such as X-Opaque-Id that tracing tools depend on.
After the handshake, four groups of endpoints do almost all the work. First, index creation with a mappings block, so field types such as text, keyword, integer, boolean, object, and dense_vector are accepted as written. Second, the document endpoints, which include _doc, _bulk, _mget, and _count. Third, _search and _msearch with the query DSL your application uses, which for most applications means match, term, terms, range, bool, prefix, regexp, ids, and exists, plus sort and size. Fourth, aggregations, at least terms on keyword fields.
Elasticsearch client
Python, Java, JavaScript, Rust
REST requests
mappings, _bulk, _search, _msearch
TopK Elasticsearch API
same requests, same response shape
TopK engine
What has to match beyond the endpoints?
Accepting the request is the easier half, because the client also expects Elasticsearch's answers. Scores are the clearest example. Elasticsearch reports a dense_vector cosine hit as (1 + cosine) / 2, so identical vectors score 1.0 and orthogonal vectors score 0.5, and it reports dot_product the same way (Elastic field reference). An application that thresholds on _score or reads max_score breaks if a replacement returns the raw cosine instead, so the replacement has to return the normalized score, and TopK does.
Field semantics have to match too. A match on a keyword field must compare the whole value, case sensitively, while a term on a text field must match one indexed token. Sorting or aggregating on an analyzed text field must fail with a 400 the way Elasticsearch fails, because an application that relies on the error to pick a .keyword subfield would otherwise get a wrong answer instead of an exception. TopK's Elasticsearch API behaves the same way in each of those cases.
What will not carry over?
Any engine that implements the Elasticsearch API without being Elasticsearch has gaps, and the useful gaps are the ones that fail loudly. TopK answers with a 400 and a message for mappings and clauses it does not implement, which includes date fields, custom analyzer settings, minimum_should_match, and scripted updates in _bulk. A migration test run finds each of those on the first pass, because nothing is silently reinterpreted. If your application depends on date math, custom analyzers, or Painless scripts, budget for changing those call sites, and the rest of the request path stays as it is.
When should you keep Elasticsearch?
Keep Elasticsearch when the workload is what Elasticsearch was built for. If your indexes are logs and metrics, your queries lean on date histograms and pipeline aggregations, and your team lives in Kibana, a retrieval engine behind the same API does not help you. Move when the workload is retrieval for an application, which means kNN over your own embeddings, keyword and semantic search over documents, filters that have to stay fast, and hybrid ranking, and you want a different engine underneath without a rewrite.
What does this look like on TopK?
You keep the client and change the endpoint and the key. The client library, the mappings, the bulk loader, and the queries are the ones you already have.
from elasticsearch import Elasticsearches = Elasticsearch("https://<your-topk-es-endpoint>", api_key="<topk-api-key>")es.indices.create(index="books", mappings={"properties": {"title": {"type": "text"},"genre": {"type": "keyword"},"embedding": {"type": "dense_vector", "dims": 768, "similarity": "cosine"},}})es.search(index="books", knn={"field": "embedding", "query_vector": query_embedding, "k": 10,"filter": {"term": {"genre": "fantasy"}},})
Your mapping decides what TopK indexes. A dense_vector field is served by a vector index with the metric you named, a semantic_text field is embedded and served by TopK's managed inference, and Elastic's rank_vectors type is served by a multi-vector index scored by MaxSim, which turns a rescoring field into a retriever. The implementation is open source, and the kNN and hybrid guides cover the query side.