diff --git a/.github/scripts/build-c-api-bindings.sh b/.github/scripts/build-c-api-bindings.sh
new file mode 100755
index 00000000..e0a8a495
--- /dev/null
+++ b/.github/scripts/build-c-api-bindings.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Configure, build, install and package the C API bindings.
+#
+# Inputs (all optional, with defaults suitable for a local run):
+# ENABLE_LVQ_LEANVEC ON to statically link the LVQ/LeanVec backend
+# REQUIRE_LTO_ARCHIVE ON to fail (not warn) if the compiler can't consume the
+# LTO archive; set in CI, left off for local builds
+# SUFFIX artifact name suffix (e.g. -public-only)
+# WORKSPACE repository root; defaults to this script's repo so it
+# also runs outside the container
+
+set -e
+
+# In the manylinux/rockylinux containers the pinned gcc-toolset lives behind an
+# scl profile script; harmless no-op on a plain runner.
+source /etc/bashrc 2>/dev/null || true
+
+# Repo root, derived from this script's location so no git metadata is needed.
+WORKSPACE="${WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
+BUILD_DIR="${WORKSPACE}/build_c_api"
+INSTALL_DIR="${WORKSPACE}/install_c_api"
+ENABLE_LVQ_LEANVEC="${ENABLE_LVQ_LEANVEC:-OFF}"
+REQUIRE_LTO_ARCHIVE="${REQUIRE_LTO_ARCHIVE:-OFF}"
+
+echo "compiler: $(${CXX:-c++} --version | head -1)"
+
+rm -rf "${BUILD_DIR}" "${INSTALL_DIR}"
+
+cmake -B"${BUILD_DIR}" -S"${WORKSPACE}/bindings/c" \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
+ -DCMAKE_INSTALL_LIBDIR=lib \
+ -DSVS_BUILD_C_API_TESTS=ON \
+ -DSVS_RUNTIME_ENABLE_LVQ_LEANVEC="${ENABLE_LVQ_LEANVEC}" \
+ -DSVS_REQUIRE_LTO_ARCHIVE="${REQUIRE_LTO_ARCHIVE}"
+
+cmake --build "${BUILD_DIR}" -j"$(nproc)"
+
+# Install only the C API component: the dependency headers that a full install
+# would also emit are not part of the shipped interface.
+cmake --install "${BUILD_DIR}" --component C_API
+
+tar -czf "${WORKSPACE}/svs-c-api${SUFFIX}.tar.gz" -C "${INSTALL_DIR}" .
+echo "Packaged ${WORKSPACE}/svs-c-api${SUFFIX}.tar.gz"
diff --git a/.github/scripts/build-cpp-runtime-bindings.sh b/.github/scripts/build-cpp-runtime-bindings.sh
index c454eb72..bbe56509 100644
--- a/.github/scripts/build-cpp-runtime-bindings.sh
+++ b/.github/scripts/build-cpp-runtime-bindings.sh
@@ -50,6 +50,7 @@ CMAKE_ARGS=(
"-DCMAKE_INSTALL_PREFIX=/workspace/install_cpp_bindings"
"-DCMAKE_INSTALL_LIBDIR=lib"
"-DSVS_RUNTIME_ENABLE_LVQ_LEANVEC=${ENABLE_LVQ_LEANVEC:-ON}"
+ "-DSVS_REQUIRE_LTO_ARCHIVE=${REQUIRE_LTO_ARCHIVE:-OFF}"
"-DSVS_RUNTIME_ENABLE_IVF=ON"
"-DSVS_EXPERIMENTAL_CLANG_TIDY=ON"
)
diff --git a/.github/scripts/test-c-api-bindings.sh b/.github/scripts/test-c-api-bindings.sh
new file mode 100755
index 00000000..81476a1e
--- /dev/null
+++ b/.github/scripts/test-c-api-bindings.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Integration test for the packaged C API: verifies the tarball is a usable
+# package rather than just a successful compile. Runs against the artifact only,
+# with no access to the build tree.
+#
+# Inputs:
+# SUFFIX artifact name suffix (e.g. -public-only)
+# WORKSPACE repository root; defaults to this script's repo so it also runs
+# outside the container
+
+set -e
+
+# Match build-c-api-bindings.sh: pick up the container's pinned gcc-toolset.
+source /etc/bashrc 2>/dev/null || true
+
+# Repo root, derived from this script's location so no git metadata is needed.
+WORKSPACE="${WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
+STAGE_DIR="${WORKSPACE}/c_api_integration"
+
+# Prefer the artifact downloaded by the workflow, else a tarball built locally.
+TARBALL="${WORKSPACE}/c_api_artifact/svs-c-api${SUFFIX}.tar.gz"
+if [ ! -e "${TARBALL}" ]; then
+ TARBALL="${WORKSPACE}/svs-c-api${SUFFIX}.tar.gz"
+fi
+
+INSTALL_DIR="${STAGE_DIR}/install"
+CONSUMER_BUILD="${STAGE_DIR}/consumer-build"
+
+rm -rf "${STAGE_DIR}"
+mkdir -p "${INSTALL_DIR}"
+tar -xzf "${TARBALL}" -C "${INSTALL_DIR}"
+
+echo "::group::Package contents"
+find "${INSTALL_DIR}" -type f -o -type l | sort
+echo "::endgroup::"
+
+LIBDIR="${INSTALL_DIR}/lib"
+LIB="${LIBDIR}/libsvs_c_api.so"
+if [ ! -e "${LIB}" ]; then
+ echo "ERROR: ${LIB} missing from the package"
+ exit 1
+fi
+
+echo "::group::Strong exported symbols"
+nm -D --defined-only "${LIB}" | awk '$2=="T"{print $3}' | sort
+echo "::endgroup::"
+
+# Only the documented svs_* C ABI may be exported with strong linkage. This also
+# guards the statically linked LVQ/LeanVec backend against leaking symbols.
+#
+# std:: template instantiations (_ZNSt/_ZSt) are excluded: GCC emits some of these
+# with strong linkage from the LTO archive, and they are standard-library code
+# rather than SVS implementation detail. The check still catches any leak of an
+# actual svs/proprietary internal.
+LEAKED=$(nm -D --defined-only "${LIB}" | awk '$2=="T"{print $3}' \
+ | grep -v '^svs_' | grep -vE '^_Z+(N?)St' || true)
+if [ -n "${LEAKED}" ]; then
+ echo "ERROR: non-svs_ symbols exported from the C API:"
+ echo "${LEAKED}"
+ exit 1
+fi
+
+# Build a standalone C project against the installed CMake package, the way a
+# downstream integration would. Catches exported-target defects (a missing
+# find_dependency, or a C++ requirement leaking onto a C consumer) that a
+# build-tree-only test cannot see.
+cmake -B"${CONSUMER_BUILD}" -S"${WORKSPACE}/bindings/c/tests/consumer" \
+ -DCMAKE_PREFIX_PATH="${INSTALL_DIR}"
+cmake --build "${CONSUMER_BUILD}"
+
+LD_LIBRARY_PATH="${LIBDIR}" "${CONSUMER_BUILD}/c_api_consumer"
diff --git a/.github/scripts/test-c-api-unit.sh b/.github/scripts/test-c-api-unit.sh
new file mode 100755
index 00000000..7ea6e322
--- /dev/null
+++ b/.github/scripts/test-c-api-unit.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Run the C API unit tests and samples out of an existing build tree.
+#
+# Inputs:
+# WORKSPACE repository root; defaults to this script's repo so it also runs
+# outside the container
+
+set -e
+
+# Match build-c-api-bindings.sh: pick up the container's pinned gcc-toolset.
+source /etc/bashrc 2>/dev/null || true
+
+# Repo root, derived from this script's location so no git metadata is needed.
+WORKSPACE="${WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
+BUILD_DIR="${WORKSPACE}/build_c_api"
+
+# LVQ/LeanVec need a specific ISA. The tests already accept
+# SVS_ERROR_UNSUPPORTED_HW (but never SVS_ERROR_NOT_IMPLEMENTED), so this is
+# reported for triage rather than used to skip anything.
+echo "vendor: $(grep -m1 vendor_id /proc/cpuinfo || echo unknown)"
+echo "model: $(grep -m1 'model name' /proc/cpuinfo || echo unknown)"
+echo "avx512: $(grep -o 'avx512[a-z_0-9]*' /proc/cpuinfo | sort -u | tr '\n' ' ')"
+
+ctest --test-dir "${BUILD_DIR}" --output-on-failure --no-tests=error
+
+# The samples are the only executable check that the public headers are usable
+# from C and that an end-to-end build/search runs. They regressed to a non-zero
+# exit once already, so they are part of the gate.
+for sample in c_api_simple c_api_save_load c_api_dynamic; do
+ echo "::group::${sample}"
+ "${BUILD_DIR}/samples/${sample}"
+ echo "::endgroup::"
+done
diff --git a/.github/workflows/build-c-api-bindings.yml b/.github/workflows/build-c-api-bindings.yml
new file mode 100644
index 00000000..a70c68ef
--- /dev/null
+++ b/.github/workflows/build-c-api-bindings.yml
@@ -0,0 +1,123 @@
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Build and test C API bindings
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+# This allows a subsequently queued workflow run to interrupt previous runs
+concurrency:
+ group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
+ cancel-in-progress: true
+
+jobs:
+ build-c-api-bindings:
+ name: Build and unit tests for C API (${{ matrix.name }})
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ # Mirrors build-cpp-runtime-bindings.yml.
+ include:
+ - name: "with static library"
+ enable_lvq_leanvec: "ON"
+ require_lto: "ON"
+ suffix: ""
+ - name: "public only"
+ enable_lvq_leanvec: "OFF"
+ require_lto: "OFF"
+ suffix: "-public-only"
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Build Docker image
+ run: docker build -t svs-manylinux228:latest -f docker/x86_64/manylinux228/Dockerfile .
+
+ - name: Build C API bindings in Docker container
+ run: |
+ docker run --rm \
+ -v ${{ github.workspace }}:/workspace \
+ -w /workspace \
+ -e ENABLE_LVQ_LEANVEC=${{ matrix.enable_lvq_leanvec }} \
+ -e REQUIRE_LTO_ARCHIVE=${{ matrix.require_lto }} \
+ -e SUFFIX=${{ matrix.suffix }} \
+ svs-manylinux228:latest \
+ /bin/bash .github/scripts/build-c-api-bindings.sh
+
+ - name: Upload C API bindings artifacts
+ uses: actions/upload-artifact@v7
+ with:
+ name: svs-c-api${{ matrix.suffix }}
+ path: svs-c-api${{ matrix.suffix }}.tar.gz
+ retention-days: 7
+
+ # Run unit tests that were built as part of this job
+ - name: Run unit tests in Docker container
+ run: |
+ docker run --rm \
+ -v ${{ github.workspace }}:/workspace \
+ -w /workspace \
+ svs-manylinux228:latest \
+ /bin/bash /workspace/.github/scripts/test-c-api-unit.sh
+
+ # Run integration tests against the packaged artifact. Eventually this should
+ # run the setup and test scope of the actual downstream integrations; for now it
+ # just confirms the tarball is functional - it installs, exports only the svs_*
+ # C ABI, and can be consumed from a standalone C project.
+ test:
+ name: Integration tests for C API (${{ matrix.name }})
+ needs: build-c-api-bindings
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ include:
+ - name: "with static library"
+ suffix: ""
+ - name: "public only"
+ suffix: "-public-only"
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Build Docker image
+ run: docker build -t svs-manylinux228:latest -f docker/x86_64/manylinux228/Dockerfile .
+
+ # Need to download for a new job
+ - name: Download C API package
+ uses: actions/download-artifact@v8
+ with:
+ name: svs-c-api${{ matrix.suffix }}
+ path: c_api_artifact
+
+ - name: List available artifacts
+ run: ls -la c_api_artifact/
+
+ - name: Test packaged C API in Docker container
+ run: |
+ docker run --rm \
+ -v ${{ github.workspace }}:/workspace \
+ -w /workspace \
+ -e SUFFIX=${{ matrix.suffix }} \
+ svs-manylinux228:latest \
+ /bin/bash .github/scripts/test-c-api-bindings.sh
diff --git a/.github/workflows/build-cpp-runtime-bindings.yml b/.github/workflows/build-cpp-runtime-bindings.yml
index e438752d..f13a540e 100644
--- a/.github/workflows/build-cpp-runtime-bindings.yml
+++ b/.github/workflows/build-cpp-runtime-bindings.yml
@@ -38,9 +38,11 @@ jobs:
include:
- name: "with static library"
enable_lvq_leanvec: "ON"
+ require_lto: "ON"
suffix: ""
- name: "public only"
enable_lvq_leanvec: "OFF"
+ require_lto: "OFF"
suffix: "-public-only"
fail-fast: false
@@ -57,6 +59,7 @@ jobs:
-v ${{ github.workspace }}:/workspace \
-w /workspace \
-e ENABLE_LVQ_LEANVEC=${{ matrix.enable_lvq_leanvec }} \
+ -e REQUIRE_LTO_ARCHIVE=${{ matrix.require_lto }} \
-e SUFFIX=${{ matrix.suffix }} \
svs-manylinux228:latest \
/bin/bash .github/scripts/build-cpp-runtime-bindings.sh
diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt
new file mode 100644
index 00000000..be466bd7
--- /dev/null
+++ b/bindings/c/CMakeLists.txt
@@ -0,0 +1,256 @@
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.21)
+project(svs_c_api VERSION 0.4.0 LANGUAGES CXX C)
+set(TARGET_NAME svs_c_api)
+
+set(SVS_C_API_HEADERS
+ include/svs/c_api/svs_c_config.h
+ include/svs/c_api/svs_c.h
+)
+
+set(SVS_C_API_SOURCES
+ src/algorithm.hpp
+ src/error.hpp
+ src/filtered_search.hpp
+ src/index.hpp
+ src/index_builder.hpp
+ src/storage.hpp
+ src/threadpool.hpp
+ src/types_support.hpp
+
+ src/error.cpp
+ src/svs_c.cpp
+ src/dispatcher_vamana.cpp
+ src/dispatcher_dynamic_vamana.cpp
+)
+
+add_library(${TARGET_NAME} SHARED
+ ${SVS_C_API_HEADERS}
+ ${SVS_C_API_SOURCES}
+)
+
+target_include_directories(${TARGET_NAME} PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+)
+
+find_package(OpenMP REQUIRED)
+# PRIVATE: OpenMP is an implementation detail linked into the shared library.
+# Exporting it would force consumers of the C ABI to resolve a C++ OpenMP
+# target they never asked for.
+target_link_libraries(${TARGET_NAME} PRIVATE OpenMP::OpenMP_CXX)
+
+target_compile_options(${TARGET_NAME} PRIVATE
+ -DSVS_ENABLE_OMP=1
+ -fvisibility=hidden
+)
+
+if(UNIX AND NOT APPLE)
+ # Don't export 3rd-party symbols from the lib
+ target_link_options(${TARGET_NAME} PRIVATE "SHELL:-Wl,--exclude-libs,ALL")
+endif()
+
+# C++20 is required to build this library, but not to consume it: the public
+# surface is a C ABI. Keep the requirement PRIVATE so that pure-C consumers are
+# not forced to compile as C++20.
+target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20)
+if (NOT DEFINED SVS_CXX_STANDARD OR SVS_CXX_STANDARD STREQUAL "")
+ set(SVS_CXX_STANDARD 20)
+endif()
+set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${SVS_C_API_HEADERS}")
+set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD ${SVS_CXX_STANDARD})
+set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
+set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF)
+set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} )
+
+target_link_libraries(${TARGET_NAME} PRIVATE
+ svs::svs
+)
+
+# The non-LTO fallback below is correct but slower, so nothing fails and CI stays
+# green. Set this where the LTO archive is the point (CI) to make the drop an error.
+option(SVS_REQUIRE_LTO_ARCHIVE
+ "Fail instead of warn when the compiler cannot consume the LVQ/LeanVec LTO archive" OFF)
+
+if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
+ message(STATUS "Enabling LVQ/LeanVec support in C API")
+ target_compile_definitions(${TARGET_NAME} PRIVATE SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
+ if(SVS_LVQ_HEADER)
+ target_compile_definitions(${TARGET_NAME} PRIVATE
+ SVS_LVQ_HEADER="${SVS_LVQ_HEADER}"
+ )
+ # "SVS_LVQ_HEADER=\"${SVS_LVQ_HEADER}\""
+ endif()
+ if(SVS_LEANVEC_HEADER)
+ target_compile_definitions(${TARGET_NAME} PRIVATE
+ SVS_LEANVEC_HEADER="${SVS_LEANVEC_HEADER}"
+ )
+ # "SVS_LEANVEC_HEADER=\"${SVS_LEANVEC_HEADER}\""
+ endif()
+
+ if (RUNTIME_BINDINGS_PRIVATE_SOURCE_BUILD)
+ message(STATUS "Building directly from private sources with IVF support")
+ target_link_libraries(${TARGET_NAME} PRIVATE
+ svs::svs
+ svs_compile_options
+ )
+ if(SVS_EXPERIMENTAL_LINK_STATIC_MKL)
+ link_mkl_static(${TARGET_NAME})
+ endif()
+ elseif(TARGET svs::svs)
+ message(FATAL_ERROR
+ "Pre-built LVQ/LeanVec SVS library cannot be used in SVS main build. "
+ "Please build SVS Runtime using bindings/cpp directory as CMake source root."
+ )
+ else()
+ # Links to LTO-enabled static library, requires GCC/G++ 11.2
+ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3")
+ set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-07-21-127.tar.gz"
+ CACHE STRING "URL to download SVS shared library")
+ else()
+ # The fallback is correct but slower, so nothing downstream fails and CI
+ # stays green. Set SVS_REQUIRE_LTO_ARCHIVE=ON where the LTO archive is
+ # the point (the containerised CI job) to make the drop an error.
+ if(SVS_REQUIRE_LTO_ARCHIVE)
+ set(SVS_LTO_MESSAGE_LEVEL FATAL_ERROR)
+ else()
+ set(SVS_LTO_MESSAGE_LEVEL WARNING)
+ endif()
+ message(${SVS_LTO_MESSAGE_LEVEL}
+ "Pre-built LVQ/LeanVec SVS library requires GCC/G++ v.11.2 to apply LTO optimizations."
+ "Current compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}"
+ )
+ set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/v0.4.0/svs-shared-library.tar.gz"
+ CACHE STRING "URL to download SVS shared library")
+ endif()
+ include(FetchContent)
+ # DOWNLOAD_EXTRACT_TIMESTAMP needs CMake 3.24+; 3.22 is still around locally.
+ set(SVS_FETCH_EXTRA_ARGS)
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
+ list(APPEND SVS_FETCH_EXTRA_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
+ endif()
+ FetchContent_Declare(
+ svs
+ URL ${SVS_URL}
+ ${SVS_FETCH_EXTRA_ARGS}
+ )
+ FetchContent_MakeAvailable(svs)
+ list(APPEND CMAKE_PREFIX_PATH "${svs_SOURCE_DIR}")
+ find_package(svs REQUIRED)
+ target_link_libraries(${TARGET_NAME} PRIVATE
+ svs::svs
+ svs::svs_compile_options
+ svs::svs_static_library
+ )
+ endif()
+else()
+ message(STATUS "LVQ/LeanVec support is disabled in C API")
+ # Include the SVS library directly if needed.
+ if (NOT TARGET svs::svs)
+ add_subdirectory("../.." "${CMAKE_CURRENT_BINARY_DIR}/svs")
+ endif()
+ target_link_libraries(${TARGET_NAME} PRIVATE
+ svs::svs
+ svs_compile_options
+ svs_x86_options_base
+ )
+endif()
+
+# Installing
+include(GNUInstallDirs)
+
+set(SVS_C_API_EXPORT_NAME ${TARGET_NAME})
+set(VERSION_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/${SVS_C_API_EXPORT_NAME}ConfigVersion.cmake")
+set(SVS_C_API_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/svs_c_api)
+set(SVS_C_API_COMPONENT_NAME "C_API")
+
+install(TARGETS ${TARGET_NAME}
+ EXPORT ${SVS_C_API_EXPORT_NAME}
+ COMPONENT ${SVS_C_API_COMPONENT_NAME}
+ LIBRARY DESTINATION lib
+ PUBLIC_HEADER DESTINATION include/svs/c_api
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
+)
+
+install(DIRECTORY include/svs/c_api
+ COMPONENT ${SVS_C_API_COMPONENT_NAME}
+ DESTINATION include/svs
+ FILES_MATCHING PATTERN "*.h"
+)
+
+install(EXPORT ${SVS_C_API_EXPORT_NAME}
+ COMPONENT ${SVS_C_API_COMPONENT_NAME}
+ NAMESPACE svs::
+ DESTINATION ${SVS_C_API_CONFIG_INSTALL_DIR}
+)
+
+include(CMakePackageConfigHelpers)
+configure_package_config_file(
+ "${CMAKE_CURRENT_LIST_DIR}/c_apiConfig.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/${SVS_C_API_EXPORT_NAME}Config.cmake"
+ INSTALL_DESTINATION "${SVS_C_API_CONFIG_INSTALL_DIR}"
+)
+
+# Don't make compatibility guarantees until we reach a compatibility milestone.
+write_basic_package_version_file(
+ ${VERSION_CONFIG}
+ VERSION ${PROJECT_VERSION}
+ COMPATIBILITY ExactVersion
+)
+
+install(FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/${SVS_C_API_EXPORT_NAME}Config.cmake"
+ "${VERSION_CONFIG}"
+ COMPONENT ${SVS_C_API_COMPONENT_NAME}
+ DESTINATION "${SVS_C_API_CONFIG_INSTALL_DIR}"
+)
+
+# Build tests if requested
+if(DEFINED SVS_BUILD_TESTS)
+ option(SVS_BUILD_C_API_TESTS "Build C API tests" ${SVS_BUILD_TESTS})
+else()
+ option(SVS_BUILD_C_API_TESTS "Build C API tests" OFF)
+endif()
+
+
+if(SVS_BUILD_C_API_TESTS)
+ # Add test to CTest
+ include(CTest)
+ enable_testing()
+ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ target_compile_options(${TARGET_NAME} PRIVATE --coverage)
+ target_link_options(${TARGET_NAME} PRIVATE --coverage)
+ # add coverage target
+ add_custom_target(clean_coverage
+ COMMAND ${CMAKE_COMMAND} -E echo "Cleaning coverage data..."
+ COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} find . -name "*.gcda" -delete
+ COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.info
+ COMMENT "Cleaning coverage data..."
+ )
+ add_custom_target(coverage
+ COMMAND ${CMAKE_COMMAND} -E echo "Generating coverage report..."
+ COMMAND ${CMAKE_COMMAND} -E env GCOV_PREFIX=${CMAKE_BINARY_DIR}/coverage GCOV_PREFIX_STRIP=1 ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage
+ COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --capture --directory . --output-file coverage.info
+ COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --remove coverage.info '/usr/*' --output-file coverage.info
+ COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --remove coverage.info '*/_deps/*' --output-file coverage.info
+ COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --list coverage.info
+ COMMENT "Generating code coverage report..."
+ )
+ endif()
+ add_subdirectory(tests)
+endif()
+
+add_subdirectory(samples)
diff --git a/bindings/c/SVS_C_API_Design.md b/bindings/c/SVS_C_API_Design.md
new file mode 100644
index 00000000..5c5bcbfe
--- /dev/null
+++ b/bindings/c/SVS_C_API_Design.md
@@ -0,0 +1,673 @@
+
+
+# SVS C API Design Proposal
+
+## Overview
+
+This document describes the design proposal for the Scalable Vector Search (SVS) C API. The API provides a C interface to SVS's vector similarity search capabilities, enabling integration with C applications and other languages that support C FFI (Foreign Function Interface).
+
+### Design Goals
+
+The SVS C API is designed with the following principles:
+
+1. **Simplicity** - Provide a minimal, intuitive set of operations to create and use vector search indices
+2. **Flexibility** - Allow fine-grained control over:
+ - Index building parameters (graph degree, window sizes, etc.)
+ - Memory allocation strategies (simple, hugepage, custom)
+ - Thread pool configuration (native, OpenMP, custom)
+ - Vector storage formats (simple, compressed, quantized)
+ - Search parameters and filters
+ - Logging system
+3. **Safety** - Comprehensive error handling with detailed error messages
+4. **Portability** - Standard C interface that works across platforms and languages
+
+## Architecture Overview
+
+The API is built around a builder pattern with the following core abstractions:
+
+```
+┌─────────────────┐
+│ Index Builder │ Configure index parameters
+│ - Algorithm │
+│ - Storage │
+│ - Threadpool │
+└────────┬────────┘
+ │ build()
+ ↓
+┌─────────────────┐
+│ Index │ Perform searches
+│ - search() │ with optional search params
+└─────────────────┘
+ │
+ ├─ Search Params (optional)
+ └─ Search Results
+```
+
+## Core Components
+
+### 1. Index
+
+The main search structure providing vector similarity search operations.
+
+**Current Capabilities:**
+- **TopK Search** - Find the k nearest neighbors for query vectors
+- Configurable search parameters (window size, etc.)
+- Multiple distance metrics (Euclidean, Cosine, Inner Product)
+
+**Requirements:**
+- Built from a non-empty dataset using Index Builder
+- Immutable after creation
+
+**Future Extensions:**
+- Range search (all neighbors within distance threshold)
+- Filtered search (predicate-based filtering)
+- Dynamic updates (add/remove vectors)
+
+### 2. Index Builder
+
+Configures and creates index instances using the builder pattern.
+
+**Required Parameters:**
+- Algorithm configuration handle
+- Vector dimensions
+- Distance metric (Euclidean, Cosine, Inner Product)
+
+**Optional Configuration:**
+- Storage format (default: Simple FP32)
+- Thread pool kind and size (default: native with hardware concurrency)
+- Custom thread pool interface (for advanced use cases)
+
+### 3. Algorithm Configuration
+
+Defines the search algorithm and its parameters.
+
+**Current Support:**
+- **Vamana** - Graph-based approximate nearest neighbor search
+ - Graph degree (connectivity)
+ - Build window size (construction search budget)
+ - Default search window size
+ - Alpha parameter (pruning threshold)
+ - Search history mode
+
+**Future Support:**
+- **Flat** - Exhaustive brute-force search
+- **IVF** - Inverted file with clustering
+
+### 4. Storage Configuration
+
+Defines how vectors are stored in memory, supporting various compression schemes.
+
+| Storage Type | Configuration Options | Description |
+|--------------|----------------------|-------------|
+| **Simple** | FP32, FP16, INT8, UINT8, INT4, UINT4 | Uncompressed storage |
+| **SQ** | INT8, UINT8 | Scalar quantization |
+| **LVQ** | Primary: INT4/UINT4/INT8/UINT8 Residual: VOID/INT4/UINT4/INT8/UINT8 | Locally-adaptive vector quantization |
+| **LeanVec** | Dimensions Primary: data type Secondary: data type | LeanVec dimensionality reduced storage |
+
+**Example:**
+```c
+// Simple FP32 storage (default)
+svs_storage_h storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, err);
+
+// LVQ with 8-bit primary and 4-bit residual
+svs_storage_h storage = svs_storage_create_lvq(
+ SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT4, err
+);
+
+// LeanVec with 128 dimensions
+svs_storage_h storage = svs_storage_create_leanvec(
+ 128, SVS_DATA_TYPE_FLOAT16, SVS_DATA_TYPE_INT8, err
+);
+
+// Scalar quantization
+svs_storage_h storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, err);
+```
+
+### 5. Thread Pool Configuration
+
+Controls parallelization strategy for index operations.
+
+| Type | Configuration | Use Case |
+|------|---------------|----------|
+| **Native** | Thread count | Default SVS thread pool (recommended) |
+| **OpenMP** | Uses OMP_NUM_THREADS | Integration with OpenMP applications |
+| **Single Thread** | No parallelization | Debugging or minimal overhead |
+| **Custom** | User-defined interface | Custom scheduling/work-stealing |
+
+**Custom Interface:**
+```c
+struct svs_threadpool_interface_ops {
+ size_t (*size)(void* self);
+ void (*parallel_for)(
+ void* self,
+ void (*func)(void* svs_param, size_t i),
+ void* svs_param, // SVS state
+ size_t n // Number of tasks
+ );
+};
+
+struct svs_threadpool_interface {
+ struct svs_threadpool_interface_ops ops;
+ void* self; // User-defined state
+};
+```
+
+### 6. Search Parameters
+
+Configures runtime search behavior (algorithm-specific).
+
+**Vamana Search Parameters:**
+- **Search window size** - Controls search accuracy vs. speed tradeoff
+ - Larger values: more accurate but slower
+ - Smaller values: faster but less accurate
+ - Typically 50-200 for good recall
+
+**Usage:**
+```c
+// Use custom search parameters
+svs_search_params_h params = svs_search_params_create_vamana(100, err);
+svs_search_results_t results = svs_index_search(
+ index, queries, num_queries, k, params, err
+);
+svs_search_params_free(params);
+
+// Or use defaults from algorithm configuration
+svs_search_results_t results = svs_index_search(
+ index, queries, num_queries, k, NULL, err
+);
+```
+
+## Error Handling Strategy
+
+The API uses a dual approach for error reporting: return codes and optional detailed error information.
+
+### Return Values
+
+- Functions returning handles return `NULL` on failure
+- Functions returning booleans return `false` on failure
+- All functions accept an optional `svs_error_h` parameter for detailed diagnostics
+
+### Detailed Error Information
+
+For comprehensive error diagnostics, create an error handle and pass it to API calls:
+
+```c
+// Create error handle
+svs_error_h err = svs_error_create();
+
+// Use in API calls (last parameter, can be NULL)
+svs_algorithm_h algo = svs_algorithm_create_vamana(
+ 64, // graph_degree
+ 128, // build_window_size
+ 128, // search_window_size
+ err // optional error handle (can be NULL)
+);
+
+if (algo == NULL) {
+ // Check error status
+ if (!svs_error_ok(err)) {
+ // Query error details
+ svs_error_code_t code = svs_error_get_code(err);
+ const char* msg = svs_error_get_message(err);
+ fprintf(stderr, "Error [%d]: %s\n", code, msg);
+ }
+}
+
+// Error handle can be reused across multiple calls
+svs_storage_h storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, err);
+
+// Free error handle when done
+svs_error_free(err);
+```
+
+### Error Codes
+
+```c
+enum svs_error_code {
+ SVS_OK = 0, // Success
+ SVS_ERROR_GENERIC = 1, // Generic/unspecified error
+ SVS_ERROR_INVALID_ARGUMENT = 2, // Invalid function parameter
+ SVS_ERROR_OUT_OF_MEMORY = 3, // Memory allocation failed
+ SVS_ERROR_NOT_IMPLEMENTED = 5, // Feature not yet available
+ SVS_ERROR_UNSUPPORTED_HW = 6, // Hardware doesn't support required features
+ SVS_ERROR_RUNTIME = 7, // Runtime error during operation
+ SVS_ERROR_UNKNOWN = 1000 // Unknown/unexpected error
+};
+```
+
+### Best Practices
+
+1. **Always check return values** - Test for `NULL` or `false` before using results
+2. **Use error handles during development** - Provides detailed diagnostics and error messages
+3. **Reuse error handles** - Single handle can be reused across multiple API calls
+4. **Free all resources** - Always call appropriate `_free()` functions to prevent leaks
+5. **Pass NULL for optional parameters** - Error handle and search params can be `NULL` if not needed
+6. **Check `svs_error_ok()`** - Use this helper to check if operation succeeded
+
+## Naming Conventions
+
+Consistent naming improves API discoverability and reduces cognitive load.
+
+### Prefixes
+
+- `svs_` - All functions and types
+- `SVS_` - Macros and constants
+
+### Type Suffixes
+
+| Suffix | Meaning | Example |
+|--------|---------|----------|
+| `_t` | Value type (enum, struct) | `svs_metric_t`, `svs_error_code_t` |
+| `_h` | Handle (opaque pointer) | `svs_index_h`, `svs_algorithm_h` |
+| `_i` | Interface structure | `svs_allocator_i`, `svs_threadpool_i` |
+
+### Function Naming Pattern
+
+```
+svs_[_]_
+```
+
+**Examples:**
+
+| Function | Breakdown | Description |
+|----------|-----------|-------------|
+| `svs_index_search()` | `svs` + `index` + `search` | Generic index search |
+| `svs_algo_vamana_set_alpha()` | `svs` + `algo` + `vamana` + `set_alpha` | Set Vamana-specific parameter |
+| `svs_storage_create_lvq()` | `svs` + `storage` + `create` + `lvq` | Create LVQ storage configuration |
+| `svs_factory_set_threadpool()` | `svs` + `factory` + `set` + `threadpool` | Configure builder thread pool |
+
+### Examples by Category
+
+```c
+// Handles (opaque pointers)
+typedef struct svs_index* svs_index_h;
+typedef struct svs_algorithm* svs_algorithm_h;
+typedef struct svs_storage* svs_storage_h;
+
+// Value types
+typedef enum svs_metric svs_metric_t;
+typedef enum svs_error_code svs_error_code_t;
+
+// Interface structures
+typedef struct svs_allocator_interface svs_allocator_i;
+typedef struct svs_threadpool_interface svs_threadpool_i;
+```
+
+## API Reference
+
+### Type Definitions
+
+```c
+// Opaque handles (suffix: _h)
+typedef struct svs_error_desc* svs_error_h;
+typedef struct svs_index* svs_index_h;
+typedef struct svs_index_builder* svs_index_builder_h;
+typedef struct svs_algorithm* svs_algorithm_h;
+typedef struct svs_storage* svs_storage_h;
+typedef struct svs_search_params* svs_search_params_h;
+
+// Fully defined types (suffix: _t)
+typedef enum svs_error_code svs_error_code_t;
+typedef enum svs_distance_metric svs_distance_metric_t;
+typedef enum svs_algorithm_type svs_algorithm_type_t;
+typedef enum svs_data_type svs_data_type_t;
+typedef enum svs_storage_kind svs_storage_kind_t;
+typedef enum svs_threadpool_kind svs_threadpool_kind_t;
+
+// Interface pointers
+typedef struct svs_threadpool_interface* svs_threadpool_i;
+typedef struct svs_search_results* svs_search_results_t;
+```
+
+### Error Handling API
+
+```c
+// Create and manage error handles
+svs_error_h svs_error_create(void);
+void svs_error_free(svs_error_h err);
+
+// Query error information
+bool svs_error_ok(svs_error_h err);
+svs_error_code_t svs_error_get_code(svs_error_h err);
+const char* svs_error_get_message(svs_error_h err);
+```
+
+### Algorithm API
+
+Create and configure search algorithms.
+
+```c
+// Vamana graph-based approximate nearest neighbor search
+svs_algorithm_h svs_algorithm_create_vamana(
+ size_t graph_degree, // Graph connectivity (e.g., 64)
+ size_t build_window_size, // Construction search window (e.g., 128)
+ size_t search_window_size, // Default query search window (e.g., 128)
+ svs_error_h out_err // optional, can be NULL
+);
+
+// Cleanup
+void svs_algorithm_free(svs_algorithm_h algorithm);
+
+// Get/Set Vamana parameters
+bool svs_algorithm_vamana_get_alpha(
+ svs_algorithm_h algorithm,
+ float* out_alpha,
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_set_alpha(
+ svs_algorithm_h algorithm,
+ float alpha, // Pruning parameter (typically 1.0 - 1.4)
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_get_graph_degree(
+ svs_algorithm_h algorithm,
+ size_t* out_graph_degree,
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_set_graph_degree(
+ svs_algorithm_h algorithm,
+ size_t graph_degree,
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_get_build_window_size(
+ svs_algorithm_h algorithm,
+ size_t* out_build_window_size,
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_set_build_window_size(
+ svs_algorithm_h algorithm,
+ size_t build_window_size,
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_get_use_search_history(
+ svs_algorithm_h algorithm,
+ bool* out_use_full_search_history,
+ svs_error_h out_err
+);
+
+bool svs_algorithm_vamana_set_use_search_history(
+ svs_algorithm_h algorithm,
+ bool use_full_search_history,
+ svs_error_h out_err
+);
+```
+
+### Storage API
+
+Configure vector storage format and compression.
+
+```c
+// Simple uncompressed storage
+svs_storage_h svs_storage_create_simple(
+ svs_data_type_t data_type, // SVS_DATA_TYPE_FLOAT32, FLOAT16, INT8, etc.
+ svs_error_h out_err // optional, can be NULL
+);
+
+// Scalar quantization
+svs_storage_h svs_storage_create_sq(
+ svs_data_type_t data_type, // SVS_DATA_TYPE_INT8, SVS_DATA_TYPE_UINT8
+ svs_error_h out_err
+);
+
+// Locally-adaptive Vector Quantization (LVQ)
+svs_storage_h svs_storage_create_lvq(
+ svs_data_type_t primary, // Primary quantization type
+ svs_data_type_t residual, // Residual type (or SVS_DATA_TYPE_VOID)
+ svs_error_h out_err
+);
+
+// LeanVec two-level hierarchical storage
+svs_storage_h svs_storage_create_leanvec(
+ size_t leanvec_dims, // Primary dimensions (usually much smaller)
+ svs_data_type_t primary, // Primary storage type
+ svs_data_type_t secondary, // Secondary/residual storage type
+ svs_error_h out_err
+);
+
+// Cleanup
+void svs_storage_free(svs_storage_h storage);
+```
+
+
+### Search Parameters API
+
+Configure runtime search behavior.
+
+```c
+// Create Vamana search parameters
+svs_search_params_h svs_search_params_create_vamana(
+ size_t search_window_size, // Search window size (e.g., 100)
+ svs_error_h out_err // optional, can be NULL
+);
+
+// Cleanup
+void svs_search_params_free(svs_search_params_h params);
+```
+
+### Index Builder API
+
+Configure and build index instances.
+
+```c
+// Create index builder with required parameters
+svs_index_builder_h svs_index_builder_create(
+ svs_distance_metric_t metric, // Distance metric
+ size_t dimension, // Vector dimensionality
+ svs_algorithm_h algorithm, // Algorithm configuration
+ svs_error_h out_err // optional, can be NULL
+);
+
+// Configure storage (optional, default: Simple FP32)
+bool svs_index_builder_set_storage(
+ svs_index_builder_h builder,
+ svs_storage_h storage, // Storage configuration
+ svs_error_h out_err
+);
+
+// Configure thread pool (optional, default: native)
+bool svs_index_builder_set_threadpool(
+ svs_index_builder_h builder,
+ svs_threadpool_kind_t kind, // Thread pool type
+ size_t num_threads, // Number of threads (for native)
+ svs_error_h out_err
+);
+
+// Configure custom thread pool (advanced)
+bool svs_index_builder_set_threadpool_custom(
+ svs_index_builder_h builder,
+ svs_threadpool_interface_t pool, // Custom thread pool interface
+ svs_error_h out_err
+);
+
+// Cleanup
+void svs_index_builder_free(svs_index_builder_h builder);
+```
+
+
+### Index API
+
+Build and query vector search indices.
+
+```c
+// Build index from vector data
+svs_index_h svs_index_build(
+ svs_index_builder_h builder,
+ const float* data, // Vector data [num_vectors × dimensions]
+ size_t num_vectors,
+ svs_error_h out_err // optional, can be NULL
+);
+
+// Cleanup
+void svs_index_free(svs_index_h index);
+```
+
+### Search Results
+
+```c
+// Search results structure
+struct svs_search_results {
+ size_t num_queries; // Number of query vectors
+ size_t* results_per_query; // Number of results per query
+ size_t* indices; // Indices of the nearest neighbors
+ float* distances; // Distances to the nearest neighbors
+};
+
+typedef struct svs_search_results* svs_search_results_t;
+
+// Access pattern:
+// For query i, neighbor j (where k is the number of neighbors):
+// index = results->indices[i * k + j]
+// distance = results->distances[i * k + j]
+```
+
+### Search Operations
+
+```c
+// Top-K nearest neighbor search
+svs_search_results_t svs_index_search(
+ svs_index_h index,
+ const float* queries, // Query vectors [num_queries × dimensions]
+ size_t num_queries,
+ size_t k, // Number of neighbors to return
+ svs_search_params_h search_params, // optional, can be NULL for defaults
+ svs_error_h out_err // optional, can be NULL
+);
+
+// Cleanup search results
+void svs_search_results_free(svs_search_results_t results);
+```
+
+## Complete Usage Example
+
+```c
+#include "svs/c_api/svs_c.h"
+#include
+#include
+
+int main() {
+ // 1. Create error handle for diagnostics
+ svs_error_h err = svs_error_create();
+
+ // 2. Create Vamana algorithm configuration
+ svs_algorithm_h algo = svs_algorithm_create_vamana(
+ 64, // graph_degree
+ 128, // build_window_size
+ 128, // default search_window_size
+ err
+ );
+ if (!algo || !svs_error_ok(err)) {
+ fprintf(stderr, "Algorithm creation failed: %s\n",
+ svs_error_get_message(err));
+ svs_error_free(err);
+ return 1;
+ }
+
+ // 3. Create index builder
+ size_t dimensions = 128;
+ svs_index_builder_h builder = svs_index_builder_create(
+ SVS_DISTANCE_METRIC_EUCLIDEAN,
+ dimensions,
+ algo,
+ err
+ );
+
+ // 4. Optional: Configure storage (default is FP32)
+ svs_storage_h storage = svs_storage_create_simple(
+ SVS_DATA_TYPE_FLOAT32, err
+ );
+ svs_index_builder_set_storage(builder, storage, err);
+
+ // 5. Optional: Configure thread pool
+ svs_index_builder_set_threadpool(
+ builder,
+ SVS_THREADPOOL_KIND_NATIVE,
+ 8, // num_threads
+ err
+ );
+
+ // 6. Prepare data
+ size_t num_vectors = 10000;
+ float* data = (float*)malloc(num_vectors * dimensions * sizeof(float));
+ // ... fill data with vectors ...
+
+ // 7. Build index
+ svs_index_h index = svs_index_build(builder, data, num_vectors, err);
+ if (!index || !svs_error_ok(err)) {
+ fprintf(stderr, "Index build failed: %s\n",
+ svs_error_get_message(err));
+ goto cleanup;
+ }
+
+ // 8. Prepare queries
+ size_t num_queries = 10;
+ float* queries = (float*)malloc(num_queries * dimensions * sizeof(float));
+ // ... fill queries ...
+
+ // 9. Perform search with default parameters
+ size_t k = 5;
+ svs_search_results_t results = svs_index_search(
+ index, queries, num_queries, k, NULL, err
+ );
+
+ // Or with custom search parameters:
+ // svs_search_params_h params = svs_search_params_create_vamana(100, err);
+ // svs_search_results_t results = svs_index_search(
+ // index, queries, num_queries, k, params, err
+ // );
+ // svs_search_params_free(params);
+
+ // 10. Process results
+ if (results && svs_error_ok(err)) {
+ for (size_t i = 0; i < results->num_queries; i++) {
+ printf("Query %zu:\n", i);
+ for (size_t j = 0; j < k; j++) {
+ size_t idx = i * k + j;
+ printf(" Index: %zu, Distance: %f\n",
+ results->indices[idx], results->distances[idx]);
+ }
+ }
+ svs_search_results_free(results);
+ }
+
+ // 11. Cleanup
+cleanup:
+ if (index) svs_index_free(index);
+ if (builder) svs_index_builder_free(builder);
+ if (storage) svs_storage_free(storage);
+ if (algo) svs_algorithm_free(algo);
+ svs_error_free(err);
+
+ free(data);
+ free(queries);
+
+ return 0;
+}
+```
+
+## Next Steps
+
+- See [ERROR_HANDLING.md](c/ERROR_HANDLING.md) for comprehensive error handling guide
+- See [examples/c/](../examples/c/) for additional usage examples
+- See [bindings/c/samples/](c/samples/) for complete sample applications
+
+```
diff --git a/bindings/c/c_apiConfig.cmake.in b/bindings/c/c_apiConfig.cmake.in
new file mode 100644
index 00000000..d6ee49d4
--- /dev/null
+++ b/bindings/c/c_apiConfig.cmake.in
@@ -0,0 +1,19 @@
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+@PACKAGE_INIT@
+
+set(TARGET_NAME svs_c_api)
+include("${CMAKE_CURRENT_LIST_DIR}/${TARGET_NAME}.cmake")
+check_required_components(${TARGET_NAME})
diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h
new file mode 100644
index 00000000..dccccb97
--- /dev/null
+++ b/bindings/c/include/svs/c_api/svs_c.h
@@ -0,0 +1,612 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "svs_c_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include
+#include
+
+enum svs_error_code {
+ SVS_OK = 0,
+ SVS_ERROR_GENERIC = 1,
+ SVS_ERROR_INVALID_ARGUMENT = 2,
+ SVS_ERROR_OUT_OF_MEMORY = 3,
+ SVS_ERROR_NOT_IMPLEMENTED = 5,
+ SVS_ERROR_UNSUPPORTED_HW = 6,
+ SVS_ERROR_RUNTIME = 7,
+ SVS_ERROR_INVALID_OPERATION = 8,
+ SVS_ERROR_UNKNOWN = 1000
+};
+
+enum svs_distance_metric {
+ SVS_DISTANCE_METRIC_EUCLIDEAN = 0,
+ SVS_DISTANCE_METRIC_COSINE = 1,
+ SVS_DISTANCE_METRIC_DOT_PRODUCT = 2
+};
+
+enum svs_algorithm_type {
+ SVS_ALGORITHM_TYPE_VAMANA = 0,
+ SVS_ALGORITHM_TYPE_FLAT = 1,
+ SVS_ALGORITHM_TYPE_IVF = 2,
+};
+
+enum svs_data_type {
+ SVS_DATA_TYPE_VOID = 0,
+ SVS_DATA_TYPE_FLOAT32 = 32,
+ SVS_DATA_TYPE_FLOAT16 = 16,
+ SVS_DATA_TYPE_INT8 = 8,
+ SVS_DATA_TYPE_UINT8 = SVS_DATA_TYPE_INT8 - 1,
+ SVS_DATA_TYPE_INT4 = 4,
+ SVS_DATA_TYPE_UINT4 = SVS_DATA_TYPE_INT4 - 1
+};
+
+enum svs_storage_kind {
+ SVS_STORAGE_KIND_SIMPLE = 0,
+ SVS_STORAGE_KIND_LEANVEC = 1,
+ SVS_STORAGE_KIND_LVQ = 2,
+ SVS_STORAGE_KIND_SQ = 3
+};
+
+enum svs_threadpool_kind {
+ SVS_THREADPOOL_KIND_NATIVE = 0,
+ SVS_THREADPOOL_KIND_OMP = 1,
+ SVS_THREADPOOL_KIND_SINGLE_THREAD = 2,
+ SVS_THREADPOOL_KIND_CUSTOM = 3
+};
+
+// clang-format off
+struct svs_threadpool_interface_ops {
+ size_t (*size)(void* self);
+ void (*parallel_for)(
+ void* self,
+ void (*func)(void* svs_param, size_t n),
+ void* svs_param,
+ size_t n
+ );
+};
+// clang-format on
+
+struct svs_threadpool_interface {
+ struct svs_threadpool_interface_ops ops;
+ void* self;
+};
+
+struct svs_id_filter_interface_ops {
+ bool (*is_member)(void* self, size_t id);
+};
+
+struct svs_id_filter_interface {
+ struct svs_id_filter_interface_ops ops;
+ void* self;
+ // filter_rate provides the estimated selectivity of the filter, i.e., the fraction of
+ // IDs that are expected to pass the filter. A value of 0.01 indicates that 1% of IDs
+ // are expected to pass, while a value of 1.0 indicates that all IDs are expected to
+ // pass. If the filter does not provide an estimate, it should be set to 0.0.
+ float filter_rate;
+};
+
+/// @brief Structure to hold search results
+struct svs_search_results {
+ size_t num_queries; /// Number of query vectors
+ size_t* results_per_query; /// Number of results per query
+ size_t* indices; /// Indices of the nearest neighbors
+ float* distances; /// Distances to the nearest neighbors
+};
+
+/// @brief Structure to hold memory breakdown for an index
+struct svs_memory_breakdown {
+ size_t graph_bytes; /// Allocated bytes for the graph structure
+ size_t data_bytes; /// Allocated bytes for the data vectors
+ size_t metadata_bytes; /// Allocated bytes for metadata (entry points, status, etc.)
+};
+
+// Handle typedefs; "_h" suffix indicates a handle to an opaque struct
+typedef struct svs_error_desc* svs_error_h;
+typedef struct svs_index* svs_index_h;
+typedef struct svs_index_builder* svs_index_builder_h;
+typedef struct svs_algorithm* svs_algorithm_h;
+typedef struct svs_storage* svs_storage_h;
+typedef struct svs_search_params* svs_search_params_h;
+
+// Fully defined types; "_t" suffix indicates a fully defined struct
+typedef enum svs_error_code svs_error_code_t;
+typedef enum svs_distance_metric svs_distance_metric_t;
+typedef enum svs_algorithm_type svs_algorithm_type_t;
+typedef enum svs_data_type svs_data_type_t;
+typedef enum svs_threadpool_kind svs_threadpool_kind_t;
+
+typedef struct svs_threadpool_interface* svs_threadpool_i;
+typedef struct svs_id_filter_interface* svs_id_filter_i;
+typedef struct svs_search_results* svs_search_results_t;
+typedef struct svs_memory_breakdown svs_memory_breakdown_t;
+
+/// @brief Create an error handle
+/// @return A handle to the created error object
+SVS_API svs_error_h svs_error_create();
+
+/// @brief Check if the error handle indicates success
+/// @param err The error handle to check
+/// @return true if no error occurred, false otherwise
+SVS_API bool svs_error_ok(svs_error_h err);
+
+/// @brief Get the error code from the error handle
+/// @param err The error handle
+/// @return The error code
+SVS_API svs_error_code_t svs_error_get_code(svs_error_h err);
+
+/// @brief Get the error message from the error handle
+/// @param err The error handle
+/// @return A string describing the error
+SVS_API const char* svs_error_get_message(svs_error_h err);
+
+/// @brief Free the error handle
+/// @param err The error handle to free
+SVS_API void svs_error_free(svs_error_h err);
+
+/// @brief Create a Vamana algorithm configuration
+/// @param graph_degree The graph degree parameter
+/// @param build_window_size The build window size parameter
+/// @param search_window_size Default search window size parameter
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created Vamana algorithm
+SVS_API svs_algorithm_h svs_algorithm_create_vamana(
+ size_t graph_degree,
+ size_t build_window_size,
+ size_t search_window_size,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Free the algorithm configuration handle
+/// @param algorithm The algorithm handle to free
+SVS_API void svs_algorithm_free(svs_algorithm_h algorithm);
+
+/// @brief Get the alpha parameter from a Vamana algorithm configuration
+/// @param algorithm The algorithm handle
+/// @param out_alpha Pointer to store the retrieved alpha parameter
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_get_alpha(
+ svs_algorithm_h algorithm, float* out_alpha, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set the alpha parameter in a Vamana algorithm configuration
+/// @param algorithm The algorithm handle
+/// @param alpha The alpha parameter to set
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_set_alpha(
+ svs_algorithm_h algorithm, float alpha, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get the graph degree parameter from a Vamana algorithm configuration
+/// @param algorithm The algorithm handle
+/// @param out_graph_degree Pointer to store the retrieved graph degree parameter
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_get_graph_degree(
+ svs_algorithm_h algorithm, size_t* out_graph_degree, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set the graph degree parameter in a Vamana algorithm configuration
+/// @param algorithm The algorithm handle
+/// @param graph_degree The graph degree parameter to set
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_set_graph_degree(
+ svs_algorithm_h algorithm, size_t graph_degree, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get the build window size parameter from a Vamana algorithm configuration
+/// @param algorithm The algorithm handle
+/// @param out_build_window_size Pointer to store the retrieved build window size parameter
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_get_build_window_size(
+ svs_algorithm_h algorithm, size_t* out_build_window_size, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set the build window size parameter in a Vamana algorithm configuration
+/// @param algorithm The algorithm handle
+/// @param build_window_size The build window size parameter to set
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_set_build_window_size(
+ svs_algorithm_h algorithm, size_t build_window_size, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get whether to use full search history in the Vamana algorithm
+/// @param algorithm The algorithm handle
+/// @param out_use_full_search_history Pointer to store whether full search history is used
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_get_use_search_history(
+ svs_algorithm_h algorithm,
+ bool* out_use_full_search_history,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set whether to use full search history in the Vamana algorithm
+/// @param algorithm The algorithm handle
+/// @param use_full_search_history Whether to use full search history
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_algorithm_vamana_set_use_search_history(
+ svs_algorithm_h algorithm, bool use_full_search_history, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Create Vamana search parameters
+/// @param search_window_size The search window size parameter
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created Vamana search parameters
+SVS_API svs_search_params_h svs_search_params_create_vamana(
+ size_t search_window_size, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Free the search parameters handle
+/// @param params The search parameters handle to free
+SVS_API void svs_search_params_free(svs_search_params_h params);
+
+/// @brief Create a simple storage configuration
+/// @param data_type The data type of the vectors
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created simple storage
+SVS_API svs_storage_h
+svs_storage_create_simple(svs_data_type_t data_type, svs_error_h out_err);
+
+/// @brief Create a LeanVec storage configuration
+/// @param leanvec_dims The number of LeanVec dimensions
+/// @param primary The data type of the primary quantization
+/// @param secondary The data type of the secondary quantization
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created LeanVec storage
+SVS_API svs_storage_h svs_storage_create_leanvec(
+ size_t leanvec_dims,
+ svs_data_type_t primary,
+ svs_data_type_t secondary,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Create an LVQ storage configuration
+/// @param primary The data type of the primary quantization
+/// @param residual The data type of the residual quantization
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created LVQ storage
+SVS_API svs_storage_h svs_storage_create_lvq(
+ svs_data_type_t primary, svs_data_type_t residual, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Create a Scalar Quantization storage configuration
+/// @param data_type The data type of the quantized vectors
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created Scalar Quantization storage
+SVS_API svs_storage_h svs_storage_create_sq(
+ svs_data_type_t data_type, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Free the storage handle
+/// @param storage The storage handle to free
+SVS_API void svs_storage_free(svs_storage_h storage);
+
+/// @brief Create an index builder configuration
+/// @param metric The distance metric to use
+/// @param dimension The dimensionality of the vectors
+/// @param algorithm The algorithm configuration to use
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the created index builder
+/// @remarks Default storage configuration is equivalent to
+/// svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32)
+SVS_API svs_index_builder_h svs_index_builder_create(
+ svs_distance_metric_t metric,
+ size_t dimension,
+ svs_algorithm_h algorithm,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Free the index builder handle
+/// @param builder The index builder handle to free
+SVS_API void svs_index_builder_free(svs_index_builder_h builder);
+
+/// @brief Set the storage configuration for the index builder
+/// @param builder The index builder handle
+/// @param storage The storage configuration handle
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_builder_set_storage(
+ svs_index_builder_h builder, svs_storage_h storage, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set the thread pool configuration for the index builder
+/// @param builder The index builder handle
+/// @param kind The kind of thread pool to use
+/// @param num_threads The number of threads to use (if applicable)
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_builder_set_threadpool(
+ svs_index_builder_h builder,
+ svs_threadpool_kind_t kind,
+ size_t num_threads,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set the custom thread pool for the index builder
+/// @param builder The index builder handle
+/// @param pool The custom thread pool interface
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_builder_set_threadpool_custom(
+ svs_index_builder_h builder, svs_threadpool_i pool, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Build an index from the provided data
+/// @param builder The index builder handle
+/// @param data Pointer to the vector data (float array)
+/// @param num_vectors The number of vectors in the data
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the built index
+SVS_API svs_index_h svs_index_build(
+ svs_index_builder_h builder,
+ const float* data,
+ size_t num_vectors,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Build a dynamic index from the provided data and IDs
+/// @param builder The index builder handle
+/// @param data Pointer to the vector data (float array)
+/// @param ids Pointer to the vector IDs (size_t array). Can be NULL if IDs should be
+/// auto-generated from 0 to num_vectors-1.
+/// @param num_vectors The number of vectors in the data
+/// @param blocksize_bytes The block size in bytes for dynamic index building (0 for
+/// default)
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the built dynamic index
+SVS_API svs_index_h svs_index_build_dynamic(
+ svs_index_builder_h builder,
+ const float* data,
+ const size_t* ids /*=NULL*/,
+ size_t num_vectors,
+ size_t blocksize_bytes /*=0*/,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Load an index from disk
+/// @param builder The index builder handle (used for configuration)
+/// @param directory The directory path to load the index from
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the loaded index
+SVS_API svs_index_h svs_index_load(
+ svs_index_builder_h builder, const char* directory, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Load a dynamic index from disk
+/// @param builder The index builder handle (used for configuration)
+/// @param directory The directory path to load the index from
+/// @param blocksize_bytes The block size in bytes for dynamic index loading (0 for default)
+/// @param out_err An optional error handle to capture errors
+/// @return A handle to the loaded dynamic index
+SVS_API svs_index_h svs_index_load_dynamic(
+ svs_index_builder_h builder,
+ const char* directory,
+ size_t blocksize_bytes /*=0*/,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Free the index handle
+/// @param index The index handle to free
+SVS_API void svs_index_free(svs_index_h index);
+
+/// @brief Search the index with the provided queries
+/// @param index The index handle
+/// @param queries Pointer to the query data (float array)
+/// @param num_queries The number of query vectors
+/// @param k The number of nearest neighbors to retrieve per query
+/// @param search_params The search parameters handle (can be NULL for defaults)
+/// @param out_err An optional error handle to capture errors
+/// @return A pointer to the search results structure
+/// @deprecated Use svs_index_search_topK() instead, which additionally supports an
+/// optional ID filter. This function is equivalent to calling svs_index_search_topK()
+/// with a NULL id_filter.
+SVS_DEPRECATED("Use svs_index_search_topK() instead")
+SVS_API svs_search_results_t svs_index_search(
+ svs_index_h index,
+ const float* queries,
+ size_t num_queries,
+ size_t k,
+ svs_search_params_h search_params /*=NULL*/,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief TopK search the index with the provided queries and an optional ID filter
+/// @details Performs a TopK search on the index with the provided queries and an optional
+/// ID filter. The ID filter allows for filtering the search results based on specific IDs,
+/// enabling more targeted searches. If the ID filter is NULL, the search will return the
+/// top K results. If ID filter is provided, only the results that pass the filter will be
+/// returned. The function returns a pointer to the search results structure, which contains
+/// the indices and distances of the nearest neighbors for each query. If ID filter is
+/// provided with `filter_rate > 0.0` then the function will account for the actual filter
+/// hit rate during the search. If the actual observed filter hit rate is less than the
+/// provided `filter_rate` value, the function returns an empty result set.
+/// @note The search results structure must be freed using svs_search_results_free() to
+/// avoid memory leaks.
+/// @param index The index handle
+/// @param queries Pointer to the query data (float array)
+/// @param num_queries The number of query vectors
+/// @param k The number of nearest neighbors to retrieve per query
+/// @param search_params The search parameters handle (can be NULL for defaults)
+/// @param id_filter The ID filter interface (can be NULL for no filtering)
+/// @param out_err An optional error handle to capture errors
+/// @return A pointer to the search results structure
+SVS_API svs_search_results_t svs_index_search_topK(
+ svs_index_h index,
+ const float* queries,
+ size_t num_queries,
+ size_t k,
+ svs_search_params_h search_params /*=NULL*/,
+ svs_id_filter_i id_filter /*=NULL*/,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Free the search results structure
+/// @param results The search results structure to release
+SVS_API void svs_search_results_free(svs_search_results_t results);
+
+/// @brief Save the index to disk
+/// @param index The index handle
+/// @param directory The directory path to save the index to
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool
+svs_index_save(svs_index_h index, const char* directory, svs_error_h out_err /*=NULL*/);
+
+/// @brief Add points to a dynamic index
+/// @param index The dynamic index handle
+/// @param new_points Pointer to the new vector data (float array)
+/// @param ids Pointer to the new vector IDs (size_t array)
+/// @param num_vectors The number of new vectors to add
+/// @param out_err An optional error handle to capture errors
+/// @return number of points successfully added, or (size_t)-1 on failure
+SVS_API size_t svs_index_dynamic_add_points(
+ svs_index_h index,
+ const float* new_points,
+ const size_t* ids,
+ size_t num_vectors,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Delete points from a dynamic index
+/// @param index The dynamic index handle
+/// @param ids Pointer to the vector IDs to delete (size_t array)
+/// @param num_ids The number of vector IDs to delete
+/// @param out_err An optional error handle to capture errors
+/// @return number of points successfully deleted, or (size_t)-1 on failure
+SVS_API size_t svs_index_dynamic_delete_points(
+ svs_index_h index, const size_t* ids, size_t num_ids, svs_error_h out_err /*=NULL*/
+);
+/// @brief Check if a dynamic index has a specific ID
+/// @param index The dynamic index handle
+/// @param id The vector ID to check for
+/// @param out_has_id Pointer to store whether the ID exists in the index
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_dynamic_has_id(
+ svs_index_h index, size_t id, bool* out_has_id, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get the distance from a specific ID to a query vector in an index
+/// @param index The index handle
+/// @param id The vector ID to get the distance for
+/// @param query Pointer to the query vector data (float array)
+/// @param out_distance Pointer to store the retrieved distance
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_get_distance(
+ svs_index_h index,
+ size_t id,
+ const float* query,
+ float* out_distance,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Reconstruct the vectors for specific IDs in an index
+/// @param index The index handle
+/// @param ids Pointer to the vector IDs to reconstruct (size_t array)
+/// @param num_ids The number of vector IDs to reconstruct
+/// @param out_data Pointer to store the reconstructed vector data (float array with size
+/// num_ids * data_dim)
+/// @param data_dim The dimensionality of the vectors
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_reconstruct(
+ svs_index_h index,
+ const size_t* ids,
+ size_t num_ids,
+ float* out_data,
+ size_t data_dim,
+ svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Consolidate a dynamic index to optimize storage and search performance
+/// @param index The dynamic index handle
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool
+svs_index_dynamic_consolidate(svs_index_h index, svs_error_h out_err /*=NULL*/);
+
+/// @brief Compact a dynamic index to remove deleted entries and optimize storage
+/// @param index The dynamic index handle
+/// @param batchsize The batch size for compaction (0 for default)
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_dynamic_compact(
+ svs_index_h index, size_t batchsize /*=0*/, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get number of threads used for search in the index's thread pool
+/// @param index The index handle
+/// @param out_num_threads Pointer to store the retrieved number of threads
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+SVS_API bool svs_index_get_num_threads(
+ svs_index_h index, size_t* out_num_threads, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Set number of threads for search in the index's thread pool
+/// @param index The index handle
+/// @param num_threads The number of threads to set
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+/// @remarks This function is only supported for indices built with threadpool kinds
+/// SVS_THREADPOOL_KIND_NATIVE or SVS_THREADPOOL_KIND_OMP. Attempting to call this
+/// function on indices built with SVS_THREADPOOL_KIND_CUSTOM or
+/// SVS_THREADPOOL_KIND_SINGLE_THREAD will fail and return false.
+/// @error On failure, if out_err is provided, it will contain:
+/// - SVS_ERROR_INVALID_OPERATION if the index's threadpool kind is unresizable
+/// - SVS_ERROR_INVALID_ARGUMENT if num_threads is invalid or zero
+/// - SVS_ERROR_RUNTIME for other runtime failures
+SVS_API bool svs_index_set_num_threads(
+ svs_index_h index, size_t num_threads, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get the total memory usage of the index in bytes
+/// @param index The index handle
+/// @param out_bytes Pointer to store the total memory usage in bytes
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+/// @remarks This returns the sum of graph_bytes + data_bytes + metadata_bytes
+SVS_API bool svs_index_get_memory_usage(
+ svs_index_h index, size_t* out_bytes, svs_error_h out_err /*=NULL*/
+);
+
+/// @brief Get the memory breakdown for the index
+/// @param index The index handle
+/// @param out_breakdown Pointer to store the memory breakdown structure
+/// @param out_err An optional error handle to capture errors
+/// @return true on success, false on failure
+/// @remarks The breakdown reports allocated memory for graph, data, and metadata
+/// components. Uses capacity-based accounting for datasets that support it, reflecting
+/// the true memory footprint including over-allocation.
+SVS_API bool svs_index_get_memory_breakdown(
+ svs_index_h index, svs_memory_breakdown_t* out_breakdown, svs_error_h out_err /*=NULL*/
+);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/bindings/c/include/svs/c_api/svs_c_config.h b/bindings/c/include/svs/c_api/svs_c_config.h
new file mode 100644
index 00000000..14de7efb
--- /dev/null
+++ b/bindings/c/include/svs/c_api/svs_c_config.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// All symbols shall be internal unless marked as SVS_API
+#if defined _WIN32 || defined __CYGWIN__
+#define SVS_HELPER_DLL_IMPORT __declspec(dllimport)
+#define SVS_HELPER_DLL_EXPORT __declspec(dllexport)
+#else
+#if __GNUC__ >= 4
+#define SVS_HELPER_DLL_IMPORT __attribute__((visibility("default")))
+#define SVS_HELPER_DLL_EXPORT __attribute__((visibility("default")))
+#else
+#define SVS_HELPER_DLL_IMPORT
+#define SVS_HELPER_DLL_EXPORT
+#endif
+#endif
+
+#ifdef svs_c_api_EXPORTS
+#define SVS_API SVS_HELPER_DLL_EXPORT
+#else
+#define SVS_API SVS_HELPER_DLL_IMPORT
+#endif
+
+// Mark an API as deprecated, optionally providing a message for callers.
+#if defined _WIN32 || defined __CYGWIN__
+#define SVS_DEPRECATED(msg) __declspec(deprecated(msg))
+#elif defined __GNUC__ || defined __clang__
+#define SVS_DEPRECATED(msg) __attribute__((deprecated(msg)))
+#else
+#define SVS_DEPRECATED(msg)
+#endif
diff --git a/bindings/c/samples/CMakeLists.txt b/bindings/c/samples/CMakeLists.txt
new file mode 100644
index 00000000..f758504e
--- /dev/null
+++ b/bindings/c/samples/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2026 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+foreach(SAMPLE_NAME simple save_load dynamic)
+ set(SAMPLE_TARGET c_api_${SAMPLE_NAME})
+ list(APPEND SAMPLE_TARGETS ${SAMPLE_TARGET})
+ add_executable(${SAMPLE_TARGET} ${SAMPLE_NAME}.c)
+
+ target_link_libraries(${SAMPLE_TARGET} PRIVATE svs_c_api)
+
+ target_include_directories(${SAMPLE_TARGET} PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/../include
+ )
+endforeach(SAMPLE_NAME)
+
+add_custom_target(c_api_samples ALL DEPENDS ${SAMPLE_TARGETS})
diff --git a/bindings/c/samples/dynamic.c b/bindings/c/samples/dynamic.c
new file mode 100644
index 00000000..960e45bd
--- /dev/null
+++ b/bindings/c/samples/dynamic.c
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "svs/c_api/svs_c.h"
+#include
+#include
+#include
+
+#define INITIAL_VECTORS 10000
+#define TAILING_VECTORS 1000
+#define NUM_VECTORS (INITIAL_VECTORS + TAILING_VECTORS)
+#define DELETE_VECTORS_BEGIN 5000
+#define DELETE_VECTORS_END 8000
+#define NUM_QUERIES 5
+#define DIMENSION 128
+#define K 10
+
+void generate_random_data(float* data, size_t count, size_t dim) {
+ for (size_t i = 0; i < count * dim; i++) {
+ data[i] = (float)rand() / RAND_MAX;
+ }
+}
+
+size_t sequential_tp_size(void* self) { return 1; }
+
+void sequential_tp_parallel_for(
+ void* self, void (*func)(void*, size_t), void* svs_param, size_t n
+) {
+ for (size_t i = 0; i < n; ++i) {
+ func(svs_param, i);
+ }
+}
+
+static struct svs_threadpool_interface sequential_threadpool = {
+ {
+ &sequential_tp_size,
+ &sequential_tp_parallel_for,
+ },
+ NULL,
+};
+
+int main() {
+ int ret = 0;
+ srand(time(NULL));
+ svs_error_h error = svs_error_create();
+
+ float* data = NULL;
+ size_t* ids = NULL;
+ float* queries = NULL;
+ svs_algorithm_h algorithm = NULL;
+ svs_storage_h storage = NULL;
+ svs_index_builder_h builder = NULL;
+ svs_index_h index = NULL;
+ svs_search_params_h search_params = NULL;
+ svs_search_results_t results = NULL;
+
+ // Allocate random data
+ data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float));
+ ids = (size_t*)malloc(NUM_VECTORS * sizeof(size_t));
+ queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float));
+
+ if (!data || !ids || !queries) {
+ fprintf(stderr, "Failed to allocate memory\n");
+ ret = 1;
+ goto cleanup;
+ }
+
+ generate_random_data(data, NUM_VECTORS, DIMENSION);
+ for (size_t i = 0; i < NUM_VECTORS; i++) {
+ ids[i] = i;
+ }
+ generate_random_data(queries, NUM_QUERIES, DIMENSION);
+
+ // Create Vamana algorithm
+ algorithm = svs_algorithm_create_vamana(64, 128, 100, error);
+ if (!algorithm) {
+ fprintf(stderr, "Failed to create algorithm: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Create storage
+ // Simple storage
+ // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error);
+ // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT16, error);
+
+ // LeanVec storage
+ size_t leanvec_dims = DIMENSION / 2;
+ // OK:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4,
+ // SVS_DATA_TYPE_UINT4, error);
+
+ // OK:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4,
+ // SVS_DATA_TYPE_UINT8, error);
+
+ // OK:
+ storage = svs_storage_create_leanvec(
+ leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error
+ );
+
+ // ERROR:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8,
+ // SVS_DATA_TYPE_UINT4, error);
+
+ // LVQ Storage
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error);
+
+ // Scalar Quantized Storage
+ // storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error);
+
+ // storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error);
+
+ // LeanVec/LVQ are only available in builds that include the compression
+ // backend. When they are unavailable the build reports NOT_IMPLEMENTED (or
+ // UNSUPPORTED_HW on hardware lacking the required ISA); fall back to simple
+ // storage so this sample stays runnable against a public build.
+ if (!storage) {
+ svs_error_code_t code = svs_error_get_code(error);
+ if (code == SVS_ERROR_NOT_IMPLEMENTED || code == SVS_ERROR_UNSUPPORTED_HW) {
+ fprintf(
+ stderr,
+ "LeanVec storage unavailable (%s); falling back to simple float32 "
+ "storage.\n",
+ svs_error_get_message(error)
+ );
+ storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error);
+ }
+ }
+
+ if (!storage) {
+ fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Create index builder
+ builder = svs_index_builder_create(
+ SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error
+ );
+ if (!builder) {
+ fprintf(
+ stderr, "Failed to create index builder: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+
+ if (!svs_index_builder_set_storage(builder, storage, error)) {
+ fprintf(stderr, "Failed to set storage: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Set custom sequential threadpool
+ if (!svs_index_builder_set_threadpool_custom(builder, &sequential_threadpool, error)) {
+ fprintf(stderr, "Failed to set threadpool: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Build index
+ printf(
+ "Building dynamic index with %d vectors of dimension %d...\n",
+ INITIAL_VECTORS,
+ DIMENSION
+ );
+ index = svs_index_build_dynamic(builder, data, ids, INITIAL_VECTORS, 0, error);
+ if (!index) {
+ fprintf(stderr, "Failed to build index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Index built successfully!\n");
+
+ // Add more points to the index
+ printf("Adding %d more vectors to the index...\n", TAILING_VECTORS);
+ size_t num_added = svs_index_dynamic_add_points(
+ index,
+ data + INITIAL_VECTORS * DIMENSION,
+ ids + INITIAL_VECTORS,
+ TAILING_VECTORS,
+ error
+ );
+ if (num_added == (size_t)-1) {
+ fprintf(
+ stderr, "Failed to add points to index: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Points added successfully!\n");
+
+ // Search params
+ search_params = svs_search_params_create_vamana(100, error);
+ if (!search_params) {
+ fprintf(
+ stderr, "Failed to create search params: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Search
+ printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K);
+ results = svs_index_search_topK(
+ index, queries, NUM_QUERIES, K, search_params, NULL /* id_filter */, error
+ );
+ if (!results) {
+ fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Search completed successfully!\n");
+
+ // Print results
+ size_t offset = 0;
+ for (size_t q = 0; q < results->num_queries; q++) {
+ printf("Query %zu results:\n", q);
+ for (size_t i = 0; i < results->results_per_query[q]; i++) {
+ printf(
+ " [%zu] id=%zu, distance=%.4f\n",
+ i,
+ results->indices[offset + i],
+ results->distances[offset + i]
+ );
+ }
+ offset += results->results_per_query[q];
+ }
+ svs_search_results_free(results);
+ results = NULL;
+
+ // Delete some points
+ printf(
+ "Deleting vectors %d-%d from the index...\n",
+ DELETE_VECTORS_BEGIN,
+ DELETE_VECTORS_END - 1
+ );
+ size_t num_deleted = svs_index_dynamic_delete_points(
+ index, ids + DELETE_VECTORS_BEGIN, DELETE_VECTORS_END - DELETE_VECTORS_BEGIN, error
+ );
+ if (num_deleted == (size_t)-1) {
+ fprintf(
+ stderr, "Failed to delete points from index: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Deleted %zu points successfully!\n", num_deleted);
+
+ // Search again after deletion
+ printf("Searching again after deletion...\n");
+ results = svs_index_search_topK(
+ index, queries, NUM_QUERIES, K, search_params, NULL /* id_filter */, error
+ );
+ if (!results) {
+ fprintf(
+ stderr,
+ "Failed to search index after deletion: %s\n",
+ svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Search after deletion completed successfully!\n");
+
+ // Validate that deleted points are not returned in search results
+ printf("Validating results after deletion...\n");
+ offset = 0;
+ for (size_t q = 0; q < results->num_queries; q++) {
+ for (size_t i = 0; i < results->results_per_query[q]; i++) {
+ size_t id = results->indices[offset + i];
+ if (id >= DELETE_VECTORS_BEGIN && id < DELETE_VECTORS_END) {
+ fprintf(stderr, "Error: Deleted id %zu returned in search results!\n", id);
+ ret = 1;
+ }
+ }
+ offset += results->results_per_query[q];
+ }
+
+ // Check if specific IDs exist in the index
+ printf("Checking if specific IDs exist in the index...\n");
+ size_t check_ids[] = {
+ DELETE_VECTORS_BEGIN - 1,
+ DELETE_VECTORS_BEGIN,
+ DELETE_VECTORS_BEGIN + 1,
+ DELETE_VECTORS_END - 1,
+ DELETE_VECTORS_END,
+ DELETE_VECTORS_END + 1};
+ for (size_t i = 0; i < sizeof(check_ids) / sizeof(check_ids[0]); i++) {
+ size_t id = check_ids[i];
+ bool has_id = false;
+ if (!svs_index_dynamic_has_id(index, id, &has_id, error)) {
+ fprintf(
+ stderr,
+ "Failed to check if index has id %zu: %s\n",
+ id,
+ svs_error_get_message(error)
+ );
+ ret = 1;
+ continue;
+ }
+ if (id >= DELETE_VECTORS_BEGIN && id < DELETE_VECTORS_END) {
+ if (has_id) {
+ fprintf(stderr, "Error: Deleted id %zu still exists in the index!\n", id);
+ ret = 1;
+ }
+ } else {
+ if (!has_id) {
+ fprintf(stderr, "Error: Existing id %zu not found in the index!\n", id);
+ ret = 1;
+ }
+ }
+ }
+ if (ret == 0) {
+ printf("ID existence validation passed!\n");
+ }
+
+ // Get distance to a specific ID
+ printf("Getting distance to a specific ID...\n");
+ size_t test_id = DELETE_VECTORS_BEGIN - 1;
+ float distance = 0.0f;
+ if (!svs_index_get_distance(index, test_id, queries, &distance, error)) {
+ fprintf(
+ stderr,
+ "Failed to get distance to id %zu: %s\n",
+ test_id,
+ svs_error_get_message(error)
+ );
+ ret = 1;
+ } else {
+ printf("Distance from id %zu to query[0]: %.4f\n", test_id, distance);
+ }
+
+ // Reconstruct a specific ID
+ printf("Reconstructing a specific ID...\n");
+ float* origin_vector = data + test_id * DIMENSION;
+ float* reconstructed = (float*)malloc(DIMENSION * sizeof(float));
+ if (!reconstructed) {
+ fprintf(stderr, "Failed to allocate memory for reconstruction\n");
+ ret = 1;
+ goto cleanup;
+ }
+ if (!svs_index_reconstruct(index, &test_id, 1, reconstructed, DIMENSION, error)) {
+ fprintf(
+ stderr,
+ "Failed to reconstruct id %zu: %s\n",
+ test_id,
+ svs_error_get_message(error)
+ );
+ ret = 1;
+ } else {
+ printf(
+ "Original vector for id %zu: [%.4f, %.4f, ...]\n",
+ test_id,
+ origin_vector[0],
+ origin_vector[1]
+ );
+ printf(
+ "Reconstructed vector for id %zu: [%.4f, %.4f, ...]\n",
+ test_id,
+ reconstructed[0],
+ reconstructed[1]
+ );
+ }
+ free(reconstructed);
+
+ // Consolidate the dynamic index
+ printf("Consolidating the dynamic index...\n");
+ if (!svs_index_dynamic_consolidate(index, error)) {
+ fprintf(stderr, "Failed to consolidate index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ } else {
+ printf("Index consolidated successfully!\n");
+ }
+
+ // Compact the dynamic index
+ printf("Compacting the dynamic index...\n");
+ if (!svs_index_dynamic_compact(index, 0, error)) {
+ fprintf(stderr, "Failed to compact index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ } else {
+ printf("Index compacted successfully!\n");
+ }
+
+ printf("Done!\n");
+
+cleanup:
+ // Cleanup
+ svs_search_results_free(results);
+ svs_search_params_free(search_params);
+ svs_index_free(index);
+ svs_index_builder_free(builder);
+ svs_storage_free(storage);
+ svs_algorithm_free(algorithm);
+ free(data);
+ free(ids);
+ free(queries);
+ svs_error_free(error);
+
+ return ret;
+}
diff --git a/bindings/c/samples/save_load.c b/bindings/c/samples/save_load.c
new file mode 100644
index 00000000..2315020a
--- /dev/null
+++ b/bindings/c/samples/save_load.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// required for nftw
+#define _XOPEN_SOURCE 500
+// required for mkdtemp
+#define _GNU_SOURCE
+
+#include "svs/c_api/svs_c.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define NUM_VECTORS 10000
+#define NUM_QUERIES 1
+#define DIMENSION 128
+#define K 10
+
+void generate_random_data(float* data, size_t count, size_t dim) {
+ for (size_t i = 0; i < count * dim; i++) {
+ data[i] = (float)rand() / RAND_MAX;
+ }
+}
+
+int nftw_callback(
+ const char* fpath, const struct stat* sb, int typeflag, struct FTW* ftwbuf
+) {
+ if (typeflag == FTW_DP) {
+ // directory, remove it
+ return rmdir(fpath);
+ } else {
+ // file, remove it
+ return unlink(fpath);
+ }
+}
+
+int remove_directory_recursive(const char* path) {
+ // remove the directory and its contents using function nftw()
+ return nftw(path, nftw_callback, 64, FTW_DEPTH | FTW_PHYS);
+}
+
+int main() {
+ int ret = 0;
+ srand(time(NULL));
+ svs_error_h error = svs_error_create();
+
+ float* data = NULL;
+ float* queries = NULL;
+ svs_algorithm_h algorithm = NULL;
+ svs_storage_h storage = NULL;
+ svs_index_builder_h builder = NULL;
+ svs_index_h index = NULL;
+ svs_search_results_t results = NULL;
+ char tmp_dir_template[] = "svs_index_XXXXXX";
+ char* tmp_dir = NULL;
+ svs_search_results_t loaded_results = NULL;
+
+ // Allocate random data
+ data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float));
+ queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float));
+
+ if (!data || !queries) {
+ fprintf(stderr, "Failed to allocate memory\n");
+ ret = 1;
+ goto cleanup;
+ }
+
+ generate_random_data(data, NUM_VECTORS, DIMENSION);
+ generate_random_data(queries, NUM_QUERIES, DIMENSION);
+
+ // Create Vamana algorithm
+ algorithm = svs_algorithm_create_vamana(64, 128, 100, error);
+ if (!algorithm) {
+ fprintf(stderr, "Failed to create algorithm: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Create storage
+ // Simple storage
+ // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error);
+ // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT16, error);
+
+ // LeanVec storage
+ size_t leanvec_dims = DIMENSION / 2;
+ // OK:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4,
+ // SVS_DATA_TYPE_UINT4, error);
+
+ // OK:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4,
+ // SVS_DATA_TYPE_UINT8, error);
+
+ // OK:
+ storage = svs_storage_create_leanvec(
+ leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error
+ );
+
+ // ERROR:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8,
+ // SVS_DATA_TYPE_UINT4, error);
+
+ // LVQ Storage
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error);
+
+ // Scalar Quantized Storage
+ // storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error);
+
+ // storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error);
+
+ // LeanVec/LVQ are only available in builds that include the compression
+ // backend. When they are unavailable the build reports NOT_IMPLEMENTED (or
+ // UNSUPPORTED_HW on hardware lacking the required ISA); fall back to simple
+ // storage so this sample stays runnable against a public build.
+ if (!storage) {
+ svs_error_code_t code = svs_error_get_code(error);
+ if (code == SVS_ERROR_NOT_IMPLEMENTED || code == SVS_ERROR_UNSUPPORTED_HW) {
+ fprintf(
+ stderr,
+ "LeanVec storage unavailable (%s); falling back to simple float32 "
+ "storage.\n",
+ svs_error_get_message(error)
+ );
+ storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error);
+ }
+ }
+
+ if (!storage) {
+ fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Create index builder
+ builder = svs_index_builder_create(
+ SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error
+ );
+ if (!builder) {
+ fprintf(
+ stderr, "Failed to create index builder: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+
+ if (!svs_index_builder_set_storage(builder, storage, error)) {
+ fprintf(stderr, "Failed to set storage: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Build index
+ printf("Building index with %d vectors of dimension %d...\n", NUM_VECTORS, DIMENSION);
+ index = svs_index_build(builder, data, NUM_VECTORS, error);
+ if (!index) {
+ fprintf(stderr, "Failed to build index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Index built successfully!\n");
+
+ // Search
+ printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K);
+ results = svs_index_search_topK(
+ index,
+ queries,
+ NUM_QUERIES,
+ K,
+ NULL /* search_params */,
+ NULL /* id_filter */,
+ error
+ );
+ if (!results) {
+ fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Search completed successfully!\n");
+
+ // Create temporary directory for saving the index
+ tmp_dir = mkdtemp(tmp_dir_template);
+ if (!tmp_dir) {
+ fprintf(stderr, "Failed to create temporary directory\n");
+ ret = 1;
+ goto cleanup;
+ }
+
+ printf("Saving index to directory: %s\n", tmp_dir);
+ // Save the index to disk
+ if (!svs_index_save(index, tmp_dir, error)) {
+ fprintf(stderr, "Failed to save index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Index saved successfully!\n");
+
+ svs_index_free(index);
+ index = NULL;
+ // Load the index from disk
+ printf("Loading index from directory: %s\n", tmp_dir);
+ index = svs_index_load(builder, tmp_dir, error);
+ if (!index) {
+ fprintf(stderr, "Failed to load index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Index loaded successfully!\n");
+
+ // Search the loaded index
+ printf(
+ "Searching loaded index for %d queries for top-%d neighbors...\n", NUM_QUERIES, K
+ );
+ loaded_results = svs_index_search_topK(
+ index,
+ queries,
+ NUM_QUERIES,
+ K,
+ NULL /* search_params */,
+ NULL /* id_filter */,
+ error
+ );
+ if (!loaded_results) {
+ fprintf(
+ stderr, "Failed to search loaded index: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Search on loaded index completed successfully!\n");
+
+ // Compare results
+ if (results->num_queries != loaded_results->num_queries) {
+ fprintf(
+ stderr, "Mismatch in number of queries between original and loaded results\n"
+ );
+ ret = 1;
+ goto cleanup;
+ }
+
+ size_t offset = 0;
+ for (size_t q = 0; q < results->num_queries; q++) {
+ if (results->results_per_query[q] != loaded_results->results_per_query[q]) {
+ fprintf(stderr, "Mismatch in number of results for query %zu\n", q);
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Query %zu results:\n", q);
+ for (size_t i = 0; i < results->results_per_query[q]; i++) {
+ if (results->indices[offset + i] != loaded_results->indices[offset + i]) {
+ fprintf(
+ stderr, "Mismatch in neighbor indices for query %zu, result %zu\n", q, i
+ );
+ ret = 1;
+ goto cleanup;
+ }
+ printf(
+ " [%zu] id=%zu, distance=%.4f, diff=%.4f\n",
+ i,
+ results->indices[offset + i],
+ results->distances[offset + i],
+ results->distances[offset + i] - loaded_results->distances[offset + i]
+ );
+ }
+ offset += results->results_per_query[q];
+ }
+
+ printf("Done!\n");
+
+cleanup:
+ // Cleanup
+ if (tmp_dir) {
+ // remove the temporary directory and its contents
+ remove_directory_recursive(tmp_dir);
+ }
+ svs_search_results_free(results);
+ svs_search_results_free(loaded_results);
+ svs_index_free(index);
+ svs_index_builder_free(builder);
+ svs_storage_free(storage);
+ svs_algorithm_free(algorithm);
+ free(data);
+ free(queries);
+ svs_error_free(error);
+
+ return ret;
+}
diff --git a/bindings/c/samples/simple.c b/bindings/c/samples/simple.c
new file mode 100644
index 00000000..8b79dc9b
--- /dev/null
+++ b/bindings/c/samples/simple.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "svs/c_api/svs_c.h"
+#include
+#include
+#include
+
+#define NUM_VECTORS 10000
+#define NUM_QUERIES 5
+#define DIMENSION 128
+#define K 10
+
+void generate_random_data(float* data, size_t count, size_t dim) {
+ for (size_t i = 0; i < count * dim; i++) {
+ data[i] = (float)rand() / RAND_MAX;
+ }
+}
+
+size_t sequential_tp_size(void* self) { return 1; }
+
+void sequential_tp_parallel_for(
+ void* self, void (*func)(void*, size_t), void* svs_param, size_t n
+) {
+ for (size_t i = 0; i < n; ++i) {
+ func(svs_param, i);
+ }
+}
+
+static struct svs_threadpool_interface sequential_threadpool = {
+ {
+ &sequential_tp_size,
+ &sequential_tp_parallel_for,
+ },
+ NULL,
+};
+
+int main() {
+ int ret = 0;
+ srand(time(NULL));
+ svs_error_h error = svs_error_create();
+
+ float* data = NULL;
+ float* queries = NULL;
+ svs_algorithm_h algorithm = NULL;
+ svs_storage_h storage = NULL;
+ svs_index_builder_h builder = NULL;
+ svs_index_h index = NULL;
+ svs_search_results_t results = NULL;
+
+ // Allocate random data
+ data = (float*)malloc(NUM_VECTORS * DIMENSION * sizeof(float));
+ queries = (float*)malloc(NUM_QUERIES * DIMENSION * sizeof(float));
+
+ if (!data || !queries) {
+ fprintf(stderr, "Failed to allocate memory\n");
+ ret = 1;
+ goto cleanup;
+ }
+
+ generate_random_data(data, NUM_VECTORS, DIMENSION);
+ generate_random_data(queries, NUM_QUERIES, DIMENSION);
+
+ // Create Vamana algorithm
+ algorithm = svs_algorithm_create_vamana(64, 128, 100, error);
+ if (!algorithm) {
+ fprintf(stderr, "Failed to create algorithm: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Create storage
+ // Simple storage
+ // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error);
+ // storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT16, error);
+
+ // LeanVec storage
+ size_t leanvec_dims = DIMENSION / 2;
+ // OK:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4,
+ // SVS_DATA_TYPE_UINT4, error);
+
+ // OK:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT4,
+ // SVS_DATA_TYPE_UINT8, error);
+
+ // OK:
+ storage = svs_storage_create_leanvec(
+ leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error
+ );
+
+ // ERROR:
+ // storage = svs_storage_create_leanvec(leanvec_dims, SVS_DATA_TYPE_UINT8,
+ // SVS_DATA_TYPE_UINT4, error);
+
+ // LVQ Storage
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error);
+
+ // storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error);
+
+ // Scalar Quantized Storage
+ // storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error);
+
+ // storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error);
+
+ // LeanVec/LVQ are only available in builds that include the compression
+ // backend. When they are unavailable the build reports NOT_IMPLEMENTED (or
+ // UNSUPPORTED_HW on hardware lacking the required ISA); fall back to simple
+ // storage so this sample stays runnable against a public build.
+ if (!storage) {
+ svs_error_code_t code = svs_error_get_code(error);
+ if (code == SVS_ERROR_NOT_IMPLEMENTED || code == SVS_ERROR_UNSUPPORTED_HW) {
+ fprintf(
+ stderr,
+ "LeanVec storage unavailable (%s); falling back to simple float32 "
+ "storage.\n",
+ svs_error_get_message(error)
+ );
+ storage = svs_storage_create_simple(SVS_DATA_TYPE_FLOAT32, error);
+ }
+ }
+
+ if (!storage) {
+ fprintf(stderr, "Failed to create storage: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Create index builder
+ builder = svs_index_builder_create(
+ SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error
+ );
+ if (!builder) {
+ fprintf(
+ stderr, "Failed to create index builder: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+
+ if (!svs_index_builder_set_storage(builder, storage, error)) {
+ fprintf(stderr, "Failed to set storage: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Set custom sequential threadpool
+ if (!svs_index_builder_set_threadpool_custom(builder, &sequential_threadpool, error)) {
+ fprintf(stderr, "Failed to set threadpool: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Build index
+ printf("Building index with %d vectors of dimension %d...\n", NUM_VECTORS, DIMENSION);
+ index = svs_index_build(builder, data, NUM_VECTORS, error);
+ if (!index) {
+ fprintf(stderr, "Failed to build index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Index built successfully!\n");
+
+ // Search params
+ svs_search_params_h search_params = svs_search_params_create_vamana(100, error);
+ if (!search_params) {
+ fprintf(
+ stderr, "Failed to create search params: %s\n", svs_error_get_message(error)
+ );
+ ret = 1;
+ goto cleanup;
+ }
+
+ // Search
+ printf("Searching %d queries for top-%d neighbors...\n", NUM_QUERIES, K);
+ results = svs_index_search_topK(
+ index, queries, NUM_QUERIES, K, search_params, NULL /* id_filter */, error
+ );
+ if (!results) {
+ fprintf(stderr, "Failed to search index: %s\n", svs_error_get_message(error));
+ ret = 1;
+ goto cleanup;
+ }
+ printf("Search completed successfully!\n");
+
+ // Print results
+ size_t offset = 0;
+ for (size_t q = 0; q < results->num_queries; q++) {
+ printf("Query %zu results:\n", q);
+ for (size_t i = 0; i < results->results_per_query[q]; i++) {
+ printf(
+ " [%zu] id=%zu, distance=%.4f\n",
+ i,
+ results->indices[offset + i],
+ results->distances[offset + i]
+ );
+ }
+ offset += results->results_per_query[q];
+ }
+
+ printf("Done!\n");
+
+cleanup:
+ // Cleanup
+ svs_search_results_free(results);
+ svs_index_free(index);
+ svs_index_builder_free(builder);
+ svs_storage_free(storage);
+ svs_algorithm_free(algorithm);
+ free(data);
+ free(queries);
+ svs_error_free(error);
+
+ return ret;
+}
diff --git a/bindings/c/src/algorithm.hpp b/bindings/c/src/algorithm.hpp
new file mode 100644
index 00000000..127a7902
--- /dev/null
+++ b/bindings/c/src/algorithm.hpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "svs/c_api/svs_c.h"
+
+// #include
+// #include
+// #include
+#include
+#include
+#include
+
+namespace svs::c_runtime {
+
+struct Algorithm {
+ struct SearchParams {
+ svs_algorithm_type type;
+ SearchParams(svs_algorithm_type type)
+ : type(type) {}
+ virtual ~SearchParams() = default;
+ };
+
+ svs_algorithm_type type;
+ Algorithm(svs_algorithm_type type)
+ : type(type) {}
+ virtual ~Algorithm() = default;
+
+ virtual std::shared_ptr get_default_search_params() const = 0;
+ virtual void set_default_search_params(const std::shared_ptr& params) = 0;
+};
+
+struct AlgorithmVamana : public Algorithm {
+ struct SearchParams : public Algorithm::SearchParams {
+ size_t search_window_size;
+ SearchParams(size_t search_window_size)
+ : Algorithm::SearchParams{SVS_ALGORITHM_TYPE_VAMANA}
+ , search_window_size(search_window_size) {}
+
+ svs::index::vamana::VamanaSearchParameters get_search_parameters() const {
+ svs::index::vamana::VamanaSearchParameters params;
+ params.buffer_config_ =
+ svs::index::vamana::SearchBufferConfig{search_window_size};
+ return params;
+ }
+ };
+
+ svs::index::vamana::VamanaBuildParameters build_params;
+ SearchParams default_search_params;
+
+ AlgorithmVamana(
+ size_t graph_degree, size_t build_window_size, size_t search_window_size
+ )
+ : Algorithm{SVS_ALGORITHM_TYPE_VAMANA}
+ , build_params()
+ , default_search_params(search_window_size) {
+ build_params.graph_max_degree = graph_degree;
+ build_params.window_size = build_window_size;
+ }
+
+ svs::index::vamana::VamanaBuildParameters& build_parameters() { return build_params; }
+
+ const svs::index::vamana::VamanaBuildParameters& build_parameters() const {
+ return build_params;
+ }
+
+ std::shared_ptr get_default_search_params() const override {
+ return std::make_shared(default_search_params);
+ }
+
+ void set_default_search_params(const std::shared_ptr& params
+ ) override {
+ default_search_params = *std::static_pointer_cast(params);
+ }
+};
+
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/allocator.hpp b/bindings/c/src/allocator.hpp
new file mode 100644
index 00000000..b6859945
--- /dev/null
+++ b/bindings/c/src/allocator.hpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include
+#include
+
+namespace svs {
+namespace c_runtime {
+
+template >
+using MaybeBlockedAlloc =
+ std::conditional_t, Allocator>;
+
+} // namespace c_runtime
+} // namespace svs
diff --git a/bindings/c/src/data_builder.hpp b/bindings/c/src/data_builder.hpp
new file mode 100644
index 00000000..f4dfafd9
--- /dev/null
+++ b/bindings/c/src/data_builder.hpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "data_builder/leanvec.hpp"
+#include "data_builder/lvq.hpp"
+#include "data_builder/simple.hpp"
+#include "data_builder/sq.hpp"
diff --git a/bindings/c/src/data_builder/leanvec.hpp b/bindings/c/src/data_builder/leanvec.hpp
new file mode 100644
index 00000000..13ba58a5
--- /dev/null
+++ b/bindings/c/src/data_builder/leanvec.hpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
+
+#include "svs/c_api/svs_c.h"
+
+#include "storage.hpp"
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef SVS_LEANVEC_HEADER
+#include SVS_LEANVEC_HEADER
+#else // SVS_LEANVEC_HEADER not defined
+#ifdef SVS_RUNTIME_ENABLE_IVF
+#include
+#endif
+#include
+#endif // SVS_LEANVEC_HEADER
+
+#include
+#include
+
+namespace svs {
+
+template >
+class LeanVecDataBuilder {
+ size_t leanvec_dims_;
+
+ public:
+ LeanVecDataBuilder(size_t leanvec_dims)
+ : leanvec_dims_(leanvec_dims) {}
+
+ using data_type = svs::leanvec::LeanDataset<
+ svs::leanvec::UsingLVQ,
+ svs::leanvec::UsingLVQ,
+ svs::Dynamic,
+ svs::Dynamic,
+ Allocator>;
+ using allocator_type = Allocator;
+
+ template
+ data_type build(
+ svs::data::ConstSimpleDataView view,
+ svs::threads::ThreadPoolHandle& pool,
+ const allocator_type& allocator = {}
+ ) {
+ return data_type::reduce(
+ view, std::nullopt, pool, 0, svs::lib::MaybeStatic{leanvec_dims_}, allocator
+ );
+ }
+
+ data_type
+ load(const std::filesystem::path& path, const allocator_type& allocator = {}) {
+ return svs::lib::load_from_disk(path, allocator);
+ }
+};
+
+template
+struct lib::
+ DispatchConverter> {
+ using From = const svs::c_runtime::Storage*;
+ using To = LeanVecDataBuilder;
+
+ static int64_t match(From from) {
+ if (from->kind == SVS_STORAGE_KIND_LEANVEC) {
+ auto leanvec = static_cast(from);
+ if (leanvec->primary_bits == I1 && leanvec->secondary_bits == I2) {
+ return svs::lib::perfect_match;
+ }
+ }
+ return svs::lib::invalid_match;
+ }
+
+ static To convert(From from) {
+ auto leanvec = static_cast(from);
+ return To{leanvec->lenavec_dims};
+ }
+};
+
+template void for_leanvec_specializations(F&& f) {
+ using byte_alloc = svs::c_runtime::MaybeBlockedAlloc;
+
+#define X(P, S, D) f.template operator(), D>();
+#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity)
+ // Pattern:
+ // PrimaryBits, SecondaryBits, Distance
+ XX(4, 4)
+ XX(4, 8)
+ XX(8, 8)
+#undef XX
+#undef X
+}
+
+} // namespace svs
+
+#else // SVS_RUNTIME_ENABLE_LVQ_LEANVEC not enabled
+namespace svs {
+// Define empty stubs for LeanVec-related functions when LVQ/LeanVec support is disabled
+template void for_leanvec_specializations(F&&) {}
+} // namespace svs
+
+#endif // SVS_RUNTIME_ENABLE_LVQ_LEANVEC
diff --git a/bindings/c/src/data_builder/lvq.hpp b/bindings/c/src/data_builder/lvq.hpp
new file mode 100644
index 00000000..d7d5912a
--- /dev/null
+++ b/bindings/c/src/data_builder/lvq.hpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#ifdef SVS_RUNTIME_ENABLE_LVQ_LEANVEC
+
+#include "svs/c_api/svs_c.h"
+
+#include "storage.hpp"
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef SVS_LVQ_HEADER
+#include SVS_LVQ_HEADER
+#else // SVS_LVQ_HEADER not defined
+#ifdef SVS_RUNTIME_ENABLE_IVF
+#include
+#endif
+#include
+#endif // SVS_LVQ_HEADER
+
+#include
+#include
+
+namespace svs {
+
+template <
+ size_t PrimaryBits,
+ size_t ResidualBits,
+ typename Allocator = svs::lib::Allocator>
+class LVQDataBuilder {
+ public:
+ LVQDataBuilder() {}
+
+ using data_type = svs::quantization::lvq::LVQDataset<
+ PrimaryBits,
+ ResidualBits,
+ svs::Dynamic,
+ svs::quantization::lvq::Sequential,
+ Allocator>;
+ using allocator_type = Allocator;
+
+ template
+ data_type build(
+ svs::data::ConstSimpleDataView view,
+ svs::threads::ThreadPoolHandle& pool,
+ const allocator_type& allocator = {}
+ ) {
+ return data_type::compress(view, pool, 0, allocator);
+ }
+
+ data_type
+ load(const std::filesystem::path& path, const allocator_type& allocator = {}) {
+ return svs::lib::load_from_disk(path, allocator);
+ }
+};
+
+template
+struct lib::DispatchConverter<
+ const c_runtime::Storage*,
+ LVQDataBuilder> {
+ using From = const svs::c_runtime::Storage*;
+ using To = LVQDataBuilder;
+
+ static int64_t match(From from) {
+ if (from->kind == SVS_STORAGE_KIND_LVQ) {
+ auto lvq = static_cast(from);
+ if (lvq->primary_bits == PrimaryBits && lvq->residual_bits == ResidualBits) {
+ return svs::lib::perfect_match;
+ }
+ }
+ return svs::lib::invalid_match;
+ }
+
+ static To convert(From SVS_UNUSED(from)) { return To{}; }
+};
+
+template void for_lvq_specializations(F&& f) {
+ using byte_alloc = svs::c_runtime::MaybeBlockedAlloc;
+#define X(P, S, D) f.template operator(), D>();
+#define XX(P, S) X(P, S, DistanceL2) X(P, S, DistanceIP) X(P, S, DistanceCosineSimilarity)
+ // Pattern:
+ // PrimaryBits, SecondaryBits, Distance
+ XX(4, 0)
+ XX(8, 0)
+ XX(4, 4)
+ XX(4, 8)
+#undef XX
+#undef X
+}
+
+} // namespace svs
+
+#else // SVS_RUNTIME_ENABLE_LVQ_LEANVEC not enabled
+namespace svs {
+// Define empty stubs for LVQ-related functions when LVQ/LeanVec support is disabled
+template void for_lvq_specializations(F&&) {}
+} // namespace svs
+
+#endif // SVS_RUNTIME_ENABLE_LVQ_LEANVEC
diff --git a/bindings/c/src/data_builder/simple.hpp b/bindings/c/src/data_builder/simple.hpp
new file mode 100644
index 00000000..1f016241
--- /dev/null
+++ b/bindings/c/src/data_builder/simple.hpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "svs/c_api/svs_c.h"
+
+#include "allocator.hpp"
+#include "storage.hpp"
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace svs {
+
+template >
+class SimpleDataBuilder {
+ public:
+ SimpleDataBuilder() {}
+
+ using data_type = svs::data::SimpleData;
+ using allocator_type = Allocator;
+
+ template
+ data_type build(
+ svs::data::ConstSimpleDataView view,
+ svs::threads::ThreadPoolHandle& SVS_UNUSED(pool),
+ const allocator_type& allocator = {}
+ ) {
+ auto data = data_type(view.size(), view.dimensions(), allocator);
+ svs::data::copy(view, data);
+ return data;
+ }
+
+ data_type
+ load(const std::filesystem::path& path, const allocator_type& allocator = {}) {
+ return svs::lib::load_from_disk(path, allocator);
+ }
+};
+
+template
+struct lib::DispatchConverter> {
+ using From = const svs::c_runtime::Storage*;
+ using To = SimpleDataBuilder;
+
+ static int64_t match(From from) {
+ if constexpr (svs::is_arithmetic_v) {
+ if (from->kind == SVS_STORAGE_KIND_SIMPLE) {
+ auto simple = static_cast(from);
+ if (simple->data_type == svs::datatype_v) {
+ return svs::lib::perfect_match;
+ }
+ }
+ }
+ return svs::lib::invalid_match;
+ }
+
+ static To convert(From SVS_UNUSED(from)) { return To{}; }
+};
+
+template void for_simple_specializations(F&& f) {
+ using float_alloc = svs::c_runtime::MaybeBlockedAlloc;
+ using float16_alloc = svs::c_runtime::MaybeBlockedAlloc;
+#define X(T, A, D) f.template operator(), D>();
+#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity)
+ XX(float, float_alloc)
+ XX(svs::Float16, float16_alloc)
+#undef XX
+#undef X
+}
+
+} // namespace svs
diff --git a/bindings/c/src/data_builder/sq.hpp b/bindings/c/src/data_builder/sq.hpp
new file mode 100644
index 00000000..f2fd9559
--- /dev/null
+++ b/bindings/c/src/data_builder/sq.hpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "svs/c_api/svs_c.h"
+
+#include "allocator.hpp"
+#include "storage.hpp"
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace svs {
+
+template > class SQDataBuilder {
+ public:
+ SQDataBuilder() {}
+
+ using data_type = svs::quantization::scalar::SQDataset;
+ using allocator_type = Allocator;
+
+ template
+ data_type build(
+ svs::data::ConstSimpleDataView view,
+ svs::threads::ThreadPoolHandle& pool,
+ const allocator_type& allocator = {}
+ ) {
+ return data_type::compress(view, pool, allocator);
+ }
+
+ data_type
+ load(const std::filesystem::path& path, const allocator_type& allocator = {}) {
+ return svs::lib::load_from_disk(path, allocator);
+ }
+};
+
+template
+struct lib::DispatchConverter> {
+ using From = const svs::c_runtime::Storage*;
+ using To = SQDataBuilder;
+
+ static int64_t match(From from) {
+ if (from->kind == SVS_STORAGE_KIND_SQ) {
+ auto sq = static_cast(from);
+ if (sq->data_type == svs::datatype_v) {
+ return svs::lib::perfect_match;
+ }
+ }
+ return svs::lib::invalid_match;
+ }
+
+ static To convert(From SVS_UNUSED(from)) { return To{}; }
+};
+
+template void for_sq_specializations(F&& f) {
+ using int8_alloc = svs::c_runtime::MaybeBlockedAlloc;
+ using uint8_alloc = svs::c_runtime::MaybeBlockedAlloc;
+#define X(T, A, D) f.template operator(), D>();
+#define XX(T, A) X(T, A, DistanceL2) X(T, A, DistanceIP) X(T, A, DistanceCosineSimilarity)
+ XX(uint8_t, uint8_alloc)
+ XX(int8_t, int8_alloc)
+#undef XX
+#undef X
+}
+
+} // namespace svs
diff --git a/bindings/c/src/dispatcher_dynamic_vamana.cpp b/bindings/c/src/dispatcher_dynamic_vamana.cpp
new file mode 100644
index 00000000..3d5669fb
--- /dev/null
+++ b/bindings/c/src/dispatcher_dynamic_vamana.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "dispatcher_dynamic_vamana.hpp"
+
+#include "algorithm.hpp"
+#include "allocator.hpp"
+#include "data_builder.hpp"
+#include "index.hpp"
+#include "storage.hpp"
+#include "threadpool.hpp"
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace svs::c_runtime {
+
+template
+svs::DynamicVamana build_dynamic_vamana_index(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ std::pair, std::span> src_data,
+ DataBuilder builder,
+ Distance D,
+ svs::threads::ThreadPoolHandle pool,
+ size_t blocksize_bytes
+) {
+ svs::data::BlockingParameters block_params;
+ if (blocksize_bytes != 0) {
+ block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes);
+ }
+ using allocator_type = typename DataBuilder::allocator_type;
+ auto allocator = allocator_type{block_params};
+ auto data = builder.build(std::move(src_data.first), pool, allocator);
+ return svs::DynamicVamana::build(
+ build_params,
+ std::move(data),
+ std::move(src_data.second),
+ std::move(D),
+ std::move(pool)
+ );
+}
+
+template
+svs::DynamicVamana load_dynamic_vamana_index(
+ const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params),
+ const std::filesystem::path& directory,
+ DataLoader loader,
+ Distance D,
+ svs::threads::ThreadPoolHandle pool,
+ size_t blocksize_bytes
+) {
+ svs::data::BlockingParameters block_params;
+ if (blocksize_bytes != 0) {
+ block_params.blocksize_bytes = svs::lib::prevpow2(blocksize_bytes);
+ }
+ using allocator_type = typename DataLoader::allocator_type;
+ auto allocator = allocator_type{block_params};
+ auto data = loader.load(directory / "data", allocator);
+ return svs::DynamicVamana::assemble(
+ directory / "config",
+ svs::GraphLoader{directory / "graph"},
+ std::move(data),
+ std::move(D),
+ std::move(pool)
+ );
+}
+
+template
+void register_dynamic_vamana_index_specializations(Dispatcher& dispatcher) {
+ auto build_closure = [&dispatcher]() {
+ dispatcher.register_target(&build_dynamic_vamana_index);
+ };
+ auto load_closure = [&dispatcher]() {
+ dispatcher.register_target(&load_dynamic_vamana_index);
+ };
+
+ for_simple_specializations(build_closure);
+ for_simple_specializations(load_closure);
+ for_leanvec_specializations(build_closure);
+ for_leanvec_specializations(load_closure);
+ for_lvq_specializations(build_closure);
+ for_lvq_specializations(load_closure);
+ for_sq_specializations(build_closure);
+ for_sq_specializations(load_closure);
+}
+
+using DynamicVamanaSource = std::variant<
+ std::pair, std::span>,
+ std::filesystem::path>;
+
+using BuildDynamicIndexDispatcher = svs::lib::Dispatcher<
+ svs::DynamicVamana,
+ const svs::index::vamana::VamanaBuildParameters&,
+ DynamicVamanaSource,
+ const Storage*,
+ svs::DistanceType,
+ svs::threads::ThreadPoolHandle,
+ size_t>;
+
+const BuildDynamicIndexDispatcher& build_dynamic_vamana_index_dispatcher() {
+ static BuildDynamicIndexDispatcher dispatcher = [] {
+ BuildDynamicIndexDispatcher d{};
+ register_dynamic_vamana_index_specializations(d);
+ return d;
+ }();
+ return dispatcher;
+}
+
+svs::DynamicVamana dispatch_dynamic_vamana_index_build(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ svs::data::ConstSimpleDataView data,
+ std::span ids,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool,
+ size_t blocksize_bytes
+) {
+ return build_dynamic_vamana_index_dispatcher().invoke(
+ build_params,
+ DynamicVamanaSource{std::make_pair(data, ids)},
+ storage,
+ distance_type,
+ std::move(pool),
+ blocksize_bytes
+ );
+}
+
+svs::DynamicVamana dispatch_dynamic_vamana_index_load(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ const std::filesystem::path& directory,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool,
+ size_t blocksize_bytes
+) {
+ return build_dynamic_vamana_index_dispatcher().invoke(
+ build_params,
+ DynamicVamanaSource{directory},
+ storage,
+ distance_type,
+ std::move(pool),
+ blocksize_bytes
+ );
+}
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/dispatcher_dynamic_vamana.hpp b/bindings/c/src/dispatcher_dynamic_vamana.hpp
new file mode 100644
index 00000000..41ac71da
--- /dev/null
+++ b/bindings/c/src/dispatcher_dynamic_vamana.hpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "storage.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace svs::c_runtime {
+
+svs::DynamicVamana dispatch_dynamic_vamana_index_build(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ svs::data::ConstSimpleDataView data,
+ std::span ids,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool,
+ size_t blocksize_bytes
+);
+
+svs::DynamicVamana dispatch_dynamic_vamana_index_load(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ const std::filesystem::path& directory,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool,
+ size_t blocksize_bytes
+);
+
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/dispatcher_vamana.cpp b/bindings/c/src/dispatcher_vamana.cpp
new file mode 100644
index 00000000..1c9b873b
--- /dev/null
+++ b/bindings/c/src/dispatcher_vamana.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "dispatcher_vamana.hpp"
+
+#include "algorithm.hpp"
+#include "data_builder.hpp"
+#include "index.hpp"
+#include "storage.hpp"
+#include "threadpool.hpp"
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace svs::c_runtime {
+
+template
+svs::Vamana build_vamana_index(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ svs::data::ConstSimpleDataView src_data,
+ DataBuilder builder,
+ Distance distance,
+ svs::threads::ThreadPoolHandle pool
+) {
+ auto data = builder.build(std::move(src_data), pool);
+ return svs::Vamana::build(
+ build_params, std::move(data), distance, std::move(pool)
+ );
+}
+
+template
+svs::Vamana load_vamana_index(
+ const svs::index::vamana::VamanaBuildParameters& SVS_UNUSED(build_params),
+ const std::filesystem::path& directory,
+ DataLoader loader,
+ Distance distance,
+ svs::threads::ThreadPoolHandle pool
+) {
+ auto data = loader.load(directory / "data");
+ return svs::Vamana::assemble(
+ directory / "config",
+ svs::GraphLoader{directory / "graph"},
+ std::move(data),
+ distance,
+ std::move(pool)
+ );
+}
+
+template
+void register_vamana_index_specializations(Dispatcher& dispatcher) {
+ auto build_closure = [&dispatcher]() {
+ dispatcher.register_target(&build_vamana_index);
+ };
+ auto load_closure = [&dispatcher]() {
+ dispatcher.register_target(&load_vamana_index);
+ };
+
+ for_simple_specializations(build_closure);
+ for_simple_specializations(load_closure);
+ for_leanvec_specializations(build_closure);
+ for_leanvec_specializations(load_closure);
+ for_lvq_specializations(build_closure);
+ for_lvq_specializations(load_closure);
+ for_sq_specializations(build_closure);
+ for_sq_specializations(load_closure);
+}
+
+using VamanaSource =
+ std::variant, std::filesystem::path>;
+
+using BuildIndexDispatcher = svs::lib::Dispatcher<
+ svs::Vamana,
+ const svs::index::vamana::VamanaBuildParameters&,
+ VamanaSource,
+ const Storage*,
+ svs::DistanceType,
+ svs::threads::ThreadPoolHandle>;
+
+const BuildIndexDispatcher& build_vamana_index_dispatcher() {
+ static BuildIndexDispatcher dispatcher = [] {
+ BuildIndexDispatcher d{};
+ register_vamana_index_specializations(d);
+ return d;
+ }();
+ return dispatcher;
+}
+
+svs::Vamana dispatch_vamana_index_build(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ svs::data::ConstSimpleDataView data,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool
+) {
+ return build_vamana_index_dispatcher().invoke(
+ build_params, VamanaSource{std::move(data)}, storage, distance_type, std::move(pool)
+ );
+}
+
+svs::Vamana dispatch_vamana_index_load(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ const std::filesystem::path& directory,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool
+) {
+ return build_vamana_index_dispatcher().invoke(
+ build_params, VamanaSource{directory}, storage, distance_type, std::move(pool)
+ );
+}
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/dispatcher_vamana.hpp b/bindings/c/src/dispatcher_vamana.hpp
new file mode 100644
index 00000000..90174dfe
--- /dev/null
+++ b/bindings/c/src/dispatcher_vamana.hpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "storage.hpp"
+#include "threadpool.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace svs::c_runtime {
+svs::Vamana dispatch_vamana_index_build(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ svs::data::ConstSimpleDataView data,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool
+);
+
+svs::Vamana dispatch_vamana_index_load(
+ const svs::index::vamana::VamanaBuildParameters& build_params,
+ const std::filesystem::path& directory,
+ const Storage* storage,
+ svs::DistanceType distance_type,
+ svs::threads::ThreadPoolHandle pool
+);
+
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/error.cpp b/bindings/c/src/error.cpp
new file mode 100644
index 00000000..f3d845f7
--- /dev/null
+++ b/bindings/c/src/error.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "svs/c_api/svs_c.h"
+
+#include "error.hpp"
+
+#include
+
+extern "C" svs_error_h svs_error_create() { return new svs_error_desc{SVS_OK, "Success"}; }
+extern "C" bool svs_error_ok(svs_error_h err) { return err->code == SVS_OK; }
+extern "C" svs_error_code_t svs_error_get_code(svs_error_h err) { return err->code; }
+extern "C" const char* svs_error_get_message(svs_error_h err) {
+ return err->message.c_str();
+}
+extern "C" void svs_error_free(svs_error_h err) { delete err; }
diff --git a/bindings/c/src/error.hpp b/bindings/c/src/error.hpp
new file mode 100644
index 00000000..1c183dd3
--- /dev/null
+++ b/bindings/c/src/error.hpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "svs/c_api/svs_c.h"
+
+#include
+#include
+
+#include
+
+// C API error structure
+struct svs_error_desc {
+ svs_error_code_t code;
+ std::string message;
+};
+
+#define SET_ERROR(err, c, msg) \
+ do { \
+ if (err) { \
+ (err)->code = (c); \
+ (err)->message = (msg); \
+ } \
+ } while (0)
+
+#define NOT_IMPLEMENTED_IF(cond, msg) \
+ do { \
+ if (cond) { \
+ throw svs::c_runtime::not_implemented(msg); \
+ } \
+ } while (0)
+
+#define INVALID_ARGUMENT_IF(cond, msg) \
+ do { \
+ if (cond) { \
+ throw std::invalid_argument(msg); \
+ } \
+ } while (0)
+
+#define EXPECT_ARG_IN_RANGE(arg, min_val, max_val) \
+ INVALID_ARGUMENT_IF( \
+ (arg) < (min_val) || (arg) > (max_val), \
+ #arg " should be in range [" #min_val ", " #max_val "]" \
+ )
+
+#define EXPECT_ARG_GT_THAN(arg, threshold) \
+ INVALID_ARGUMENT_IF((arg) <= (threshold), #arg " should be greater than " #threshold)
+
+#define EXPECT_ARG_GE_THAN(arg, threshold) \
+ INVALID_ARGUMENT_IF( \
+ (arg) < (threshold), #arg " should be greater than or equal to " #threshold \
+ )
+
+#define EXPECT_ARG_NOT_NULL(arg) \
+ INVALID_ARGUMENT_IF((arg) == nullptr, #arg " should not be NULL")
+
+#define EXPECT_ARG_IS_NULL(arg) \
+ INVALID_ARGUMENT_IF((arg) != nullptr, #arg " should be NULL")
+
+#define EXPECT_ARG_EQ_TO(actual, expected) \
+ INVALID_ARGUMENT_IF( \
+ (actual) != (expected), "Expected " #actual " to be equal to " #expected \
+ )
+
+#define EXPECT_ARG_NE_TO(actual, expected) \
+ INVALID_ARGUMENT_IF( \
+ (actual) == (expected), "Expected " #actual " to be not equal to " #expected \
+ )
+
+namespace svs::c_runtime {
+
+class not_implemented : public std::logic_error {
+ public:
+ using std::logic_error::logic_error;
+};
+
+class invalid_operation : public std::logic_error {
+ public:
+ using std::logic_error::logic_error;
+};
+
+class unsupported_hw : public std::runtime_error {
+ public:
+ using std::runtime_error::runtime_error;
+};
+
+// A helper to wrap C++ exceptions and convert them to C error codes/messages.
+template >
+Result wrap_exceptions(Callable&& func, svs_error_h err, Result err_res = {}) noexcept {
+ try {
+ SET_ERROR(err, SVS_OK, "Success");
+ return func();
+ } catch (const std::invalid_argument& ex) {
+ SET_ERROR(err, SVS_ERROR_INVALID_ARGUMENT, ex.what());
+ return err_res;
+ } catch (const svs::c_runtime::not_implemented& ex) {
+ SET_ERROR(err, SVS_ERROR_NOT_IMPLEMENTED, ex.what());
+ return err_res;
+ } catch (const svs::c_runtime::invalid_operation& ex) {
+ SET_ERROR(err, SVS_ERROR_INVALID_OPERATION, ex.what());
+ return err_res;
+ } catch (const svs::c_runtime::unsupported_hw& ex) {
+ SET_ERROR(err, SVS_ERROR_UNSUPPORTED_HW, ex.what());
+ return err_res;
+ } catch (const svs::lib::ANNException& ex) {
+ SET_ERROR(err, SVS_ERROR_GENERIC, ex.what());
+ return err_res;
+ } catch (const std::runtime_error& ex) {
+ SET_ERROR(err, SVS_ERROR_RUNTIME, ex.what());
+ return err_res;
+ } catch (const std::exception& ex) {
+ SET_ERROR(err, SVS_ERROR_UNKNOWN, ex.what());
+ return err_res;
+ } catch (...) {
+ SET_ERROR(err, SVS_ERROR_UNKNOWN, "An unknown error has occurred.");
+ return err_res;
+ }
+}
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/filtered_search.hpp b/bindings/c/src/filtered_search.hpp
new file mode 100644
index 00000000..b75f4e69
--- /dev/null
+++ b/bindings/c/src/filtered_search.hpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "svs/c_api/svs_c.h"
+
+#include "types_support.hpp"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace svs::c_runtime {
+
+/// @brief Estimate the batch size for filtered search based on the number of total
+/// candidates, hits, goal, hint, and limit.
+/// @param total The total number of candidates.
+/// @param hits The number of filter hits.
+/// @param goal The target number of hits to achieve.
+/// @param hint A hint for the batch size - usually based on prior knowledge.
+/// @param limit The maximum allowed batch size. E.g. index size.
+/// @return The estimated batch size.
+inline size_t
+estimate_batch_size(size_t total, size_t hits, size_t goal, size_t hint, size_t limit) {
+ assert(total >= hits);
+ assert(goal > 0);
+ if (total == 0 || hits == 0 || hits >= goal) {
+ return std::min(hint, limit);
+ }
+ const auto hit_rate_inv = static_cast(total) / static_cast(hits);
+ size_t estimated = static_cast(static_cast(goal - hits) * hit_rate_inv);
+ estimated = std::max(estimated, size_t{1});
+ return std::min(estimated, limit);
+}
+
+/// @brief Check if the actual hit rate is sufficient based on the minimum required filter
+/// rate.
+/// @param total The total number of candidates.
+/// @param hits The number of filter hits.
+/// @param filter_rate The minimum required filter rate.
+/// @return True if the hit rate is sufficient, false otherwise.
+inline bool hit_rate_sufficient(size_t total, size_t hits, float filter_rate) {
+ // by default, assume that the hit rate is sufficient
+ if (filter_rate <= 0.0f || total == 0) {
+ return true;
+ }
+ const auto hit_rate = static_cast(hits) / static_cast(total);
+ return hit_rate >= filter_rate;
+}
+
+/// @brief Estimate the initial batch size for filtered search based on the actual filter
+/// rate by generating sample IDs and filtering them through the ID filter.
+/// @param id_filter The ID filter interface.
+/// @param sample_generator A function that generates sample IDs.
+/// @param min_sample_size The minimum sample size to consider.
+/// @param goal The target number of hits to achieve - usually is K (from TopK).
+/// @param hint A hint for the batch size - usually based on prior knowledge.
+/// @param limit The maximum allowed batch size. E.g. index size.
+/// @return The estimated initial batch size, or 0 if the hit rate is insufficient.
+inline size_t estimate_initial_batch_size(
+ const IDFilterInterface* id_filter,
+ std::function sample_generator,
+ size_t min_sample_size,
+ size_t goal,
+ size_t hint,
+ size_t limit
+) {
+ assert(id_filter != nullptr);
+ const auto filter_rate = id_filter->filter_rate();
+ if (filter_rate <= 0.0f) {
+ // If filter rate is 0.0 or negative, return the `hint` as the initial batch size -
+ // clamped to `limit` to avoid oversizing.
+ return std::min(hint, limit);
+ }
+
+ auto sample_size =
+ std::max({goal, min_sample_size, static_cast(1.f / filter_rate)});
+ assert(sample_size > 0);
+ size_t hits = 0;
+ for (size_t i = 0; i < sample_size; ++i) {
+ size_t id = sample_generator();
+ // Stop if the sample generator returns an invalid ID
+ if (id == static_cast(-1)) {
+ sample_size = i; // Adjust sample size to the number of valid samples
+ break;
+ }
+ if (id_filter->is_member(id)) {
+ hits++;
+ }
+ }
+ // if hit rate is less than filter_rate, return 0 - which means we should not even start
+ // the search
+ if (!hit_rate_sufficient(sample_size, hits, filter_rate)) {
+ return 0;
+ }
+ return estimate_batch_size(sample_size, hits, goal, hint, limit);
+}
+
+/// @brief Pad the result with empty neighbors starting from a specific index.
+/// @param result The query result to pad.
+/// @param query_index The index of the query within the result.
+/// @param neighbor_start The starting index of neighbors to pad.
+inline void
+pad_result(svs::QueryResult& result, size_t query_index, size_t neighbor_start) {
+ assert(query_index < result.n_queries());
+ assert(neighbor_start <= result.n_neighbors());
+
+ static constexpr svs::Neighbor empty_neighbor{
+ static_cast(-1), std::numeric_limits::infinity()};
+
+ for (size_t i = neighbor_start; i < result.n_neighbors(); ++i) {
+ result.set(empty_neighbor, query_index, i);
+ }
+}
+
+/// @brief Set the query result to an empty state, with all distances set to infinity and
+/// all indices set to -1.
+/// @param result The query result to set as empty.
+inline void set_empty_result(svs::QueryResult& result) {
+ std::fill(
+ result.distances().begin(),
+ result.distances().end(),
+ std::numeric_limits::infinity()
+ );
+ std::fill(result.indices().begin(), result.indices().end(), static_cast(-1));
+}
+
+// Perform a filtered nearest-neighbor search by iterating over batches of candidates and
+// keeping only those that pass the filter. The batch size is estimated adaptively based on
+// the observed hit rate. Results are written into `results`.
+template
+void filtered_topk_search(
+ IndexType& index,
+ svs::QueryResult& results,
+ svs::data::ConstSimpleDataView queries,
+ size_t initial_batch_hint,
+ const IDFilterInterface* id_filter,
+ const std::function& sample_generator
+) {
+ // Minimum number of samples to estimate the filter hit rate. This is a trade-off
+ // between accuracy and performance. A larger sample size gives a more accurate
+ // estimate of the filter hit rate, but takes longer to compute.
+ const size_t MIN_SAMPLE_SIZE = 200;
+
+ // Filtered search: we need to estimate the batch size based on the filter rate and
+ // the number of hits
+ const auto num_neighbors = results.n_neighbors();
+ const auto index_size = index.size();
+
+ auto initial_batch_size = estimate_initial_batch_size(
+ id_filter,
+ sample_generator,
+ MIN_SAMPLE_SIZE,
+ num_neighbors,
+ initial_batch_hint,
+ index_size
+ );
+ if (initial_batch_size == 0) {
+ // If the batch size is 0, it means that the filter rate is too low than
+ // expected and we should not even start the search
+ set_empty_result(results);
+ return;
+ }
+
+ const auto filter_rate = id_filter->filter_rate();
+
+ auto search_closure = [&](const auto& range, uint64_t SVS_UNUSED(tid)) {
+ for (auto i : range) {
+ auto query = queries.get_datum(i);
+ auto iterator = index.batch_iterator(query);
+ size_t found = 0;
+ size_t total_checked = 0;
+ auto batch_size = initial_batch_size;
+ do {
+ batch_size = estimate_batch_size(
+ total_checked, found, num_neighbors, batch_size, index_size
+ );
+ iterator.next(batch_size);
+ total_checked += iterator.size();
+ for (auto& neighbor : iterator.results()) {
+ if (id_filter->is_member(neighbor.id())) {
+ results.set(neighbor, i, found);
+ found++;
+ if (found == num_neighbors) {
+ break;
+ }
+ }
+ }
+ // TODO: clarify the contract here - should we return partial or no
+ // result if the hit rate is too low
+ if (found < num_neighbors &&
+ !hit_rate_sufficient(total_checked, found, filter_rate)) {
+ found = 0;
+ break;
+ }
+ } while (found < num_neighbors && !iterator.done());
+
+ // Pad results if not enough neighbors found
+ pad_result(results, i, found);
+ }
+ };
+
+ svs::threads::parallel_for(
+ index.get_threadpool_handle(),
+ svs::threads::StaticPartition{queries.size()},
+ search_closure
+ );
+}
+
+} // namespace svs::c_runtime
diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp
new file mode 100644
index 00000000..59ae0184
--- /dev/null
+++ b/bindings/c/src/index.hpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2026 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "svs/c_api/svs_c.h"
+
+#include "algorithm.hpp"
+#include "filtered_search.hpp"
+#include "threadpool.hpp"
+
+#include