XTAL Search — Integration Guide
https://xtalsearch.com/docs/integration
Integration Guide
XTAL Search is an AI-powered product search engine for e-commerce. It combines vector similarity search, AI query understanding, and real-time faceting to deliver highly relevant product results.
Architecture
The storefront sends search queries to the XTAL API (hosted at xtalsearch.com), which returns ranked product results with facets and AI-generated aspects.
Quick Start
The fastest way to integrate XTAL Search is via our JavaScript snippet. Add this to your storefront's <head> tag:
<link rel="preconnect" href="https://www.xtalsearch.com">
<script>
(function(){
var s = document.createElement('script');
s.src = 'https://www.xtalsearch.com/client/v1/xtal.js';
s.async = true;
s.dataset.shopId = 'YOUR_COLLECTION_ID';
document.head.appendChild(s);
})();
</script>Replace YOUR_COLLECTION_ID with your assigned collection identifier. The snippet automatically discovers your search input and renders results inline in your search results container.
Integration Options
XTAL supports two integration approaches. Choose based on your platform's architecture and requirements.
JavaScript Snippet
- Single
<script>tag in storefront head - Zero backend changes required
- Renders results inline in your existing search results container
- Intercepts your existing search input and submit form
- Best for: quick integration, minimal dev effort
Server-Side API
- Your backend calls XTAL REST endpoints
- Render results in your own templates
- Full control over result display and UX
- Better for SEO (server-side rendering)
- Best for: custom UX, SSR, deep platform integration
| Aspect | JS Snippet | Server-Side API |
|---|---|---|
| Implementation effort | Minimal (1 script tag) | Moderate (API + templates) |
| Backend changes | None | Required |
| SEO / SSR | Client-only rendering | Full SSR support |
| UX customization | Card template + collection settings | Full control |
| Render mode | Inline (replaces native results) | Your templates |
| Latency | Direct browser → XTAL | Browser → your server → XTAL |
API Reference
/api/xtal/searchExecute a product search query. Returns ranked results, facets, relevance scores, and search context for subsequent filtering.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | The search query text |
| collection | string | Yes | Collection identifier (e.g., "willow") |
| limit | number | No | Results per page (default: 48) |
| offset | number | No | Offset for pagination / Load More (default: 0) |
| search_context | SearchContext | No | Context from a previous search. Pass back to filter/paginate without re-running query understanding |
| selected_aspects | string[] | No | AI-generated aspect filters to apply (billable as aspect_click) |
| facet_filters | Record<string, string[]> | No | Tag-based facet filters (e.g., {"color": ["red"]}) |
| price_range | {min, max} | No | Price filter in dollars (e.g., {"min": 10, "max": 100}) |
| session_id | string | No | Stable per-tab session id. Used to stitch search → click → conversion in analytics |
| search_source | string | No | Origin of the search: "hero", "header", "url_param", "api", or "filter" |
| is_demo | boolean | No | Marks the call as test traffic. REQUIRED for any CLI / script / curl call against a live customer collection — unmarked traffic is billed |
Example Request
POST /api/xtal/search
Content-Type: application/json
{
"query": "wireless headphones",
"limit": 24,
"collection": "willow"
}Example Response
{
"results": [
{
"id": "prod_8291",
"title": "Premium Wireless Over-Ear Headphones",
"name": "Premium Wireless Over-Ear Headphones",
"price": 149.99,
"image_url": "https://cdn.example.com/headphones-1.jpg",
"product_url": "https://store.example.com/products/headphones-1",
"vendor": "AudioTech",
"product_type": "Headphones",
"tags": ["category_electronics", "brand_audiotech", "color_black"],
"description": "High-fidelity wireless headphones with ANC...",
"images": [{"src": "https://cdn.example.com/headphones-1.jpg"}],
"variants": [{"price": 149.99, "compare_at_price": 199.99, "sku": "AT-WH100-BLK"}],
"available": true
}
],
"total": 42,
"query_time": 187,
"relevance_scores": {
"prod_8291": 0.92
},
"search_context": {
"augmented_query": "wireless bluetooth headphones over-ear",
"extracted_price_lte": null,
"extracted_price_gte": null,
"product_keyword": "headphones"
},
"computed_facets": {
"category": {"electronics": 42, "audio": 38},
"brand": {"audiotech": 12, "soundwave": 8},
"color": {"black": 20, "white": 15}
},
"is_sku_search": false,
"sku_found": false,
"top_dense_score": 0.71,
"redirect_url": null
}Response Fields (Beyond Results)
| Field | Type | Required | Description |
|---|---|---|---|
| total | number | Yes | Total matching products (across all pages) |
| query_time | number | Yes | Backend search time in milliseconds |
| search_context | SearchContext | No | Pass back on subsequent filter/pagination calls |
| computed_facets | Record<string, Record<string, number>> | No | Facet value counts, grouped by tag prefix |
| relevance_scores | Record<string, number> | No | Per-product relevance score, keyed by product id |
| is_sku_search | boolean | No | True when the query looks like a SKU (used by the SDK to gate the showcase fallback) |
| sku_found | boolean | No | When is_sku_search is true, whether the SKU matched a product |
| top_dense_score | number | null | No | Top dense cosine score. SDK shows a showcase below ~0.24 (configurable) |
| redirect_url | string | null | No | Taxonomy redirect target. When present, the SDK navigates to this URL instead of rendering results |
/api/xtal/aspectsGenerate AI-powered search aspects (dynamic facets) for a given query. Aspects are natural-language refinement options like "noise cancelling" or "under $50".
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | The search query text |
| selected_aspects | string[] | No | Previously selected aspects (for context) |
| collection | string | No | Collection identifier |
Example Request
POST /api/xtal/aspects
Content-Type: application/json
{
"query": "wireless headphones",
"collection": "willow"
}Example Response
{
"aspects": [
"noise cancelling",
"over-ear",
"under $100",
"bluetooth 5.0",
"long battery life"
],
"aspects_enabled": true
}/api/xtal/search-fullCombined search + aspects in a single request. Fires both in parallel and returns search results with AI-generated aspects. This is what the JS snippet uses internally.
Request Body
Same fields as /api/xtal/search.
Response
Returns the full search response merged with aspect data:
{
"results": [...],
"total": 42,
"query_time": 187,
"relevance_scores": { ... },
"search_context": { ... },
"computed_facets": { ... },
"aspects": ["noise cancelling", "over-ear", "under $100"],
"aspects_enabled": true
}/api/xtal/explainGenerate an AI explanation for why a specific product was returned for a query.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | The original search query |
| collection | string | Yes | Collection identifier |
| product_id | string | Yes | The product to explain |
| score | number | No | Relevance score from the search response |
Example Response
{
"explanation": "This product matches your search because it features wireless Bluetooth 5.0 connectivity and active noise cancellation, which are key attributes of premium wireless headphones."
}/api/xtal/browseReturn the full catalog without a query, paginated and filterable. Powers the SDK's "Browse All" mode (triggered by an empty or * query) and is only available when browse_all_enabled is set for the collection. Non-billable; tracked for analytics only.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| collection | string | Yes | Collection identifier |
| limit | number | No | Page size (default: 48) |
| offset | number | No | Offset for pagination (default: 0) |
| facet_filters | Record<string, string[]> | No | Tag-based facet filters |
| price_range | {min, max} | No | Price filter in dollars |
Response
Same shape as /api/xtal/search (results, total, computed_facets). Returns 403 if browse-all is not enabled for the collection.
/api/xtal/eventsEngagement and telemetry sink. The JS snippet calls this via navigator.sendBeacon on product clicks, add-to-cart events, abandoned searches, and SDK errors, so events survive tab close. Server-side integrations should call it directly.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | Yes | One of: "product_click", "add_to_cart", "search_abandon", "error" |
| collection | string | Yes | Collection identifier |
| product_id | string | No | Required for product_click and add_to_cart |
| product_title | string | No | Product title (for analytics readability) |
| query | string | No | The originating search query |
| ts | number | No | Client timestamp in ms |
| error | string | No | Error message (for action: "error") |
| context | string | No | Where the error happened (e.g., "search", "filter", "config") |
| is_demo | boolean | No | Skip billing-log + analytics for test traffic |
/api/xtal/feedbackRecord relevance feedback for a query × product pair (thumbs up/down style signals). Used to seed the search-quality dataset for prompt and reranking tuning.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | The original search query |
| collection | string | Yes | Collection identifier |
| product_id | string | Yes | The product receiving feedback |
| action | string | Yes | Feedback action (e.g., "positive", "negative") |
| score | number | No | Relevance score from the originating search response |
| augmented_query | string | No | The augmented query from search_context (if available) |
| product_title | string | No | Product title — stored for dataset readability |
| product_vendor | string | No | Product vendor |
| product_type | string | No | Product type |
| product_tags | string[] | No | Product tags |
| product_price | number | No | Product price |
| product_image_url | string | No | Product image URL |
| prompt_hash | string | No | Returned by /api/xtal/explain. Links feedback to the explain prompt variant |
/api/xtal/conversionConversion pixel. Called from the order confirmation page with the completed order and the _xtal_utm attribution cookie. Used to compute XTAL-influenced revenue. Attribution is granted when the cookie'sshop matches thecollection.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| collection | string | Yes | Collection identifier |
| order_id | string | Yes | Merchant order id (used for dedupe) |
| order_total | number | No | Order total in dollars |
| items | Item[] | No | Array of {sku, name, qty, price} line items |
| cookie_value | string | No | Raw value of the _xtal_utm cookie, JSON-stringified {q, pid, shop, ts} |
Example Response
{
"status": "ok",
"attributed": true
}Authentication
Storefront Endpoints (CORS-Public)
The storefront-facing endpoints — /api/xtal/search, /api/xtal/search-full, /api/xtal/aspects, /api/xtal/explain, /api/xtal/browse, /api/xtal/events, /api/xtal/feedback, /api/xtal/conversion, /api/xtal/config — do not require authentication. They return CORS headers scoped to the collection's configured site URL so the JS snippet can call them directly from the browser.
Calls are gated by collection (must be a valid configured collection) and billed per request unless is_demo: true is sent in the body. CLI, script, and curl calls against a live customer collection must include is_demo: true or they will be billed.
Server-to-Server Keys
For private server-to-server integrations (back-office tooling, recommendation pipelines, etc.), XTAL can issue collection-scoped API keys in the format xtal_<token>, passed via the X-API-Key header. Contact us if you need one.
/api/xtal/configRetrieve the configuration for a collection. Used by the JS snippet to configure its behavior. Cached for 5 minutes.
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| shopId | string | Yes | Collection identifier (e.g., "willow") |
Example Request
GET /api/xtal/config?shopId=willowExample Response
{
"enabled": true,
"searchSelector": "input[type=\"search\"]",
"displayMode": "inline",
"resultsSelector": ".product-grid",
"siteUrl": "https://store.example.com",
"resultsPerPage": 48,
"features": {
"aspects": true,
"explain": true,
"filters": true,
"hidePrices": false,
"toolbar": true
},
"cardTemplate": { "html": "<a class=\"xtal-card\">...</a>", "css": ".xtal-card { ... }" },
"productUrlPattern": "/products/{sku}",
"pricePresets": [{ "label": "Under $25", "max": 25 }],
"hiddenFacets": ["vendor"],
"facetOrder": ["category", "color", "brand"],
"showcaseQueries": [{ "query": "best sellers", "label": "Best sellers" }],
"showcaseHeader": "It's not you, it's us",
"showcaseSubheader": "We don't have a great match for {query}. Try one of these:",
"confidenceThreshold": 0.24,
"browseAllEnabled": true,
"searchPagePath": "/shop/",
"searchQueryParam": "Search"
}Response is cached for 5 minutes. All fields beyond enabled, searchSelector, displayMode, siteUrl, resultsPerPage, and features are optional and only included when configured for the collection. SDK behavior (card template, showcase fallback, taxonomy redirects, browse-all, hero nav routing) is driven entirely by these fields.
Data Models
Product
Each result in the search response is a Product object with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Unique product identifier |
| title | string | Yes | Product name |
| name | string | Yes | Alternate name field |
| price | number | number[] | Yes | Price in dollars. Array for price ranges (e.g., variant min/max) |
| image_url | string | null | Yes | Primary product image URL |
| product_url | string | Yes | Link to the product detail page |
| vendor | string | Yes | Brand or manufacturer name |
| product_type | string | Yes | Product category (e.g., "Headphones") |
| tags | string[] | Yes | Metadata tags used for faceting (e.g., ["color_black", "brand_sony"]) |
| description | string | No | Product description text |
| images | Image[] | Yes | Array of {src, alt_text} image objects |
| variants | Variant[] | Yes | Array of {price, compare_at_price} variant objects |
| available | boolean | Yes | Whether the product is in stock |
SearchContext
Returned with initial search results. Pass this back on subsequent requests (filtering, pagination) to avoid re-processing the query.
| Field | Type | Required | Description |
|---|---|---|---|
| augmented_query | string | Yes | AI-enhanced version of the original query |
| extracted_price_lte | number | null | Yes | Extracted max price from natural language (e.g., "under $50" → 50) |
| extracted_price_gte | number | null | Yes | Extracted min price from natural language |
| product_keyword | string | null | No | Extracted product type keyword |
Facets & Filtering
Products are tagged with prefixed metadata (e.g., color_black, brand_sony). The search response groups these into computed_facets, keyed by prefix with value counts:
{
"computed_facets": {
"color": {"black": 20, "white": 15, "red": 8},
"brand": {"sony": 12, "bose": 10, "apple": 7},
"category": {"headphones": 30, "earbuds": 12}
}
}To filter by facet, send facet_filters in the search request:
{
"query": "wireless headphones",
"search_context": { "..." : "..." },
"facet_filters": {
"color": ["black"],
"brand": ["sony", "bose"]
}
}Configuration
Collection Settings
Each integration is configured as a "collection" — XTAL's term for one customer's catalog. Settings live in Redis under admin:settings:<collection>:* and are managed from the XTAL admin panel. The proxy reads them at request time and the JS snippet picks up display-related ones via /api/xtal/config.
Search behavior
| Field | Type | Required | Description |
|---|---|---|---|
| store_type | string | No | Store category for AI prompts (e.g., "home goods retailer") |
| results_per_page | number | No | Default page size (default: 48) |
| query_enhancement_enabled | boolean | No | AI query expansion for better recall (default: true) |
| aspects_enabled | boolean | No | AI-generated aspect suggestions (default: true) |
| browse_all_enabled | boolean | No | Whether /api/xtal/browse is exposed for this collection |
| searchable_filter_enabled | boolean | No | Restrict to products flagged searchable (storefront sellability rules) |
| confidence_threshold | number | No | Top dense cosine below this shows the showcase fallback (default: 0.24) |
Snippet / SDK display
| Field | Type | Required | Description |
|---|---|---|---|
| site_url | string | No | Merchant origin (used to resolve relative product URLs and route hero searches) |
| snippet_search_selector | string | No | CSS selector for the search input (default: input[type="search"]) |
| snippet_display_mode | "inline" | No | Render mode. SDK currently only ships inline rendering |
| results_selector | string | No | CSS selector for the merchant's existing results container (required for inline mode) |
| search_page_path | string | No | Path of the merchant search page (default: "/shop/"). Hero searches navigate here |
| search_query_param | string | No | Query-string param the merchant uses for search (default: "Search") |
| card_template | {html, css} | No | Per-collection product card template. Generated from the merchant's site styling |
| product_url_pattern | string | No | Template like "/products/{sku}" used to build product links from SKU |
| price_presets | {label, min, max}[] | No | Quick-filter price buttons in the filter rail |
| hidden_facets | string[] | No | Facet prefixes to exclude from the filter rail |
| facet_order | string[] | No | Display order for facet sections in the filter rail |
| hide_prices | boolean | No | Gate pricing behind auth (renders prices only when the merchant page reports the user as authenticated) |
| toolbar_enabled | boolean | No | Show the results-per-page + sort toolbar (default: true) |
| showcase_queries | {query, label}[] | No | Suggested searches shown when a query returns nothing or scores below the confidence threshold |
| showcase_header | string | No | Headline for the showcase fallback |
| showcase_subheader | string | No | Subhead for the showcase fallback. {query} is interpolated |
UTM Tracking
The JS snippet automatically appends UTM parameters to product links for attribution tracking. These are added client-side by the SDK — they are not part of the API response:
Features
The following features are available per collection:
AI Aspects
Generates natural-language refinement options for each query (e.g., "noise cancelling", "under $50"). Displayed as clickable chips that filter results.
Explain
On-demand AI explanation for why a specific product was returned for a query. Helps build user trust in search relevance.
Query Enhancement
Automatically expands queries using AI to improve recall. For example, "comfy chair" may be enhanced to include "ergonomic", "cushioned", "lounge".
Next Steps
To begin your integration, we recommend:
- Choose an integration approach — JS snippet for quick deployment, or server-side API for full control.
- Share your platform constraints — Can you add custom script tags to the storefront? Do you have server-side extension points for API calls?
- Schedule a technical walkthrough — We'll walk through the API together and answer any questions about data formats, authentication, or customization.
Contact: team@xtalsearch.com
XTAL Search · xtalsearch.com