From 0d69d6d327a55b28140156dcb199c9675a11f451 Mon Sep 17 00:00:00 2001
From: pankajLaunchX
Date: Wed, 4 Feb 2026 15:38:12 +0530
Subject: [PATCH 01/12] refactor: add dense index operations
---
.gitignore | 92 ++++
pom.xml | 89 ++++
src/main/java/io/endee/client/Endee.java | 289 ++++++++++
src/main/java/io/endee/client/Index.java | 496 ++++++++++++++++++
.../client/exception/EndeeApiException.java | 40 ++
.../client/exception/EndeeException.java | 19 +
.../client/types/CreateIndexOptions.java | 75 +++
.../endee/client/types/IndexDescription.java | 43 ++
.../java/io/endee/client/types/IndexInfo.java | 45 ++
.../java/io/endee/client/types/Precision.java | 36 ++
.../io/endee/client/types/QueryOptions.java | 93 ++++
.../io/endee/client/types/QueryResult.java | 44 ++
.../java/io/endee/client/types/SpaceType.java | 34 ++
.../io/endee/client/types/VectorInfo.java | 36 ++
.../io/endee/client/types/VectorItem.java | 63 +++
.../io/endee/client/util/CryptoUtils.java | 268 ++++++++++
.../java/io/endee/client/util/JsonUtils.java | 37 ++
.../endee/client/util/MessagePackUtils.java | 164 ++++++
.../io/endee/client/util/ValidationUtils.java | 54 ++
19 files changed, 2017 insertions(+)
create mode 100644 .gitignore
create mode 100644 pom.xml
create mode 100644 src/main/java/io/endee/client/Endee.java
create mode 100644 src/main/java/io/endee/client/Index.java
create mode 100644 src/main/java/io/endee/client/exception/EndeeApiException.java
create mode 100644 src/main/java/io/endee/client/exception/EndeeException.java
create mode 100644 src/main/java/io/endee/client/types/CreateIndexOptions.java
create mode 100644 src/main/java/io/endee/client/types/IndexDescription.java
create mode 100644 src/main/java/io/endee/client/types/IndexInfo.java
create mode 100644 src/main/java/io/endee/client/types/Precision.java
create mode 100644 src/main/java/io/endee/client/types/QueryOptions.java
create mode 100644 src/main/java/io/endee/client/types/QueryResult.java
create mode 100644 src/main/java/io/endee/client/types/SpaceType.java
create mode 100644 src/main/java/io/endee/client/types/VectorInfo.java
create mode 100644 src/main/java/io/endee/client/types/VectorItem.java
create mode 100644 src/main/java/io/endee/client/util/CryptoUtils.java
create mode 100644 src/main/java/io/endee/client/util/JsonUtils.java
create mode 100644 src/main/java/io/endee/client/util/MessagePackUtils.java
create mode 100644 src/main/java/io/endee/client/util/ValidationUtils.java
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5d91fac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,92 @@
+# ==================== Build Output ====================
+target/
+build/
+out/
+bin/
+*.class
+*.jar
+*.war
+*.ear
+*.nar
+
+# ==================== Maven ====================
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+# ==================== IDE - IntelliJ IDEA ====================
+.idea/
+*.iml
+*.ipr
+*.iws
+.idea_modules/
+atlassian-ide-plugin.xml
+
+# ==================== IDE - Eclipse ====================
+.classpath
+.project
+.settings/
+.metadata/
+*.launch
+.loadpath
+.recommenders/
+.externalToolBuilders/
+
+# ==================== IDE - NetBeans ====================
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+# ==================== IDE - VS Code ====================
+.vscode/
+*.code-workspace
+
+# ==================== OS Files ====================
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+desktop.ini
+
+# ==================== Logs ====================
+*.log
+logs/
+
+# ==================== Environment & Secrets ====================
+.env
+.env.*
+*.env
+*.pem
+*.key
+credentials.json
+secrets.json
+
+# ==================== Test Files (Manual Tests) ====================
+src/test/
+
+# ==================== Temporary Files ====================
+*.tmp
+*.temp
+*.swp
+*.swo
+*~
+\#*\#
+
+# ==================== Package Manager ====================
+node_modules/
+
+# ==================== Coverage Reports ====================
+coverage/
+*.lcov
+jacoco.exec
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3ac529c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,89 @@
+
+
+ 4.0.0
+
+ io.endee
+ endee-java-client
+ 1.0.0-SNAPSHOT
+ jar
+
+ Endee Java Client
+ Java client library for Endee-DB vector database
+
+
+ UTF-8
+ 17
+ 17
+ 2.17.0
+ 0.9.8
+ 2.0.12
+ 5.10.2
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+
+
+ org.msgpack
+ msgpack-core
+ ${msgpack.version}
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.12.1
+
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+ io.endee.client.ManualTest
+ test
+
+
+
+
+
diff --git a/src/main/java/io/endee/client/Endee.java b/src/main/java/io/endee/client/Endee.java
new file mode 100644
index 0000000..a3e11bc
--- /dev/null
+++ b/src/main/java/io/endee/client/Endee.java
@@ -0,0 +1,289 @@
+package io.endee.client;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.endee.client.exception.EndeeApiException;
+import io.endee.client.exception.EndeeException;
+import io.endee.client.types.CreateIndexOptions;
+import io.endee.client.types.IndexInfo;
+import io.endee.client.types.Precision;
+import io.endee.client.types.SpaceType;
+import io.endee.client.util.JsonUtils;
+import io.endee.client.util.ValidationUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.*;
+
+/**
+ * Main Endee client for Endee-DB.
+ *
+ *
+ * Example usage:
+ *
+ *
+ * {@code
+ * Endee client = new Endee("api-key:secret:region");
+ *
+ * // Create an index
+ * CreateIndexOptions options = CreateIndexOptions.builder("my_index", 128)
+ * .spaceType(SpaceType.COSINE)
+ * .precision(Precision.INT8D)
+ * .build();
+ * client.createIndex(options);
+ *
+ * // Get an index and perform operations
+ * Index index = client.getIndex("my_index");
+ * }
+ */
+public class Endee {
+ private static final Logger logger = LoggerFactory.getLogger(Endee.class);
+ private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
+ private static final int MAX_DIMENSION = 10000;
+
+ private String token;
+ private String baseUrl;
+ private final int version;
+ private final HttpClient httpClient;
+ private final ObjectMapper objectMapper;
+
+ /**
+ * Creates a new Endee client without authentication.
+ * Uses local server at http://127.0.0.1:8080/api/v1
+ */
+ public Endee() {
+ this(null);
+ this.baseUrl = "http://127.0.0.1:8080/api/v1";
+ }
+
+ /**
+ * Creates a new Endee client.
+ *
+ * @param token the API token (optional, format: "key:secret" or
+ * "key:secret:region")
+ */
+ public Endee(String token) {
+ this.token = token;
+ this.baseUrl = "http://127.0.0.1:8080/api/v1";
+ this.version = 1;
+ this.objectMapper = new ObjectMapper();
+
+ if (token != null && !token.isEmpty()) {
+ String[] tokenParts = token.split(":");
+ if (tokenParts.length > 2) {
+ this.baseUrl = "https://" + tokenParts[2] + ".endee.io/api/v1";
+ this.token = tokenParts[0] + ":" + tokenParts[1];
+ }
+ }
+
+ this.httpClient = HttpClient.newBuilder()
+ .version(HttpClient.Version.HTTP_2)
+ .connectTimeout(DEFAULT_TIMEOUT)
+ .build();
+ }
+
+ /**
+ * Sets a custom base URL for the API.
+ *
+ * @param url the base URL
+ * @return the URL that was set
+ */
+ public String setBaseUrl(String url) {
+ this.baseUrl = url;
+ return url;
+ }
+
+ /**
+ * Creates a new index.
+ *
+ * @param options the index creation options
+ * @return success message
+ * @throws EndeeException if the operation fails
+ */
+ public String createIndex(CreateIndexOptions options) {
+ if (!ValidationUtils.isValidIndexName(options.getName())) {
+ throw new IllegalArgumentException(
+ "Invalid index name. Index name must be alphanumeric and can contain underscores and less than 48 characters");
+ }
+ if (options.getDimension() > MAX_DIMENSION) {
+ throw new IllegalArgumentException("Dimension cannot be greater than " + MAX_DIMENSION);
+ }
+ if (options.getSparseDimension() != null && options.getSparseDimension() < 0) {
+ throw new IllegalArgumentException("Sparse dimension cannot be less than 0");
+ }
+
+ String normalizedSpaceType = options.getSpaceType().getValue().toLowerCase();
+ if (!List.of("cosine", "l2", "ip").contains(normalizedSpaceType)) {
+ throw new IllegalArgumentException("Invalid space type: " + options.getSpaceType());
+ }
+
+ Map data = new HashMap<>();
+ data.put("index_name", options.getName());
+ data.put("dim", options.getDimension());
+ data.put("space_type", normalizedSpaceType);
+ data.put("M", options.getM());
+ data.put("ef_con", options.getEfCon());
+ data.put("checksum", -1);
+ data.put("precision", options.getPrecision().getValue());
+
+ if (options.getSparseDimension() != null) {
+ data.put("sparse_dim", options.getSparseDimension());
+ }
+ if (options.getVersion() != null) {
+ data.put("version", options.getVersion());
+ }
+
+ try {
+ HttpRequest request = buildPostRequest("/index/create", data);
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+ if (response.statusCode() != 200) {
+ logger.error("Error: {}", response.body());
+ EndeeApiException.raiseException(response.statusCode(), response.body());
+ }
+
+ return "Index created successfully";
+ } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ throw new EndeeException("Failed to create index", e);
+ }
+ }
+
+ /**
+ * Lists all indexes.
+ *
+ * @return list of index information
+ * @throws EndeeException if the operation fails
+ */
+ public String listIndexes() {
+ try {
+ HttpRequest request = buildGetRequest("/index/list");
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response.body();
+ } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ throw new EndeeException("Failed to list indexes", e);
+ }
+ }
+
+ /**
+ * Deletes an index.
+ *
+ * @param name the index name to delete
+ * @return success message
+ * @throws EndeeException if the operation fails
+ */
+ public String deleteIndex(String name) {
+ try {
+ HttpRequest request = buildDeleteRequest("/index/" + name + "/delete");
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+ if (response.statusCode() != 200) {
+ logger.error("Error: {}", response.body());
+ EndeeApiException.raiseException(response.statusCode(), response.body());
+ }
+
+ return "Index " + name + " deleted successfully";
+ } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ throw new EndeeException("Failed to delete index", e);
+ }
+ }
+
+ /**
+ * Gets an index by name.
+ *
+ * @param name the index name
+ * @return the Index object for performing vector operations
+ * @throws EndeeException if the operation fails
+ */
+ public Index getIndex(String name) {
+ try {
+ HttpRequest request = buildGetRequest("/index/" + name + "/info");
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+ if (response.statusCode() != 200) {
+ EndeeApiException.raiseException(response.statusCode(), response.body());
+ }
+
+ JsonNode data = objectMapper.readTree(response.body());
+
+ IndexInfo indexInfo = new IndexInfo();
+ indexInfo.setSpaceType(SpaceType.fromValue(data.get("space_type").asText()));
+ indexInfo.setDimension(data.get("dimension").asInt());
+ indexInfo.setTotalElements(data.get("total_elements").asLong());
+ indexInfo.setPrecision(Precision.fromValue(data.get("precision").asText()));
+ indexInfo.setM(data.get("M").asInt());
+ indexInfo.setChecksum(data.get("checksum").asLong());
+
+ if (data.has("version") && !data.get("version").isNull()) {
+ indexInfo.setVersion(data.get("version").asInt());
+ }
+ if (data.has("sparse_dim") && !data.get("sparse_dim").isNull()) {
+ indexInfo.setSparseDimension(data.get("sparse_dim").asInt());
+ }
+
+ return new Index(name, token, baseUrl, version, indexInfo);
+ } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ throw new EndeeException("Failed to get index", e);
+ }
+ }
+
+ private HttpRequest buildGetRequest(String path) {
+ HttpRequest.Builder builder = HttpRequest.newBuilder()
+ .uri(URI.create(baseUrl + path))
+ .header("Content-Type", "application/json")
+ .timeout(DEFAULT_TIMEOUT)
+ .GET();
+
+ if (token != null && !token.isEmpty()) {
+ builder.header("Authorization", token);
+ }
+
+ return builder.build();
+ }
+
+ private HttpRequest buildPostRequest(String path, Map data) {
+ String json = JsonUtils.toJson(data);
+ HttpRequest.Builder builder = HttpRequest.newBuilder()
+ .uri(URI.create(baseUrl + path))
+ .header("Content-Type", "application/json")
+ .timeout(DEFAULT_TIMEOUT)
+ .POST(HttpRequest.BodyPublishers.ofString(json));
+
+ if (token != null && !token.isEmpty()) {
+ builder.header("Authorization", token);
+ }
+
+ return builder.build();
+ }
+
+ private HttpRequest buildDeleteRequest(String path) {
+ HttpRequest.Builder builder = HttpRequest.newBuilder()
+ .uri(URI.create(baseUrl + path))
+ .timeout(DEFAULT_TIMEOUT)
+ .DELETE();
+
+ if (token != null && !token.isEmpty()) {
+ builder.header("Authorization", token);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/src/main/java/io/endee/client/Index.java b/src/main/java/io/endee/client/Index.java
new file mode 100644
index 0000000..36c158d
--- /dev/null
+++ b/src/main/java/io/endee/client/Index.java
@@ -0,0 +1,496 @@
+package io.endee.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.endee.client.exception.EndeeApiException;
+import io.endee.client.exception.EndeeException;
+import io.endee.client.types.*;
+import io.endee.client.util.CryptoUtils;
+import io.endee.client.util.JsonUtils;
+import io.endee.client.util.MessagePackUtils;
+import io.endee.client.util.ValidationUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Index client for Endee-DB vector operations.
+ *
+ *
+ * Example usage:
+ *
+ *
+ * {@code
+ * Index index = client.getIndex("my_index");
+ *
+ * // Upsert vectors
+ * List vectors = List.of(
+ * VectorItem.builder("vec1", new double[] { 0.1, 0.2, 0.3 })
+ * .meta(Map.of("label", "example"))
+ * .build());
+ * index.upsert(vectors);
+ *
+ * // Query
+ * List results = index.query(
+ * QueryOptions.builder()
+ * .vector(new double[] { 0.1, 0.2, 0.3 })
+ * .topK(10)
+ * .build());
+ * }
+ */
+public class Index {
+ private static final Logger logger = LoggerFactory.getLogger(Index.class);
+ private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
+ private static final int MAX_BATCH_SIZE = 1000;
+ private static final int MAX_TOP_K = 512;
+ private static final int MAX_EF = 1024;
+
+ private final String name;
+ private final String token;
+ private final String url;
+ private final HttpClient httpClient;
+ private final ObjectMapper objectMapper;
+
+ private long count;
+ private SpaceType spaceType;
+ private int dimension;
+ private Precision precision;
+ private int m;
+ private int sparseDimension;
+
+ /**
+ * Creates a new Index instance.
+ */
+ public Index(String name, String token, String url, int version, IndexInfo params) {
+ this.name = name;
+ this.token = token;
+ this.url = url;
+ this.objectMapper = JsonUtils.getObjectMapper();
+
+ this.count = params != null ? params.getTotalElements() : 0;
+ this.spaceType = params != null && params.getSpaceType() != null ? params.getSpaceType() : SpaceType.COSINE;
+ this.dimension = params != null ? params.getDimension() : 0;
+ this.precision = params != null && params.getPrecision() != null ? params.getPrecision() : Precision.INT8D;
+ this.m = params != null ? params.getM() : 16;
+ this.sparseDimension = params != null && params.getSparseDimension() != null ? params.getSparseDimension() : 0;
+
+ this.httpClient = HttpClient.newBuilder()
+ .version(HttpClient.Version.HTTP_2)
+ .connectTimeout(DEFAULT_TIMEOUT)
+ .build();
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Checks if this index supports hybrid (sparse + dense) vectors.
+ */
+ public boolean isHybrid() {
+ return sparseDimension > 0;
+ }
+
+ /**
+ * Normalizes a vector for cosine similarity.
+ * Returns [normalizedVector, norm].
+ */
+ private double[][] normalizeVector(double[] vector) {
+ if (vector.length != dimension) {
+ throw new IllegalArgumentException(
+ "Vector dimension mismatch: expected " + dimension + ", got " + vector.length);
+ }
+
+ if (spaceType != SpaceType.COSINE) {
+ return new double[][] { vector, { 1.0 } };
+ }
+
+ double sumSquares = 0;
+ for (double v : vector) {
+ sumSquares += v * v;
+ }
+ double norm = Math.sqrt(sumSquares);
+
+ if (norm == 0) {
+ return new double[][] { vector, { 1.0 } };
+ }
+
+ double[] normalized = new double[vector.length];
+ for (int i = 0; i < vector.length; i++) {
+ normalized[i] = vector[i] / norm;
+ }
+
+ return new double[][] { normalized, { norm } };
+ }
+
+ /**
+ * Upserts vectors into the index.
+ *
+ * @param inputArray list of vector items to upsert
+ * @return success message
+ */
+ public String upsert(List inputArray) {
+ if (inputArray.size() > MAX_BATCH_SIZE) {
+ throw new IllegalArgumentException("Cannot insert more than " + MAX_BATCH_SIZE + " vectors at a time");
+ }
+
+ List ids = inputArray.stream()
+ .map(item -> item.getId() != null ? item.getId() : "")
+ .collect(Collectors.toList());
+ ValidationUtils.validateVectorIds(ids);
+
+ List
*
* {@code
- * Endee client = new Endee("api-key:secret:region");
+ * Endee client = new Endee("auth-token");
*
* // Create an index
* CreateIndexOptions options = CreateIndexOptions.builder("my_index", 128)
@@ -65,8 +65,7 @@ public Endee() {
/**
* Creates a new Endee client.
*
- * @param token the API token (optional, format: "key:secret" or
- * "key:secret:region")
+ * @param token the Auth token (optional)
*/
public Endee(String token) {
this.token = token;
From 37459bc1a52b7337a4ae812c1f87126c27c92059 Mon Sep 17 00:00:00 2001
From: pankajLaunchX
Date: Thu, 5 Feb 2026 11:33:07 +0530
Subject: [PATCH 06/12] update README.md
---
README.md | 598 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 597 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f678f97..8ef72ea 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,597 @@
-# endee-java-client
+# Endee - Java Vector Database Client
+
+Endee is a Java client for a local vector database designed for maximum speed and efficiency. This package provides type-safe operations, modern Java features, and optimized code for rapid Approximate Nearest Neighbor (ANN) searches on vector data.
+
+## Key Features
+
+- **Type Safe**: Full compile-time type checking with builder patterns
+- **Fast ANN Searches**: Efficient similarity searches on vector data
+- **Multiple Distance Metrics**: Support for cosine, L2, and inner product distance metrics
+- **Hybrid Indexes**: Support for dense vectors, sparse vectors, and hybrid (dense + sparse) searches
+- **Metadata Support**: Attach and search with metadata and filters
+- **High Performance**: HTTP/2, MessagePack serialization, and DEFLATE compression
+- **Modern Java**: Requires Java 17+, uses modern APIs
+
+## Requirements
+
+- Java 17 or higher
+- Endee Local server running (see [Quick Start](https://docs.endee.io/quick-start))
+
+## Installation
+
+### Maven
+
+```xml
+
+ io.endee
+ endee-java-client
+ 1.0.0
+
+```
+
+### Gradle
+
+```groovy
+implementation 'io.endee:endee-java-client:1.0.0'
+```
+
+## Quick Start
+
+### Initialize the Client
+
+The Endee client connects to your local server (defaults to `http://127.0.0.1:8080/api/v1`):
+
+```java
+import io.endee.client.Endee;
+import io.endee.client.Index;
+import io.endee.client.types.*;
+
+// Connect to local Endee server (defaults to localhost:8080)
+Endee client = new Endee();
+```
+
+**Using Authentication?** If your server has `NDD_AUTH_TOKEN` set, pass the token when initializing:
+
+```java
+// With Auth Token
+Endee client = new Endee("auth-token");
+```
+
+### Setting a Custom Base URL
+
+If your server runs on a different port, use `setBaseUrl()`:
+
+```java
+Endee client = new Endee();
+
+// Set custom base URL for non-default port
+client.setBaseUrl("http://0.0.0.0:8081/api/v1");
+```
+
+### Create a Dense Index
+
+```java
+import io.endee.client.types.CreateIndexOptions;
+import io.endee.client.types.Precision;
+import io.endee.client.types.SpaceType;
+
+CreateIndexOptions options = CreateIndexOptions.builder("my_vectors", 384)
+ .spaceType(SpaceType.COSINE)
+ .precision(Precision.INT8D)
+ .build();
+
+client.createIndex(options);
+```
+
+**Dense Index Parameters:**
+
+| Parameter | Description | Default |
+| ----------- | ---------------------------------------------------------------------------- | -------- |
+| `name` | Unique name for your index (alphanumeric + underscore, max 48 chars) | Required |
+| `dimension` | Vector dimensionality (must match your embedding model's output, max 10,000) | Required |
+| `spaceType` | Distance metric - `COSINE`, `L2`, or `IP` (inner product) | `COSINE` |
+| `m` | Graph connectivity - higher values increase recall but use more memory | 16 |
+| `efCon` | Construction-time parameter - higher values improve index quality | 128 |
+| `precision` | Quantization precision | `INT8D` |
+
+### Create a Hybrid Index
+
+Hybrid indexes combine dense vector search with sparse vector search. Add the `sparseDimension` parameter:
+
+```java
+CreateIndexOptions options = CreateIndexOptions.builder("hybrid_index", 384)
+ .sparseDimension(30000) // Sparse vector dimension (vocabulary size)
+ .spaceType(SpaceType.COSINE)
+ .precision(Precision.INT8D)
+ .build();
+
+client.createIndex(options);
+```
+
+### List and Access Indexes
+
+```java
+// List all indexes (returns JSON string)
+String indexes = client.listIndexes();
+
+// Get reference to an existing index
+Index index = client.getIndex("my_vectors");
+
+// Delete an index
+client.deleteIndex("my_vectors");
+```
+
+## Upserting Vectors
+
+The `index.upsert()` method adds or updates vectors in an existing index.
+
+```java
+import io.endee.client.types.VectorItem;
+import java.util.List;
+import java.util.Map;
+
+Index index = client.getIndex("my_index");
+
+List vectors = List.of(
+ VectorItem.builder("vec1", new double[] {0.1, 0.2, 0.3 /* ... */})
+ .meta(Map.of("title", "First document", "group" , 10)) // meta : {"title" : "First Document", "label" : 10 }
+ .filter(Map.of("category", "tech", "group" , 10)) // filter : {"category" : "tech" , "group" : 10}
+ .build(),
+
+ VectorItem.builder("vec2", new double[] {0.3, 0.4, 0.5 /* ... */})
+ .meta(Map.of("title", "Second document"))
+ .filter(Map.of("category", "science"))
+ .build()
+);
+
+index.upsert(vectors);
+```
+
+**VectorItem Fields:**
+
+| Field | Required | Description |
+| -------- | -------- | --------------------------------------------------- |
+| `id` | Yes | Unique identifier for the vector (non-empty string) |
+| `vector` | Yes | Array of doubles representing the embedding |
+| `meta` | No | Arbitrary metadata map |
+| `filter` | No | Key-value pairs for filtering during queries |
+
+**Limits:**
+
+- Maximum 1,000 vectors per upsert call
+- Vector dimension must match index dimension
+- IDs must be unique within a single upsert batch
+
+## Querying the Index
+
+The `index.query()` method performs a similarity search.
+
+```java
+import io.endee.client.types.QueryOptions;
+import io.endee.client.types.QueryResult;
+
+List results = index.query(
+ QueryOptions.builder()
+ .vector(new double[] {0.15, 0.25 /* ... */})
+ .topK(5)
+ .ef(128)
+ .includeVectors(true)
+ .build()
+);
+
+for (QueryResult item : results) {
+ System.out.println("ID: " + item.getId());
+ System.out.println("Similarity: " + item.getSimilarity());
+ System.out.println("Distance: " + item.getDistance());
+ System.out.println("Meta: " + item.getMeta());
+}
+```
+
+**Query Parameters:**
+
+| Parameter | Description | Default | Max |
+| ---------------- | ------------------------------------------------------- | -------- | ---- |
+| `vector` | Query vector (must match index dimension) | Required | - |
+| `topK` | Number of results to return | 10 | 512 |
+| `ef` | Search quality parameter - higher values improve recall | 128 | 1024 |
+| `includeVectors` | Include vector data in results | false | - |
+
+## Filtered Querying
+
+Use the `filter` parameter to restrict results. All filters are combined with **logical AND**.
+
+```java
+List results = index.query(
+ QueryOptions.builder()
+ .vector(new double[] {0.15, 0.25 /* ... */})
+ .topK(5)
+ .filter(List.of(
+ Map.of("category", Map.of("$eq", "tech")),
+ Map.of("score", Map.of("$range", List.of(80, 100)))
+ ))
+ .build()
+);
+```
+
+### Filtering Operators
+
+| Operator | Description | Example |
+| -------- | ------------------------- | ---------------------------------------------------- |
+| `$eq` | Exact match | `Map.of("status", Map.of("$eq", "published"))` |
+| `$in` | Match any in list | `Map.of("tags", Map.of("$in", List.of("ai", "ml")))` |
+| `$range` | Numeric range (inclusive) | `Map.of("score", Map.of("$range", List.of(70, 95)))` |
+
+> **Note:** The `$range` operator supports values within **[0 - 999]**. Normalize larger values before upserting.
+
+## Hybrid Search
+
+### Upserting Hybrid Vectors
+
+Provide both dense vectors and sparse representations:
+
+```java
+Index index = client.getIndex("hybrid_index");
+
+List vectors = List.of(
+ VectorItem.builder("doc1", new double[] {0.1, 0.2 /* ... */})
+ .sparseIndices(new int[] {10, 50, 200}) // Non-zero term positions
+ .sparseValues(new double[] {0.8, 0.5, 0.3}) // Weights for each position
+ .meta(Map.of("title", "Document 1"))
+ .build(),
+
+ VectorItem.builder("doc2", new double[] {0.3, 0.4 /* ... */})
+ .sparseIndices(new int[] {15, 100, 500})
+ .sparseValues(new double[] {0.9, 0.4, 0.6})
+ .meta(Map.of("title", "Document 2"))
+ .build()
+);
+
+index.upsert(vectors);
+```
+
+**Hybrid Vector Fields:**
+
+| Field | Required | Description |
+| --------------- | ------------ | ---------------------------------------- |
+| `id` | Yes | Unique identifier |
+| `vector` | Yes | Dense embedding vector |
+| `sparseIndices` | Yes (hybrid) | Non-zero term positions in sparse vector |
+| `sparseValues` | Yes (hybrid) | Weights for each sparse index |
+| `meta` | No | Metadata map |
+| `filter` | No | Filter fields |
+
+> **Important:** `sparseIndices` and `sparseValues` must have the same length. Values in `sparseIndices` must be within `[0, sparseDimension)`.
+
+### Querying Hybrid Index
+
+Provide both dense and sparse query vectors:
+
+```java
+List results = index.query(
+ QueryOptions.builder()
+ .vector(new double[] {0.15, 0.25 /* ... */}) // Dense query
+ .sparseIndices(new int[] {10, 100, 300}) // Sparse query positions
+ .sparseValues(new double[] {0.7, 0.5, 0.4}) // Sparse query weights
+ .topK(5)
+ .build()
+);
+
+for (QueryResult item : results) {
+ System.out.println("ID: " + item.getId() + ", Similarity: " + item.getSimilarity());
+}
+```
+
+You can also query with:
+
+- **Dense only**: Provide only `vector`
+- **Sparse only**: Provide only `sparseIndices` and `sparseValues`
+- **Hybrid**: Provide all three for combined results
+
+## Deletion Methods
+
+### Delete by ID
+
+Delete a vector with a specific vector ID.
+
+```java
+index.deleteVector("vec1");
+```
+
+### Delete by Filter
+
+Delete all vectors matching specific filters.
+
+```java
+index.deleteWithFilter(List.of(
+ Map.of("category", Map.of("$eq", "tech"))
+));
+```
+
+### Delete Index
+
+Delete an entire index.
+
+```java
+client.deleteIndex("my_index");
+```
+
+> **Warning:** Deletion operations are **irreversible**.
+
+## Additional Operations
+
+### Get Vector by ID
+
+```java
+import io.endee.client.types.VectorInfo;
+
+VectorInfo vector = index.getVector("vec1");
+System.out.println("ID: " + vector.getId());
+System.out.println("Vector: " + Arrays.toString(vector.getVector()));
+System.out.println("Meta: " + vector.getMeta());
+System.out.println("Norm: " + vector.getNorm());
+```
+
+### Describe Index
+
+```java
+import io.endee.client.types.IndexDescription;
+
+IndexDescription info = index.describe();
+System.out.println(info);
+// IndexDescription{name='my_index', spaceType=COSINE, dimension=384,
+// sparseDimension=0, isHybrid=false, count=1000,
+// precision=INT8D, m=16}
+```
+
+### Check if Index is Hybrid
+
+```java
+boolean isHybrid = index.isHybrid();
+```
+
+## Precision Options
+
+Endee supports different quantization precision levels:
+
+```java
+import io.endee.client.types.Precision;
+
+Precision.BINARY // Binary quantization (1-bit) - smallest storage, fastest search
+Precision.INT8D // 8-bit integer quantization (default) - balanced performance
+Precision.INT16D // 16-bit integer quantization - higher precision
+Precision.FLOAT16 // 16-bit floating point - good balance
+Precision.FLOAT32 // 32-bit floating point - highest precision
+```
+
+**Choosing Precision:**
+
+| Precision | Use Case |
+| --------- | ------------------------------------------------------------------------- |
+| `BINARY` | Very large datasets where speed and storage are critical |
+| `INT8D` | Recommended for most use cases - good balance of accuracy and performance |
+| `INT16D` | Better accuracy than INT8D but less storage than FLOAT32 |
+| `FLOAT16` | Good compromise between precision and storage for embeddings |
+| `FLOAT32` | Maximum precision when storage is not a concern |
+
+## Space Types (Distance Metrics)
+
+```java
+import io.endee.client.types.SpaceType;
+
+SpaceType.COSINE // Cosine similarity (default) - best for normalized embeddings
+SpaceType.L2 // Euclidean distance - best for spatial data
+SpaceType.IP // Inner product - best for unnormalized embeddings
+```
+
+## Error Handling
+
+The client throws specific exceptions for different error scenarios:
+
+```java
+import io.endee.client.exception.EndeeException;
+import io.endee.client.exception.EndeeApiException;
+
+try {
+ client.createIndex(options);
+} catch (EndeeApiException e) {
+ // API-specific errors (e.g., 400, 401, 404, 409, 500)
+ System.err.println("Status Code: " + e.getStatusCode());
+ System.err.println("Error Body: " + e.getErrorBody());
+} catch (EndeeException e) {
+ // Client errors (network, serialization, etc.)
+ System.err.println("Client Error: " + e.getMessage());
+} catch (IllegalArgumentException e) {
+ // Validation errors
+ System.err.println("Validation Error: " + e.getMessage());
+}
+```
+
+**HTTP Status Codes:**
+
+| Code | Description |
+| ---- | ------------------------------------------------ |
+| 400 | Bad Request - Invalid parameters |
+| 401 | Unauthorized - Invalid or missing authentication |
+| 403 | Forbidden - Insufficient permissions |
+| 404 | Not Found - Index or vector doesn't exist |
+| 409 | Conflict - Index already exists |
+| 500 | Internal Server Error |
+
+## Complete Example
+
+```java
+import io.endee.client.Endee;
+import io.endee.client.Index;
+import io.endee.client.types.*;
+
+import java.util.List;
+import java.util.Map;
+
+public class EndeeExample {
+ public static void main(String[] args) {
+ // Initialize client
+ Endee client = new Endee();
+
+ // Create a dense index
+ CreateIndexOptions createOptions = CreateIndexOptions.builder("documents", 384)
+ .spaceType(SpaceType.COSINE)
+ .precision(Precision.INT8D)
+ .build();
+
+ client.createIndex(createOptions);
+
+ // Get the index
+ Index index = client.getIndex("documents");
+
+ // Add vectors
+ List vectors = List.of(
+ VectorItem.builder("doc1", new double[384]{/*...*/}) // 384 dimensions
+ .meta(Map.of("title", "First Document"))
+ .filter(Map.of("category", "tech"))
+ .build(),
+
+ VectorItem.builder("doc2", new double[384])
+ .meta(Map.of("title", "Second Document"))
+ .filter(Map.of("category", "science"))
+ .build()
+ );
+
+ index.upsert(vectors);
+
+ // Query the index
+ List results = index.query(
+ QueryOptions.builder()
+ .vector(new double[384]) // Query vector
+ .topK(5)
+ .build()
+ );
+
+ for (QueryResult item : results) {
+ System.out.println("ID: " + item.getId() + ", Similarity: " + item.getSimilarity());
+ }
+
+ // Describe the index
+ IndexDescription description = index.describe();
+ System.out.println(description);
+
+ // Clean up
+ client.deleteIndex("documents");
+ }
+}
+```
+
+## API Reference
+
+### Endee Class
+
+| Method | Parameters | Return Type | Description |
+| --------------------------------- | ---------- | ----------- | ----------------------------- |
+| `Endee()` | - | - | Create client without auth |
+| `Endee(String token)` | `token` | - | Create client with auth token |
+| `setBaseUrl(String url)` | `url` | `String` | Set custom base URL |
+| `createIndex(CreateIndexOptions)` | `options` | `String` | Create a new index |
+| `listIndexes()` | - | `String` | List all indexes (JSON) |
+| `deleteIndex(String name)` | `name` | `String` | Delete an index |
+| `getIndex(String name)` | `name` | `Index` | Get reference to an index |
+
+### Index Class
+
+| Method | Parameters | Return Type | Description |
+| ----------------------------- | ---------- | ------------------- | -------------------------- |
+| `upsert(List)` | `vectors` | `String` | Insert or update vectors |
+| `query(QueryOptions)` | `options` | `List` | Search for similar vectors |
+| `deleteVector(String id)` | `id` | `String` | Delete a vector by ID |
+| `deleteWithFilter(List