This is the full developer documentation for Searchcraft # What Is Searchcraft? Searchcraft is a highly performant, content-optimized search engine written in Rust. It aims to be faster, easier to implement, and easier to manage than Elasticsearch, OpenSearch, Agolia and Solr with more accurate results for human-oriented content. ## Why Did We Build Searchcraft? [Section titled "Why Did We Build Searchcraft?"](#why-did-we-build-searchcraft) Search is notoriously difficult and expensive to integrate. Competing solutions require large amounts of compute resources and have a steep learning curve for developers. Our background was originally as technology consultants and we were often tasked with building search using these competing solutions for start-ups that did not have huge budgets and had get to market quickly. None of the existing solutions met those needs. Developers integrating search will either need to already possess specialized knowledge or spend time in training to learn about search. Searchcraft aims to make the developer experience smoother with a lower cost and better performance. Companies should focus on building features that make their products unique rather than building search. Searchcraft's focus is providing a solution for human-oriented, content search rather than a one-size fits all solution for problems like logs and machine data. # Authentication Most of the API endpoints for Searchcraft Cloud require authentication. For self-hosted instances of Searchcraft, authentication is optional and may be configured in the CLI options. These endpoints are only available to self-hosted instances of Searchcraft, Searhcraft Cloud customers only interact with these via the Vektron application. Requires an admin level authentication key to access. See the [CLI documentation](/tools/self-hosted-cli/) for more information. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /auth/application/:application_id` Returns a list of all keys for the application. * `GET /auth/federation/:federation_name` Returns a list of all keys associated with a federation entity. * `GET /auth/index/:index_name` Returns a list of all keys associated with a specific index. * `GET /auth/organization/:organization_id` Returns a list of all keys for the organization. * `GET /auth/key` Returns a list of all keys on the cluster. * `GET /auth/key/check/:permission` Checks whether the provided key has the specified permission. The `:permission` path parameter is the numeric value of the permission bit(s) to check (e.g. `1` for `SEARCH`, `8` for `MODIFY_AUTH`). Returns `200` with `"Key has sufficient permissions."` when the key holds all requested bits, or `403` with `"Key has insufficient permissions."` otherwise. Unlike other auth endpoints, this route always requires a valid `Authorization` header even when the server runs without auth enabled. * `POST /auth/key` Create a key. * Payload format: ```json { "allowed_indexes": ["index_name_1", "index_name_2"], // may contain one to several index names "permissions": 1, // 1 = read, 15 = ingest, 63 = admin. "name": "Name Of This Key", "organization_id": 0, "organization_name": "My Org Name", "application_id": 1, "application_name": "My App Name", "status": "active | inactive", "federation_name": "1_rest_name", // * optional and only needed for read keys associated with a federation } ``` * `DELETE /auth/key` Delete all keys. * `GET /auth/key/:key` Get an individual key. * `DELETE /auth/key/:key` Delete an individual key. * `POST /auth/key/:key` Update an individual key. ## Notes [Section titled "Notes"](#notes) * Organization and application IDs are used within Searchcraft cloud but are available for use in self-hosted instances at the customer's discretion. * The permissions identifiers may change in future releases of Searchcraft as more capabilities are added to the API. # Client Libraries Overview > A list of the available client libraries for Searchcraft. Searchcraft's client libraries provide a convenient way to interact with the Searchcraft API from your application. They wrap the API requests in a simple to use interface, providing type safety, unified error handling, centralized configuration and more. They bring the full breath of the Searchcraft API directly into your application without need to manually construct your REST calls and worry about duplication of request configuration. The client libraries saves developers from having to write boilerplate code to interact with the API. ## Available Libraries [Section titled "Available Libraries"](#available-libraries) The following client libraries are available for Searchcraft: * [PHP](/api/client-libraries/php/) * [Rust](/api/client-libraries/rust/) * [TypeScript / JavaScript](/api/client-libraries/typescript/) ## License [Section titled "License"](#license) All API client libraries are licensed under the Apache 2.0 license. # Searchcraft API PHP Client Library > A PHP client library for the Searchcraft API. The [Searchcraft API PHP Client](https://github.com/searchcraft-inc/searchcraft-client-php) is a fully PSR-compatible PHP client that acts as a language specific wrapper around the Searchcraft API. ## Prerequisites [Section titled "Prerequisites"](#prerequisites) To use the client library you will need to have the following installed: * PHP 8.0 or higher * Composer ## Installation [Section titled "Installation"](#installation) ```bash composer require searchcraft/searchcraft-php ``` You will also need to install a PSR-18 compatible HTTP client, such as [Guzzle](https://github.com/guzzle/guzzle). ```bash composer require guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0 ``` ## Basic Usage [Section titled "Basic Usage"](#basic-usage) ### Initialize the client [Section titled "Initialize the client"](#initialize-the-client) The client can be initialized with different types of API keys, depending on your access requirements: ```php use Searchcraft\Searchcraft; // Using an admin key (full access) $searchcraft_full = new Searchcraft('your-admin-key', Searchcraft::KEY_TYPE_ADMIN); // Using a read-only key (search and read operations only) $searchcraft_reader = new Searchcraft('your-read-key', Searchcraft::KEY_TYPE_READ); // Using an ingest key (document operations only) $searchcraft_ingestion = new Searchcraft('your-ingest-key', Searchcraft::KEY_TYPE_INGEST); ``` By default, the client connects to `http://localhost:8000`. To use a different endpoint such as a Searchcraft Cloud cluster: ```php // Replace with your cluster endpoint $searchcraft = new Searchcraft( 'your-api-key', Searchcraft::KEY_TYPE_ADMIN, 'https://yourcluster.io' ); ``` ### Using PSR-18 HTTP Client [Section titled "Using PSR-18 HTTP Client"](#using-psr-18-http-client) The client uses [PSR-18 HTTP Client discovery](https://github.com/php-http/discovery) to find an available HTTP client. You can also provide your own: ```php use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; $httpClient = new Client(); $requestFactory = new HttpFactory(); $streamFactory = new HttpFactory(); $searchcraft = new Searchcraft( 'your-api-key', Searchcraft::KEY_TYPE_ADMIN, 'https://api.searchcraft.io/v1', $httpClient, $requestFactory, $streamFactory ); ``` Searchcraft PHP Client does not include a specific HTTP client in its `require` dependencies but Guzzle is recommended. You will need to install one in order to use the Searchcraft client. ## Search Operations [Section titled "Search Operations"](#search-operations) Search operations require an admin key or read key. ### Basic Search [Section titled "Basic Search"](#basic-search) ```php // Simple search query $results = $searchcraft->search()->query('my_index', 'search term'); // Search with additional parameters $results = $searchcraft->search()->query('my_index', 'search term', [ 'limit' => 20, 'offset' => 0, 'sort' => 'price:asc', 'mode' => 'fuzzy' ]); // Fuzzy search (default) $results = $searchcraft->search()->query('my_index', 'search term'); // Explicit fuzzy search $results = $searchcraft->search()->query('my_index', 'search term', ['mode' => 'fuzzy']); // Exact search $results = $searchcraft->search()->query('my_index', 'search term', ['mode' => 'exact']); ``` ### Federation Search [Section titled "Federation Search"](#federation-search) ```php // Search across all indexes in a federation using federatedQuery $searchResults = $searchcraft->search()->federatedQuery('my_federation', 'breaking news', [ 'limit' => 20, 'offset' => 0, 'mode' => 'fuzzy' ]); // Federation search with additional options $searchResults = $searchcraft->search()->federatedQuery('my_federation', 'search term', [ 'limit' => 50, 'offset' => 10, 'order_by' => 'publishedAt', 'sort' => 'desc', 'occur' => 'should', 'mode' => 'exact' ]); ``` ## Index Operations [Section titled "Index Operations"](#index-operations) Index administration operations require an admin key. ### List Indexes [Section titled "List Indexes"](#list-indexes) ```php $indexes = $searchcraft->index()->listIndexes(); ``` ### Get Index Details [Section titled "Get Index Details"](#get-index-details) ```php $indexDetails = $searchcraft->index()->getIndex('products'); ``` ### Create Index [Section titled "Create Index"](#create-index) ```php $newIndex = $searchcraft->index()->createIndex('blog', [ 'index' => [ 'name' => 'blog', 'language' => 'en', 'search_fields' => ['title', 'content', 'tags'], 'fields' => [ 'id' => [ 'type' => 'text', 'required' => true, 'stored' => true, 'indexed' => false ], 'title' => [ 'type' => 'text', 'stored' => true ], 'content' => [ 'type' => 'text', 'stored' => true ], 'tags' => [ 'type' => 'text', 'stored' => true, 'multi' => true ], 'category' => [ 'type' => 'facet', 'stored' => true ], 'publishedAt' => [ 'type' => 'datetime', 'fast' => true, 'stored' => true, 'indexed' => true ] ], 'weight_multipliers' => [ 'title' => 2.0, 'tags' => 1.0, 'content' => 0.6 ] ] ]); ``` ### Update Index [Section titled "Update Index"](#update-index) Note if you are not adding, removing or changing properties of schema fields will likely want to use the `PATCH` operation instead. An update request will remove existing documents but for patchable updates your index is not emptied. See the [docs](https://docs.searchcraft.io/api/schema/?utm_campaign=oss\&utm_source=github\&utm_medium=searchcraft-php-client) for details on which properties are patchable. ```php $updatedIndex = $searchcraft->index()->updateIndex('blog', [ 'index' => [ 'name' => 'blog', 'language' => 'en', 'search_fields' => ['title', 'content', 'tags', 'summary'], 'fields' => [ 'id' => [ 'type' => 'text', 'required' => true, 'stored' => true, 'indexed' => false ], 'title' => [ 'type' => 'text', 'stored' => true ], 'content' => [ 'type' => 'text', 'stored' => true ], 'summary' => [ 'type' => 'text', 'stored' => true ], 'tags' => [ 'type' => 'text', 'stored' => true, 'multi' => true ], 'category' => [ 'type' => 'facet', 'stored' => true ], 'publishedAt' => [ 'type' => 'datetime', 'fast' => true, 'stored' => true, 'indexed' => true ] ], 'weight_multipliers' => [ 'title' => 2.0, 'content' => 1.0, 'summary' => 1.5 ] ] ]); ``` ### Patch Index [Section titled "Patch Index"](#patch-index) ```php $patchedIndex = $searchcraft->index()->patchIndex('blog', [ 'search_fields' => ['title', 'content', 'tags', 'summary'], 'weight_multipliers' => [ 'title' => 3.0, 'content' => 1.0, 'summary' => 1.5, 'tags' => 0.8 ], 'language' => 'en', 'time_decay_field' => 'publishedAt', 'auto_commit_delay' => 2, 'exclude_stop_words' => true ]); ``` ### Delete Index [Section titled "Delete Index"](#delete-index) ```php $result = $searchcraft->index()->deleteIndex('my-index'); ``` ## Document Operations [Section titled "Document Operations"](#document-operations) Document operations require an admin key or ingest key. ### Add Documents [Section titled "Add Documents"](#add-documents) ```php $result = $searchcraft->index()->addDocuments('products', [ [ 'id' => '1', 'name' => 'Smartphone X', 'price' => 699.99, 'category' => 'Electronics', 'brand' => 'BrandName' ], [ 'id' => '2', 'name' => 'Laptop Pro', 'price' => 1299.99, 'category' => 'Electronics', 'brand' => 'BrandName' ] ]); ``` ### Update Documents [Section titled "Update Documents"](#update-documents) ```php $result = $searchcraft->index()->updateDocuments('products', [ [ 'id' => '1', 'price' => 649.99, 'in_stock' => true ] ]); ``` ### Get Document [Section titled "Get Document"](#get-document) ```php $document = $searchcraft->index()->getDocument('products', '1'); ``` ### Delete Documents [Section titled "Delete Documents"](#delete-documents) ```php $result = $searchcraft->index()->deleteDocuments('products', ['1', '2']); ``` ## Federation Operations [Section titled "Federation Operations"](#federation-operations) Federation operations allow you to manage federations that combine multiple indexes for cross-index search. Federation administration operations require an admin key, while federation search requires a read key. ### List Federations [Section titled "List Federations"](#list-federations) ```php // List all federations $federations = $searchcraft->federation()->listFederations(); ``` ### Get Federation Details [Section titled "Get Federation Details"](#get-federation-details) ```php // Get details of a specific federation $federation = $searchcraft->federation()->getFederation('galaxy_news_federation'); ``` ### Get Federations by Organization [Section titled "Get Federations by Organization"](#get-federations-by-organization) ```php // Get all federations for a specific organization $organizationFederations = $searchcraft->federation()->getFederationsByOrganization('4'); ``` ### Create Federation [Section titled "Create Federation"](#create-federation) ```php // Create a new federation with weighted index configurations $newFederation = $searchcraft->federation()->createFederation([ 'name' => '4_galaxy_news_test', 'friendly_name' => 'Galaxy News Test Federation', 'created_by' => '1', 'last_modified_by' => '1', 'organization_id' => '4', 'index_configurations' => [ [ 'name' => 'news_articles', 'weight_multiplier' => 1.0 ], [ 'name' => 'blog_posts', 'weight_multiplier' => 0.8 ], [ 'name' => 'press_releases', 'weight_multiplier' => 1.5 ] ] ]); ``` ### Update Federation [Section titled "Update Federation"](#update-federation) ```php // Update an existing federation $updatedFederation = $searchcraft->federation()->updateFederation('galaxy_news_federation', [ 'friendly_name' => 'Updated Galaxy News Federation', 'last_modified_by' => '1', 'organization_id' => '4', 'index_configurations' => [ [ 'name' => 'news_articles', 'weight_multiplier' => 1.2 ], [ 'name' => 'blog_posts', 'weight_multiplier' => 0.9 ], [ 'name' => 'press_releases', 'weight_multiplier' => 1.8 ], [ 'name' => 'social_media', 'weight_multiplier' => 0.6 ] ] ]); ``` ### Delete Federation [Section titled "Delete Federation"](#delete-federation) ```php // Delete a federation $result = $searchcraft->federation()->deleteFederation('old_federation'); ``` ## Error Handling [Section titled "Error Handling"](#error-handling) All operations should be wrapped in a try/catch block to handle errors: ```php use Searchcraft\Exception\SearchcraftException; try { $results = $searchcraft->search()->query('products', 'smartphone'); } catch (SearchcraftException $e) { echo 'Error: ' . $e->getMessage(); } ``` ## Feature Requests / Issues [Section titled "Feature Requests / Issues"](#feature-requests--issues) If you encounter any issues with the client library or wish to request additional features please [open an issue](https://github.com/searchcraft-inc/searchcraft-issues/issues/new/choose) att the Searchraft Issues repository. ## License and Source code [Section titled "License and Source code"](#license-and-source-code) The Searchcraft API PHP client is Apache 2.0 licensed and the source code is available on [Github](https://github.com/searchcraft-inc/searchcraft-client-php). # Searchcraft API Rust Client Library > An async Rust client library for the Searchcraft API. The [Searchcraft API Rust Client](https://github.com/searchcraft-inc/searchcraft-client-rust) is an async, fully typed client that acts as a language-specific wrapper around the Searchcraft API. ## Features [Section titled "Features"](#features) * Async throughout, built on [`reqwest`](https://docs.rs/reqwest). * Fully typed requests and responses, with no untyped JSON in the public API. * Composable, immutable query builder. * Support for every Searchcraft query kind (fuzzy, exact, dynamic, term, more-like-this) and operation. * Full index, document, federation, synonym, and stopword management. * Streaming AI search summaries over Server-Sent Events. * `rustls` by default, with an optional `native-tls` backend. * API keys held in [`secrecy`](https://docs.rs/secrecy) wrappers so they are not logged by accident. ## Installation [Section titled "Installation"](#installation) ```bash cargo add searchcraft ``` Or add it to your `Cargo.toml` directly: ```toml [dependencies] searchcraft = "0.1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` The client is async and does not bundle a runtime, so you need one of your own. The examples here use [Tokio](https://tokio.rs). ### TLS backends [Section titled "TLS backends"](#tls-backends) `rustls` is enabled by default. To use the platform's native TLS instead: ```toml [dependencies] searchcraft = { version = "0.1", default-features = false, features = ["native-tls"] } ``` The minimum supported Rust version is **1.75**. ## Quick Start [Section titled "Quick Start"](#quick-start) ```rust use searchcraft::SearchcraftClient; use searchcraft::search::query::QueryBuilder; #[tokio::main] async fn main() -> Result<(), searchcraft::Error> { // Initialize the client let client = SearchcraftClient::new( "https://your-searchcraft-instance.com", Some("your-read-key"), None::, )?; // Perform a search let request = QueryBuilder::fuzzy().term("search query").limit(10).build_request(); let response = client .search_index::("your-index", &request) .await?; println!("Found {} results", response.data.count); for hit in &response.data.hits { println!("{}: {:?}", hit.document_id, hit.doc); } Ok(()) } ``` ## Usage [Section titled "Usage"](#usage) ### Client Initialization [Section titled "Client Initialization"](#client-initialization) For a read-only client, `SearchcraftClient::new` takes the endpoint and up to two keys: ```rust use searchcraft::SearchcraftClient; let client = SearchcraftClient::new( "https://your-instance.com", Some("your-read-key"), Some("your-ingest-key"), // Optional, for document operations )?; ``` At least one of the read or ingest key must be supplied. For an admin key, a custom timeout, or extra headers, build a `Config` instead: ```rust use std::time::Duration; use searchcraft::{Config, SearchcraftClient}; let config = Config::new( "https://your-instance.com", Some("your-read-key"), Some("your-ingest-key"), )? .with_admin_key("your-admin-key") // Only needed for self-hosted clusters .with_timeout(Duration::from_secs(30)) // Optional, default 30 seconds // Optional Searchcraft headers for analytics and tracking: .with_header("X-Sc-User-Id", "user-123") .with_header("X-Sc-Session-Id", "session-abc") .with_header("X-Sc-User-Type", "authenticated"); let client = SearchcraftClient::from_config(config)?; ``` The client picks the right key per operation, so you never pass a key to an individual call. `SearchcraftClient` is cheap to clone and holds a pooled connection — build it once and share it rather than creating one per request. > **Note:** The admin key is only required for self-hosted Searchcraft clusters. If you're using Searchcraft Cloud, you do not need to provide one. ### Basic Search [Section titled "Basic Search"](#basic-search) ```rust use searchcraft::search::query::QueryBuilder; // Fuzzy search (typo-tolerant, uses weight ranking, synonyms and stopwords) let fuzzy_query = QueryBuilder::fuzzy().term("search term").limit(20).build_request(); // Exact search let exact_query = QueryBuilder::exact().term("exact match").build_request(); // Dynamic search (adapts based on word count) let dynamic_query = QueryBuilder::dynamic().term("adaptive search").build_request(); let response = client .search_index::("your-index", &fuzzy_query) .await?; ``` Search results deserialize into any type implementing `serde::Deserialize`, so you can work with your own structs instead of raw JSON: ```rust #[derive(serde::Deserialize)] struct Product { title: String, price: f64, } let response = client.search_index::("products", &request).await?; for hit in &response.data.hits { println!("{} — ${}", hit.doc.title, hit.doc.price); } ``` ### Query Builder [Section titled "Query Builder"](#query-builder) The query builder provides a fluent, immutable API for constructing complex queries. Each method returns a new builder, leaving the original unchanged. > **📚 Full Query Syntax Documentation:** For complete details on the Searchcraft query language, operators, and advanced features, see [Using the Searchcraft Query Language](/api/search/#using-the-searchcraft-query-language). ```rust use searchcraft::search::query::QueryBuilder; use searchcraft::types::SortDirection; let request = QueryBuilder::fuzzy() .term("laptop") .and("gaming") .not("refurbished") .order_by("price", SortDirection::Asc) .limit(20) .build_request(); ``` ### Field Queries [Section titled "Field Queries"](#field-queries) ```rust // Match a single field let request = QueryBuilder::exact().field("category", "electronics").build_request(); // Match any of several values let request = QueryBuilder::exact() .field_in("status", &["active", "pending"]) .build_request(); ``` ### Range and Comparison Queries [Section titled "Range and Comparison Queries"](#range-and-comparison-queries) ```rust use searchcraft::search::query::CompareOp; // Inclusive range: price:[10 TO 100] let request = QueryBuilder::exact().range("price", 10, 100, true).build_request(); // Exclusive range: price:{10 TO 100} let request = QueryBuilder::exact().range("price", 10, 100, false).build_request(); // Comparisons: Gt, Lt, Gte, Lte let request = QueryBuilder::exact() .compare("price", CompareOp::Gte, 50) .build_request(); ``` ### Boolean Operators [Section titled "Boolean Operators"](#boolean-operators) ```rust let request = QueryBuilder::fuzzy() .term("laptop") .and("gaming") // AND .or("notebook") // OR .not("refurbished") // NOT .group("(ssd OR nvme)") // Grouping .build_request(); ``` ### Pagination and Sorting [Section titled "Pagination and Sorting"](#pagination-and-sorting) ```rust use searchcraft::types::SortDirection; let request = QueryBuilder::fuzzy() .term("laptop") .limit(20) .offset(40) // Third page of 20 .order_by("created_at", SortDirection::Desc) .build_request(); ``` The engine clamps `limit` to the instance's configured maximum (200 unless the operator raised it) rather than rejecting the request. > **Note:** Ordering by a field means results carry no relevance score, so `hit.score` is `None` for those queries. ### Federated Search [Section titled "Federated Search"](#federated-search) Search several indices at once through a federation: ```rust let request = QueryBuilder::fuzzy().term("laptop").limit(10).build_request(); let response = client .search_federation::("your-federation", &request) .await?; for hit in &response.data.hits { // source_index tells you which index each hit came from println!("[{}] {}", hit.source_index, hit.document_id); } ``` ### Advanced: Raw Query Objects [Section titled "Advanced: Raw Query Objects"](#advanced-raw-query-objects) The builder covers most cases, but you can construct requests directly for anything it does not express — including the `term` and `more-like-this` query kinds, field-scoped fuzzy matching, and `minimum_number_should_match`: ```rust use searchcraft::search::types::{ FieldSelector, OccurMode, QueryPayload, SearchQuery, SearchRequest, }; let request = SearchRequest { query: QueryPayload::Multiple(vec![ SearchQuery::fuzzy("laptop") .with_fields(FieldSelector::Multi(vec!["title".into(), "body".into()])) .with_occur(OccurMode::Must), SearchQuery::term("electronics", "category").with_occur(OccurMode::Must), SearchQuery::exact("refurbished").with_occur(OccurMode::MustNot), ]), limit: Some(20), offset: None, order_by: None, sort: None, time_decay_field: None, index_weighting: None, minimum_number_should_match: Some(1), }; let response = client.search_index::("products", &request).await?; ``` Find documents similar to an existing one using its internal Searchcraft ID: ```rust let request = SearchRequest { query: QueryPayload::Single(SearchQuery::more_like_this("12345")), limit: Some(10), ..request }; ``` ### AI Search Summaries [Section titled "AI Search Summaries"](#ai-search-summaries) On engine 0.10.0 and later, `search_summary` streams an LLM-generated summary of a result set as it is produced. Check the index's capabilities first — the endpoint requires AI features to be enabled and a key with summary permissions: ```rust use searchcraft::search::StreamExt; use searchcraft::search::types::SummaryStreamEvent; let capabilities = client.get_index_capabilities("products").await?; if capabilities.ai.enabled && capabilities.ai.search_summary_configured { let request = QueryBuilder::fuzzy().term("laptop").build_request(); let mut stream = client.search_summary("products", &request).await?; while let Some(event) = stream.next().await { match event { SummaryStreamEvent::Metadata(m) => { println!("Summarizing {} results (cached: {})", m.results_count, m.cached); } SummaryStreamEvent::Delta(d) => print!("{}", d.content), SummaryStreamEvent::Done(d) => println!("\nDone ({} results)", d.results_count), SummaryStreamEvent::Error(e) => eprintln!("Summary failed: {}", e.message), } } } ``` Malformed frames and mid-stream connection failures arrive as `Error` events rather than ending the stream. Unlike other requests, the configured timeout bounds only connection setup here, so a slow generation is never cut short. ### Document Management [Section titled "Document Management"](#document-management) ```rust use serde_json::json; // Insert a single document — it must include an `id` field let doc = json!({ "id": "doc-1", "title": "Gaming Laptop", "price": 999.99 }); client.insert_document("products", &doc).await?; // Batch insert let docs = vec![ json!({ "id": "doc-2", "title": "Ultrabook" }), json!({ "id": "doc-3", "title": "Workstation" }), ]; client.batch_insert_documents("products", &docs).await?; // Delete by your own `id` field let result = client.delete_document("products", "doc-1").await?; println!("Removed {} documents", result.num_removed); // Delete several at once client.batch_delete_documents("products", &["doc-2", "doc-3"]).await?; // Delete by Searchcraft's internal document ID (the `document_id` on a search hit) client.delete_document_by_internal_id("products", "12345").await?; // Delete every document, leaving the index in place client.delete_all_documents("products").await?; // Fetch one document by its internal ID let hit = client.get_document::("products", "12345").await?; ``` Writes are buffered until committed. An index's `auto_commit_delay` commits them on a timer, or you can commit immediately: ```rust client.commit_transaction("products").await?; // Make buffered writes searchable client.rollback_transaction("products").await?; // Discard them instead ``` ### Index Management [Section titled "Index Management"](#index-management) ```rust use std::collections::HashMap; use searchcraft::admin::types::{FieldConfig, FieldType, IndexConfig}; // List every index let indices = client.list_indices().await?; println!("{:?}", indices.index_names); // Create an index let config = IndexConfig { language: Some("en".into()), search_fields: Some(vec!["title".into(), "body".into()]), fields: Some(HashMap::from([ ( "title".to_string(), FieldConfig { stored: Some(true), required: Some(true), ..FieldConfig::new(FieldType::Text) }, ), ( "price".to_string(), FieldConfig { fast: Some(true), ..FieldConfig::new(FieldType::F64) }, ), ])), ..Default::default() }; client.create_index("products", &config).await?; // Read the current configuration let current = client.get_index("products").await?; // Patch specific settings, leaving everything else alone let patch = IndexConfig { auto_commit_delay: Some(5000), ..Default::default() }; client.update_index("products", &patch).await?; // Stats let stats = client.get_index_stats("products").await?; println!("Documents: {}", stats.document_count); let all_stats = client.get_all_index_stats().await?; println!("Total documents: {}", all_stats.total_document_count); // Delete the index and everything in it client.delete_index("products").await?; ``` Field types are `Text`, `Facet`, `Bool`, `F64`, `U64`, `I64`, `Datetime`, and `Json`. Every option on `FieldConfig` is optional — anything left unset takes the engine's default. > **Note:** The patch endpoint cannot change schema fields, and treats an empty `search_fields`, `weight_multipliers`, or `language` as "leave unchanged" rather than a reset. Use `replace_index` to change the schema or clear those values. To overwrite an existing index on create, use `create_index_overwriting`. ### Federation Management [Section titled "Federation Management"](#federation-management) ```rust use searchcraft::admin::types::FederationRequest; // List federations let federations = client.list_federations().await?; // Create one over several indices, each weighted equally let request = FederationRequest::new("global", "Global", ["products", "articles"]); client.create_federation(&request).await?; // Inspect let federation = client.get_federation("global").await?; let index_names = client.list_federation_indices("global").await?; let stats = client.get_federation_stats("global").await?; println!("{} documents across {} indices", stats.num_docs, stats.indices.len()); // Update or delete client.update_federation("global", &request).await?; client.delete_federation("global").await?; ``` ### Synonyms [Section titled "Synonyms"](#synonyms) Synonym entries use the format `"synonym:original-term"`. ```rust let synonyms = client.get_synonyms("products").await?; client.add_synonyms("products", &["notebook:laptop"]).await?; client.delete_synonyms("products", &["notebook:laptop"]).await?; client.delete_all_synonyms("products").await?; ``` ### Stopwords [Section titled "Stopwords"](#stopwords) ```rust // The index's current list let stopwords = client.get_stopwords("products").await?; // The engine's built-in defaults for the index language let defaults = client.get_default_stopwords("products").await?; client.add_stopwords("products", &["the", "and"]).await?; client.delete_stopwords("products", &["the"]).await?; client.delete_all_stopwords("products").await?; ``` ### Authentication Keys [Section titled "Authentication Keys"](#authentication-keys) Key management is available on self-hosted clusters and requires an admin key. ```rust use searchcraft::admin::types::{permissions, AuthKeyPermission, CreateAuthKeyRequest}; // Permissions are a bitmask. Named presets cover the common cases. let request = CreateAuthKeyRequest::new("web-frontend", AuthKeyPermission::READ, ["products"]); let created = client.create_auth_key(&request).await?; println!("New key: {}", created.token); // Or build an exact mask from individual bits let custom = AuthKeyPermission::from_bits(permissions::SEARCH | permissions::READ_ANALYTICS); assert!(custom.contains(permissions::READ_ANALYTICS)); // List keys, optionally scoped by owner let all_keys = client.list_auth_keys().await?; let index_keys = client.list_index_auth_keys("products").await?; let app_keys = client.list_application_auth_keys("app-1").await?; // Look one up — returns None when it does not exist if let Some(key) = client.get_auth_key("sc-abc").await? { println!("{:?}", key.permissions); } // Ask whether the configured admin key carries a permission let can_read_analytics = client .check_auth_key_permissions(permissions::READ_ANALYTICS) .await?; ``` The presets are `READ` (search only), `INGEST` (search plus document, synonym, and stopword writes), `ADMIN` (adds index and key management), and `SUPER_USER` (everything, including AI and analytics). ### Analytics [Section titled "Analytics"](#analytics) Measure records search and click telemetry. Analytics must be configured server-side, so check the status first — the write endpoints silently succeed as no-ops when it is disabled. ```rust use searchcraft::admin::types::{ event_names, MeasureDashboardParams, MeasureEvent, MeasureQueryGranularity, MeasureRequestProperties, MeasureRequestUser, }; let status = client.get_measure_status().await?; if status.enabled { let event = MeasureEvent::new( event_names::DOCUMENT_CLICKED, MeasureRequestProperties { external_document_id: Some("doc-1".into()), document_position: Some(3), ..MeasureRequestProperties::new(["products"]) }, MeasureRequestUser::new("user-42"), ); client.track_measure_event(&event).await?; } // Dashboard reports accept filters for scope, date range, granularity and paging let params = MeasureDashboardParams { organization_id: Some("org-1".into()), index_names: vec!["products".into()], date_start: Some(1_700_000_000), granularity: Some(MeasureQueryGranularity::Days), ..Default::default() }; let summary = client.get_measure_dashboard_summary(¶ms).await?; ``` `track_measure_batch` sends several events in one request, and `get_measure_dashboard_conversion` and `get_measure_dashboard_usage` return the other two reports. ### Health Check [Section titled "Health Check"](#health-check) ```rust let health = client.health_check().await?; println!("Status: {}", health.status); // 200 println!("Message: {}", health.data); // "Searchcraft is healthy." ``` ## API Reference [Section titled "API Reference"](#api-reference) All methods are `async` and return `searchcraft::error::Result`. ### Client [Section titled "Client"](#client) | Method | Description | | --------------------------------------------------- | --------------------------------------------------- | | `SearchcraftClient::new(url, read_key, ingest_key)` | Create a client from an endpoint and up to two keys | | `SearchcraftClient::from_config(config)` | Create a client from a `Config` | | `client.config()` | The configuration the client was built with | ### Search [Section titled "Search"](#search) | Method | Returns | | --------------------------------------------- | -------------------------------- | | `search_index::(index, request)` | `SearchResponse` | | `search_federation::(federation, request)` | `SearchResponse` | | `search_summary(index, request)` | A stream of `SummaryStreamEvent` | ### Documents [Section titled "Documents"](#documents) | Method | Returns | | ---------------------------------------------------- | ------------------------ | | `insert_document(index, document)` | `String` | | `batch_insert_documents(index, documents)` | `String` | | `delete_document(index, document_id)` | `DocumentDeleteResponse` | | `batch_delete_documents(index, document_ids)` | `DocumentDeleteResponse` | | `delete_document_by_internal_id(index, internal_id)` | `String` | | `delete_all_documents(index)` | `String` | | `get_document::(index, internal_id)` | `SearchHit` | ### Indices [Section titled "Indices"](#indices) | Method | Returns | | ----------------------------------------- | ----------------------- | | `list_indices()` | `IndexListResponse` | | `get_index(index)` | `IndexConfig` | | `create_index(index, config)` | `String` | | `create_index_overwriting(index, config)` | `String` | | `replace_index(index, config)` | `String` | | `update_index(index, config)` | `String` | | `delete_index(index)` | `String` | | `get_index_stats(index)` | `IndexStats` | | `get_all_index_stats()` | `AllIndexStatsResponse` | | `get_index_capabilities(index)` | `IndexCapabilities` | ### Federations [Section titled "Federations"](#federations) | Method | Returns | | ------------------------------------------ | ------------------- | | `list_federations()` | `Vec` | | `list_federations_by_organization(org_id)` | `Vec` | | `get_federation(federation)` | `Federation` | | `create_federation(request)` | `serde_json::Value` | | `update_federation(federation, request)` | `serde_json::Value` | | `delete_federation(federation)` | `String` | | `list_federation_indices(federation)` | `Vec` | | `get_federation_stats(federation)` | `FederationStats` | ### Synonyms and Stopwords [Section titled "Synonyms and Stopwords"](#synonyms-and-stopwords) | Method | Returns | | ------------------------------------ | ------------- | | `get_synonyms(index)` | `SynonymsMap` | | `add_synonyms(index, synonyms)` | `String` | | `delete_synonyms(index, synonyms)` | `String` | | `delete_all_synonyms(index)` | `String` | | `get_stopwords(index)` | `Vec` | | `get_default_stopwords(index)` | `Vec` | | `add_stopwords(index, stopwords)` | `String` | | `delete_stopwords(index, stopwords)` | `String` | | `delete_all_stopwords(index)` | `String` | ### Transactions [Section titled "Transactions"](#transactions) | Method | Returns | | ----------------------------- | -------- | | `commit_transaction(index)` | `String` | | `rollback_transaction(index)` | `String` | ### Authentication [Section titled "Authentication"](#authentication) | Method | Returns | | --------------------------------------- | ----------------- | | `list_auth_keys()` | `Vec` | | `get_auth_key(key)` | `Option` | | `create_auth_key(request)` | `AuthKey` | | `update_auth_key(key, request)` | `AuthKey` | | `delete_auth_key(key)` | `String` | | `delete_all_auth_keys()` | `String` | | `list_application_auth_keys(app_id)` | `Vec` | | `list_organization_auth_keys(org_id)` | `Vec` | | `list_federation_auth_keys(federation)` | `Vec` | | `list_index_auth_keys(index)` | `Vec` | | `check_auth_key_permissions(bits)` | `bool` | ### Measure and Health [Section titled "Measure and Health"](#measure-and-health) | Method | Returns | | ------------------------------------------ | --------------------- | | `get_measure_status()` | `MeasureStatus` | | `track_measure_event(event)` | `String` | | `track_measure_batch(events)` | `String` | | `get_measure_dashboard_summary(params)` | `serde_json::Value` | | `get_measure_dashboard_conversion(params)` | `serde_json::Value` | | `get_measure_dashboard_usage(params)` | `serde_json::Value` | | `health_check()` | `HealthCheckResponse` | ## Error Handling [Section titled "Error Handling"](#error-handling) Every fallible call returns `searchcraft::error::Result`, whose error type separates the cases you are likely to handle differently: ```rust use searchcraft::Error; match client.search_index::("products", &request).await { Ok(response) => println!("{} hits", response.data.count), Err(Error::Authentication { status, .. }) => eprintln!("Auth failed ({status})"), Err(Error::NotFound(message)) => eprintln!("Missing: {message}"), Err(Error::Validation { message, field }) => { eprintln!("Invalid request: {message} ({field:?})") } Err(e) if e.is_retryable() => eprintln!("Transient failure, retry: {e}"), Err(e) => eprintln!("Failed: {e}"), } ``` `is_retryable()` is true for network failures and 429/5xx responses, and false for configuration, authentication, not-found, and validation errors, which will fail the same way on a retry. `status()` returns the HTTP status where one applies. `Error` is marked `#[non_exhaustive]`, so match the variants you handle and include a `_` arm. ## Feature Requests / Issues [Section titled "Feature Requests / Issues"](#feature-requests--issues) If you encounter any issues with the client library or wish to request additional features please [open an issue](https://github.com/searchcraft-inc/searchcraft-issues/issues/new/choose) at the Searchcraft Issues repository. ## License and Source code [Section titled "License and Source code"](#license-and-source-code) The Searchcraft API Rust client is Apache 2.0 licensed and the source code is available on [Github](https://github.com/searchcraft-inc/searchcraft-client-rust). # Searchcraft API TypeScript Client Library > A TypeScript client library for the Searchcraft API. The [Searchcraft API TypeScript Client](https://github.com/searchcraft-inc/searchcraft-client-js) is a fully-featured TypeScript client that acts as a language-specific wrapper around the Searchcraft API, with support for both TypeScript and JavaScript projects. ## Features [Section titled "Features"](#features) * Full TypeScript support with comprehensive type definitions. * Works with JavaScript projects as well. * Functional, immutable API design. * Composable query builder. * Support for all Searchcraft query modes (fuzzy, exact, dynamic) and operations. * Full index, federation, synonyms, and stopwords management. * Complete query language support. * Works in both Node.js and browser environments. * Available via NPM and CDN. * Zero runtime dependencies. ## Installation [Section titled "Installation"](#installation) ### NPM [Section titled "NPM"](#npm) ```bash npm install @searchcraft/client ``` ### Yarn [Section titled "Yarn"](#yarn) ```bash yarn add @searchcraft/client ``` ### PNPM [Section titled "PNPM"](#pnpm) ```bash pnpm add @searchcraft/client ``` ### CDN (UMD) [Section titled "CDN (UMD)"](#cdn-umd) ```html ``` ## Quick Start [Section titled "Quick Start"](#quick-start) ```typescript import { createClient, createApiKey, createIndexName, fuzzy } from '@searchcraft/client'; // Initialize the client const client = createClient({ endpointUrl: 'https://your-searchcraft-instance.com', readKey: createApiKey('your-read-key'), }); // Perform a search const indexName = createIndexName('your-index'); const request = fuzzy().term('search query').limit(10).buildRequest(); const response = await client.search.searchIndex(indexName, request); console.log(`Found ${response.data.count} results`); console.log(response.data.hits); ``` ## Usage [Section titled "Usage"](#usage) ### Client Initialization [Section titled "Client Initialization"](#client-initialization) ```typescript import { createClient, createApiKey } from '@searchcraft/client'; const client = createClient({ endpointUrl: 'https://your-instance.com', readKey: createApiKey('your-read-key'), ingestKey: createApiKey('your-ingest-key'), // Optional, for document operations adminKey: createApiKey('your-admin-key'), // Optional, only needed for self-hosted clusters timeout: 30000, // Optional, default 30 seconds headers: { // Optional Searchcraft headers for analytics and tracking: 'X-Sc-User-Id': 'user-123', // Unique identifier for the current user 'X-Sc-Session-Id': 'session-abc', // Session identifier for tracking user sessions 'X-Sc-User-Type': 'authenticated', // User type: 'authenticated' or 'anonymous' }, }); ``` > **Note:** The `adminKey` is only required for self-hosted Searchcraft clusters. If you're using Searchcraft Cloud, you do not need to provide an admin key. ### Basic Search [Section titled "Basic Search"](#basic-search) ```typescript import { fuzzy, exact, dynamic } from '@searchcraft/client'; // Fuzzy search (typo-tolerant, uses weight ranking, synonyms and stopwords) const fuzzyQuery = fuzzy().term('search term').limit(20).buildRequest(); // Exact search const exactQuery = exact().term('exact match').buildRequest(); // Dynamic search (adapts based on word count) const dynamicQuery = dynamic().term('adaptive search').buildRequest(); const response = await client.search.searchIndex(indexName, fuzzyQuery); ``` ### Query Builder [Section titled "Query Builder"](#query-builder) The query builder provides a fluent, immutable API for constructing complex queries. > **📚 Full Query Syntax Documentation:** For complete details on the Searchcraft query language, operators, and advanced features, see the [Query Language Reference](/api/query-language). ```typescript import { exact } from '@searchcraft/client'; const query = exact() .field('category', 'electronics') .and(exact().range('price', 10, 100)) .not('discontinued') .orderBy('rating', 'desc') .limit(25) .buildRequest(); const response = await client.search.searchIndex(indexName, query); ``` ### Field Queries [Section titled "Field Queries"](#field-queries) ```typescript // Simple field match exact().field('title', 'laptop').buildRequest(); // IN query exact().fieldIn('tags', ['tech', 'gadgets', 'mobile']).buildRequest(); // Range query exact().range('price', 10, 100).buildRequest(); // Inclusive exact().range('price', 10, 100, false).buildRequest(); // Exclusive // Comparison queries exact().compare('rating', '>', 4.5).buildRequest(); exact().compare('stock', '<=', 10).buildRequest(); ``` ### Date Queries [Section titled "Date Queries"](#date-queries) ```typescript const from = new Date('2024-01-01'); const to = new Date('2024-12-31'); exact().range('created_at', from, to).buildRequest(); exact().compare('updated_at', '>=', new Date('2024-06-01')).buildRequest(); ``` ### Boolean Operators [Section titled "Boolean Operators"](#boolean-operators) ```typescript // AND exact().field('active', true).and(exact().compare('price', '<', 100)).buildRequest(); // OR exact().field('brand', 'Apple').or(exact().field('brand', 'Samsung')).buildRequest(); // NOT (exclusion) fuzzy().term('laptop').not('refurbished').buildRequest(); // Grouping const subQuery = exact().field('category', 'tech').or('category:science'); exact().group(subQuery).and(exact().compare('rating', '>', 4)).buildRequest(); ``` ### Pagination and Sorting [Section titled "Pagination and Sorting"](#pagination-and-sorting) ```typescript fuzzy() .term('query') .limit(20) .offset(40) .orderBy('created_at', 'desc') .buildRequest(); ``` ### Federated Search [Section titled "Federated Search"](#federated-search) Search across multiple indices: ```typescript import { createFederationName } from '@searchcraft/client'; const federationName = createFederationName('my-federation'); const request = fuzzy().term('search term').buildRequest(); const response = await client.search.searchFederation(federationName, request); ``` ### Advanced: Raw Query Objects [Section titled "Advanced: Raw Query Objects"](#advanced-raw-query-objects) For complex queries or when you need maximum control, you can construct request objects directly instead of using the query builder: ```typescript const request = { query: [ { occur: 'must', exact: { ctx: 'active:true' } }, { occur: 'must', exact: { ctx: 'category:/electronics' } }, { fuzzy: { ctx: 'wireless headphones' } }, ], limit: 20, }; const response = await client.search.searchIndex(indexName, request); ``` **Note:** The query builder (e.g., `fuzzy().term()...`) is recommended for most use cases. Use raw objects when you need to dynamically construct complex queries or have specific requirements the builder doesn't cover. ### Document Management [Section titled "Document Management"](#document-management) ```typescript import { createIndexName } from '@searchcraft/client'; const indexName = createIndexName('my-index'); // Insert a document await client.documents.insert(indexName, { id: '123', title: 'Product Title', description: 'Product description', price: 99.99, }); // Delete a document by its id await client.documents.delete(indexName, '123'); // Batch insert documents await client.documents.batchInsert(indexName, [ { id: '1', title: 'Product 1' }, { id: '2', title: 'Product 2' }, ]); // Batch delete documents by their ids await client.documents.batchDelete(indexName, ['1', '2']); // Delete all documents from an index await client.documents.deleteAll(indexName); ``` ### Index Management [Section titled "Index Management"](#index-management) ```typescript import { createIndexName } from '@searchcraft/client'; const indexName = createIndexName('my-index'); // List all index names const { index_names } = await client.indices.list(); // Get index configuration const config = await client.indices.get(indexName); // Create a new index await client.indices.create(indexName, { search_fields: ['title', 'body'], fields: { title: { type: 'text', indexed: true, stored: true }, body: { type: 'text', indexed: true, stored: true }, }, }); // Update index configuration (partial) await client.indices.update(indexName, { weight_multipliers: { title: 2.0, body: 0.7 }, }); // Delete an index await client.indices.delete(indexName); ``` ### Federation Management [Section titled "Federation Management"](#federation-management) ```typescript import { createFederationName } from '@searchcraft/client'; const federationName = createFederationName('my-federation'); // List all federations const federations = await client.federations.list(); // Get a specific federation const federation = await client.federations.get(federationName); // Delete a federation await client.federations.delete(federationName); ``` ### Synonyms [Section titled "Synonyms"](#synonyms) ```typescript // Get all synonyms for an index const synonyms = await client.synonyms.get(indexName); // Add synonyms — format: "synonym:original-term" or "many,synonyms:original,terms" await client.synonyms.add(indexName, [ 'nyc:new york city', 'usa:united states', ]); // Delete specific synonyms by key await client.synonyms.delete(indexName, ['nyc', 'usa']); // Delete all synonyms await client.synonyms.deleteAll(indexName); ``` ### Stopwords [Section titled "Stopwords"](#stopwords) ```typescript // Get all stopwords for an index const stopwords = await client.stopwords.get(indexName); // Add custom stopwords await client.stopwords.add(indexName, ['foo', 'bar']); // Delete specific stopwords await client.stopwords.delete(indexName, ['foo', 'bar']); // Delete all custom stopwords await client.stopwords.deleteAll(indexName); ``` ### Health Check [Section titled "Health Check"](#health-check) ```typescript // check() throws on error, so if it returns the service is healthy const health = await client.health.check(); console.log('Status:', health.status); // 200 console.log('Message:', health.data); // "Searchcraft is healthy." ``` ## API Reference [Section titled "API Reference"](#api-reference) ### Client [Section titled "Client"](#client) ```typescript // Create a new Searchcraft client createClient(config: SearchcraftConfig): SearchcraftClient ``` ### Search API [Section titled "Search API"](#search-api) ```typescript // Search an index and return typed results searchIndex(indexName: IndexName, request: SearchRequest): Promise> // Search across a federation of indices and return typed results searchFederation(federationName: FederationName, request: SearchRequest): Promise> ``` ### Document API [Section titled "Document API"](#document-api) ```typescript // Insert a document insert(indexName: IndexName, document: DocumentWithId): Promise // Delete a document by id delete(indexName: IndexName, documentId: string | number): Promise // Batch insert documents batchInsert(indexName: IndexName, documents: DocumentWithId[]): Promise // Batch delete documents by ids batchDelete(indexName: IndexName, documentIds: (string | number)[]): Promise // Delete all documents from an index deleteAll(indexName: IndexName): Promise // Get a document by its internal Searchcraft ID (_id) get(indexName: IndexName, internalId: string): Promise> ``` ### Index API [Section titled "Index API"](#index-api) ```typescript // List all index names list(): Promise // Get the configuration for a specific index get(indexName: IndexName): Promise // Create a new index create(indexName: IndexName, indexConfig: IndexConfig): Promise // Update an existing index (partial) update(indexName: IndexName, indexConfig: Partial): Promise // Delete an index delete(indexName: IndexName): Promise ``` ### Federation API [Section titled "Federation API"](#federation-api) ```typescript // List all federations list(): Promise // Get a specific federation get(federationName: FederationName): Promise // Delete a federation delete(federationName: FederationName): Promise ``` ### Synonyms API [Section titled "Synonyms API"](#synonyms-api) ```typescript // Get all synonyms for an index get(indexName: IndexName): Promise // Add synonyms in "synonym:original-term" format add(indexName: IndexName, synonyms: string[]): Promise // Delete specific synonyms by key delete(indexName: IndexName, synonyms: string[]): Promise // Delete all synonyms from an index deleteAll(indexName: IndexName): Promise ``` ### Stopwords API [Section titled "Stopwords API"](#stopwords-api) ```typescript // Get all stopwords for an index get(indexName: IndexName): Promise // Add custom stopwords add(indexName: IndexName, stopwords: string[]): Promise // Delete specific stopwords delete(indexName: IndexName, stopwords: string[]): Promise // Delete all stopwords from an index deleteAll(indexName: IndexName): Promise ``` ### Health API [Section titled "Health API"](#health-api) ```typescript // Throws on error, so if it returns the service is healthy check(): Promise ``` ### Query Builder [Section titled "Query Builder"](#query-builder-1) ```typescript // Create a fuzzy query builder (typo-tolerant, uses weight ranking, synonyms and stopwords) fuzzy(): QueryBuilder // Create an exact query builder exact(): QueryBuilder // Create a dynamic query builder (adapts based on word count) dynamic(): QueryBuilder ``` #### QueryBuilder Methods [Section titled "QueryBuilder Methods"](#querybuilder-methods) All methods return a new `QueryBuilder` instance (immutable): ```typescript // Add a search term term(term: string): QueryBuilder // Add a field:value query field(field: string, value: string | number | boolean): QueryBuilder // Add a field IN query fieldIn(field: string, values: (string | number)[]): QueryBuilder // Add a range query range(field: string, from: string | number | Date, to: string | number | Date, inclusive?: boolean): QueryBuilder // Add a comparison query compare(field: string, operator: '>' | '<' | '>=' | '<=', value: number | Date): QueryBuilder // Add an AND operator and(query: string | QueryBuilder): QueryBuilder // Add an OR operator or(query: string | QueryBuilder): QueryBuilder // Add a NOT operator not(term: string): QueryBuilder // Group a query with parentheses group(query: string | QueryBuilder): QueryBuilder // Set result limit limit(limit: number): QueryBuilder // Set result offset offset(offset: number): QueryBuilder // Set ordering orderBy(field: string, sort?: 'asc' | 'desc'): QueryBuilder // Set occur mode occur(occur: 'should' | 'must'): QueryBuilder // Build the query object build(): SearchQuery // Build the complete request object buildRequest(): SearchRequest ``` ## Type Safety [Section titled "Type Safety"](#type-safety) The library uses **branded types** for enhanced compile-time type safety. Branded types prevent you from accidentally mixing up different string-based identifiers. ### Benefits of Branded Types [Section titled "Benefits of Branded Types"](#benefits-of-branded-types) * **Compile-time safety** - TypeScript catches type mismatches before runtime * **Prevents mixing types** - Can't accidentally use an `IndexName` where a `FederationName` is expected * **IDE support** - Better autocomplete and inline error detection * **Zero runtime overhead** - Types are erased during compilation * **Works in JavaScript** - Functions work normally, just without compile-time checks ### Usage [Section titled "Usage"](#usage-1) ```typescript import { createApiKey, createIndexName, createFederationName, createDocumentId } from '@searchcraft/client'; const apiKey = createApiKey('key'); // ApiKey type const indexName = createIndexName('my-index'); // IndexName type const federationName = createFederationName('my-fed'); // FederationName type const docId = createDocumentId('123'); // DocumentId type // ✅ TypeScript ensures you use the correct type await client.search.searchIndex(indexName, request); await client.search.searchFederation(federationName, request); // ❌ TypeScript error - prevents mistakes at compile time await client.search.searchFederation(indexName, request); // Error! ``` ## Error Handling [Section titled "Error Handling"](#error-handling) ```typescript import { SearchcraftError, AuthenticationError, NotFoundError, ValidationError, NetworkError, ApiError, } from '@searchcraft/client'; try { const response = await client.search.searchIndex(indexName, request); } catch (error) { if (error instanceof AuthenticationError) { console.error('Authentication failed'); } else if (error instanceof ValidationError) { console.error('Validation error:', error.field); } else if (error instanceof NetworkError) { console.error('Network error'); } } ``` ## Feature Requests / Issues [Section titled "Feature Requests / Issues"](#feature-requests--issues) If you encounter any issues with the client library or wish to request additional features please [open an issue](https://github.com/searchcraft-inc/searchcraft-issues/issues/new/choose) at the Searchcraft Issues repository. ## License and Source code [Section titled "License and Source code"](#license-and-source-code) The Searchcraft API TypeScript client is Apache 2.0 licensed and the source code is available on [Github](https://github.com/searchcraft-inc/searchcraft-client-js). # Document Management Documents are JSON objects that are stored in a Searchcraft index. They represent a single record of your data that is optimized for search. Each document has two id fields, `id` which is the identifier from the source data system (your CMS, database, etc) and `_id` which is the internal Searchcraft ID. Documents do not need to contain all fields from the source record, just the fields you want to search against and display in search results. If you are using a CMS integration you do not need to use these endpoints, the CMS integration will handle this for you. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `POST /index/:index/documents` Add one or several documents to an index. * `DELETE /index/:index/documents` Delete one or several documents from an index by field term match. `{title: foo}` or `{id: "xyz"}` * `GET /index/:index/documents/:document_id` Get a single document from an index by it's internal Searchcraft ID (\_id). * `DELETE /index/:index/documents/:document_id` Delete a single document from an index by it's internal Searchcraft ID (\_id). * `DELETE /index/:index/documents/query` Delete one or several documents from an index by QUERY match. * `DELETE /index/:index/documents/all` Delete all documents from an index. ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has ingestion permissions. ## Insert Documents [Section titled "Insert Documents"](#insert-documents) You can insert one or more documents into an index at once. If you are planning on inserting a large number of documents in a row it's recommended to batch them into multiple larger requests rathn than sending them one at a time. Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: ingest-key-value" --data '[{"id": "5", "title": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "body": "Maecenas sed mauris commodo ligula porttitor euismod a vitae nunc. Nam placerat consequat arcu, ut consectetur nisi feugiat eget. Nam in tellus vel ligula cursus sollicitudin non id ex. Praesent sollicitudin ultrices tempor. Phasellus sed tortor tristique, vehicula augue quis, sagittis enim. Praesent egestas vitae mi vitae egestas. Mauris eget interdum est. Maecenas in purus sed erat varius tempor ut in massa. Sed neque risus, semper iaculis metus quis, aliquet ultricies felis. Praesent dictum id sapien sed vehicula.", "categories": ["category_1", "category_2"]}]' https://searchcraft-cluster-url/index/:index_name/documents ``` ## Delete Documents [Section titled "Delete Documents"](#delete-documents) When deleting an individual document you most likely want to use the source ID (your `id` field) not the Searchcraft `_id` field. Example to delete by source ID (assuming it is named "id" in the schema): ```bash curl -X DELETE -H "Content-Type: application/json" -H "Authorization: ingest-key-value" --data '{"query": { "exact": {"ctx": "id:youridval"} }}' https://searchcraft-cluster-url/index/:index_name/documents/query ``` You may also delete documents via any type of query that would match a document or set of documents in a search request query. Eventually Searchcraft will support a RESTful endpoint for deleting and update documents by source ID, this feature is not yet available. You may delete all documents from an index by using the `DELETE /index/:index/documents/all` endpoint. An index will continue to exist after all documents are deleted. # Federation Management > Creating, updating, and deleting federations for federated search. Federations are an entity that relates two or more indicies under an organization to allow cross-index (federated) search queries. Federation create/update/patch operations should only be used if you are using the self-hosted version of Searchcraft. Federation operations for Searchcraft Cloud are managed via the Vektron UI. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /federation` Returns a list of all federations. * `POST /federation/:federation_name/search` Returns search results data across all indices defined in a federation that match the query criteria. Query format is the same as search by index. See the [search](/api/search/) reference for more information. * `GET /federation/:federation_name` Returns the entity for a federation. * `GET /federation/:federation_name/stats` Returns document counts per index for a federation as well as the total document count. * `GET /federation/organization/:organization_id` Returns a list of all federations for an organization. These federations must have read keys assigned to them that contain the organization ID. * `POST /federation` Creates or updates an federation. * `PUT /federation/:federation_name` Replace the current federation entity with an updated one. Expects a federation entity object in the request body. Same payload as `POST /federation`. If the federation does not exist you will receive a 404. * `DELETE /federation/:index_name` Deletes a federation. ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has permissions to access the endpoint. The search endpoint requires a `read` key, all others require an `admin` key. ## Federation creation [Section titled "Federation creation"](#federation-creation) Payload to create a federation on a self-hosted instance of Searchcraft. ### Example Federation [Section titled "Example Federation"](#example-federation) ```json { "name": "4_galaxy_news_test", "friendly_name": "Galaxy News Test Federation", "created_by": "1", "last_modified_by": "1", "organization_id": "4", "index_configurations": [ { "name": "index_1", "weight_multiplier": 1.0 }, { "name": "index_2", "weight_multiplier": 0.5 }, { "name": "index_3", "weight_multiplier": 2.0 }, ] } ``` The `created_by` and `last_modified_by` fields are the ID of the user who created the federation and the last user who modified the federation. These are optional and not required. Similarly `organization_id` is the ID of the organization the federation belongs to. If you do not need to query by organization, you can omit this field. ### Example CURL Request to create a federation [Section titled "Example CURL Request to create a federation"](#example-curl-request-to-create-a-federation) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: yourkey" --data '{"name":"4_galaxy_news_test","friendly_name":"Galaxy News Test Federation","last_modified_by":"1","organization_id":"4","index_configurations":[{"name":"index_1","weight_multiplier":1},{"name":"index_2","weight_multiplier":0.5},{"name":"index_3","weight_multiplier":2}]}' http://yoursearchcrafthost/federation ``` ### Delete this example index [Section titled "Delete this example index"](#delete-this-example-index) ```bash curl -X DELETE -H "Content-Type: application/json" -H "Authorization: yourkey" http://yoursearchcrafthost/federation/4_galaxy_news_test/ ``` ### Response for a successful creation or update [Section titled "Response for a successful creation or update"](#response-for-a-successful-creation-or-update) ```json { "status": 200, "data": { "name": "4_galaxy_news_test", "friendly_name": "Galaxy News Test Federation", "created_at": "2025-04-15T19:34:00.388448873Z", "created_by": "1", "last_modified": "2025-04-15T19:34:00.388449316Z", "last_modified_by": "1", "organization_id": "4", "index_configurations": [ { "name": "index_1", "weight_multiplier": 1.0 }, { "name": "index_2", "weight_multiplier": 0.5 }, { "name": "index_3", "weight_multiplier": 2.0 }, ] } } ``` ### Response for a successful deletion [Section titled "Response for a successful deletion"](#response-for-a-successful-deletion) ```json { "status": 200, "data": "federation deleted" } ``` # Healthcheck Used to check the health of the Searchcraft instance. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /healthcheck` Returns a healthcheck response. # Measure The measure endpoints are used for metric event collection and reporting for Searchcraft Cloud. If you are using Searchcraft Cloud and an SDK or CMS integration you do not need to use these endpoints directly. If you are using an API implementation these endpoints are available for use for capturing usage metrics. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /measure/status` Returns whether the measurement system is enabled on this server instance (`{ "enabled": true | false }`). No authentication required. When `enabled` is `false` all other `/measure/*` endpoints are no-ops. * `POST /measure/event` Records a single measurement event. * `POST /measure/batch` Records multiple measurement events in a single request. All-or-nothing: if any event fails authorisation the entire batch is rejected. * `GET /measure/dashboard/summary` Returns summary analytics including total searches, click-through rate, active users, and popular search terms. * `GET /measure/dashboard/conversion` Returns conversion analytics including CTR chart data, popular/unpopular search terms with click positions, device breakdown, and session statistics. * `GET /measure/dashboard/usage` Returns simple usage metrics for search query tracking and billing (`total_searches` and `total_searches_in_billing_period`). The dashboard endpoints (`/measure/dashboard/*`) accept the following optional query parameters: `organization_id`, `application_id`, `index_names` (pipe-delimited, e.g. `index1|index2`), `user_id`, `user_type`, `session_id`, `event_name`, `date_start` (Unix timestamp), `date_end` (Unix timestamp), `granularity` (`minutes` | `hours` | `days` | `weeks` | `months` | `years`), `rpp` (results per page), `page`. The following payload structure is used for measure event requests. `/measure/event` expects a single `MeasureRequest` object; `/measure/batch` expects `{ "items": [MeasureRequest, ...] }`. ```typescript /** * The type representing a measure request. */ export interface MeasureRequest { event_name: MeasureEventName; properties: MeasureRequestProperties; user: MeasureRequestUser; } /** * Properties attached to a measure request. */ export interface MeasureRequestProperties { searchcraft_organization_id?: string; searchcraft_application_id?: string; searchcraft_federation_name?: string; searchcraft_index_names: string[]; search_term?: string; search_kind?: string; ai_provider?: string; number_of_documents?: number; external_document_id?: string; document_position?: number; session_id?: string; } /** * User properties attached to a measure request. */ export interface MeasureRequestUser { user_id: string; country?: string; city?: string; device_id?: string; client_ip?: string; locale?: string; os?: string; platform?: string; region?: string; sdk_name?: string; sdk_version?: string; user_agent?: string; user_type?: "anonymous" | "authenticated"; latitude?: number; longitude?: number; } ``` ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has ingestion permissions. ## Self-Hosted Support [Section titled "Self-Hosted Support"](#self-hosted-support) Self-hosted instances of Searchcraft may also make use of these endpoints but that involves setting up a Clickhouse cluster and configuring the Searchcraft instance to use it. Managing and hosting Clickhouse clusters are outside the scope of this documentation and are not supported by Searchcraft technical support. # API Overview Searchcraft's API provides RESTful endpoints for updating and managing your search application back-end. Utilizing our pre-built, customizable [SDKs](/sdks/javascript/overview/) or a CMS platform integration is the preferred method for integrating Searchcraft into your application. However, if you wish to use the API directly it is available for custom integrations. ## Prerequisites [Section titled "Prerequisites"](#prerequisites) You will need an authentication token in order to access the API. You may generate and retrieve these from your account dashboard. If you are self-hosting, refer to the [CLI documentation](/tools/self-hosted-cli/) for more information. ## Security [Section titled "Security"](#security) Your authentication key for ingestion should never be used in a front-end application. All requests related to write/delete operations using the ingestion authentication key should be back-end to back-end requests. When using one of the front-end [SDKs](/sdks/javascript/overview/) you should use your read-only authentication key to make search query requests. See the [Authentication](/api/authentication/) section for more information on key endpoints and the [Access Keys](/api/reference/keys/) section for more information the different types. Customers are responsible for managing the control over their access keys, Searchcraft is not liable for data loss or additional usage charges incurred as a result of customer security practices. # Schema Field Types Search fields must follow specific types. Types indicate the type of data that is intended to be stored in the field. ## Available field types [Section titled "Available field types"](#available-field-types) The currently avaiable field types are [text](#text), [facet](#facet), [i64](#i64), [u64](#u64), [f64](#f64), [datetime](#datetime), [bool](#bool) and [json](#json). Any field type may be used for filtering but `facet` fields differ in that they have a hierarchy and produce facet counts. ## Field type configuration options [Section titled "Field type configuration options"](#field-type-configuration-options) Every field may be used for either searching or just as display fields within search results. You may also choose not to display a field and only use it for indexing purposes. ### text [Section titled "text"](#text) A text field that may be analyzed and split into tokens before indexing. Used for full text searching or to store display only data that is not indexed. | Configuration Property | Description | Default value | | ---------------------- | --------------------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed | true | | multi | Determines if the field contains a single value or an array of values | false | ### facet [Section titled "facet"](#facet) A facet field. Values are expected to be in the format of "/section/subsection" (multiple levels of depth are optional). Think of this as a taxonomical hierarchy field that can be walked down. Search results will include a count of documents that match the facet. | Configuration Property | Description | Default value | | ---------------------- | --------------------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | multi | Determines if the field contains a single value or an array of values | false | > Note: facet fields are always indexed. ### bool [Section titled "bool"](#bool) A boolean field. `true` or `false` values without quotes. | Configuration Property | Description | Default value | | ---------------------- | ---------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed | true | | fast | Store the value in a fast field | true | | required | Is the field required? | false | ### i64 [Section titled "i64"](#i64) A 64 bit signed integer field. Signed integers are used for representing negative numbers. If you aren't using negative values, use a u64 integer instead. Examples: `3`, `-1.0`, `-200`, `42` | Configuration Property | Description | Default value | | ---------------------- | ---------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed | true | | fast | Store the value in a fast field | true | | required | Is the field required? | false | ### u64 [Section titled "u64"](#u64) A 64 bit unsigned integer field. Unsigned integers are used for representing values that contain positive, whole numbers only. Examples: `5`, `42` | Configuration Property | Description | Default value | | ---------------------- | ---------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed | true | | fast | Store the value in a fast field | true | | required | Is the field required? | false | ### f64 [Section titled "f64"](#f64) A 64 bit floating point number field. Floating point numbers are used for representing decimal values. Examples: `3.6`, `2.0` | Configuration Property | Description | Default value | | ---------------------- | ---------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed | true | | fast | Store the value in a fast field | true | | required | Is the field required? | false | ### datetime [Section titled "datetime"](#datetime) A 64 bit floating point number field. The datetime type handles dates and datetimes. Since JSON doesn't have a date type, the datetime field support multiple input types and formats. Values can be in the in the format of integer numbers representing a Unix timestamp or strings containing a `rfc3339` formatted date or Unix timestamp. Examples: `2024-07-16T00:25:39Z1`, `1736197316` | Configuration Property | Description | Default value | | ---------------------- | ---------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed | true | | fast | Store the value in a fast field | true | | required | Is the field required? | false | ### json [Section titled "json"](#json) A nested JSON object field. Stores an arbitrary JSON object and lets you query nested keys using dotted paths. For example, given a `metadata` field containing `{ "author": "tolkien", "year": 1954 }`, you can query `metadata.author:tolkien` or `metadata.year:1954`. | Configuration Property | Description | Default value | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | stored | Whether or not the value is retained in the document store | true | | indexed | Determines if the values are indexed (searchable) | true | | tokenizer | Tokenizer applied to **string** values inside the JSON object. `raw` indexes each value verbatim as a single token for exact, case-sensitive matching. `lowercase` indexes each value as a single lowercased token for case-insensitive exact filtering (e.g. `Sony` matches a stored `sony`). `default` is the **name of the standard Searchcraft tokenizer** (the same one `text` fields use) and performs tokenized, lowercased full-text search. Has no effect on numeric, boolean, or other non-string JSON values. **Note:** the value `default` names a tokenizer — it is *not* the default value of this setting. When `tokenizer` is omitted, JSON fields use `raw`. | raw | | expand\_dots | Treat dots in JSON keys as nested-object path separators. When enabled, a stored key like `"a.b"` is indexed as `{ "a": { "b": ... } }`, so queries do not need to escape the dot. | true | | fast | Store values in a fast field (required for sorting/aggregating on JSON values) | true | | multi | Determines if the field contains a single object or an array of objects | false | | required | Is the field required? | false | Example field declaration: ```json { "metadata": { "type": "json", "stored": true, "indexed": true, "tokenizer": "raw", "expand_dots": true, "fast": true } } ``` > Note: the default value of `tokenizer` is `raw`, so JSON string values are matched exactly and are case-sensitive (`metadata.brand:Sony` matches a stored `Sony`, but not `sony`). Set `tokenizer` to `lowercase` for case-insensitive exact filtering, or to `default` for tokenized, case-insensitive full-text matching on JSON string values. > > Don't confuse the two meanings of "default" here: `default` is the **name** of the standard Searchcraft full-text tokenizer (the one `text` fields use by default), whereas the **default value** of this `tokenizer` setting is `raw`. Selecting the `default` tokenizer for a JSON field is therefore an explicit, opt-in choice. ### Fast Fields for Numeric, Datetime and Bool types [Section titled "Fast Fields for Numeric, Datetime and Bool types"](#fast-fields-for-numeric-datetime-and-bool-types) Numeric, datetime and bool values can be stored in a fast scoring field which is a column-oriented storage used for range queries and aggregations. For those familiar with Lucene, this is similar to [DocValues](https://solr.apache.org/guide/6_6/docvalues.html). There is a trade-off of speed vs. accuracy when using fast scoring fields. If you want to be able to order by a field it must be configured as a fast field. For number fields you typically always want `fast` enabled unless you know what this does. The fast attribute is not used on text fields or facet fields. > Fast fields are not recommended for multi-value fields as only the last value is retained ### Additional Types [Section titled "Additional Types"](#additional-types) Additional types are planned to be supported in the future such as geospatial. Additional format options for datetime are also planned to be supported. # Access Keys There are two main types of access keys, ingest and read keys. There is also an admin level key that may be used for self-hosted installations. By default, when a new index is launched a set of read and ingest keys are created. You may create additional keys of either type within [Vektron](https://vektron.searchcraft.io/). Keys can have access to multiple indexes and are limited to the permissions of the index they are associated with. You may revoke and regenerate keys at any time within [Vektron](https://vektron.searchcraft.io/). ## Ingest Keys [Section titled "Ingest Keys"](#ingest-keys) Ingest keys are used to perform operations that modify data within an index. They should never be used in browser served client-side code as they are considered sensitive information and would allow anyone to modify data in the index. ## Read Keys [Section titled "Read Keys"](#read-keys) Read keys are the keys that should be used with SDK integrations or any client-side code that may be exposed to the public. As the name implies, they have read only permissions. ## Administrative Keys [Section titled "Administrative Keys"](#administrative-keys) If you are using the self-hosted version of Searchcraft, you can create administrative keys that have higher level permissions such as the ability to create and delete indexes and manage authentication keys. See the [Searchcraft Core CLI documentation](/tools/self-hosted-cli/) for more information. # Index Schema configuration The schema is the foundation of your index. It defines the structure of your data and the fields that can be searched. On Searchcraft Cloud you will create and manage the schema via the Vektron UI. If you are using the self-hosted version of Searchcraft you will need to create the schema via the [REST API](/api/reference/schema/). ## Index Properties [Section titled "Index Properties"](#index-properties) * `name` - The name of the index. Should be url friedly (i.e. no spaces or special characters). * `fields` - Object containing the field definitions. Field objects may contain the [properties listed below](#field-properties). * `search_fields` - An array of default fields to search against when a specific field is not specified. Should match what is included in `weight_multipliers`. These must be text fields for the index to make use of fuzzy matching and typo-tolerance. * `weight_multipliers` - A map of field names to weight multipliers. The weight gives more or less importance to specific fields when running a search query. Using a number greater than 1.0 gives more importance to a field and less than 1.0 reduces the importance of a field. The baseline is 1.0. More information on weight multipliers can be found in the [weight multipliers](/guides/relevancy-tuning/weight-multipliers/) guide. * `language` - The two letter [language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) for the index. This is used for language specific stemming and stop word filtering. * `enable_language_stemming` - (optional) `true|false` boolean setting. Whether or not to enable a language specific stemming algorithm. Requires that you have a language code set for the index and the [language is supported](/guides/relevancy-tuning/language-stemming/). * `exclude_stop_words` - (optional) `true|false` boolean setting. Whether to strip stop words when performing a search. On Searchcraft Cloud this is enabled by default. For self-hosted you can enable this by setting `exclude_stop_words: true` in your schema. If you enabled and don't have a language code specified it will load the `en` dictionary by default. * `auto_commit_delay` - (optional) integer value. The number of seconds to wait since last receiving a ingestion request before automatically committing a batch of documents. The Searchcraft API will wait this amount of time and if has not received another ingestion request it will commit the batch. This is useful if you don't want to explicitly use the `commit` endpoint after succesfully POSTing documents. However, it does cede a level of control. * `time_decay_field` - (optional) string value. Setting this configuration option enables a exponential temporal decay function on document relevancy scoring. This must match the name of a date field that exists in your schema, typically a `date_published` field. This field will be used to calculate the number of days since publish used in the decay factor for time decay function. Note, this chosen field must be marked as `fast` and `indexed` in your schema. * `ai_enabled` - (optional) `true|false` boolean setting. This is the per-index master switch for AI-powered features. New indexes default to `false`, so an admin must explicitly set `ai_enabled: true` to turn AI features on. When set to `false`, AI endpoints for the index are disabled even if an `ai` configuration is still present. * `ai` - (optional) object containing AI provider and search summary configuration. This block may remain present while `ai_enabled` is `false`. See the [Index Management](/api/schema/) page for the full AI configuration examples and field descriptions. ## Field Properties [Section titled "Field Properties"](#field-properties) * `type` - The field type. * `required` - A required field. If a document is missing a required field the request will get rejected. * `indexed` - Whether the field should be indexed. If you are not searching on this field you can disable indexing to save space. For fields like text fields you can still do normal mode exact matches against the field using `fieldname:value` without the need to index the field you just can't do things stuch as range queries. One reason to use non-indexed fields is have them for search result display purposes but not have them impact relevancy. This is common for fields that contain data like page urls, image urls, etc. * `stored` - Whether or not the value is returned in search result documents or just used during ingestion. * `fast` - Whether or not the field should be used for fast scoring. Fast fields in Searchcraft are a way to store and retrieve structured data efficiently, especially for filtering and aggregations. Think of them as an optimized way to store numeric or categorical values so they can be accessed quickly without scanning the entire index. For example, if you have an e-commerce book store site with fields `release_date`, `rating`, and `author_id`, you might want to: 1. Filtering: "Find books published after 2015." or "Find all books written by a specific author."" Since `publication_year` and `author_id` are fast fields, Searchcraft can efficiently scan it without checking every document. 2. Sorting: "Show top-rated books first." With rating as a fast field, sorting is much faster because Searchcraft doesn't need to extract the value from each document dynamically. Fast fields store this data in a columnar format (like a spreadsheet where each column is optimized for fast lookups) instead of keeping it mixed with the full text. This makes queries like filtering and sorting much faster compared to searching through the raw text index. Not used on text fields or facet fields. For those familiar with Lucene this is similar to `doc_values`. If you want to be able to perform range or comparison queries this needs to be enabled. For number fields you typically always want this enabled unless you know what this does. * `multi` - Whether or not the field is a multi-valued field. In the document JSON the expected value is an array of values, eg, `"tags": ["tag1", "tag2"]`. See the [Field Types](/api/reference/fields/) page for details on which properties are available for each field type. ## Field types [Section titled "Field types"](#field-types) * `text` - A text field. * `datetime` - A datetime field. Example: `"2024-07-16T00:25:39Z"`. Could also be a unix timestamp. * `bool` - A boolean field. `true` or `false` without quotes. * `f64` - A 64-bit floating point field. Example: `3.4` * `u64` - A 64-bit unsigned integer field. Example: `9` * `facet` - A facet field. Expects a format of "/section/subsection". Think of this as a taxonomical category that can be walked down. * `json` - A nested JSON object field. Stores arbitrary JSON and lets you query nested keys using dotted paths (e.g. `metadata.author`). These are detailed in depth on the [Field Types](/api/reference/fields/) page. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) See the [REST API](/api/overview/) documentation for more information on the API endpoints. # Index Management > Creating, updating, and deleting indexes. Index create/update/patch operations should only be used if you are using the self-hosted version of Searchcraft. Index operations for Searchcraft Cloud are managed via the Vektron UI. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /index` Returns a list of all indexes. * `GET /index/stats` Returns document counts for every index on the instance. * `GET /index/:index_name` Returns the schema for an index. * `GET /index/:index_name/stats` Returns meta data about an index such as the number of documents. * `POST /index` Creates or updates an index. Expects a schema definition object in the request body. This will empty your index if it exists and has documents. * `PATCH /index/:index_name` Allows you to make a partial configuration change to an index schema without having to re-ingest all of your data. Updates are limited to `search_fields`, `weight_multipliers`, `language`, `time_decay_field`, `auto_commit_delay`, `exclude_stop_words`, `enable_language_stemming`, `ingestion`, `ai_enabled`, and `ai` settings. Unlike the other index endpoints, the payload values should not be nested inside an `index` object. * `PUT /index/:index_name` Replace the contents. Expects a schema definition object in the request body. Same payload as `POST /index`. If the index does not exist you will receive a 404. * `DELETE /index/:index_name` Deletes an index. ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has permissions to access the endpoint. * `POST /index` requires an `admin` key. * `PATCH /index/:index_name` and `PUT /index/:index_name` can use an `ingest` key for normal schema updates, including updates to the `ai` configuration block. * If a `PATCH` or `PUT` request includes `ai_enabled`, the caller must use an `admin` key with index-modification permissions. * New indexes default to `ai_enabled: false` unless an admin explicitly sets `ai_enabled: true` during creation. ## Optional AI configuration [Section titled "Optional AI configuration"](#optional-ai-configuration) The `ai` block is optional on an index schema. If you omit `ai`, standard Searchcraft indexing and search continue to work normally, but AI-specific endpoints such as [Search Result Summaries](/api/search-summaries/) will not be available until AI configuration is added. Searchcraft also stores a separate top-level `ai_enabled` master switch for each index. This flag is independent of the `ai` configuration and defaults to `false` for newly created indexes unless an admin explicitly turns it on. * `ai_enabled` controls whether AI-powered endpoints are available for the index. * The `ai` block stores the provider, credentials, and summary configuration details. * You can keep `ai` configured while setting `ai_enabled` to `false`. * For `POST /index` and `PUT /index/:index_name`, include `ai_enabled` and `ai` inside the nested `index` object. * For `PATCH /index/:index_name`, send `ai_enabled` and `ai` as top-level fields in the patch payload. * If a `PATCH` request omits `ai`, the existing AI configuration is preserved. * If a `PATCH` request includes `ai`, the existing AI configuration is replaced with the new `ai` object. * If a `PATCH` request omits `ai_enabled`, the existing `ai_enabled` value is preserved. * If a `PUT` request omits `ai_enabled`, the existing `ai_enabled` value is preserved. * Changing `ai_enabled` requires an admin key. Updating only `ai` can still be done with an ingest key. ### AI master switch property [Section titled "AI master switch property"](#ai-master-switch-property) * `ai_enabled` - Optional boolean. Defaults to `false` for newly created indexes. When `false`, AI capabilities are reported as disabled for the index and `POST /index/:index_name/search/summary` will return a `400` response until an admin turns the switch on. ### AI configuration properties [Section titled "AI configuration properties"](#ai-configuration-properties) * `ai.llm_provider` - Required when `ai` is present. Supported values are: * `bedrock` - AWS Bedrock. Requires `llm_region`. * `llamacpp` - llama.cpp server mode. Requires `llm_base_url`. * `ollama` - Ollama. No additional field is required. * `openai` - OpenAI or OpenAI-compatible APIs. Requires `llm_api_key`. * `anthropic` - Anthropic Claude. Requires `llm_api_key`. * `google` - Google Gemini. Requires `llm_api_key`. * `xai` - xAI (Grok). Requires `llm_api_key`. * `mistral` - Mistral AI. Requires `llm_api_key`. * `ai.llm_region` - Optional string. Required when `llm_provider` is `bedrock`. * `ai.llm_base_url` - Optional string. Used for Ollama, llama.cpp server mode, or OpenAI-compatible APIs. Required when `llm_provider` is `llamacpp`. * `ai.llm_api_key` - Optional string. Required when `llm_provider` is `openai`, `anthropic`, `google`, `xai`, or `mistral`. * `ai.search_summary` - Optional object. Configure this when you want to enable `POST /index/:index_name/search/summary` for the index. ### Search summary properties [Section titled "Search summary properties"](#search-summary-properties) * `ai.search_summary.model` - Required non-empty string. The model identifier used to generate summaries. * `ai.search_summary.role` - Optional string. The system role/persona for the summary prompt. Defaults to `a helpful assistant skilled at creating comprehensive summaries`. * `ai.search_summary.character_limit` - Optional integer. Maximum character limit for the generated summary. Defaults to `500`. * `ai.search_summary.max_results` - Optional integer. Maximum number of search results to include in the summary prompt. Defaults to `5`. * `ai.search_summary.document_trim_length` - Optional integer. Maximum number of characters to include from each matched document. Defaults to `1000`. * `ai.search_summary.temperature` - Optional number between `0.0` and `1.0`. Controls response variability. Defaults to `0.2`. * `ai.search_summary.empty_state_message` - Optional string. Returned when a summary search finds no results and no keyword rule overrides it. Supports `${searchTopic}` placeholder replacement. * `ai.search_summary.additional_prompt_instructions` - Optional array of additional prompt instructions. Each item contains: * `custom_instruction` - Required string. * `order` - Required integer. Lower numbers are applied first. * `ai.search_summary.keyword_rules` - Optional array of keyword-based rules for modifying summary behavior. Each rule contains: * `rule_name` - Required string. Used to identify the rule in server logs. Has no effect on matching or summary behaviour. * `detect_keywords` - Required array of strings. Matching is case-insensitive and substring-based — a rule fires if any keyword in the array is found anywhere in the search term. For example, a keyword of `"nutrition"` would also match the query `"malnutrition"`. * `replacement_term` - Optional string. When set, replaces the search term submitted to the search engine and used as the topic in the LLM prompt. The original search term is preserved only for display purposes. * `custom_instruction` - Optional string. Appended to the index-level `additional_prompt_instructions` when the rule matches. * `empty_state_message` - Optional string. Overrides the index-level `empty_state_message` when the rule matches and the search returns no results. Supports `${searchTopic}` placeholder replacement. * `order` - Required integer. Lower numbers are evaluated first. The first matching rule wins and no further rules are evaluated. Keyword rules are sorted by `order` and the first matching rule wins. See [Search Result Summaries](/api/search-summaries/#keyword-rules) for endpoint behaviour details. ## Schema creation [Section titled "Schema creation"](#schema-creation) Payload to create an index on a self-hosted instance of Searchcraft. See the [schema configuration reference](/api/reference/schema/) for more information on the properties. ### Example schema without AI [Section titled "Example schema without AI"](#example-schema-without-ai) This example omits the optional `ai` block. Because `ai_enabled` is omitted, the new index will also start with AI disabled. ```json { "index": { "auto_commit_delay": 1, "language": "en", "name": "creation_test", "search_fields": [ "title", "body" ], "fields": { "id": { "type": "text", "required": true, "stored": true, "indexed": false }, "created_at": { "type": "datetime", "fast": true, "stored": true, "indexed": true }, "title": { "type": "text", "stored": true }, "body": { "type": "text", "stored": true }, "active": { "type": "bool", "fast": true, "stored": true }, "rating": { "type": "f64", "stored": true, "fast": true }, "reviews": { "type": "u64", "stored": true, "fast": true }, "tags": { "type": "text", "stored": true, "multi": true }, "category": { "type": "facet", "stored": true }, "formats": { "type": "facet", "stored": true, "multi": true } }, "weight_multipliers": { "title": 2, "body": 0.7 } } } ``` ### Example CURL request to create an index without AI [Section titled "Example CURL request to create an index without AI"](#example-curl-request-to-create-an-index-without-ai) * `override_if_exists` - Optional boolean value that dictates whether or not the POST request will override the existing index if it already exists. Defaults to `false`. * `index` - The index schema. See the [schema configuration reference](/api/reference/schema/). ```bash curl -X POST \ -H "Content-Type: application/json" \ --data '{ "override_if_exists": true, "index": { "name": "creation_test", "search_fields": [ "title", "body" ], "fields": { "id": { "type": "text", "required": true, "stored": true, "indexed": false }, "created_at": { "type": "datetime", "fast": true, "stored": true, "indexed": true }, "title": { "type": "text", "stored": true }, "body": { "type": "text", "stored": true }, "active": { "type": "bool", "fast": true, "stored": true }, "rating": { "type": "f64", "stored": true, "fast": true }, "reviews": { "type": "u64", "stored": true, "fast": true }, "tags": { "type": "text", "stored": true, "multi": true }, "category": { "type": "facet", "stored": true }, "formats": { "type": "facet", "stored": true, "multi": true } }, "weight_multipliers": { "title": 2, "body": 0.7 } } }' \ http://yoursearchcrafthost/index ``` ### Example CURL request to create an index with OpenAI summaries [Section titled "Example CURL request to create an index with OpenAI summaries"](#example-curl-request-to-create-an-index-with-openai-summaries) ```bash curl -X POST \ -H "Content-Type: application/json" \ --data '{ "override_if_exists": true, "index": { "name": "product_docs", "search_fields": [ "title", "body" ], "fields": { "title": { "type": "text", "stored": true }, "body": { "type": "text", "stored": true }, "category": { "type": "facet", "stored": true } }, "weight_multipliers": { "title": 2, "body": 0.8 }, "ai_enabled": true, "ai": { "llm_provider": "openai", "llm_api_key": "sk-your-api-key", "search_summary": { "model": "gpt-4o-mini", "role": "a helpful product documentation assistant", "character_limit": 600, "max_results": 5, "document_trim_length": 1200, "temperature": 0.2, "additional_prompt_instructions": [ { "custom_instruction": "Prefer concise bullet point summaries when appropriate.", "order": 1 } ] } } } }' \ http://yoursearchcrafthost/index ``` ### Example CURL request to create an index with AI configured but disabled [Section titled "Example CURL request to create an index with AI configured but disabled"](#example-curl-request-to-create-an-index-with-ai-configured-but-disabled) This can be useful when an admin wants to stage AI configuration before exposing it to clients. ```bash curl -X POST \ -H "Content-Type: application/json" \ --data '{ "index": { "name": "product_docs_staged", "search_fields": ["title", "body"], "fields": { "title": { "type": "text", "stored": true }, "body": { "type": "text", "stored": true } }, "ai_enabled": false, "ai": { "llm_provider": "ollama", "search_summary": { "model": "llama3.1" } } } }' \ http://yoursearchcrafthost/index ``` ## Patching an existing index [Section titled "Patching an existing index"](#patching-an-existing-index) `PATCH /index/:index_name` accepts top-level fields instead of a nested `index` object. If you include `ai` in a patch request, send the full AI configuration you want the index to keep. If you include `ai_enabled` in a patch request, use an admin key. Patching only the `ai` block continues to work with an ingest key. If an index was created without explicitly setting `ai_enabled: true`, patching only the `ai` block configures AI but does not turn it on. ### Example CURL request to disable AI without removing configuration [Section titled "Example CURL request to disable AI without removing configuration"](#example-curl-request-to-disable-ai-without-removing-configuration) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ -H "Authorization: admin-key-value" \ --data '{ "ai_enabled": false }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to enable AI after configuration is in place [Section titled "Example CURL request to enable AI after configuration is in place"](#example-curl-request-to-enable-ai-after-configuration-is-in-place) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ -H "Authorization: admin-key-value" \ --data '{ "ai_enabled": true }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to add AI summaries with Bedrock [Section titled "Example CURL request to add AI summaries with Bedrock"](#example-curl-request-to-add-ai-summaries-with-bedrock) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ --data '{ "ai": { "llm_provider": "bedrock", "llm_region": "us-east-1", "search_summary": { "model": "anthropic.claude-3-5-sonnet-20240620-v1:0", "temperature": 0.2 } } }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to add AI summaries with Anthropic [Section titled "Example CURL request to add AI summaries with Anthropic"](#example-curl-request-to-add-ai-summaries-with-anthropic) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ --data '{ "ai": { "llm_provider": "anthropic", "llm_api_key": "sk-ant-your-api-key", "search_summary": { "model": "claude-3-5-sonnet-latest", "temperature": 0.2 } } }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to add AI summaries with Google Gemini [Section titled "Example CURL request to add AI summaries with Google Gemini"](#example-curl-request-to-add-ai-summaries-with-google-gemini) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ --data '{ "ai": { "llm_provider": "google", "llm_api_key": "gemini-api-key", "search_summary": { "model": "gemini-2.5-flash", "temperature": 0.2 } } }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to add AI summaries with xAI [Section titled "Example CURL request to add AI summaries with xAI"](#example-curl-request-to-add-ai-summaries-with-xai) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ --data '{ "ai": { "llm_provider": "xai", "llm_api_key": "xai-your-api-key", "search_summary": { "model": "grok-3-mini", "temperature": 0.2 } } }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to add AI summaries with Mistral [Section titled "Example CURL request to add AI summaries with Mistral"](#example-curl-request-to-add-ai-summaries-with-mistral) ```bash curl -X PATCH \ -H "Content-Type: application/json" \ --data '{ "ai": { "llm_provider": "mistral", "llm_api_key": "your-mistral-api-key", "search_summary": { "model": "mistral-small-latest", "temperature": 0.2 } } }' \ http://yoursearchcrafthost/index/product_docs ``` ### Example CURL request to replace summary instructions and keyword rules [Section titled "Example CURL request to replace summary instructions and keyword rules"](#example-curl-request-to-replace-summary-instructions-and-keyword-rules) Because `PATCH` replaces the existing `ai` block when it is present, include the entire desired `ai` configuration when updating prompt instructions or keyword rules. ```bash curl -X PATCH \ -H "Content-Type: application/json" \ --data '{ "ai": { "llm_provider": "bedrock", "llm_region": "us-east-1", "search_summary": { "model": "anthropic.claude-3-5-sonnet-20240620-v1:0", "role": "a helpful product documentation assistant", "temperature": 0.2, "additional_prompt_instructions": [ { "custom_instruction": "Highlight editorial picks first.", "order": 1 }, { "custom_instruction": "Call out seasonal availability when relevant.", "order": 2 } ], "keyword_rules": [ { "rule_name": "nutrition", "detect_keywords": [ "nutrition", "diet" ], "replacement_term": "healthy eating", "custom_instruction": "Use a wellness-focused tone.", "empty_state_message": "Try our healthy eating guides instead.", "order": 10 } ] } } }' \ http://yoursearchcrafthost/index/product_docs ``` ### Delete this example index [Section titled "Delete this example index"](#delete-this-example-index) ```bash curl -X DELETE -H "Content-Type: application/json" http://yoursearchcrafthost/index/creation_test/ ``` ### Response from stats endpoint [Section titled "Response from stats endpoint"](#response-from-stats-endpoint) ```json { "status": 200, "data": { "document_count": 541 } } ``` # Search Result Summaries This reference covers the AI-related search endpoints available on a Searchcraft index. ## API Endpoint Reference [Section titled "API Endpoint Reference"](#api-endpoint-reference) * `GET /index/:index/capabilities` Returns AI capability flags for an index. * `POST /index/:index/search/summary` Streams an AI-generated summary of search results using server-sent events. ## Capabilities Endpoint [Section titled "Capabilities Endpoint"](#capabilities-endpoint) Use the capabilities endpoint to determine whether an index has AI enabled and whether summary generation is configured. The `enabled` flag reflects the index's stored `ai_enabled` master switch. An index may still report `searchSummaryConfigured: true` while `enabled: false` if AI configuration has been saved but the master switch is currently turned off. Authenticated requests to this endpoint require the `READ` permission. ### Example capabilities request [Section titled "Example capabilities request"](#example-capabilities-request) ```bash curl -H "Authorization: read-key-value" https://searchcraft-cluster-url/index/data_test/capabilities ``` ### Example capabilities response [Section titled "Example capabilities response"](#example-capabilities-response) ```json { "status": 200, "data": { "ai": { "enabled": true, "searchSummaryConfigured": true, "llmProviderConfigured": true, "llmModelConfigured": true } } } ``` ## Search Summary Endpoint [Section titled "Search Summary Endpoint"](#search-summary-endpoint) The summary endpoint accepts the same request body shape as `POST /index/:index/search`. The payload is a standard [Searchcraft query payload](/glossary/#searchcraft-query-payload). Authenticated requests to this endpoint require the `LLM_RAG_SUMMARIES` permission. Summary responses are streamed as `text/event-stream; charset=utf-8`. ### 400 conditions [Section titled "400 conditions"](#400-conditions) The endpoint returns `400` in the following situations. Use `GET /index/:index/capabilities` to check availability before calling the summary endpoint. See [Optional AI configuration](/api/schema/#optional-ai-configuration) for how to configure these settings. | Condition | Message | | ----------------------------------------- | ---------------------------------------------- | | `ai_enabled` is `false` | `AI features are disabled for this index` | | No `ai` block configured | `AI features not configured for this index` | | `ai` exists but no `search_summary` block | `Search summary not configured for this index` | | Invalid `llm_provider` value | `Invalid LLM provider` | ### Example summary request [Section titled "Example summary request"](#example-summary-request) ```bash curl -N -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query":"wireless headphones","limit":5}' https://searchcraft-cluster-url/index/data_test/search/summary ``` ### Example event stream [Section titled "Example event stream"](#example-event-stream) ```text event: metadata data: {"results_count":3,"cached":false} event: delta data: {"content":"Top options include..."} event: done data: {"results_count":3} ``` ### Event types [Section titled "Event types"](#event-types) | Event | Payload | Description | | ---------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `metadata` | `{"results_count": number, "cached": boolean}` | Always the first event. `results_count` is the number of search hits found. `cached` is `true` when both the search results and the generated summary were served from cache. | | `delta` | `{"content": string}` | One or more delta events containing the streamed summary text. When there are zero results, a single delta is emitted with the configured `empty_state_message` instead of calling the LLM. | | `done` | `{"results_count": number}` | Final event indicating the stream has completed. | | `error` | `{"message": string}` | Emitted if a streaming error occurs. | ### Zero results behaviour [Section titled "Zero results behaviour"](#zero-results-behaviour) When a search query returns no hits, the LLM is not called. Instead, the endpoint immediately emits a `delta` event containing the `empty_state_message` configured on the index, with the `${searchTopic}` placeholder substituted for the matched search term. If a matching keyword rule provides its own `empty_state_message`, that takes precedence over the index-level default. ## Structured Query Support [Section titled "Structured Query Support"](#structured-query-support) The summary endpoint supports the same structured queries as the standard search endpoint. ```bash curl -N -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query":[{"dynamic":{"ctx":"gaming laptop"}},{"term":{"ctx":"Asus","fields":"brand"}}],"limit":5}' https://searchcraft-cluster-url/index/data_test/search/summary ``` When keyword rules are configured, Searchcraft derives human-readable match text from supported query fragments including `fuzzy`, `exact`, `dynamic` queries. ## Keyword Rules [Section titled "Keyword Rules"](#keyword-rules) Keyword rules are configured on the index declaration under `ai.search_summary.keyword_rules`. See [Optional AI configuration](/api/schema/#optional-ai-configuration) for the full rule schema. Rules are sorted by `order` and evaluated in that order. The first matching rule wins and no further rules are evaluated. ### Matching behaviour [Section titled "Matching behaviour"](#matching-behaviour) A rule matches when any keyword in its `detect_keywords` array is found in the search term. Matching is case-insensitive and substring-based, so a keyword of `"nutrition"` would also match a query like `"malnutrition"`. ### Rule fields [Section titled "Rule fields"](#rule-fields) | Field | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `replacement_term` | When set, replaces the search term submitted to the search engine and used as the topic in the LLM prompt. The original search term is preserved only for display purposes. | | `custom_instruction` | Appended to the index-level `additional_prompt_instructions` when the rule matches. | | `empty_state_message` | Overrides the index-level `empty_state_message` when the rule matches and the search returns no results. Supports `${searchTopic}` placeholder replacement. | ## Supported LLM Providers [Section titled "Supported LLM Providers"](#supported-llm-providers) The summary endpoint works with any LLM provider configured on the index. Supported providers are: | Provider | `llm_provider` value | Credential | | ------------- | -------------------- | ------------------------------------------------ | | AWS Bedrock | `bedrock` | AWS SDK credential chain (`llm_region` required) | | llama.cpp | `llamacpp` | None (`llm_base_url` required) | | Ollama | `ollama` | None | | OpenAI | `openai` | `llm_api_key` | | Anthropic | `anthropic` | `llm_api_key` | | Google Gemini | `google` | `llm_api_key` | | xAI (Grok) | `xai` | `llm_api_key` | | Mistral AI | `mistral` | `llm_api_key` | See [Optional AI configuration](/api/schema/#optional-ai-configuration) for full configuration examples and field descriptions. # Performing a Search with the API This is a reference on how to perform a full-text search using the Searchcraft API. This reference is intended for developers that intend to build out their own custom applications or desire to programmatically test search queries. If you are using the SDK components, building search queries is handled for you. All of the provided examples may be run against your Searchcraft cluster, the `data_test` index is available on all clusters for query testing. Just make sure to replace the `read-key-value` with your key and the `searchcraft-cluster-url` with your cluster URL. ## API Endpoint Reference [Section titled "API Endpoint Reference"](#api-endpoint-reference) * `POST /index/:index/search` Returns search results data that match the query criteria. * `POST /federation/:federation_name/search` Returns search results data across all indices defined in a federation that match the query criteria. Query format is the same as search by index. * `GET /index/:index/capabilities` Returns AI capability flags for an index. See the [Search Result Summaries](/api/search-summaries/) reference. * `POST /index/:index/search/summary` Streams an AI-generated summary of search results over server-sent events. See the [Search Result Summaries](/api/search-summaries/) reference. ## Query Modes [Section titled "Query Modes"](#query-modes) There are currently three types of search query modes that Searchcraft accepts. * `fuzzy` This mode is typo-tolerant but may be rank less relevant content further up in the results due to it matching on more than the original term tokens. A good fit for humans as humans are terrible at spelling. Fuzzy utilizes levenshtein distance to match typos within a certains distance of errors from the original term. * `exact` This is an excellent fit for when you want exact matches only. `exact` should always be used on filters. * `dynamic` This is a hybrid between `fuzzy` and `exact` that adapts based on the number of words in the search term. Stopwords configured on an index do not count towards the word count logic. * 1 word: Exact query with occur (must) * 2 words: Base fuzzy whole term (should) + exact first word (must) + fuzzy second word (must) * 3 words: Base fuzzy whole term (should) + exact words with conditional occur + fuzzy last word (should) * 4+ words: Single fuzzy query Queries will run against the fields marked as searchable defaults in the index schema. You do not need to specify the field name in those cases unless you want to restrict the search to just a single field. You may choose specific fields to search against in your query using the [query language syntax](#using-the-searchcraft-query-language). To control the requirements of what should return in your results you need to make use of the `occur` parameter. See the [Occur Parameter](#occur-parameter) section for more details. While these three options are the primary modes, you may also combine multiple query modes together by performing a compound query via sending an array of queries. This will allow you to customize the query to your specific application needs. ## Order\_By, Limit, Offset, and Sort Parameters [Section titled "Order\_By, Limit, Offset, and Sort Parameters"](#order_by-limit-offset-and-sort-parameters) The `order_by`, `limit`, `offset`, `sort`, and `minimum_number_should_match` parameters are optional, top level payload parameters. `limit` sets the number of results to return. If you do not set a value the default is `20`. Cannot be greater than the `max_result_limit`. For Searchcraft Cloud customers this is set to `200` but if you have a special use case reach out to support. `offset` sets the offset of the first result to return. This can be used to paginate through results. `order_by` by allows you to specify a field to order the results by. This will discard relevance scoring. If you need a combination of relevance and recency its recommended to instead keep the default order with a date value or date range query. `sort` Allows you to change the sort of the results to either ascending or descending. Options are `asc` and `desc`. If you do not set a value the default is `desc`. `minimum_number_should_match` sets the minimum number of optional (`should`) clauses a document must match to be returned. It must be a non-negative integer and cannot exceed the number of `should` clauses in the query (a request that no document could satisfy is rejected). When omitted, the standard Boolean default applies: a query containing only `should` clauses requires at least one to match, while a query that also contains `must` clauses treats `should` clauses as relevance boosts that are not required for matching. Setting a value overrides that default — for example, `2` requires at least two of the `should` clauses to match. Setting `0` lifts the minimum but does not widen the candidate set: a query made up only of `should` clauses still matches just the documents that match at least one of them. See the [Occur Parameter](#occur-parameter) section for more on `should` and `must` clauses. **Note on fuzzy clauses:** A clause counts toward the minimum whenever it matches a document — and `fuzzy` clauses match terms within an edit distance of the query term, not just the exact term. A single document can therefore satisfy more `should` clauses than you expect: for example, the term `rover` can fuzzy-match an indexed `river`, and `mission` can fuzzy-match `missing`. When you need the minimum to count only precise term matches, use `exact` clauses (as in the example below). Reserve `fuzzy` `should` clauses for when typo-tolerant, approximate matching is genuinely intended. ### Example request [Section titled "Example request"](#example-request) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"limit":2, "offset": 2, "order_by": "title", "sort": "asc", "query":{"fuzzy":{"ctx":"human"}}}' http:///index/data_test/search ``` ### Example request using `minimum_number_should_match` [Section titled "Example request using minimum\_number\_should\_match"](#example-request-using-minimum_number_should_match) This query has three optional (`should`) clauses and requires at least two of them to match for a document to be returned. It uses `exact` clauses so that each clause counts only when the precise term is present (see the caution above about fuzzy matching). ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"minimum_number_should_match": 2, "query":[{"occur":"should","exact":{"ctx":"mars"}},{"occur":"should","exact":{"ctx":"colonization"}},{"occur":"should","exact":{"ctx":"mystery"}}]}' http:///index/data_test/search ``` ### Example response [Section titled "Example response"](#example-response) ```json { "status": 200, "data": { "hits": [ { "doc": { "title": "The Time Traveler's Last Stand", "id": "2", "rating": 6.7, "tags": [ "time travel", "future", "history" ], "body": "In a future where time travel is outlawed, one man races to prevent a catastrophe from altering the course of history.", "reviews": 1973, "category": "/science-fiction/time-travel", "created_at": "2024-08-03T17:09:24Z", "formats": [ "/hardback", "/paperback" ], "active": true }, "document_id": "9581601997514028734", "score": 0.46052486, "source_index": "data_test" }, { "doc": { "tags": [ "rogue planet", "astronauts", "gravity" ], "created_at": "2024-07-24T01:17:08Z", "category": "/science-fiction/space", "reviews": 3912, "formats": [ "/hardback", "/paperback", "/audio-book", "/e-book" ], "active": true, "body": "When a rogue planet enters the solar system, a team of astronauts must save Earth from being pulled into its gravity well.", "rating": 9.3, "id": "6", "title": "The Gravity Well" }, "document_id": "8716278579726351691", "score": 0.46052486, "source_index": "data_test" } ], "count": 26, "time_taken": 0.002419625, "facets": [ { "formats": [ { "path": "/audio-book", "count": 11 }, { "path": "/e-book", "count": 17 }, { "path": "/hardback", "count": 10 }, { "path": "/paperback", "count": 24 } ] }, { "category": [ { "path": "/fantasy", "count": 7, "children": [ { "path": "/fantasy/dark-fantasy", "count": 1 }, { "path": "/fantasy/epic-fantasy", "count": 3 }, { "path": "/fantasy/faerie-tales", "count": 1 }, { "path": "/fantasy/heroic-fantasy", "count": 1 }, { "path": "/fantasy/magic", "count": 1 } ] }, { "path": "/mystery", "count": 8, "children": [ { "path": "/mystery/cold-case", "count": 1 }, { "path": "/mystery/crime", "count": 2 }, { "path": "/mystery/legal", "count": 1 }, { "path": "/mystery/psychological", "count": 1 }, { "path": "/mystery/thriller", "count": 3 } ] }, { "path": "/science-fiction", "count": 11, "children": [ { "path": "/science-fiction/biotechnology", "count": 1 }, { "path": "/science-fiction/cyberpunk", "count": 2 }, { "path": "/science-fiction/space", "count": 2 }, { "path": "/science-fiction/space-colonization", "count": 2 }, { "path": "/science-fiction/space-mystery", "count": 1 }, { "path": "/science-fiction/space-opera", "count": 1 }, { "path": "/science-fiction/time-travel", "count": 2 } ] } ] } ] } } ``` The `hits` object contains the documents within this page of results that matched the query. Page size is determined by the `limit` parameter (or the default if it is not set) If you are making a federated search, `source_index` indicates where the document originated from. The `score` value is the relevance score of the document. The `facets` object is only present on an index that contains facet fields, if not are present it will be omitted. For federated searches, all facets are combined from across the index matches. ## Occur Parameter [Section titled "Occur Parameter"](#occur-parameter) The occur parameter is optional and defaults to `should` if a value is not provided. Underneath, the `should` and `must` settings are used to define the behavior of clauses in Boolean queries, controlling how documents are matched based on specified conditions. Here's the difference: ### `should` Clause [Section titled "should Clause"](#should-clause) **Purpose:** Specifies clauses that are optional but influence the relevance scoring. **Behavior:** * Documents that match should clauses are ranked higher in the results. * If no `must` clause is present, at least one `should` clause must match for a document to be included in the results. * If `must` clauses are present, `should` clauses act as a boost to relevance scoring without being required for matching. * You can override how many `should` clauses are required with the top-level [`minimum_number_should_match`](#order_by-limit-offset-and-sort-parameters) parameter. Example: Searching for documents where a certain keyword is preferred but not required. ```rust let query = BooleanQuery::new(vec![ (Occur::Should, term_query_1), // Optional but boosts relevance (Occur::Must, term_query_2), // Required match ]); ``` ### `must` Clause [Section titled "must Clause"](#must-clause) **Purpose:** Specifies clauses that are mandatory for a document to be included in the results. **Behavior:** * Documents that do not match a must clause are excluded from the results. * Used to enforce strict conditions that documents must satisfy. * Example: Searching for documents where a keyword is required. ```rust let query = BooleanQuery::new(vec![ (Occur::Must, term_query_1), // Mandatory match (Occur::Should, term_query_2), // Optional but boosts relevance ]); ``` ### Key Differences [Section titled "Key Differences"](#key-differences) | Aspect | should | must | | --------------------------- | ----------------------------------------------------------- | --------------------------- | | Result Criteria Requirement | Optional (but boosts score) | Mandatory | | Matching | At least one should must match if no must clause is defined | All must clauses must match | | Purpose | Adjusts relevance ranking | Enforces strict matching | When using a combination of a filter with a string query you typically want to combine `must` clause queries. This will function as a logical `AND`. #### Example must request [Section titled "Example must request"](#example-must-request) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query":[{"occur":"must","exact":{"ctx":"category: IN [/science-fiction]"}},{"occur":"must","fuzzy":{"ctx":"world"}}],"limit":20}' https://searchcraft-cluster-url/index/data_test/search | jq ``` The reason this example uses an array of queries instead of a single query is due to the desire to have fuzzy matching on the query term combined with a filter. This forms a logical AND query. If these used `occur: "should"` then it would function as a logical `OR` query. By combining should and must clauses you can build complex queries that balance precision (with `must`) and recall/relevance (with `should`). ## Using the Searchcraft Query Language [Section titled "Using the Searchcraft Query Language"](#using-the-searchcraft-query-language) Searchcraft's query language is a powerful tool for crafting complex search queries. It allows you to specify the fields to search, the operator to use, and the value to search for. It's inspired by Lucene's query syntax but is Searchcraft does not utilize Lucene. You can combine search queries against multiple fields using [AND](#and-logical-operator) or [OR](#or-logical-operator) operators. You can also do exclusion queries using [-](#--exclusion-operator). You can also use [IN](#field-in-query) queries. field:IN \[foo bar] will match 'foo' or 'bar', but nothing else. Range queries are possible using the [TO](#field-to-query) operator. Refer to the specific query type sections below for more details. ### IMPORTANT [Section titled "IMPORTANT"](#important) In order to use the query language you need to search in `exact` mode. You may combine fuzzy matching with a query language exact query via the API by making a mutiple-query request like so: `curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": [{ "occur": "must", "exact": {"ctx": "active:false"} }, { "fuzzy": {"ctx": "galaxy"} }]}' https://searchcraft-cluster-url/index/data_test/search` ### Query Writing Guidelines [Section titled "Query Writing Guidelines"](#query-writing-guidelines) #### Escape Special Characters [Section titled "Escape Special Characters"](#escape-special-characters) Some characters need to be escaped in non quoted terms because they are used as part of the query language syntax. Special reserved characters are: ``+ , ^, `, :, {, }, ", [, ], (, ), ~, !, \\, *, SPACE``. If these characters are desired in a query term, they need to be escaped by prefixing them with an back slash `\`. Within quoted terms, the quote character in use `'` or `"` needs to be escaped. #### Datetime Format [Section titled "Datetime Format"](#datetime-format) Datetime values must be provided in rfc3339 format, such as `1970-01-01T00:00:00Z` or as Unix epoch timestamps `1736367048`. #### `AND` logical operator [Section titled "AND logical operator"](#and-logical-operator) An `AND` query will match only if both conditions match. Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "active:false AND rating:>9.0"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### `OR` logical operator [Section titled "OR logical operator"](#or-logical-operator) An `OR` query will match if either conditions match. Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "active:false AND rating:>9.0"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### `-` exclusion operator [Section titled "- exclusion operator"](#--exclusion-operator) Using the `-` operator will exclude results that match the term. ```bash {"query": { "exact": {"ctx": "searchterm -excludedterm} }} ``` #### Grouping () [Section titled "Grouping ()"](#grouping) Parentheses are may be used to force the order of evaluation of operators. For instance, if a query should match if 'field1' is 'one' or 'two', and 'field2' is 'three', you can use (field1:one OR field1:two) AND field2:three. #### Operator Precedence [Section titled "Operator Precedence"](#operator-precedence) Without parentheses, AND takes precedence over OR. That is, a AND b OR c is interpreted as (a AND b) or c. Exclusion operator `-` takes precedence over everything, such that -a AND b means (-a) AND b, not -(a AND B). ### Field Queries [Section titled "Field Queries"](#field-queries) You are not limited to searching across just the default fields. You can also search against specific field values. This is often useful for filtering results or narrowing down the scope of a search. #### Field Term Match [Section titled "Field Term Match"](#field-term-match) Returns results where a field value matches the provided term. ```bash field:term ``` Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "title:\"cybernetic rebellion\""} }}' https://searchcraft-cluster-url/index/data_test/search ``` This example uses quotes around the phrase because it wants to match the exact phrase "cybernetic rebellion". You could also send it without quotes and it would match any documents with the word "cybernetic" or "rebellion" in the title. #### Field `IN` Query [Section titled "Field IN Query"](#field-in-query) Returns results where a field value is one of the provided terms. The term array can be a list of terms or a single term. ```bash field:IN [term1 term2 term3] ``` Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "tags:IN [evolution colonization]"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### Field `TO` Query [Section titled "Field TO Query"](#field-to-query) Returns results where a value is within a range. ```bash `field:value TO value` ``` ##### Bounding Range Queries [Section titled "Bounding Range Queries"](#bounding-range-queries) You can set both inclusive and exclusive bounds when performing a range query. Inclusive bounds are represented by square brackets \[]. They will match tokens equal to the bound term. Exclusive bounds are represented by curly brackets {}. They will not match tokens equal to the bound term. Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "rating:[6 TO 9]"} }}' https://searchcraft-cluster-url/index/data_test/search ``` This example will return any documents that have a rating between 6 and 9, including 6 and 9. #### Field Value Comparison Queries [Section titled "Field Value Comparison Queries"](#field-value-comparison-queries) You can also use the following operators to compare field values. | Operator | Description | | -------- | ------------------------ | | `>` | Greater than | | `<` | Less than | | `>=` | Greater than or equal to | | `<=` | Less than or equal to | ```bash field:>=value ``` Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "reviews:>=3000"} }}' https://searchcraft-cluster-url/index/data_test/search ``` This example will return any documents that have 3000 or more reviews. #### Match All `*` Query [Section titled "Match All \* Query"](#match-all--query) Matches every document. Does not require a field name. The match all query is only compatible with exact matching via `exact` query mode. You will likely want to specify a limit. It is rare that you will want to use this query. Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"limit": 100, "query": { "exact": {"ctx": "*"} }}' https://searchcraft-cluster-url/index/data_test/search ``` ### Additional Examples Using Query Language Syntax and the `data_test` index [Section titled "Additional Examples Using Query Language Syntax and the data\_test index"](#additional-examples-using-query-language-syntax-and-the-data_test-index) #### Fuzzy query against default search fields [Section titled "Fuzzy query against default search fields"](#fuzzy-query-against-default-search-fields) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query":{"fuzzy":{"ctx":"Wintess"}}}' https://searchcraft-cluster-url/index/data_test/search ``` #### Fuzzy query combined with a field filter [Section titled "Fuzzy query combined with a field filter"](#fuzzy-query-combined-with-a-field-filter) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": [{ "occur": "must", "exact": {"ctx": "active:false"} }, { "fuzzy": {"ctx": "galaxy"} }]}' https://searchcraft-cluster-url/index/data_test/search ``` #### exact query against default search fields [Section titled "exact query against default search fields"](#exact-query-against-default-search-fields) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query":{"exact":{"ctx":"Alien"}}}' https://searchcraft-cluster-url/index/data_test/search ``` #### Exclusion example [Section titled "Exclusion example"](#exclusion-example) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query":{"exact":{"ctx":"planet -solar"}}}' https://searchcraft-cluster-url/index/data_test/search ``` #### By a full category facet [Section titled "By a full category facet"](#by-a-full-category-facet) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "category:/science-fiction/cyberpunk"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### By a parent level category facet [Section titled "By a parent level category facet"](#by-a-parent-level-category-facet) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "category:/science-fiction"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### Using limit and offset [Section titled "Using limit and offset"](#using-limit-and-offset) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"limit":2, "offset": 2, "query":{"fuzzy":{"ctx":"human"}}}' https://searchcraft-cluster-url/index/data_test/search ``` #### Within a group of tags [Section titled "Within a group of tags"](#within-a-group-of-tags) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "tags:IN [evolution colonization]"} }}' https://searchcraft-cluster-url/index/data_test/search | jq ``` #### By a date greater than [Section titled "By a date greater than"](#by-a-date-greater-than) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "created_at:>=2020-06-01T00:00:00Z AND created_at:>=2022-01-01T00:00:00Z"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### By a date range [Section titled "By a date range"](#by-a-date-range) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "created_at:[2021-01-01T00:00:00Z TO 2022-12-31T23:59:59Z]"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### boolean queries [Section titled "boolean queries"](#boolean-queries) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "active:false"} }}' https://searchcraft-cluster-url/index/data_test/search ``` ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "active:true"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### Range query with float field [Section titled "Range query with float field"](#range-query-with-float-field) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "rating:[6 TO 9]"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### Greater than or equal query with integer field [Section titled "Greater than or equal query with integer field"](#greater-than-or-equal-query-with-integer-field) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "reviews:>=3000"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### Float field exact value [Section titled "Float field exact value"](#float-field-exact-value) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "rating:9.4"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### By an ID value [Section titled "By an ID value"](#by-an-id-value) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "id:2"} }}' https://searchcraft-cluster-url/index/data_test/search ``` #### Sorting by date in descending order [Section titled "Sorting by date in descending order"](#sorting-by-date-in-descending-order) ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: read-key-value" --data '{"query": { "exact": {"ctx": "category:/fantasy/epic-fantasy"} }, "sort":"desc", "order_by": "created_at"}' https://searchcraft-cluster-url/index/data_test/search ``` ## Tips for better search results [Section titled "Tips for better search results"](#tips-for-better-search-results) Remember, your application's search results are only as good as the information that you give to it. You have full control over the weighting of fields and what field data gets ingested so if you find the results are not to your liking you can always adjust. Not every field that is displayed in the search result documents needs to be searchable and there can always be additional fields added such as keyword fields that you do not display but affect the results. ## Notes [Section titled "Notes"](#notes) If your index uses a stopword dictionary any stopwords included in the search term will not afffect the results. For example if you utilize the default `en` stopword dictionary and search for "the" without any other search terms the results will be empty. ## Federated Search Considerations [Section titled "Federated Search Considerations"](#federated-search-considerations) When using a fuzzy search, the request will search across the default search fields for each index. If you make an exact search using the query language syntax and specific a field or facet name explicitly, that field must exist in all of the queried indices accross a federation. If it does not, the source index missing the field will be excluded from the results. # Stopwords > Exclude commonly used words from your search to reduce false positives. A stopword is a commonly used word that is often ignored when performing a search. These words occur frequently and typically do not contribute meaningful context to a search query so they are often removed to improve search relevance. Searchcraft comes with multiple, language-specific pre-built dictionaries that pilots may utilize or you can create and manage your own. If you are a Searchcraft Cloud customer, these are managed through the Vektron UI. For self-hosted customers, these are managed via the API. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /index/:index/stopwords` Returns the stopwords for an index. * `POST /index/:index/stopwords` Add stopwords to an index. * `DELETE /index/:index/stopwords` Delete an array of stopwords from an index. * `DELETE /index/:index/stopwords/all` Delete all stopwords from an index. * No payload is required. Stopwords will only be applied to queries if they are enabled on the schema definition. Note that if you are using the default dictionaries, the POST and DELETE operations will have no effect, you may only enable/disable stopwords. The delete operations only take effect if you have added a custom set of stopwords. ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has ingestion permissions. ## Request Examples [Section titled "Request Examples"](#request-examples) ### Add Stopwords [Section titled "Add Stopwords"](#add-stopwords) `POST /index/:index/stopwords` expects an array with one to many stopwords. ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: ingest-key-value" --data '["totallyuniquestopword", "totallyuniquestopword2"]' https://searchcraft-cluster-url/index/:index/stopwords ``` ### Delete An Array of Stopword Values [Section titled "Delete An Array of Stopword Values"](#delete-an-array-of-stopword-values) ```bash curl -X DELETE -H "Content-Type: application/json" -H "Authorization: ingest-key-value" --data '["totallyuniquestopword", "totallyuniquestopword2"]' https://searchcraft-cluster-url/index/:index/stopwords ``` ## English Examples of Stop Words [Section titled "English Examples of Stop Words"](#english-examples-of-stop-words) * Articles: the, a, an * Conjunctions: and, or, but * Prepositions: in, on, at, to, with * Pronouns: he, she, it, they ## Available Stopword Dictionaries [Section titled "Available Stopword Dictionaries"](#available-stopword-dictionaries) Searchcraft currently supports stopword dictionaries for the following languages. More will be added over time. | Language | code | | ---------- | ---- | | Arabic | ar | | Chinese | zh | | Danish | da | | Dutch | nl | | English | en | | Finnish | fi | | French | fr | | German | de | | Greek | el | | Hebrew | he | | Italian | it | | Norwegian | no | | Portuguese | pt | | Romanian | ro | | Russian | ru | | Spanish | es | | Swedish | sv | | Tamil | ta | | Turkish | tr | If stop word stripping is enabled and a language was not set, Searchcraft will default to the English stopword dictionary. # Synonyms If you have mutliple words that mean the same thing, a synonym may be defined to create a relationship between them. It is common for acronyms to be defined as synonyms, for example "New York City" and "NYC" may be defined as synonyms and would mean that a match on either would return the same results. The words "dog" and "puppy" or "cat" and "kitten" are another example where synonyms may be desired. Synonyms are helpful when dealing with industry specific terminology. If you are a Searchcraft Cloud customer, these are managed through the Vektron UI. For self-hosted customers, these are managed via the API. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `GET /index/:index/synonyms` Returns the synonyms for an index. * `POST /index/:index/synonyms` Add synonyms to an index. * `DELETE /index/:index/synonyms` Delete an array of synonyms from an index. * `DELETE /index/:index/synonyms/all` Delete all synonyms from an index. * No payload is required. ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has ingestion permissions. ## Request Examples [Section titled "Request Examples"](#request-examples) ### Add Synonyms [Section titled "Add Synonyms"](#add-synonyms) The format is `synonym:original-term` The payload is expected to be an array of these. You can define one to one, one to many, or many to many, e.g., `synonym,synonym:original-term,original-term,original-term`. You should not have spaces between the commas or the space will be included as part of the term. Example: ```bash curl -X POST -H "Content-Type: application/json" -H "Authorization: ingest-key-value" --data '["ny:new york,nyc", "lotr:lord of the rings", "iphone,android,phone:android,phone,iphone"]' https://searchcraft-cluster-url/index/:index/synonyms ``` If you create many to many just note that they will be seperated when you retrieve them via a GET request. When requesting `GET /index/:index/synonyms` The above example would return: ```json { "status": 200, "data": { "phone": [ "android", "iphone" ], "android": [ "phone", "iphone" ], "lotr": [ "lord of the rings" ], "nyc": [ "new york", "nyc" ], "ny": [ "new york", "nyc" ], "iphone": [ "phone", "android" ] } } ``` ### Delete Synonyms [Section titled "Delete Synonyms"](#delete-synonyms) `DELETE /index/:index/synonyms` delete one to several synonyms sent in an array of strings. ```bash curl -X DELETE -H "Content-Type: application/json" -H "Authorization: ingest-key-value" --data '["ny", "lotr"]' https://searchcraft-cluster-url/index/:index/synonyms ``` ## Important Note on Synonyms [Section titled "Important Note on Synonyms"](#important-note-on-synonyms) Synonyms by design ONLY work with fuzzy queries. Exact match queries ignore synonyms. # Transactions Transactions are used to group write operations into a single atomic operation. This is useful for ensuring that all operations are successful or none are and for handling errors states in your application. If your index has `auto commit` enabled you do not need to use transactions. If you utilize an CMS integration these are handled for you. ## API Endpoints [Section titled "API Endpoints"](#api-endpoints) * `POST /index/:index/commit` Commits a write transaction. * `POST /index/:index/rollback` Rollback a write transaction. ## Auth Requirement [Section titled "Auth Requirement"](#auth-requirement) Requires an authentication key that has ingestion permissions. ## Notes [Section titled "Notes"](#notes) There is no json payload for these endpoint operations. **CAUTION** > Only one remote system should write to an index at a time. If multiple systems are writing to an index then all pending writes will be applied to the index when the the next commit is made. Transactions are implicit. You do not need to manually create a transaction. You may execute a transaction by starting a write operation and then committing it. Once committed a transaction can't be rolled back. You may however delete individual documents from an index. # Frequently asked questions ## How is Searchcraft easier to manage than other search providers? [Section titled "How is Searchcraft easier to manage than other search providers?"](#how-is-searchcraft-easier-to-manage-than-other-search-providers) Low setup. We configure and tune the search engine with sensible defaults that work well. If you do need to make adjustments to your field weightings this can be accomplished through the dashboard UI, no developer is needed. For custom term synonyms and stopwords those are also managed through the dashboard, no developer involvement is needed to make those adjustments. This frees up your engineering team's time to focus on building your product instead of building search. For a detailed comparison of Searchcraft to other search providers see our [comparisons page](https://searchcraft.io/). ## Why do applications need search? [Section titled "Why do applications need search?"](#why-do-applications-need-search) Once your application has data of a significant size it becomes difficult to sort through this information efficiently. While databases are amazing at storing and relating data their main focus is not for the performant searching of data. For this you need a purpose built search layer. ## Is Searchcraft memory safe? [Section titled "Is Searchcraft memory safe?"](#is-searchcraft-memory-safe) Memory safety is a high priority for Searchcraft. Our search engine and back-end cloud services are written in Rust and is memory safe by design and we approach all of our SDKs and CMS integrations with a focus security-first mindset. We believe this focus on memory safety is a key differentiator for Searchcraft. ## Why use Searchcraft Cloud over self-hosting an open source solution? [Section titled "Why use Searchcraft Cloud over self-hosting an open source solution?"](#why-use-searchcraft-cloud-over-self-hosting-an-open-source-solution) While open-source software is free, running search itself is not. There is a cost for hosting the infrastructure and engineering costs to for setup, integrate with your application and maintain the cluster. With Searchcraft Cloud the overall cost is much lower. You just add a few lines of code to your application and you are ready to go. Because our infrastructure costs are lower we can pass those saving to customers. Elasticsearch in particular is notoriously difficult to configure and tune properly and requires ongoing engineering hours to maintain. Elasticsearch has roughly 3000 configuration parameters that need to be tuned for your specific use case. On top of that you need to fine tune the JVM configuration. By building in Rust we avoid the JVM configuration completely. Since Searchcraft has a focus on app and website content search instead of trying to do everything we are able to have an opinionated approach with sensible default configurations to provide the best experience. If you are managing your own Elasticsearch cluster in the cloud you also need to configure discoverability rules between the nodes so they can talk to each other on top of security rules for ingest and Kibana access. Searchcraft Cloud has a easy to manage allow-list and access key system for external access and secure communication between cluster nodes is handled for you. ## But I need an integrated, on-prem solution [Section titled "But I need an integrated, on-prem solution"](#but-i-need-an-integrated-on-prem-solution) For applications where Searchraft Cloud is not a good fit we offer a self-hosted option with optional support plans. The self-hosted option is a great fit for on-premise deployments or for customers who need to manage their own infrastructure but it does require more engineering effort to get up and running. However, you do still benefit from lower configuration and tuning requirements with less memory and CPU requirements due to the Rust-based architecture. ## Why did you choose to build a new search product? [Section titled "Why did you choose to build a new search product?"](#why-did-you-choose-to-build-a-new-search-product) Search is an evergreen problem. As soon as any dataset reaches a threshold in size it will need to be searchable. Most of the current players out there are still building off of old technology, [Lucene](https://en.wikipedia.org/wiki/Apache_Lucene), which is over 25 years old and not lot of new players enter the space because search is a difficult problem to solve. We believe we can make the developer experience of integrating search easier while offering a product with a more modern technology stack. ## With the rise of LLMs is keyword search still relevant? [Section titled "With the rise of LLMs is keyword search still relevant?"](#with-the-rise-of-llms-is-keyword-search-still-relevant) We are currently in a hype cycle around LLMs. To put it in perspective when NoSQL solutions came onto the scene, it was the next big thing in databases but it didn't kill SQL. Postgres is doing fine. It's a matter of choosing the right solution for the problem. Keyword searching is exact, scales better with lower cost and latency. Vector searches, which is what LLMs use, return results based on proximity and semantic similarity, which is not always accurate. There is a higher cost to scale and run LLMs and from a climate sustainability perspective, the power consumption has environmental implications. For an application that needs accuracte results with high-performance, full-text keyword search is a better fit. ## Does Searchcraft support incremental indexing? [Section titled "Does Searchcraft support incremental indexing?"](#does-searchcraft-support-incremental-indexing) Yes. Payloads may be broken up into multiple requests. You may send a batch of documents or single documents. In general its best to consider network conditions, average individual document size and if there are overall limitations with your request library in regards to maximum payload size. In general we recommend limiting each ingest request to 50,000 or less documents at a time or around 150MB in size. This is a soft recommendation that may vary based on the size of the individual documents. When ingesting a large number of documents it is better to send them in batches of multiple documents rather than one at a time. ## What are the available integrations and SDKs? [Section titled "What are the available integrations and SDKs?"](#what-are-the-available-integrations-and-sdks) Searchcraft is a new product and we are working integrations for the most popular CMS platforms and SDKs for the most popular frameworks. Our currently available SDKs may be found in our [SDK](/sdks/javascript/overview/) section. If you are interested in an SDK or CMS integration that is not yet supported, please [reach out to us](https://searchcraft.io/). ## How can I update already indexed documents? [Section titled "How can I update already indexed documents?"](#how-can-i-update-already-indexed-documents) Data in Searchcraft is immutable. To edit a document, the document needs to be deleted and reindexed. # Features A non-definitive list of Searchcraft features. ## Available now [Section titled "Available now"](#available-now) * Ability to quickly search millions of documents in milliseconds using either fuzzy matching or exact term matching. * AI-powered search result summaries with configurable LLM providers and keyword-rule-driven behavior. * Typo-tolerant matching. * Multiple language stop-word support for better relevancy. * Customizable search term synonym support. * Adjustable field boosting for a customizable search experience. * Role-based permissions management. * Search engine REST API. * Incremental & multi-threaded indexing * Faceted search with facet counts. * Range queries. * Query Language for advanced query construction. * Multiple field data types. * BM25 relevancy scoring. * Memory safe by design. * Detailed usage analytics. * Multiple SDKs with pre-built, fully-stylable search UI components. * Language specific API clients. * Vektron, our full customer account dashboard experience. * WordPress CMS integration. ## Coming soon [Section titled "Coming soon"](#coming-soon) * More framework / platform SDKS. * Vektron, our full customer account dashboard experience. * Deeper analytics. * Automate machine learning based relevency tuning. * More CMS integrations. # Glossary of terms > A collection of common terms used through-out the Searchcraft documentation and Vektron dashboard. Common terms used through-out the Searchcraft documentation and Vektron dashboard. * [Access Key](#access-key) * [Application](#application) * [Click Position](#click-position) * [Conversion](#conversion) * [Default Search Field](#default-search-field) * [Document](#document) * [Federation](#federation) * [Field](#field) * [Fuel](#fuel) * [Index](#index) * [Indexing](#indexing) * [Ingestion](#ingestion) * [Module](#module) * [Organization](#organization) * [Pilot](#pilot) * [Ranking](#ranking) * [Relevance](#relevance) * [Retention](#retention) * [Searchcraft Query Payload](#searchcraft-query-payload) * [Schema](#schema) * [Space G.O.A.T](#space-goat) * [Stemming](#stemming) * [Stopword](#stopword) * [English Examples of Stop Words](#english-examples-of-stop-words) * [Synonym](#synonym) * [Vektron](#vektron) * [Weighting Multiplier](#weighting-multiplier) ### Access Key [Section titled "Access Key"](#access-key) An [access key](/api/reference/keys/) is a token that is used to authenticate a request to the Searchcraft API. It is used to authorize requests to the API and to control access to the data in the index. ### Application [Section titled "Application"](#application) An application is a collection of indexes, access keys and users that are managed together. It could represent a website or a mobile app and may contain one or severall indexes. in a multi-tiered environment it is typical for an application to contain multiple indexes representing the data set for each environment. ### Click Position [Section titled "Click Position"](#click-position) The click position is the position of a search result within a list of results when an application user clicked on that result. This information is measured and reported within Vektron. ### Conversion [Section titled "Conversion"](#conversion) That measured rate at which application users click on a search result. ### Default Search Field [Section titled "Default Search Field"](#default-search-field) A field on a schema that is configured to be used for a search query when a specific no specific field criteria is supplied. ### Document [Section titled "Document"](#document) An individual piece of content. Similar to a record in a database. Documents in Searchcraft should a single level of depth (no nested fields) but fields may contain either a single or multiple values. Once a document is ingested it is considered immutable. ### Federation [Section titled "Federation"](#federation) A [federation](/guides/federated-search/overview/) is a collection of indices that may be searched together. Each index has its own ranking weight multiplier to influence it's scoring within the federated search results. Federations are useful for combining the results of multiple indices into a single search result set. A federation may be across multiple applications and utilize their own specific read access key. ### Field [Section titled "Field"](#field) A field is a defined attribute within a [schema](#schema) that describes a specific type of data that can be stored or indexed. It specifies the structure, format, and behavior of the data. Refer to the [Field Types](/api/reference/fields/) section for more details of the different field types offered by Searchcraft. Fields may contain either a single value or an array of values. ### Fuel [Section titled "Fuel"](#fuel) The fuel gauge in Vektron that displays the current usage of the index. ### Index [Section titled "Index"](#index) An index is a collection of documents that may be searched together. Conceptually it is similar to the idea of a "database" but the technical underpinnings differ. Since a search index is typically an abstraction of a primary data source, it can either represent the data structure of a single source content type or document constructed from the common fields of multiple content type sources. However, it is more common for an index to be comprised of documents from a single content type. Documents within an index should contain fields that are used for search functionality as well as search result display. The documents do not need to contain every field from the source dataset, just the fields that are used for search and results display. ### Indexing [Section titled "Indexing"](#indexing) Indexing is the process that involves storing and parsing documents that makes data searchable. See [Ingestion](#ingestion). ### Ingestion [Section titled "Ingestion"](#ingestion) [Content ingestion](/guides/content-ingestion/overview/) is the process of adding documents to an index. Documents are added to an index by sending a request to the Searchcraft API. The request contains the document data and the index to which the document should be added. The document data in in JSON format and contains the fields that are defined in the index schema. There are three supported methods of ingestion; pull, push or direct file upload in Vektron. Push ingestion is configured through one of the available integrations, via a direct API request or via the [Space G.O.A.T](/tools/goat/) tool. Pull ingestion is configured in Vektron and involves providing a source feed url and configuring the ingestion frequency. ### Module [Section titled "Module"](#module) Modules are an a la carte bundle of additional requests that can be added to a plan to accomodate for request overages. Modules are in addition to the base plan and get triggered when the usage limit is reached. ### Organization [Section titled "Organization"](#organization) When you sign up for a Searchcraft account, you are automatically assigned to an organization. An organization is a group of pilots that share a common billing account. ### Pilot [Section titled "Pilot"](#pilot) Searchcraft Vektron users are referred to as pilots. ### Ranking [Section titled "Ranking"](#ranking) The importance of a field's value in determining the relevance of a document for a search query. ### Relevance [Section titled "Relevance"](#relevance) Relevance refers to how well a document (or search result) matches a user's query. It is the measure of a document's usefulness or appropriateness for satisfying the search intent. Relevance determines the ranking order of documents in the search results, with the most relevant results appearing first. ### Retention [Section titled "Retention"](#retention) Returning users that use the search function again on an application. ### Searchcraft Query Payload [Section titled "Searchcraft Query Payload"](#searchcraft-query-payload) A Searchcraft query payload is the JSON request body sent to Searchcraft search endpoints such as `POST /index/:index/search`. It contains the query itself plus any optional top-level request settings such as `limit`, `offset`, `order_by`, and `sort`. The search summaries endpoint accepts this same payload shape. ### Schema [Section titled "Schema"](#schema) An [index schema](/api/reference/schema/) defines the structure of the documents within an index. It defines the fields that are available for search and display and configures the behavior and data types of those fields. ### Space G.O.A.T [Section titled "Space G.O.A.T"](#space-goat) [Space G.O.A.T](/tools/goat/) (Global Object Aggregation Tool) generates and sends Searchcraft documents for ingestion given a database connection string and table name. It is a helpful option for quicking exporting from a primary data source into a Searchcraft index for situations where either a direction integration or a public feed is not available. It a push ingestion(#ingestion) mechanism. It is available as a command line tool that is downloadable from the [Vektron](https://vektron.searchcraft.io/) dashboard's tools page. ### Stemming [Section titled "Stemming"](#stemming) Stemming is the process of reducing a word to its root form. This ensures variants of a word match during a search. Enabling stemming can improve query matches. Stemming is language specific. [Additional details on stemming](https://en.wikipedia.org/wiki/Stemming). ### Stopword [Section titled "Stopword"](#stopword) A stopword is a commonly used word that is often ignored when performing a search. These words occur frequently and typically do not contribute meaningful context to a search query so they are often removed to improve search relevance. Searchcraft comes with multiple, language-specific pre-built dictionaries that pilots may utilize or you can create and manage your own. If you are a Searchcraft Cloud customer, these are managed through the Vektron UI. For self-hosted customers, these are managed via the API. #### English Examples of Stop Words [Section titled "English Examples of Stop Words"](#english-examples-of-stop-words) * Articles: the, a, an * Conjunctions: and, or, but * Prepositions: in, on, at, to, with * Pronouns: he, she, it, they ### Synonym [Section titled "Synonym"](#synonym) If you have mutliple words that mean the same thing, a synonym may be defined to create a relationship between them. It is common for acronyms to be defined as synonyms, for example "New York City" and "NYC" may be defined as synonyms and would mean that a match on either would return the same results. The words "dog" and "puppy" or "cat" and "kitten" are another example where synonyms may be desired. Synonyms are helpful when dealing with industry specific terminology. If you are a Searchcraft Cloud customer, these are managed through the Vektron UI. For self-hosted customers, these are managed via the API. ### Vektron [Section titled "Vektron"](#vektron) [Vektron](https://vektron.searchcraft.io/) is the dashboard for managing Searchcraft pilot accounts, applications, analytics reporting, billing, indexes and access keys. This is your central hub for managing your Searchcraft experience. ### Weighting Multiplier [Section titled "Weighting Multiplier"](#weighting-multiplier) Another term for [ranking](#ranking), this is the underlying multiplier value that is used by Searchcraft to determine the importance of a field's value in determining the relevance of a document for a search query. # Content Ingestion Overview Content ingestion is how your content gets into Searchcraft. Content may be ingested via either a push or a pull mechanism. Typically the model with search engines is to utilize a push mechanism as this ensures your index is immediately up to date. For applications that are updated via a CMS, a hook event tied to the publish event is typically what triggers a content push into your search index. ## Prerequisites [Section titled "Prerequisites"](#prerequisites) Your [index schema](/api/reference/schema/) must be configured before you can ingest content. ## Pushing content into your search index [Section titled "Pushing content into your search index"](#pushing-content-into-your-search-index) You may use one of our pre-built integrations, upload directly via [Vektron](https://vektron.searchcraft.io/), or use the REST API directly. To upload via the REST API, assuming your index schema is defined as such ```json { "override_if_exists": false, "index": { "auto_commit_delay": 1, "name": "my_first_index", "language": "en", "search_fields": [ "title", "body" ], "fields": { "id": { "type": "text", "required": true, "stored": true, "indexed": false }, "title": { "type": "text", "required": true, "stored": true }, "body": { "type": "text", "required": true, "stored": true } }, "weight_multipliers": { "title": 2, "body": 0.7 } } } ``` this would be a sample payload to a document into your index. ```shell curl -X POST -H "Content-Type: application/json" -H "Authorization: your-ingest-key" --data '[{"id": "1", "title": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "body": "Maecenas sed mauris commodo ligula porttitor euismod a vitae nunc. Nam placerat consequat arcu, ut consectetur nisi feugiat eget. Nam in tellus vel ligula cursus sollicitudin non id ex. Praesent sollicitudin ultrices tempor."}]' https://your-sc-server.search.searchcraft.io/index/1_my_first_index/documents ``` if auto\_commit is set to 1, the document will be committed to the index immediately otherwise you will need to follow up with a [commit request](/api/transactions/) to write the document to the index. ```shell curl -X POST -H "Content-Type: application/json" -H "Authorization: your-ingest-key" https://your-sc-server.search.searchcraft.io/index/1_my_first_index/commit ``` ## Pulling content via a scheduled crawl [Section titled "Pulling content via a scheduled crawl"](#pulling-content-via-a-scheduled-crawl) Searchcraft also offers a way to map a data feed to your index and configure a crawling schedule for content to automatically get added. This capability may be configured in your application settings within [Vektron](https://vektron.searchcraft.io/). ## Recommendations [Section titled "Recommendations"](#recommendations) ### Document Size [Section titled "Document Size"](#document-size) Ideally you should remove any unnecessary fields from your documents. The only fields that you need to store in Searchcraft are those that may be used for search terms, search filters or for display within a search result item. The more fields you have in your documents, the more data needs to be transferred and processed. This can slow down your search experience. ### Ingestion Payloads [Section titled "Ingestion Payloads"](#ingestion-payloads) Ingestion can be a heavy process. For initial population of your index it is recommended to send large batches of content in a single request rather than many small requests. Ideally ingest JSON payloads should be kept to under 150MB in size for Searchcraft Cloud customers. For self-hosted customers the limit is depending on the amount of RAM available on the server. # Federated Search > Federated search. What is it and what can it do? Federated search in Searchcraft enables a single query to search across multiple distinct indices and unify the results into one cohesive, ranked list. This is ideal for applications that organize their data into separate silos (e.g. products, articles, users), but want to offer a streamlined search experience. In Searchcraft, federated search is accomplished via an entity called a `federation`. A federation relates two or more indices together and provides a mechanism for tuning behavior when those indices are queried together. When using multiple content types you could have each source document conform to a single index and then filter based on facets. You could also accomplish this via a federated search if the source fields are too different from each other. ## Example Use Cases [Section titled "Example Use Cases"](#example-use-cases) * Searching across indices where the documents fields in each index are different. * You are a magazine publisher and each magazine website has their own search index. You want to build a new website or application that can search across all magazine properties. Instead of managing an additional index that contains a copy of the same data you could create a federation to search across the data within all of the existing indices. * You have a SaaS application with mutiple tenants, each tenant in their own index and you want to be able to have an administrative view that searches across all tenants. * You are an education platform and want your instructor bios and course descriptions to be searchable together. * You are an e-commerce store and want your product catalog and blog posts to be searchable together. ## Creating a Federation [Section titled "Creating a Federation"](#creating-a-federation) See the [Federation Management](/api/federation/) reference for more information on creating a federation. For Searchcraft Cloud customers, federations are managed via the Vektron UI. # Create your first index ### TK [Section titled "TK"](#tk) This page will discuss creating and setting up your first index. These screens are still in development. # Account creation and integration The first step is to create your account. If you do not have an account, you can create one at . Once you have an account you'll need to know what you want to call your first application and have your contact information ready. If your company organization has a Searchcraft account, ask your organization owner to invite you to join their crew. You may invite additional teammates to join your application once it's created. ## Integration [Section titled "Integration"](#integration) To integrate Seachcraft into your application you have two main paths, using one of the pre-built SDKs or communicating directly with the Searchcraft API. Using one of the SDKs is the recommended path to a quick integration with speed to market is critical. The SDKs can be styled to have the look and feel of the hosting application and customized to make use of as many or as little features and fitering as desired. If you have a special use case where the pre-built SDKs are not a good fit you may build a custom integration that talks with the API directly. Feel free to also submit a [feature request](https://searchcraft.io/). ### SDK Integration [Section titled "SDK Integration"](#sdk-integration) If you are a TypeScript/JavaScript developer, there are SDKs for many of the popular front-end frameworks. The SDKs are installable via NPM. There are also CMS specific integrations available that provide a tightly-coupled experience on those platforms. See the [SDK reference](/sdks/javascript/overview/) for specific integration details. ### API Integration [Section titled "API Integration"](#api-integration) If you are using one of the SDKs you will only need to communicate with the API for content ingestion. If you are building a custom search UI you may opt to query the back-end directly for search requests. See the [API reference](/api/overview/) for details on the available endpoints and methods. # User Roles > Searchcraft has three user roles. Multiple people from a team may collaborate on configuring their Searchcraft application. User accounts fall under three roles. ## Owner [Section titled "Owner"](#owner) The maximum level of access. Can do everything the developer account can as well as change billing details and modify plan selections. Owners may invite other pilots to join the crew and assign their level of access. ## Developer [Section titled "Developer"](#developer) Can create, delete, modify indices. Can modify index schemas to add / remove fields. Can disable existing API tokens and create new ones. Can create and update DNS settings. Can modify ingestion settings. Has analytics access. Can modify term synonyms and field weighting. ## Analyst (coming soon) [Section titled "Analyst (coming soon)"](#analyst-coming-soon) Has access to analytics, modifying search term synonyms, and modifying field weighting. ## Inviting Pilots [Section titled "Inviting Pilots"](#inviting-pilots) You may expand your team by inviting other pilots to join your crew. Pilots may be invited by email address on the crew screen within [Vektron](https://vektron.searchcraft.io). # Language Stemming > Make search more relevant by using language stemming You may configure your index to utilize language stemming. Stemming reduces a word to its root form. For example, the English stemmer maps *connection*, *connections*, *connective*, *connected*, and *connecting* to connect. ## Why use stemming? [Section titled "Why use stemming?"](#why-use-stemming) Stemming allows search terms to match a wider set of matches. Instead of having to type the exact term, all variants of the root form will match. ## How to enable [Section titled "How to enable"](#how-to-enable) Stemming is enabled in Vektron by selecting your language and enabled the stemming option. In the API, you can enable stemming by setting the `enable_language_stemming` property to `true` in the [schema definition](/api/reference/schema/). ## Things to Consider [Section titled "Things to Consider"](#things-to-consider) Stemming may possibily give unexpected results depending on your data set. Stemming is only available for fuzzy match searches. You must set a language for your index to utilize stemming. If you need to support multiple languages it is recommended to have your content located over multiple language specific indices. If you are running federated search, the language and stemming settings must be the same for all indices. ## Supported Languages for Stemming [Section titled "Supported Languages for Stemming"](#supported-languages-for-stemming) The supported languages for stemming is different than for [stopwords](/api/stopwords/) as not all written languages can support the concept of stemming. ### Languages Supported [Section titled "Languages Supported"](#languages-supported) | Language | code | | ---------- | ---- | | Arabic | ar | | Danish | da | | Dutch | nl | | English | en | | Finnish | fi | | French | fr | | German | de | | Greek | el | | Italian | it | | Norwegian | no | | Portuguese | pt | | Romanian | ro | | Russian | ru | | Spanish | es | | Swedish | sv | | Tamil | ta | | Turkish | tr | # Time Decay > Customize relevance scores based on time For a search index where you want to give more weight to newer content, you may want to use the time decay feature. This feature allows you to adjust the relevance score of search results based on their age. ## How It Works [Section titled "How It Works"](#how-it-works) Given a date field, Searchcraft will calculate the number of days since the document was created or last updated. Using that it will apply an exponential decay function to the relevance score based on the number of days since publish. As time passes, the score for the document will decrease, giving more weight to newer content. ### Example [Section titled "Example"](#example) A document from today: `decay ≈ 1.0 (full score)` A document 30 days old: `decay ≈ 0.74 (74% of original score)` A document 365 days old: `decay ≈ 0.026 (2.6% of original score)` This helps prioritize newer content in search results while still allowing older content to appear, just with reduced prominence. ## When To Use [Section titled "When To Use"](#when-to-use) For applications where newer content should have more precendence given equal relevance matching such as news websites, social media, etc. ## When Not To Use [Section titled "When Not To Use"](#when-not-to-use) Applications where the relevance score should not be affected by the age of the content such as a knowledge base or a product catalog. ## How To Configure [Section titled "How To Configure"](#how-to-configure) See [schema configuration](/api/reference/schema/) reference for details on how to configure the time decay feature. # Weight Multipliers > Customize relevance scores based on field weights As mentioned in the [schema](/api/reference/schema/) documentation, Searchcraft indexes are configured with a `weight_multipliers` object. This object contains a key-value pair for each field in the index. The key is the name of the field and the value is a floating point number. The number is a multiplier that is applied to the score of a document when that field is searched. ## Why are weight multipliers useful? [Section titled "Why are weight multipliers useful?"](#why-are-weight-multipliers-useful) Weight multipliers are useful when you want to give more or less importance to specific fields for the score ranking when performing a search. For example, if you have a field that contains the title of a document and another field that contains the body of the document, you may want to give more importance to the title field when searching. This can be achieved by setting the weight multiplier for the title field to a higher number than the body field. You can also set the weight multiplier to a number less than 1.0 to reduce the importance of a field. Our general recommendation is to keep the weight multiplier between 0.5 and 2.0 for most fields but this can vary depending on the use case and amount of fields configured as searchable by default. Title fields are generally set to 2.0 or higher and body fields are typically set to 0.6 while neutrally important fields are set to 1.0. ## Why is this needed? [Section titled "Why is this needed?"](#why-is-this-needed) When multiple fields are configured for the default search fields it scores a match within each field at an equal level if the occurance count of the term is the same in both field. In actuality this may result in less relevant results because its likely more important to have a match in the title field than the body field but we still want results in the body field to be returned in our results. ## Things To Consider [Section titled "Things To Consider"](#things-to-consider) * Weight multipliers are only applied to fields marked a default search fields because they only matter when searching across multiple fields at once. Weight multipliers are not needed when utilizing a specific `field_name: search_term` query. * The minimum value for a weight multiplier is 0.1. # WordPress CMS Integration > Searchcraft replaces the default WordPress search with a customizable, tunable, highly relevant search experience. For WordPress site owners, Searchcraft provides a plugin that allows you to easily add search to your site. The plugin is available in the [WordPress plugin repository](https://wordpress.org/plugins/searchcraft/). ## Prerequisites [Section titled "Prerequisites"](#prerequisites) To use the WordPress plugin you will need: * WordPress 5.3 or higher * PHP 7.4+ * A WordPress account with author, editor, or administrator privileges ## Installation [Section titled "Installation"](#installation) The easiest way to install the plugin is directly via the WordPress admin panel. Simply search for "Searchcraft" in the WordPress plugin repository and install the plugin. If you are on a managed WordPress host and do not have administrator privileges, you may need to contact your host to install the plugin for you. ## Setup [Section titled "Setup"](#setup) ### Create a Searchcraft Cloud account and create your index [Section titled "Create a Searchcraft Cloud account and create your index"](#create-a-searchcraft-cloud-account-and-create-your-index) 1. Create an account on [Searchcraft Cloud](https://vektron.searchcraft.io/) via the Vektron dashboard. 2. Within Vektron, follow onboarding steps to create a new application and index, selecting the "WordPress" template on index creation. Copy the provided endpoint url, index name, ingest key and read key values. ### WordPress Plugin Configuration [Section titled "WordPress Plugin Configuration"](#wordpress-plugin-configuration) 1. Activate the plugin in WordPress. 2. Within Wordpress navigate to the Searchcraft settings page and fill in the endpoint url, index name, ingest key and read key values. If you received a Cortex url from the Searchcraft team you may enter that, otherwise leave it blank. 3. That's it! Searchcraft will automatically prepare your post content for search. ## Usage [Section titled "Usage"](#usage) By default all posts and pages will appear in search. If you wish to exclude pages or specific posts from search you can use the exclude from search option in the post editor. If for some reason you need to re-add all of your content to Searchcraft or remove all content you may do so from the plugin's settings page. The `keyphrase` field is only populated if you happen to be using the Yoast plugin and have a focus keyphrase entered ### Customization [Section titled "Customization"](#customization) By default the search modules throughout the site will take on the appearance of the styles of your WordPress theme but you may also customize the look and feel as well as the placement of the search modules. See the layout screen within the WordPress admin panel settings for more information. The "Advanced Customization" option is intended for developers and requires knowledge of HTML, CSS, and JavaScript. The WordPress plugin may make use of the generative AI summaries feature. These summaries are generated using your WordPress site's own content and cites the sources within the summary. To enable this feature you will need to reach out to [Searchcraft](https://discord.gg/y3zUHkBk6e) to request access. Your content is not used for model training data nor shared with the model provider. ### Integrations with other plugins [Section titled "Integrations with other plugins"](#integrations-with-other-plugins) * Yoast (keyphase field) * PublishPress Authors (custom authors) * Molongui Authorship (custom authors) If there an an integration you'd like to see, please make a suggestion on our [issues board](https://github.com/searchcraft-inc/searchcraft-issues) or request via our community [Discord](https://discord.gg/y3zUHkBk6e) channel. ## Compatibility [Section titled "Compatibility"](#compatibility) The plugin may also be used in conjunction with a self-hosted Searchcraft instance. If you are using a self-hosted instance you will need to manually create an index with the following schema before configuring the plugin. ```json { "auto_commit_delay": 0, "enable_language_stemming": false, "exclude_stop_words": true, "fields": { "categories": { "indexed": true, "multi": true, "required": false, "stored": true, "type": "facet" }, "featured_image_url": { "indexed": false, "multi": false, "required": false, "stored": true, "type": "text" }, "id": { "indexed": false, "multi": false, "required": true, "stored": true, "type": "text" }, "keyphrase": { "indexed": true, "multi": false, "required": false, "stored": true, "type": "text" }, "permalink": { "indexed": false, "multi": false, "required": false, "stored": true, "type": "text" }, "post_author_id": { "fast": true, "indexed": true, "multi": false, "required": false, "stored": true, "type": "u64" }, "post_author_name": { "indexed": false, "multi": false, "required": false, "stored": true, "type": "text" }, "post_content": { "indexed": true, "multi": false, "required": false, "stored": true, "type": "text" }, "post_date": { "fast": true, "indexed": true, "multi": false, "precision": "seconds", "required": false, "stored": true, "type": "datetime" }, "post_excerpt": { "indexed": true, "multi": false, "required": false, "stored": true, "type": "text" }, "post_title": { "indexed": true, "multi": false, "required": false, "stored": true, "type": "text" }, "primary_category_name": { "indexed": false, "multi": false, "required": false, "stored": true, "type": "text" }, "tags": { "indexed": true, "multi": true, "required": false, "stored": true, "type": "text" } }, "language": "en", "search_fields": [ "post_title", "post_excerpt", "post_content", "keyphrase" ], "weight_multipliers": { "keyphrase": 1, "post_content": 0.5, "post_excerpt": 1, "post_title": 2 } } ``` If you need additional assistance contact the Searchcraft team on our [Discord server](https://discord.gg/y3zUHkBk6e) and we can help! # Searchcraft Cloud Beta Disclaimer *Effective Date: February 13th, 2025* ### **1. Introduction** [Section titled "1. Introduction"](#1-introduction) Welcome to the Searchcraft Cloud Beta Program. By accessing or using the Searchcraft Cloud Beta software ("Beta Software"), you acknowledge and agree to the terms outlined in this Beta Disclaimer. This Beta Software is made available solely for evaluation and testing purposes and should not be considered a final or production-ready product. ### **2. Beta Nature of the Software** [Section titled "2. Beta Nature of the Software"](#2-beta-nature-of-the-software) The Beta Software is a pre-release version and is provided **as-is, without warranties or guarantees of any kind**. It is subject to frequent updates, modifications, and improvements based on user feedback. As such: • Features, performance, and stability may vary or change without prior notice. • Bugs, errors, and unexpected behaviors may occur. • Searchcraft reserves the right to modify, suspend, or terminate access to the Beta Software at any time without liability. ### **3. No Production Use** [Section titled "3. No Production Use"](#3-no-production-use) The Beta Software is for **testing and evaluation purposes only**. It is **not intended for use in live, commercial, or production environments**. If a user chooses to deploy the Beta Software in such an environment, they do so **at their own risk**. Searchcraft shall not be responsible for any data loss, business interruptions, or damages resulting from the use of the Beta Software. ### **4. Data Handling and Confidentiality** [Section titled "4. Data Handling and Confidentiality"](#4-data-handling-and-confidentiality) Due to the nature of beta testing: • Users should **not** input, store, or process sensitive, proprietary, or personal data using the Beta Software. • Searchcraft does not guarantee data retention or security during the Beta phase. • Feedback, bug reports, and performance data provided by users may be used by Searchcraft to improve the final product. ### **5. Limited Support and Liability** [Section titled "5. Limited Support and Liability"](#5-limited-support-and-liability) • Searchcraft provides **no guarantees of support or maintenance** for the Beta Software. • Users may submit feedback and issues, but Searchcraft is not obligated to address or resolve reported problems. • **In no event shall Searchcraft be liable for any direct, indirect, incidental, consequential, or special damages arising from the use of the Beta Software, including but not limited to lost profits, business interruption, or data loss.** ### **6. Termination** [Section titled "6. Termination"](#6-termination) Users may stop using the Beta Software at any time. Searchcraft reserves the right to revoke access to the Beta Software without notice for any reason. ### **7. Beta Usage Limits & Overage Fees** [Section titled "7. Beta Usage Limits & Overage Fees"](#7-beta-usage-limits--overage-fees) **Free Usage During Beta** During the Beta period, Searchcraft offers **free usage and testing** of the application through the **Launchpad tier plan**, which includes: • **Storage of up to 10,000 documents** • **Up to 5,000 Searchcraft API requests** Searchcraft will closely monitor usage limits during the testing period to ensure fair access for all Beta participants. **Excessive Use & Abuse Policy** While Searchcraft encourages extensive testing and feedback, **excessive or abusive use** of the Beta software—especially beyond the allocated free tier—may require corrective action: 1. **Searchcraft crew members may contact the account holder** if consistent over-use of the specified limits is detected. 2. If excessive use continues without adjustments, **Searchcraft reserves the right to charge overage fees or terminate access** to maintain system integrity for all Beta participants. ### **Overage Fees** [Section titled "Overage Fees"](#overage-fees) **Searchcraft Cloud Launchpad Plan Overage Pricing:** • The Launchpad Plan includes 5,000 API requests. • If an account exceeds 5,000 requests, an additional $20 will be charged at 5,001 requests until reaching 30,000 total requests. • An additional $20 will be charged for each subsequent allotment of 25,000 requests. • Example: At 30,001 total requests, another $20 fee will be applied, totaling $40 in API usage overage fees. These overage fees only apply if we detect consistent or intentional abuse of free usage limits. Searchcraft reserves the right to determine when usage patterns suggest abuse or excessive strain on the system. **Right to Modify Beta Terms** Searchcraft reserves the right to adjust, revise, or discontinue the Beta program, including modifying storage or API limits, changing overage fees, or restricting access, at any time and without prior notice. Continued participation in the Beta constitutes acceptance of any such changes. ### **8. Acknowledgment & Agreement to Policies** [Section titled "8. Acknowledgment & Agreement to Policies"](#8-acknowledgment--agreement-to-policies) By signing up for the Searchcraft Cloud Beta, you confirm that you understand and accept these terms. You acknowledge the risks involved in using an experimental product and agree that Searchcraft bears no responsibility for any issues that may arise. Furthermore, by participating in the Beta Program, you agree to comply with and be bound by the following policies, in addition to this Beta Disclaimer: • **Privacy Policy**: [/policies/privacy-policy/](https://docs.searchcraft.io/policies/privacy-policy/) • **Terms of Service**: [/policies/terms-service/](https://docs.searchcraft.io/policies/terms-service/) • **End User License Agreement (EULA)**: [/policies/eula/](https://docs.searchcraft.io/policies/eula/) • **General Disclaimer**: [/policies/disclaimer/](https://docs.searchcraft.io/policies/disclaimer/) If you have any questions regarding this disclaimer or the applicable policies, please contact us at # Customer License Agreement THIS CUSTOMER LICENSE AGREEMENT ("Agreement") is entered into by and between Searchcraft Inc., a Colorado corporation, ("Company"), and the customer (either an individual or single legal entity) ("Licensee") that is using the Software and governs Licensee's use of Company's Searchcraft® software together with any Updates or Enhancements thereto ("Software"), related documentation ("Documentation"), access rights to Company software-as-a-service platform that host the Software ("Hosted Platform"), Support Services (defined below), and professional services ordered by Licensee under this Agreement (collectively the "Offerings"), to the extent purchased or used by Licensee. Licensee agrees to be bound by the terms of this Agreement by executing an Order Form, installing or using the Software or Hosted Platform, or signing this Agreement below. The parties agree as follows: 1. **DEFINITIONS**. Capitalized terms will have the meanings set forth in this Section 1, or in the section where they are first used. 1.1. "*Enhancement*" means any modification or addition to the Software that materially changes its utility, efficiency, function capability or application, but that does not solely consist of an Error Correction. Company may designate Enhancements as minor or major. 1.2. "*Error*" means any reproducible failure of the Software to conform in any material respect with the Documentation. 1.3. "*Error Correction*" means either a bug fix, work-around, patch, or other modification or addition that corrects an Error or a procedure or routine that avoids the practical adverse effect of an Error. 1.4. *"Fees*" means all fees and expenses specified in an Order Form and payable by Licensee to Company in acquiring and using the Offerings. 1.5. *"Licensee Data*" means any data, content, works, and information provided or delivered by Licensee to Company in connection with Licensee's use of the Offerings. 1.6. "*Operating Environmen*t" means the computer software, hardware, systems and networks through which or on which the Software will be installed and run by Licensee. Current minimum server requirements for the Software are set forth in the Documentation. 1.7. "*Order Form*" means Company's form, which is incorporated into this Agreement, that describes which of the Offerings Licensee is purchasing from Company and the Fees and payment terms for which Licensee is responsible. 1.8. "*Priority A Error*" means an Error which renders Software inoperative or causes a complete failure of the Software. 1.9. "*Priority B Error*" means an Error which substantially degrades the performance of Software or materially restricts Licensee's use of the Software. 1.10. "*Priority C Error*" means an Error which causes only a minor impact on the Licensee's use of Software or does not materially affect the performance of the Software. 1.11. "*Service Date*" means the dates indicated in the particular Order Form during which the Company provides the Offerings. 1.12. "*Support Services*" means Company support services as described in Section 2.1.5 of Agreement and the applicable Support Exhibit. 1.13. *"Technical Support*" means technical support assistance provided by Company by telephone, through email, or through any other online communication mechanism concerning the installation and use of the then-current release of Software, or the Hosted Platform. 1.14. "*Update*" means any new version of the Software, which may include Error Corrections, Enhancements or both, issued by Company from time to time to its customers. 1.15. "*Monthly Active Use*r" or "*MAU*" means a single user account, identified by a unique identifier, that identifies itself via an authentication event (such as a login using a username and password), a refresh event (such as a token refresh), a registration event, a user creation event, or any other type of event that indicates the user is actively using Licensee's application(s) that are integrated with the Software. 2. **OFFERINGS**. Subject to the terms and conditions of this Agreement and Licensee's payment of all applicable Fees, Company will provide the Offerings set forth in one or more mutually agreed upon Order Forms specifying the Offerings during the Service Dates ordered by Licensee under this Agreement, or as described in Section 2.3 below. To the extent there is any conflict between this Agreement and an Order Form, this Agreement will control, except to the extent an Order Form expressly identifies a provision of the Agreement to be superseded by the Order Form. 2.1. **On-Premise Software**. 2.1.1. **License**. Subject to the terms and conditions of this Agreement, Company grants Licensee during the Term a limited, non-exclusive, non-transferable, non-sublicensable, license to install, execute, display and otherwise use the Software and Documentation for Licensee's internal business purposes. 2.1.2. **Delivery, Acceptance, and Installation of Software**. Company will deliver to Licensee a download link prior to the commencement of the Service Dates which Licensee, and its authorized users, may use to access the Software via the download link. Licensee is solely responsible for installation of the Software in accordance with the Documentation (and any other installation instructions provided by Company), data conversion, data entry and verification of data. Delivery of the Software will be deemed complete upon the Company's delivery of the download link to Licensee. It is the responsibility of Licensee to provide the Operating Environment, any other equipment required to operate the Software, proper configuration of all hardware and other equipment, and all databases and other software used with the Software. COMPANY SHALL NOT BE RESPONSIBLE FOR ANY FAILURE OF THE SOFTWARE BASED ON THE LICENSEE'S INSTALLATION, OPERATING ENVIRONMENT AND THIRD PARTY SOFTWARE INCLUDING, BUT NOT LIMITED TO, VIRTUAL MACHINES, LIBRARIES AND/OR HARDWARE. 2.1.3. **Software Warranty**. Company warrants that the Software, when used in accordance with the Documentation, will perform substantially in accordance with the Documentation for a period of thirty (30) days from the date of delivery of the Software to Licensee (the "Warranty Period"). Licensee's sole and exclusive remedy, and Company's sole and exclusive obligation, for the Software's non-conformity with this warranty shall be to notify Company within the Warranty Period, detailing the nonconformance, and to provide Company with a reasonable opportunity to correct or replace the defective Software; which Company will use commercially reasonable efforts to correct. This limited warranty shall be void if Company determines that the Software has been (i) used other than in accordance with the Documentation; (ii) abused, modified, altered or otherwise subjected to damage from accidents, acts of nature, or other events outside the reasonable control of Company; (iii) used in combination with other products, devices, equipment, software, or data not supplied by Company or approved in writing by Company, or (iv) installed incorrectly. 2.1.4. **System Tampering**. If the Licensee notifies the Company of a material error or malfunction in the Software which, after investigation by the Company, is determined to have been caused by Licensee's unauthorized modifications, any Software warranties, expressed or implied are void. Licensee shall reimburse the Company, at its then current rates, for all costs incurred by the Company in investigating and correcting such error or malfunction, and Company, upon written notice to Licensee, may terminate this Agreement. 2.1.5. **Support Services**. This Agreement does not include support, configuration or customization of the Software to Licensee's Operating Environment, specifications, or any other services. Company may request and obtain Support Services (as described in Exhibit A) pursuant to an Order Form, and may be subject to the payment of additional Fees set forth in an Order Form. Support Services, if applicable, will be performed in a professional and competent manner in accordance with industry standards. 2.1.6. **Audit**. During the Term and for one (1) year thereafter, upon thirty (30) days written prior notice Company will have the right to have an independent audit firm inspect Licensee's records relating to Licensee's use of the Offerings solely in order to verify Licensee's compliance with the terms and conditions of this Agreement. The audit will be performed during Licensee's normal business hours, and Licensee shall make reasonable accommodations to provide representatives or employees of such audit firm access to the Licensee's facilities and records in order to complete such audit. The costs of the audit will be paid by Company, unless the audit reveals that Licensee has (a) failed to comply with the terms and conditions of this Agreement, or (b) underpaid the amounts owed to Company by five percent (5%) or more, in which case Licensee will reimburse Company for all reasonable out-of-pocket costs and expenses reasonably incurred by Company in connection with such audit. Licensee will promptly pay to Company any amounts shown by any such audit to be owing and due. In addition, Software may report certain details regarding certain usage to Company, and Licensee will not interfere with such reporting. 2.2. **Hosted Platform**. 2.2.1. **Hosted Platform License**. Subject to the terms and conditions of this Agreement, and if purchased pursuant to an Order Form, Company hereby grants Licensee, during the Term, a non-exclusive, non-sublicensable, non-transferable license, in accordance with the Documentation, to access and use the Software via the Hosted Platform subject to any usage limitations (such as number of users) described in the applicable Order Form for which Company will provide access during the Service Dates. 2.2.2. **Data Security**. Company will implement and maintain appropriate administrative, physical, and technical safeguards designed to protect the security, confidentiality and integrity of Licensee Data. If indicated in the applicable Order Form, the terms of Company's data processing addendum will apply to any processing of Licensee Data. 2.2.3. **Support and SLA**. Subject to the terms and conditions of this Agreement, Company will provide Support Services to Licensee to the extent specified in the applicable Order Form. Without limiting the foregoing, if Licensee has purchased access to the Hosted Platform pursuant to an Order Form, Company will provide Licensee the support and service level commitments as set forth in the attached Exhibit C (SLA Exhibit). 2.2.4. **Derived Data**. Licensee acknowledges and agrees that provision of the Offerings involves, and Licensee authorizes Company's: (a) collection and generation of Derived Data in connection with providing the Offerings, and (b) use of Derived Data in connection with providing, analyzing, and improving Company's products and services. Company may develop and improve products and services that it makes available to its customers using and incorporating Derived Data and for any other legal purpose in connection with its internal business purposes. Company will comply with applicable statutory requirements with respect to Derived Data. "Derived Data" means data that is (i) generated, computed, or derived from Licensee Data or other data related to Licensee's use of the Offerings, and (ii) aggregated (including with other customers' data) or de-identified as necessary so that it does not include any identifying information of, or reasonably permit the identification of, Licensee or any individual. Licensee Data excludes Derived Data. 2.3. **Free-Tier Software**. Company offers a free version of the Software that Licensee may download ("Free-Tier Software", and each Licensee using such Free-Tier Software, a "Free-Tier Licensee"). Free-Tier Licensees will be bound by the terms of this Agreement by downloading the Free-Tier Software, and the parties will not execute an Order Form unless Free-Tier Licensee also purchases fee-based Offerings. Free-Tier Licensees will only have access and use rights for the Free-Tier Software and Documentation. Free-Tier Licensees do not have access or use rights to the Hosted Platform. All terms and conditions of Section 2.1 apply to Free-Tier Licensee except section 2.1.3. 3. **RESTRICTIONS**. Licensee will have no right and will not, nor will it authorize or assist others to: (a) permit any affiliated entities or third parties to use, access, copy, download, or install the Software for their own use; (b) use the Software on equipment owned or operated by third parties unless otherwise specified in an applicable Order Form; (c) copy the Software and Documentation (other than as reasonably required for authorized use under this Agreement (provided that Licensee maintain on all such copies all proprietary rights notices of the Software and Documentation)); (d) disassemble, reverse engineer, modify, make derivative works of, translate, alter or decompile all or any portion of the Offerings or otherwise discern or attempt to discover the source code of the Offerings except and solely to the extent permitted under applicable law notwithstanding this restriction; (e) use the Offerings on a service bureau or time sharing basis or to provide services to third parties unless as otherwise specified in separate written materials provided by Company to the Licensee; (f) distribute, rent, lease, sublicense, assign, transmit, sell or otherwise transfer the Offerings or any of Licensee's rights therein to any third party; (g) use or access the Offerings to create or develop any competing product or service; (h) remove or alter any trademark, logo, copyright, or other proprietary notices, legends, symbols, or labels in the Offerings or Documentation (or any copies thereof); or (i) publish or disclose to any third party any evaluation, performance or benchmark tests or analyses, the results of audits or ethical hacks, or any other non-public information relating to the Offerings or the use thereof, except as may be expressly authorized by Company in writing. Licensee shall be responsible for any breaches or violations of this Agreement by its employees and agents. 4. **PROFESSIONAL SERVICES**. The Company does not provide any professional services, including installation services, unless specified in an Order Form. If mutually agreed to in an Order Form, professional services will be provided by Company pursuant to Exhibit B and may be subject to the payment of additional Fees set forth in an Order Form. Professional services, if provided, will be performed in a professional and competent manner in accordance with industry standards. 5. **PROPRIETARY RIGHTS**. 5.1. **Ownership**. Licensee acknowledges and agrees that the Offerings contain proprietary and trade secret information of Company. Other than the limited license granted to Licensee under Section 2 of this Agreement no license or other rights in the Offerings are granted to Licensee, and Company retains all ownership and proprietary rights in and to the Offerings, including any and all copies made by Licensee and any and all Updates or Enhancements or derivatives thereto and any work product or deliverables from any professional services, as described in Exhibit B. 5.2. **Licensee Data**. Licensee exclusively owns and retains all rights, title and interest in and to the Licensee Data. Licensee hereby grants to Company, and its authorized representatives, a fully-paid, royalty-free, worldwide, non-exclusive, non-transferable, non-assignable, sublicensable, right and license to use the Licensee Data solely for the limited purpose of, and solely as necessary for, performing Company's obligations hereunder. 5.3. **Feedback**. If Licensee provides any feedback, comments, or ideas to Company regarding the Offerings or improvements thereto ("Feedback"), Licensee hereby grants Company a fully-paid, royalty-free, worldwide, transferable, sub-licensable, irrevocable, perpetual license to use or incorporate the Feedback into Company Offerings, and other of its products and services. 5.4. **Third Party Software**. The Offerings may include third party software, including open source software, which is subject to separate license terms. To the extent there is a conflict between such terms and this Agreement, the terms of such license shall govern with respect to the use of that third party software. 6. **FEES AND PAYMENT**. 6.1. **Payment**. The Fees for the Offerings will be set forth in each Order Form. Licensee will pay all such Fees in accordance with the terms of this Agreement and the applicable Order Form. Unless otherwise set forth in the applicable Order Form, all Fees due hereunder are non-refundable, will be paid in U.S. dollars, and will be due within thirty (30) days of the date of the invoice therefor. If Licensee exceeds any usage or deployment limitations as set forth in the applicable Order Form, Licensee shall be responsible for all excess fees. Upon expiration or termination of this Agreement for any reason, any unpaid portion of any Fees, or other fee, payable by Licensee to Company under this Agreement, will be immediately due and payable to the Company. 6.2. **Taxes**. The Fees are exclusive of any and all taxes, and Licensee is responsible for payment of such taxes (excluding those based on Company's net income). If Licensee is legally obligated to make any deduction or withholding from any payment under this Agreement, Licensee shall also pay whatever additional amount is necessary to ensure that Company receives the full amount otherwise receivable had there been no deduction or withholding obligation. If Company has the legal obligation to pay or collect taxes for which Licensee is responsible under this Agreement, Company will invoice Licensee and Licensee will pay that amount unless Licensee provides Company a valid tax exemption certificate from the appropriate taxing authority. Licensee agrees to hold harmless Company from all claims and liability arising from Licensee's failure to report or pay such taxes. 6.3. **Interest**. If any portion of Fees is disputed in good faith, the remaining amounts will be paid when due and payment of such undisputed amounts may not be withheld for any reason. Any past due undisputed amounts shall be subject to a monthly service charge of one and one-half percent (1.5%) per month of the unpaid balance or the maximum rate per month allowable by law, until paid. Licensee shall be responsible for and reimburse Company's costs of collecting any delinquent amounts, including without limitation any attorney's fees. 7. **DISCLAIMER OF WARRANTIES**. EXCEPT AS EXPRESSLY PROVIDED IN SECTION 2.1.3, TO THE MAXIMUM EXTENT POSSIBLE BY LAW, COMPANY MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER EXPRESS, IMPLIED, OR STATUTORY INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT RELATING TO THE OFFERINGS OR ANY SERVICES PROVIDED HEREUNDER. WITHOUT LIMITING THE FOREGOING, THE OFFERINGS ARE PROVIDED BY COMPANY TO LICENSEE "AS IS" AND COMPANY DOES NOT WARRANT THAT THE SOFTWARE OR ANY OTHER OF THE OFFERINGS AND SERVICES PROVIDED HEREUNDER WILL MEET LICENSEE'S REQUIREMENTS, OPERATE WITHOUT INTERRUPTION OR BE ERROR FREE, OR FREE OF VIRUSES, MALICIOUS CODE, OR OTHER HARMFUL COMPONENTS, OR THAT ALL DEFECTS WILL BE CORRECTED. LICENSEE WILL BEAR ALL RISKS RELATING TO THE QUALITY AND PERFORMANCE OF THE OFFERINGS, AND ASSUME THE ENTIRE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 8. **INDEMNIFICATION**. 8.1. **Indemnification by Licensee**. Licensee will defend, at its own expense, any claim, suit, or action against Company brought by a third party to the extent that such claim, suit, or action arising from or related to (a) any Licensee Data, or (b) Licensee's breach of Section 3 (Restrictions), (each, a "Company Claim"), and Licensee shall indemnify and hold Company harmless from and against all losses, damages, liabilities, costs, and expenses (including reasonable attorneys' fees) awarded in such Company Claim or those costs and damages agreed to in a monetary settlement of such Company Claim. The foregoing obligations are conditioned on Company: (i) promptly notifying Licensee in writing of such Company Claim; (ii) giving Licensee sole control of the defense thereof and any related settlement negotiations (provided Licensee will not enter into any settlement of any claim, suit, or action that does not contain a full release of Company's liability without Company's prior written approval, which approval will not be unreasonably withheld, conditioned, or delayed); and (iii) cooperating and, at Licensee's request and expense, assisting in such defense. Notwithstanding the foregoing, Licensee shall have no obligation under this Section 8.1 or otherwise with respect to any claim to the extent based upon the gross negligence or intentional misconduct of Company. 8.2. **Indemnification by Company**. Company will defend Licensee from any claim, suit, or action brought or made by a third party against Licensee based upon any allegation that the Offerings infringe any valid United States intellectual property rights of such third party ("Licensee Claim"), and Company will indemnify and hold Licensee harmless from and against all losses, damages, liabilities, costs, and expenses (including reasonable attorneys' fees) awarded in such Licensee Claim or those costs and damages agreed to in a monetary settlement of such Licensee Claim. The foregoing obligations are conditioned on Licensee: (a) promptly notifying Company in writing of any such Licensee Claim; (b) giving Company sole control of the defense thereof and any related settlement negotiations, (provided that Company will not enter into any settlement of any claim, suit, or action that does not contain a full release of Licensee's liability without Licensee's prior written approval, which approval will not be unreasonably withheld, conditioned, or delayed); and (c) cooperating and, at Company's request and expense, assisting in such defense. 8.3. **Modifications and Improper Use**. Notwithstanding anything contrary to Section 8.2, Company will have no obligation to Licensee for any Licensee Claim that arises from: (a) any modification to the Offerings by anyone other than Company; (b) modifications made by Company at Licensee's request; (c) use of the Offerings other than as specified in this Agreement or in the applicable Documentation; (d) use of prior versions of the Software after an Update has been provided by Company to Licensee; or (e) use of the Offerings in combination with third-party products, software, hardware or data, if such alleged infringement would not have been asserted without the combination with such other products, software, hardware, or data. 8.4. **Licensee Claims**. If a Licensee Claim arises, or in Company's opinion is likely to arise, Company may at its discretion and own expense either (a) obtain for Licensee the right to continue using the Offerings as contemplated by this Agreement, (b) replace or modify the infringing Offering to make it non-infringing, or (c) if (a) or (b) above are not commercially feasible for Company, Company may terminate this Agreement and refund to Licensee any pre-paid but unused Fees. SECTIONS 8.2 - 8.4 STATE THE ENTIRE OBLIGATION OF COMPANY AND THE EXCLUSIVE REMEDIES OF LICENSEE WITH RESPECT TO ANY CLAIMS OF INFRINGEMENT OR PROPRIETARY RIGHTS VIOLATIONS. 9. **LIMITATION OF LIABILITY**. EXCEPT WITH RESPECT TO A PARTY'S BREACH OF SECTION 10, EITHER PARTY'S INDEMNIFICATION OBLIGATIONS UNDER SECTION 8 OR LICENSEE'S BREACH OF SECTION 3, (A) IN NO EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER FOR CONSEQUENTIAL, EXEMPLARY, INDIRECT, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND (INCLUDING, WITHOUT LIMITATION, LOST PROFITS), REGARDLESS OF THE LEGAL OR EQUITABLE THEORY ON THE BASIS OF WHICH ANY CLAIM FOR DAMAGES IS BROUGHT, INCLUDING, BUT NOT LIMITED TO, BREACH OF CONTRACT, TORT OR STATUTE, EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; AND (B) TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL EITHER PARTY'S AGGREGATE LIABILITY TO THE OTHER PARTY RELATED TO THIS AGREEMENT EXCEED AN AMOUNT EQUAL TO THE FEES PAID BY LICENSEE TO COMPANY DURING THE TWELVE (12) MONTH PERIOD PRECEDING THE EVENTS FIRST GIVING RISE TO ANY SUCH LIABILITY. THIS LIMITATION OF LIABILITY IS INTENDED TO APPLY WITHOUT REGARD TO WHETHER ANY REMEDY UNDER THIS AGREEMENT HAS PROVEN INEFFECTIVE. 10. **CONFIDENTIALITY**. Each party acknowledges that the Confidential Information (as hereinafter defined) of the other party may contain information valuable to the Disclosing Party, and each party that receives such Confidential Information (the "Receiving Party") from the other party (the "Disclosing Party") agrees that Confidential Information will remain the property of the Disclosing Party. Receiving Party will not make use of Disclosing Party's Confidential Information, except as authorized by this Agreement and to the extent necessary for performance or enforcement of this Agreement. Receiving Party will not disclose Disclosing Party's Confidential Information to any third party, except to such Receiving Party's employees and contractors who need to know such information in order for such party to perform under this Agreement and who are bound by confidentiality and non-use obligations not less restrictive than this Agreement. "Confidential Information" means all information that is, or should be reasonably understood to be, confidential or proprietary information of the Disclosing Party (and its suppliers, contractors and customers), including without limitation information concerning its business, products, services, finances, employees, contractors, software, notes, documentation, tools, processes, protocols, product designs and plans, customer lists and other marketing and technical information, whether disclosed orally or in writing by any other media. The terms of this Agreement are considered Confidential Information. Company's Confidential Information includes, but is not limited to, the Software and Documentation included in the Offerings. Licensee's Confidential Information includes Licensee Data. Confidential Information excludes information that (a) is or becomes generally known to the public through no fault or breach of this Agreement by the Receiving Party; (b) is independently developed by a party without reference to the Confidential Information of the other party; (c) was in the Receiving Party's possession free of any obligation of confidence at the time it was communicated to the Receiving Party; or (d) is rightfully obtained by a party from a third party without restriction on use or disclosure. Notwithstanding the foregoing, the Receiving Party will not be in violation of this Section with regard to disclosure of Confidential Information in response to an order or subpoena of a court, agency or tribunal of competent jurisdiction, or pursuant to any applicable law or regulation, provided that the Receiving Party provides the Disclosing Party with prior written notice of such disclosure to the extent reasonably practicable and legally permissible in order to permit the Disclosing Party to seek confidential treatment of such information. Upon expiration or termination of this Agreement for any reason, each party shall return to the other party all Confidential Information of the other party, and all copies thereof, in the possession, custody or control of the party unless otherwise expressly provided in this Agreement. 11. **TERM AND TERMINATION**. 11.1. **Term**. This Agreement shall commence as of the Effective Date and continue in effect for the period stated in the Order Form, and if not so stated, then for an initial term of one (1) year unless terminated earlier as provided herein. This Agreement will automatically renew for successive one (1) year periods unless either party provides the other party written notice of its intention not to renew at least ninety (90) days before the end of the then-current term (the initial term, together with any renewal terms, collectively, the "Term"). 11.2. **Termination**. Either party may terminate this Agreement by giving the other party written notice if (a) the other party is in material breach of this Agreement and such breach remains uncured for thirty (30) days after receipt of notice of such breach, or (b) the other party is insolvent, makes an assignment for the benefit of creditor, receivership, or the institution of any similar proceedings, provided that, such proceedings are not cancelled within sixty (60) days. To clarify, any professional services shall not automatically renew each year and a new Order Form must be created if Licensee wishes to continue any professional services. 11.3. **Effect of Termination**. Upon expiration or termination of this Agreement, all outstanding Order Forms will be terminated, the licenses granted to Licensee under this Agreement will terminate, and Licensee will cease all use of the Offerings. Within ten (10) business days of termination, Licensee will destroy or deliver to Company all copies of the Software or any portion thereof and Documentation in Licensee's possession or under its control, and an officer of Licensee will certify to Company such destruction or delivery. Licensee's failure to comply with the obligations of this Section will constitute unauthorized use of the Offerings, entitling Company to equitable relief as provided in this Agreement and other legal and equitable remedies. Sections 1, 2.1.6, 2.2.4, 3, 5-10, 11.3, and 12-15 shall survive any expiration or termination of this Agreement. 12. **FORCE MAJEURE**. Except for payment obligations, neither party shall be liable to the other for any performance delay or failure to perform hereunder, due to any act, omission or condition beyond the reasonable control of the affected party ("Force Majeure Event"), provided the affected party gives prompt notice to the other and makes reasonable efforts to resume performance as soon as possible. 13. **ADVERTISING**. Licensee agrees that, during the Term, Company may use Licensee's name and logo, subject to Licensee's then-current trademark usage guidelines, in Company's marketing materials or communications and customer list or to identify the Licensee as a customer and user of the Software. Subject to the terms and conditions of this Agreement, Licensee hereby grants to Company a non-exclusive and limited license to use, create derivative works of, and publicly display Licensee's logo as set forth in this Section. 14. **THIRD PARTY SOFTWARE**. 14.1. **Third Party Software**. Licensee acknowledges that the Software may contain or be accompanied by certain third party software products ("Third Party Components") and certain items of such Third Party Components may be subject to open source licenses ("Open Source Software"). Third Party Components may be accompanied by certain notices or license documentation relating to such Third Party Components (collectively, the "Third Party Notices"). Licensee shall comply with the terms of all Third Party Notices governing Licensee's use of such Third Party Components. 14.2. **Disclaimer**. THE COMPANY MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED WITH RESPECT TO OPEN SOURCE SOFTWARE AND THIRD PARTY COMPONENTS, INCLUDING, BUT NOT LIMITED TO, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT, ALL OF WHICH ARE EXPRESSLY DISCLAIMED. 15. **MISCELLANEOUS**. 15.1. **Entire Agreement**. This Agreement, including any exhibits attached hereto, and all applicable Order Forms, constitutes the entire agreement of the parties, and supersedes any prior or contemporaneous agreements between the parties, with respect to the subject of this Agreement. Except as otherwise expressly provided herein, this Agreement may be modified only by a writing signed by an authorized representative of each party. Any standard terms associated with a Licensee purchase order, Licensee ordering document, or Licensee invoice submission system or other portal are hereby rejected (regardless of any electronic or online indication of agreement to the same), will be not binding on the parties, and will be of no consequence whatsoever in interpreting the parties' legal rights and responsibilities as they pertain to this Agreement (including any billing or payment requirements) or the Offerings. This Agreement may be executed in one or more counterparts, each of which shall be deemed an original and all of which shall be taken together and deemed to be one instrument. 15.2. **Governing Law**. This Agreement shall be governed by and construed in accordance with the laws of the State of Colorado exclusive of its conflict of laws principles that would require application of the laws of a different jurisdiction. Any dispute arising under or relating to this Agreement will be resolved in the state or federal courts in Denver, Colorado, and the parties hereby expressly consent to jurisdiction therein. The prevailing party shall be awarded its reasonable attorneys' fees and costs in any suit or proceeding arising out of or related to this Agreement. Nothing in this Agreement shall be construed to limit or delay Company's ability to seek immediate relief at law or in equity for any breach by Licensee of this Agreement. 15.3. **Notice**. Notices under this Agreement shall be in writing, addressed to the party at its address below, and shall be deemed given when delivered personally, or by facsimile (with confirmation of receipt), if sent conventional mail (registered or certified, postage prepaid with return receipt requested) or overnight courier, two (2) business days after the date of mailing. 15.4. **Independent contractors**. The parties are independent contractors and nothing contained in this Agreement is intended or is to be construed to create a partnership, joint venture or agency relationship. 15.5. **Severability**. If any provision of this Agreement shall be declared invalid, illegal or unenforceable, all remaining provisions shall continue in full force and effect, and the invalid or unenforceable provision will be deemed modified so that it is valid and enforceable to the maximum extent permitted by law. 15.6. **Assignment**. Licensee may not delegate, assign or transfer this Agreement, or any of its rights and obligations under this Agreement without Company's prior written consent, and any attempt to do so shall be void. The terms of this Agreement will be binding upon the parties and their respective successors and permitted assigns. 15.7. **Waiver**. Neither party will be deemed to have waived any provision hereof unless such waiver is in writing and executed by a duly authorized officer of both parties, and no waiver of any rights hereunder shall be deemed to be a waiver of the same or other right on any other occasion. ## Exhibit A [Section titled "Exhibit A"](#exhibit-a) ### Support Services [Section titled "Support Services"](#support-services) This Support Services exhibit ("Support Exhibit") by and between Company and Licensee is hereby incorporated into the Licensee License Agreement (the "Agreement"). All terms not otherwise defined in this Support Exhibit have the meanings provided in the Agreement. The terms and conditions in this Support Exhibit only apply if Licensee has purchased Support Services as reflected in an applicable Order Form. 1. **Support Services**. Support Services consist of (a) Error Corrections and Technical Support regarding the installation and use of the Software, and (b) periodic delivery of Updates when Company makes such Updates commercially available to its customers. Telephone, e-mail, verbal and Internet-based support shall only be available during Company's regular business hours in Colorado, which are weekdays, 9:00 a.m. to 5:00 Mountain Time (MST/MDT, as applicable) unless otherwise specified in an applicable Order Form. Such support will be given (i) to answer routine questions regarding the use of the Software; (ii) to assist Licensee in identifying and reporting Errors which may need corrections; (iii) to assist Licensee in identifying and reporting new features and functional improvements that may warrant the development of an Update or Enhancement; and (iv) to provide work-around solutions when reasonably available. Support Services cover only, and Licensee is responsible for obtaining at its expense, Operating Environments designated by Company in the Documentation. If additional implementation services are required due to any incompatibility between Licensee's Operating Environment and the Software and if Licensee requests Company to perform other services and Company agrees to provide such additional services ("Additional Services"), these shall be provided by Company under Exhibit B (Professional Services). At Licensee's request, Company will provide a written quote for rates or fees for specific Additional Services. Company will not be responsible for providing Support Services for any version of the Software other than the then-most recent release of the Software, except that Company will provide Licensee with Support Services for a reasonable period of time to allow Licensee to implement the most recent Update, not to exceed six (6) months. Licensee agrees to maintain the Software to the latest version as soon as practicable and to incorporate all Error Corrections and enhancements to the Software provided by Company. Licensee understands that its failure to incorporate Error Corrections and enhancements will cause the Software to be non-conforming and that subsequent Software Error Corrections, enhancements and updates may be unusable. 2. **Duration**. This Support Exhibit will terminate upon the expiration or termination of the Agreement for any reason. Company may terminate this Support Exhibit or suspend Support Services if Licensee fails to make payment as provided under the Agreement or breaches this Support Exhibit and such breach is not remedied within fifteen (15) days after Licensee receives notice of the breach. 3. **Fees and Payment**. Licensee agrees to pay the Fees and other charges as specified in the Order Form for the Support Services. Termination of this Support Exhibit will not relieve Licensee of its obligations to pay all Fees, other charges and expenses that accrued prior to such termination. 4. **Error Priority Levels**. 4.1. Company shall exercise commercially reasonable efforts to correct any sufficiently identified Error reported in writing by Licensee in accordance with the priority level reasonably assigned to such Error by Company. (a) In the event of (i) a crash of Licensee's computer network causing a critical impact to business operations that Licensee reasonably believes is due to an Error in the Software or (ii)Priority A Errors, Company will promptly commence verification of the Error and, upon verification, will initiate work to provide Licensee with an Error Correction. If Licensee has purchased Enterprise Support, Company will make the initial verification of the Error and report its findings back to Licensee within 1 hour of the report of the Error by Licensee. Company will provide Licensee with reports on the status of the Error Correction every two (2) hours. If commercially feasible, Error Correction will be delivered within twenty four (24) hours of the report of the Error by Licensee. If Licensee has purchased Basic Support, Company will provide Licensee with periodic reports on the status of the Error Correction. If commercially feasible, Error Correction will be delivered within seventy two (72) hours of the report of the Error by Licensee. (b) In the event of Priority B Errors, Company will commence verification during normal support hours of the Error, and upon verification, initiate work to provide Licensee with Error Correction. If Licensee has purchased Enterprise Support, Company will make the initial verification of the Error and report its findings back to Licensee within six (6) hours of the report of the Error by Licensee. If commercially feasible, Error Correction will be delivered within 1 week of the report of the Error by Licensee. If Licensee has purchased Basic Support, Company will provide Error Correction in the next Update of the Software. (c) In the event of Priority C Errors, Company may include the Error Correction for the Error in the next Update of the Software. 4.2. If Company believes that a problem reported by Licensee may not be due to an Error in the Software, Company will so notify Licensee. At that time, Licensee may (a) instruct Company to proceed with problem determination at Licensee's possible expense as set forth below or (b) instruct Company that Licensee does not wish Company to pursue the problem. If Licensee requests that Company proceed with problem determination at Licensee's possible expense and Company determines that the error was not due to an Error in the Software, Licensee shall pay Company, at Company's then-current and standard consulting rates, for all work performed in connection with such determination, plus reasonable related expenses incurred by Company. If Licensee instructs Company that it does not wish the problem pursued at its possible expense or if such determination requires efforts in excess of Licensee's instructions, Company may, at its sole discretion, elect not to investigate the problem with no liability therefore. 5. **Exclusions**. 5.1. Company shall have no obligation to support: (a) altered, damaged or modified Software or any portion of the Software incorporated with or into other software, except for modifications or alterations provided as a result of Support Services provided by Company; (b) Software that is not the then current release (except as provided in Section 1 above); (c) Software problems caused by use of, or changes to, third party software with which the Software is used; or (d) Software problems caused by (i) Licensee's negligence, abuse or misapplication of Software, other than as specified in the Documentation (including incompatible operating environments and systems, unless Support Services have been specifically provided to make the Software compatible with such Operating Environments), or (ii) accidents, acts of nature, disasters, strikes, acts of war, pandemics, viruses introduced by parties other than Company, or other causes beyond the reasonable control of Company. 5.2. Company shall have no liability for any changes in Licensee's hardware which may be necessary to use Software due to an Update (including any Error Correction). 5.3. IN ADDITION TO WARRANTY DISCLAIMERS PROVIDED IN THE AGREEMENT, COMPANY DOES NOT WARRANT OR REPRESENT THAT EVERY REPORTED PROBLEM CAN OR WILL BE RESOLVED TO THE SATISFACTION OF LICENSEE AND DOES NOT WARRANT UNINTERRUPTED OR ERROR FREE OPERATION OF THE SOFTWARE OR ANY OTHER PRODUCT OR SERVICE PROVIDED BY COMPANY. ## Exhibit B [Section titled "Exhibit B"](#exhibit-b) ### Professional Services [Section titled "Professional Services"](#professional-services) This Professional Services exhibit ("Services Exhibit") by and between Company and Licensee is hereby incorporated into the Licensee License Agreement (the "Agreement") and provides the terms and conditions for certain Company professional services purchased by Licensee related to Licensee's use of the Offerings. All terms not otherwise defined in this Services Exhibit have the meanings provided in the Agreement. The terms and condition in this Services Exhibit only apply if Licensee has purchased professional services as reflected in an applicable Order Form. 1. **Services to be Provided**. Company agrees to provide the professional services ("Services"), and Licensee agrees to pay the Fees, identified in separate statement(s) of work (each a "SOW"), unless otherwise set forth in an Order Form. If any onsite visits to Licensee's premises are necessary to perform the Services, Licensee agrees to provide Company with reasonable access to the premises, Licensee's equipment and other parts of Licensee's system as may be necessary or appropriate. Company will use commercially reasonable efforts to perform the Services for Licensee in accordance with the schedule specified in the applicable SOW or applicable Order Form. 2. **Duration**. This Services Exhibit will terminate upon the expiration or termination of the Agreement for any reason. Company may terminate this Services Exhibit or suspend Services if Licensee fails to make payment as provided under this Agreement or breaches this Services Exhibit and such breach is not remedied within fifteen (15) days after Licensee receives notice of the breach. 3. **Fees and Payment**. Unless otherwise specified in a SOW or the applicable Order Form, Company will periodically invoice Licensee for Fees and other charges and reimbursable expenses for the Services. Invoices will be due and payable within thirty (30) days of invoice date. Termination of this Services Exhibit will not relieve Licensee of its obligations to pay all Fees, other charges and expenses that accrued prior to such termination. 4. **Proprietary Rights**. Unless otherwise expressly agreed to in writing by the parties, Licensee agrees that any and all deliverables or work product provided to Licensee ("Deliverables") or other results of the Services shall be owned exclusively by Company including all intellectual property and proprietary rights therein. Company hereby grants Licensee a non-exclusive and non-transferable license to use the Deliverables, during the Term of the Agreement, solely for Licensee's authorized use of the Offerings pursuant to the licenses granted to Licensee in the Agreement. 5. **Licensee Assistance**. Licensee shall provide Company with such resources, information and assistance as Company may reasonably request in connection with the performance of the Services. Licensee acknowledges and agrees that Company's ability to successfully perform the Services in a timely manner is contingent upon its receipt from Licensee of the information, resources and assistance requested. Company shall have no liability for deficiencies in the Services resulting from the acts or omissions of Licensee, its agents or employees or performance of the Services in accordance with Licensee's instructions. ## Exhibit C [Section titled "Exhibit C"](#exhibit-c) ### SLA [Section titled "SLA"](#sla) This SLA exhibit ("SLA Exhibit") by and between Company and Licensee is hereby incorporated into the Licensee License Agreement (the "Agreement"). All terms not otherwise defined in this SLA Exhibit have the meanings provided in the Agreement. The terms and condition in this exhibit only apply if Licensee has purchased access to the Hosted Platform. 1. **System Availability**. Company shall provide the Hosted Platform on a twenty four (24) hours per day, 365 days per year basis with an availability uptime set forth in the Order Form, excluding scheduled maintenance which shall not be performed during normal business hours of operation, weekdays from 9:00 a.m. to 5:00 Mountain Time (MST/MDT, as applicable) ("Service Level"). Company will provide Licensee with its maintenance schedule and will endeavor to pre-notify Licensee of any non-scheduled maintenance. The term "availability uptime" means that the Software hosted by Company, is accessible and available to Licensee and its authorized users without interference or interruption, excluding any Excused Delay. 2. **Remedies.** In the event that Company fails to meet any of the Service Levels set forth in this SLA Exhibit (each such failure, a "SLA Failure") within a calendar month, and such SLA Failure that is not excused due to any Excused Delay (as defined below), then Licensee shall be entitled to receive service level credits as follows (each, a "SLA Credit"): (a) The ratio of unavailable minutes to total potentially available minutes (net of Excused Delays) in the applicable calendar month during which the Service Level was not met multiplied by the monthly fee for the Hosted Platform due for such month (1/12 of the annual fee if the Fees for access to the Hosted Platform is paid annually). Notwithstanding anything to the contrary, in no event shall the value of the SLA Credits exceed three (3) months of Hosted Platform fees ("SLA Credit Cap"). Licensee shall notify Company in writing if Licensee believes it is entitled to any SLA Credit, which notice shall be provided no later than thirty (30) days after the end of the applicable month in which the SLA Failure(s) occurred, and shall describe the SLA Failure(s) in detail. If Company agrees that Licensee is due an SLA Credit, Company will promptly credit Licensee's account. If Company does not reasonably believe Licensee is entitled to an SLA Credit, the parties will meet and discuss the issue in good faith. If the parties cannot mutually agree on a resolution, Company's determination shall be binding and final. In the event Licensee brings any claim for damages based upon response times, then any such damages shall be offset by any SLA Credits issued to Licensee hereunder. For purposes herein, "Excused Delay" shall mean (a) scheduled maintenance, (b) maintenance or service interruptions requested by Licensee and implemented by Company, (c) Licensee's breach of any provision of this Agreement that solely and directly caused the delay, (d) any delay solely and directly caused by any Licensee Data, (e) performance of internet services, or (f) any delay caused by a Force Majeure Event. ## Version [Section titled "Version"](#version) This is the Searchcraft Customer License Agreement version 010 dated 2025-05-05. # General Disclaimer Please wait while the policy is loaded. If it does not load, please [click here to view the policy](https://app.termageddon.com/api/policy/VlRsSE5tRnphV3RuU2xCVU5IYzlQUT09?h-align=left\&h-depth=3\&no-title=true\&table-style=accordion). # End User License Agreement Please wait while the policy is loaded. If it does not load, please [click here to view the policy](https://app.termageddon.com/api/policy/V2pscGVXbDBPRlZpVlVGcUswRTlQUT09?h-align=left\&h-depth=3\&no-title=true\&table-style=accordion). # Privacy Policy Please wait while the policy is loaded. If it does not load, please [click here to view the policy](https://app.termageddon.com/api/policy/Vm1SSFUzVlpNakJZTkZZclRuYzlQUT09?h-align=left\&h-depth=3\&no-title=true\&table-style=accordion). # Terms of Service Please wait while the policy is loaded. If it does not load, please [click here to view the policy](https://app.termageddon.com/api/policy/U1hORWNXSlpOelJMUTA1VWFGRTlQUT09?h-align=left\&h-depth=3\&no-title=true\&table-style=accordion). # Pricing Please visit our [Pricing](https://searchcraft.io/pricing/) page for up to date plan information # Component Layouts There are two main methods for rendering an input box and search results in your application: An inline layout and a popover layout. In this section, we'll go over how to implement both of these layouts in each framework. ### Inline Layout [Section titled "Inline Layout"](#inline-layout) A inline layout means that the input element, search results, and filter panels will render inline with the rest of the elements on your web page. The component library provides several main components to help you with this. * JS index.html ```html
``` * React Component.tsx ```jsx import { SearchcraftTheme, SearchcraftInputForm, SearchcraftResultsInfo, SearchcraftSearchResults, SearchcraftSearchResultsPerPage, SearchcraftPagination } from '@searchcraft/react-sdk'; export const MySearchPage = () => ( <>
); ``` * Vue Component.vue ```vue ``` ### Popover Layout [Section titled "Popover Layout"](#popover-layout) A Popover Search layout means that the search input form and search results will render in a modal view above the rest of your page content, independent from your existing page layout. * JS index.html ```html

Here's some content that shows up underneath the popover. The popover should render above this content when it is active.

``` * React Component.tsx ```jsx import { SearchcraftTheme, SearchcraftPopoverForm, } from '@searchcraft/react-sdk'; const Component = () => ( <>

Here's some content that shows up underneath the popover. The popover should render above this content when it is active.

); ``` * Vue Component.vue ```vue ``` # Custom Ads Searchcraft's SDKSs provide a means for rendering custom ad containers alongside search results. These custom ad containers allow you to use your application's existing ad implementation. ### Define Your Ad Template [Section titled "Define Your Ad Template"](#define-your-ad-template) Specify the template that searchcraft should use to render your ad container. main.ts ```ts import type { CustomAdTemplate } from '@searchcraft/'; export const customAdTemplate: CustomAdTemplate = (data, { html }) => html`
Custom ad for ${data.searchTerm}
`; ``` ### SearchcraftConfig Properties [Section titled "SearchcraftConfig Properties"](#searchcraftconfig-properties) Update your `SearchcraftConfig` object with the properties needed to tell it to render custom ads. For in-depth look at all of the properties available, see the [SearchcraftConfig](/sdks/javascript/reference/searchcraft-config/) reference. ```ts const searchcraft = new Searchcraft({ indexName: process.env.SEARCH_INDEX_FROM_VEKTRON, readKey: process.env.READ_KEY_FROM_VEKTRON, endpointURL: process.env.ENPOINT_URL_FROM_VEKTRON, customAdConfig: { template: customAdTemplate, adContainerRenderedDebounceDelay: 1000, adStartQuantity: 1, adInterstitialInterval: 3, adInterstitialQuantity: 1, adEndQuantity: 1, } }) ``` ### Listen for Ad Events [Section titled "Listen for Ad Events"](#listen-for-ad-events) It's common that your application will need to make a call to your ad provider's code when an ad container is rendered. There are several events available to do that. ```ts const unsubscribeCallback = searchcraft.subscribe('ad_container_rendered', (event) => { // Make a call to your ad provider's code }); // In your cleanup function, call unsubscribe: unsubscribeCallback(); ``` ```ts const unsubscribeCallback = searchcraft.subscribe('ad_container_viewed', (event) => { // Make a call to your ad provider's code }); // In your cleanup function, call unsubscribe: unsubscribeCallback(); ``` For a complete listing of all events, see the [Events Reference](/sdks/javascript/reference/events/). # Filtering Search Results The Searchcraft SDKs provide a component called `` that renders a view that allows users to filter their search results in different ways. When a user interacts with the filters in a Filter Panel, a new search request is made, and the search results displayed in the `` component are automatically updated. ### Rendering a Filter Panel [Section titled "Rendering a Filter Panel"](#rendering-a-filter-panel) * JS index.html ```html ``` main.ts ```ts document.addEventListener('DOMContentLoaded', () => { const filterPanel = document.querySelector('searchcraft-filter-panel'); if (filterPanel) { filterPanel.items = filterPanelItems; } }); ``` * React Component.tsx ```jsx import { SearchcraftFilterPanel } from "@searchcraft/react-sdk"; // In your component: ``` * Vue Component.vue ```vue ``` ### The `items` Prop [Section titled "The items Prop"](#the-items-prop) The `` component requires a prop called `items`. This prop is an array of objects that defines which filters the panel should show. You can include as many or as few items in the array as you need. An items array will look something like this: ```ts const filterPanelItems = [ { type: 'exactMatchToggle', label: 'Exact Match', options: { subLabel: 'Specify to use exact matching or fuzzy matching.', }, }, { type: 'mostRecentToggle', label: 'Most Recent', options: { subLabel: 'Choose whether to sort by most recent.', }, }, { type: 'dateRange', fieldName: 'date_published', // This is a corresponding index field name of your index label: 'Date range example', options: { minDate: pastDate, maxDate: today, granularity: 'year', }, }, { type: 'numericRange', fieldName: 'number_field', // This is a corresponding index field name of your index label: 'Numeric range example', options: { min: 0, max: 100, granularity: 10, }, }, { type: 'facets', fieldName: 'section', label: 'Filters', options: { showSublevel: true, }, } ]; ``` For a complete reference to all available filter panel item types and their properties, see the [Filter Panel Items](/sdks/javascript/reference/filter-panel-items/) reference page. ### Toggling Filter Panel Visibility on Mobile [Section titled "Toggling Filter Panel Visibility on Mobile"](#toggling-filter-panel-visibility-on-mobile) On mobile layouts, the filter panel can be hidden by default to save screen space. You can add the `data-toggle-filter-panel` attribute to any HTML element (such as a button) to toggle the visibility of the filter panel. * JS index.html ```html

Filters

``` * React Component.tsx ```jsx import { SearchcraftFilterPanel } from "@searchcraft/react-sdk";

Filters

``` * Vue Component.vue ```vue ``` When the element with `data-toggle-filter-panel` is clicked on mobile, it will toggle the visibility of the filter panel. This may be combined with the responsiveBreakpoint setting on the filter panel component if custom breakpoints are desired. # Getting Started ## Installing the SDK [Section titled "Installing the SDK"](#installing-the-sdk) Begin by installing the SDK for your framework of choice through your favorite package manager. * JS ```bash npm install @searchcraft/javascript-sdk ``` * React ```bash npm install @searchcraft/react-sdk ``` * Vue ```bash npm install @searchcraft/vue-sdk ``` ## Instantiating the Searchcraft Class [Section titled "Instantiating the Searchcraft Class"](#instantiating-the-searchcraft-class) As early as possible in your app's lifecycle, instantiate the Searchcraft class, passing in config values that correspond with your Searchcraft environment. You only need to initialize this class one time. main.ts ```ts import { Searchcraft, type SearchcraftConfig } from '@searchcraft/[your-chosen-framework]'; const config: SearchcraftConfig = { indexName: process.env.SEARCH_INDEX_FROM_VEKTRON, readKey: process.env.READ_KEY_FROM_VEKTRON, endpointURL: process.env.ENPOINT_URL_FROM_VEKTRON, }; const searchcraft = new Searchcraft(config); ``` ### The SearchcraftConfig Object [Section titled "The SearchcraftConfig Object"](#the-searchcraftconfig-object) When instantiating the Searchcraft class, you pass in a configuration object. This object is crucial; without it, the SDK won't know which search index you'd like to use. Additionally, there are many other configuration options in SearchcraftConfig that you can specify that affect UI, functionality, and behavior of the component library. See the [SearchcraftConfig reference document](/sdks/javascript/reference/searchcraft-config/) for a full list of available options. ## Using Components [Section titled "Using Components"](#using-components) The method for adding components to your application varies by framework. Regardless of framework, however, all components receive the same properties and render the same layouts. For more framework-specific information see the following: * [Component Layouts](/sdks/javascript/component-layouts/) ## Applying CSS [Section titled "Applying CSS"](#applying-css) **IMPORTANT:** To apply Searchcraft's CSS to the components, please make sure that the `` component is placed somewhere in your app. This component adds a `