Facets and Searchabilities v2 migration guide

Constructor is upgrading the Facets and Searchabilities API from /v1 to /v2. This guide covers what's changing and how to migrate your integration.

Key dates:

  • Accounts that only use the dashboard have already been migrated automatically.
  • All new indexes, sections, and accounts created after February 17, 2026 use v2 by default.
  • Existing accounts using the API directly will be migrated within the next few months. You will be contacted with your specific migration date.

Version access:

  • Calling /v2 endpoints before your account is migrated returns 404.
  • Calling /v1 endpoints after migration returns 410 Gone.
  • Only one API version is active per index at a time.

API reference:

What are the benefits of a version 2?

In v1, creating a facet was a two-step process:

  1. create the searchability
  2. create the facet configuration

The facet configuration did not directly reference the metadata field path. Instead, the system matched facets to searchabilities behind the scenes using the field name. This coupling meant both objects had to be kept in sync, and the relationship between them was implicit. It also forced the creation of a searchability even when the field only needed to be a facet (i.e., not searchable or displayable).

v1 flow: Searchability (facetable=true, name="facets.brand")  ←→  Facet Configuration (name="brand")

In v2, we are decoupling facets from searchabilities. Each facet configuration includes a new path_in_metadata field that directly specifies the metadata field path. This eliminates the need to set facetable=true on a searchability to define a facet, and makes each facet configuration self-contained.

v2 flow: Facet Configuration (name="brand", path_in_metadata="facets.brand")

What's changing

Below is the changelog for the Facets API and Searchabilities API.

Facets API v2 changes

EndpointChangeBreaking?
AllAdded path_in_metadata field in request and response bodies. Required on POST and PUT.Yes
Alloptions removed from request and response bodies. Use the separate /v1/facets/{name}/options endpoint to manage options.Yes
AllFacet renaming is forbidden. Sending a name that differs from the URL returns 409.Yes
POST, PUTposition must be >= 1.Yes
PATCH /v2/facetsRequest body changed from bare array to {"facets": [...]} wrapper.Yes
PATCH /v2/facetsResponse body changed from bare array to {"facets": [...]} wrapper.Yes
PUT /v2/facets/{name}Replacing a facet no longer resets facet options. Options are unaffected.Behavior change

Searchabilities API v2 changes

EndpointChangeBreaking?
Allfacetable removed from request and response bodies.Yes
Alltype removed from response body. Use /v1/items_fields_stats instead.Yes
Allpercentage_presence removed from response body. Use /v1/items_fields_stats instead.Yes
Allexample_items removed from response body. Use /v1/items_fields_stats instead.Yes
Alldefault removed from response body.Yes
GET /v2/searchabilitiesfilters, includes, excludes query parameters replaced with flat query parameters. See parameter mapping below.Yes
GET /v2/searchabilitiesname filter is now case-sensitive and uses * wildcard syntax instead of implicit substring match. See filtering by name in v2 below.Yes
GET /v2/searchabilitiessearch_configurable query parameter removed.Yes
GET /v2/searchabilitiessearchable query parameter removed.Yes
GET /v2/searchabilitiessort_by=percentage_presence removed. Only sort_by=name is supported.Yes
PATCH /v2/searchabilities/{name}New searchabilities inherit displayable from the nearest parent searchability when not explicitly set.Behavior change

Searchabilities filter parameter mapping

The nested filters, includes, and excludes query parameters on GET /searchabilities have been replaced with flat query parameters:

v1 parameterv2 parameterNotes
filters[fuzzy_searchable]=truefuzzy_searchable=truematch_type=and is the default
filters[exact_searchable]=trueexact_searchable=true
filters[displayable]=truedisplayable=true
filters[name]=valuename=valueNow case-sensitive. See filtering by name in v2 below.
includes[fuzzy_searchable]=true&includes[displayable]=truefuzzy_searchable=true&displayable=true&match_type=orUse match_type=or to OR filters together
excludes[...](removed)
search_configurable=true(removed)
searchable=true(removed)
sort_by=percentage_presence(removed)Only sort_by=name is supported in v2

Filtering by name (wildcard syntax)

PatternBehaviorExample
name=valueExact matchname=brand matches only brand
name=value*Starts withname=brand* matches brand, brand_name
name=*valueEnds withname=*color matches color, shirt_color
name=*value*Contains (equivalent to v1 default)name=*brand* matches brand, my_brand_name

Note: In v1, filters[name]=brand was implicitly treated as a case-insensitive substring match (equivalent to *brand*). In v2, name=brand is an exact, case-sensitive match. To replicate v1 behavior, use name=*brand*.

How to migrate your integration

We recommend supporting both /v1 and /v2 API versions in your code, so your code can seamlessly switch from /v1 to /v2 to avoid downtime.

How migration works

What exactly happens when your index is being migrated?

  1. A brief maintenance window occurs (~5 minutes) where /v1 endpoints return 503.
  2. The system automatically populates the path_in_metadata field for all existing facets by matching them to their corresponding facetable searchabilities.
  3. Facet configurations that have no matching facetable searchability are deleted (along with their options).
  4. Searchabilities whose only purpose was to enable faceting (i.e., they are not fuzzy searchable, not exact searchable, and not displayable) are removed automatically, as they no longer serve a function in v2.
  5. After an index rebuild, /v2 endpoints become active. /v1 endpoints return 410 Gone.

Warning: Review your facet and searchability configurations before migration. Facet configurations without a corresponding facetable searchability will be deleted. Facet-only searchabilities (not searchable or displayable) will be removed. These were already inactive during index builds, so search results should not be affected.

Support both /v1 and /v2 APIs

The /v1 and /v2 API responses follow a predictable pattern depending on the migration phase:

Migration phase/v1 response/v2 responseAction
Before migration200 Normal response404Use /v1
During migration (~5 min)503404Retry with backoff for up to 10 minutes
After migration410200 Normal responseSwitch to /v2

Response body reference:

StatusResponse body
404{"message": "/v2/facets API is not enabled for your account yet; please contact customer manager for migration steps"}
503{"message": "We cannot process your request due to maintenance, please try again later."}
410{"message": "/v1/facets API is deprecated; use /v2/facets"}

The suggested way to support both API versions:

  1. Send request to the /v1 endpoint.
  2. If you receive 503 with the maintenance message, retry with exponential backoff. The maintenance window typically lasts under 5 minutes but allow up to 10 minutes before treating it as an error.
  3. If you receive 410 Gone, switch all subsequent requests for that index to /v2 endpoints.
  4. Once you confirm all your indexes have been migrated, remove the v1 fallback logic entirely.

Testing

  1. Populate your development index with production data (or create a development index).
  2. Support both /v1 and /v2 API in your code, as described above.
  3. Let us migrate development index to /v2.
    • During migration, observe how your code handles 503 Maintenance and 410 Gone response codes.
  4. Once you have tested the /v2 API and are happy with it, deploy this code (supporting /v1 and /v2) to production. It won't cause any changes as only the /v1 branch of the code will be executed.
  5. Let us migrate prod index to /v2.
  6. Once everything works as expected, you can remove the /v1 branch from your code.

Migration checklist

Use this checklist to verify your migration is complete.

Facets API

  • Add path_in_metadata to all facet create (POST) and replace (PUT) requests
  • Remove options from all facet request bodies
  • Update code that reads options from facet responses. Use GET /v1/facets/{name}/options instead
  • Remove any facet rename logic (v2 returns 409 on rename attempts)
  • Ensure position values are >= 1 in POST and PUT requests
  • Update batch PATCH /facets request format to use {"facets": [...]} wrapper
  • Update batch PATCH /facets response parsing to expect {"facets": [...]} wrapper

Searchabilities API

  • Remove facetable from all request bodies
  • Remove references to facetable, type, percentage_presence, example_items, and default from response parsing. Use /v1/items_fields_stats endpoint to retrieve type, percentage_presence and example_items.
  • Replace filters[...], includes[...], and excludes[...] with flat query parameters (fuzzy_searchable, exact_searchable, displayable, name, match_type)
  • Update name filter to use explicit * wildcards for substring matching
  • Account for case-sensitive name filtering
  • Remove usage of search_configurable, searchable, and sort_by=percentage_presence parameters

General

  • Replace /v1/ with /v2/ in facets and searchabilities endpoint URLs (note: facet options endpoints remain at /v1/facets/{name}/options)
  • Handle 410 Gone responses from v1 endpoints as an indicator that migration has occurred
  • Test your updated integration against your test index before production migration

SDK support

The official Constructor SDKs have been updated with new v2 methods alongside the existing v1 methods. V1 methods remain unchanged, and facet option methods are unaffected.

SDKFacets v2Searchabilities v2
Node.jsaddFacetConfigurationV2, getFacetConfigurationsV2, getFacetConfigurationV2, modifyFacetConfigurationsV2, modifyFacetConfigurationV2, replaceFacetConfigurationV2, createOrReplaceFacetConfigurationsV2, removeFacetConfigurationV2retrieveSearchabilitiesV2, getSearchabilityV2, patchSearchabilitiesV2, patchSearchabilityV2, deleteSearchabilitiesV2, deleteSearchabilityV2
JavacreateFacetConfigurationV2, retrieveFacetConfigurationsV2, retrieveFacetConfigurationV2, updateFacetConfigurationsV2, updateFacetConfigurationV2, replaceFacetConfigurationV2, replaceFacetConfigurationsV2, deleteFacetConfigurationV2retrieveSearchabilitiesV2, retrieveSearchabilityV2, createOrUpdateSearchabilitiesV2, createOrUpdateSearchabilityV2, deleteSearchabilitiesV2, deleteSearchabilityV2
.NETCreateFacetConfigV2, GetAllFacetConfigsV2, GetFacetConfigV2, CreateOrReplaceFacetConfigsV2, BatchPartiallyUpdateFacetConfigsV2, ReplaceFacetConfigV2, PartiallyUpdateFacetConfigV2, DeleteFacetConfigV2RetrieveSearchabilitiesV2, GetSearchabilityV2, PatchSearchabilitiesV2, PatchSearchabilityV2, DeleteSearchabilitiesV2, DeleteSearchabilityV2

To migrate:

  • Update to the latest SDK version
  • Replace v1 method calls with their V2 equivalents
  • Add pathInMetadata (or path_in_metadata in Java/.NET) to facet create/replace calls
  • Remove facetable from searchability parameters.

Migration examples by endpoint

Facets: Creating a facet

The key change: add path_in_metadata, remove options from the request body. You no longer need to create a searchability with facetable=true first.

Show before/after example

Before (v1): two separate calls required:

# 1. Create a searchability with facetable=true
curl -X PATCH "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "searchabilities": [
      {"name": "facets.brand", "facetable": true, "displayable": true}
    ]
  }'

# 2. Create the facet configuration (with inline options)
curl -X POST "https://ac.cnstrc.com/v1/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "brand",
    "type": "multiple",
    "display_name": "Brand",
    "options": [
      {"value": "Nike", "display_name": "Nike", "position": 1}
    ]
  }'

After (v2): facet configuration with path_in_metadata, options managed separately:

# 1. Create the facet configuration
curl -X POST "https://ac.cnstrc.com/v2/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "brand",
    "type": "multiple",
+   "path_in_metadata": "facets.brand",
    "display_name": "Brand"
  }'

# 2. (Optional) Configure facet options separately
curl -X PATCH "https://ac.cnstrc.com/v1/facets/brand/options?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "options": [
      {"value": "Nike", "display_name": "Nike", "position": 1}
    ]
  }'

Facets: Retrieving a facet

Response now includes path_in_metadata but no longer includes options. Retrieve options via GET /v1/facets/{name}/options.

Show before/after example

Before (v1):

curl "https://ac.cnstrc.com/v1/facets/brand?key=YOUR_KEY"

Response includes options array inline.

After (v2):

curl "https://ac.cnstrc.com/v2/facets/brand?key=YOUR_KEY"

Response includes path_in_metadata but no options. To retrieve options:

curl "https://ac.cnstrc.com/v1/facets/brand/options?key=YOUR_KEY"

Facets: Updating a facet (PATCH)

Remove options from the body. Renaming is no longer allowed. Sending a different name returns 409.

Show before/after example

Before (v1):

curl -X PATCH "https://ac.cnstrc.com/v1/facets/brand?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
-   "name": "Brand",
    "display_name": "Brand Name",
-   "options": [{"value": "Nike", "position": 1}]
  }'

After (v2):

curl -X PATCH "https://ac.cnstrc.com/v2/facets/brand?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Brand Name"
  }'

Warning: To rename a facet, delete it and create a new one. Options must be managed via /v1/facets/{name}/options.

Facets: Replacing a facet (PUT)

Add path_in_metadata, remove options. In v2, PUT no longer resets facet options.

Show before/after example

Before (v1):

curl -X PUT "https://ac.cnstrc.com/v1/facets/brand?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "brand",
    "type": "multiple",
    "display_name": "Brand",
-   "options": [{"value": "Nike", "position": 1}]
  }'

After (v2):

curl -X PUT "https://ac.cnstrc.com/v2/facets/brand?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "brand",
    "type": "multiple",
+   "path_in_metadata": "facets.brand",
    "display_name": "Brand"
  }'

Note: name in the body must match the URL parameter.

Facets: Batch update (PATCH /facets)

Request and response body changed from a bare array to an object with a facets key.

Show before/after example

Before (v1) bare array:

curl -X PATCH "https://ac.cnstrc.com/v1/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {"name": "brand", "display_name": "Brand Name"},
    {"name": "color", "display_name": "Color"}
  ]'
# Response: [{facet_1}, {facet_2}]

After (v2) wrapped in {"facets": [...]}:

curl -X PATCH "https://ac.cnstrc.com/v2/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
+   "facets": [
      {"name": "brand", "display_name": "Brand Name"},
      {"name": "color", "display_name": "Color"}
+   ]
  }'
# Response: {"facets": [{facet_1}, {facet_2}]}

Facets: Bulk create/replace (PUT /facets)

Add path_in_metadata to each facet. options are no longer accepted inline. Configure them separately via /v1/facets/{name}/options.

Show before/after example

Before (v1):

curl -X PUT "https://ac.cnstrc.com/v1/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "facets": [
-     {"name": "Brand", "type": "multiple", "display_name": "Brand Name", "options": [{"value": "Nike"}]},
-     {"name": "Price", "type": "range", "range_format": "boundaries"}
    ]
  }'

After (v2):

curl -X PUT "https://ac.cnstrc.com/v2/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "facets": [
+     {"name": "Brand", "type": "multiple", "path_in_metadata": "facets.brand", "display_name": "Brand Name"},
+     {"name": "Price", "type": "range", "path_in_metadata": "facets.price", "range_format": "boundaries"}
    ]
  }'

Searchabilities: Remove facetable from all requests

The facetable field is no longer accepted or returned. Facet definitions are managed entirely through the Facets API.

Show before/after example

Before (v1):

curl -X PATCH "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "searchabilities": [
-     {"name": "brand", "facetable": true, "displayable": true, "fuzzy_searchable": true}
    ]
  }'

After (v2):

curl -X PATCH "https://ac.cnstrc.com/v2/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "searchabilities": [
+     {"name": "brand", "displayable": true, "fuzzy_searchable": true}
    ]
  }'

Searchabilities: removed response fields

The following fields are no longer returned. If your code reads any of these, remove those references:

Removed fieldReplacement
facetableManaged via Facets API (path_in_metadata on facet configuration)
typeUse /v1/items_fields_stats
percentage_presenceUse /v1/items_fields_stats
example_itemsUse /v1/items_fields_stats
default(No replacement. This field was deprecated)

Searchabilities: update list filtering

The nested filters, includes, and excludes parameters have been replaced with flat query parameters. See the parameter mapping table above for the full reference.

Show before/after examples

AND filter before (v1):

curl "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY&filters[fuzzy_searchable]=true&filters[displayable]=true"

AND filter after (v2), flat parameters, AND by default:

curl "https://ac.cnstrc.com/v2/searchabilities?key=YOUR_KEY&fuzzy_searchable=true&displayable=true"

OR filter before (v1):

curl "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY&includes[fuzzy_searchable]=true&includes[displayable]=true"

OR filter after (v2), usematch_type=or:

curl "https://ac.cnstrc.com/v2/searchabilities?key=YOUR_KEY&fuzzy_searchable=true&displayable=true&match_type=or"

Name filter before (v1), implicit substring, case-insensitive:

curl "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY&filters[name]=brand"

Name filter after (v2), explicit wildcard, case-sensitive:

# To replicate v1 behavior (substring, but now case-sensitive):
curl "https://ac.cnstrc.com/v2/searchabilities?key=YOUR_KEY&name=*brand*"

# Exact match:
curl "https://ac.cnstrc.com/v2/searchabilities?key=YOUR_KEY&name=brand"

End-to-end examples

Range facet (price slider)

Before (v1):

# Step 1: Make "price" facetable
curl -X PATCH "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"searchabilities": [{"name": "facets.price", "facetable": true}]}'

# Step 2: Configure facet
curl -X POST "https://ac.cnstrc.com/v1/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "price", "type": "range", "range_format": "boundaries", "display_name": "Price"}'

After (v2):

# Single call, no searchability needed for faceting
curl -X POST "https://ac.cnstrc.com/v2/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "price",
    "type": "range",
    "path_in_metadata": "facets.price",
    "range_format": "boundaries",
    "display_name": "Price"
  }'

Note: You still need a searchability for price if you want it to be searchable or displayable in search results, but you no longer need facetable=true on it.

Nested metadata field as a facet

Before (v1):

curl -X PATCH "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"searchabilities": [{"name": "facets.attributes.color", "facetable": true, "displayable": true}]}'

curl -X POST "https://ac.cnstrc.com/v1/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "color", "type": "multiple"}'

After (v2):

curl -X POST "https://ac.cnstrc.com/v2/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "color",
    "type": "multiple",
    "path_in_metadata": "facets.attributes.color"
  }'

The path_in_metadata field directly references the nested path, removing the implicit name-based matching.

Full facet setup with options

Before (v1):

# Step 1: Make "brand" facetable via searchabilities
curl -X PATCH "https://ac.cnstrc.com/v1/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "searchabilities": [
      {"name": "facets.brand", "facetable": true, "displayable": true}
    ]
  }'

# Step 2: Configure facet with inline options
curl -X POST "https://ac.cnstrc.com/v1/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "brand",
    "type": "multiple",
    "display_name": "Brand",
    "sort_order": "num_matches",
    "sort_descending": true,
    "options": [
      {"value": "Nike", "display_name": "Nike", "position": 1},
      {"value": "Adidas", "display_name": "Adidas", "position": 2}
    ]
  }'

After (v2):

# Step 1: Configure searchability (without facetable)
curl -X PATCH "https://ac.cnstrc.com/v2/searchabilities?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "searchabilities": [{"name": "facets.brand", "displayable": true}]
  }'

# Step 2: Create the facet with path_in_metadata
curl -X POST "https://ac.cnstrc.com/v2/facets?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "brand",
    "type": "multiple",
    "path_in_metadata": "facets.brand",
    "display_name": "Brand",
    "sort_order": "num_matches",
    "sort_descending": true
  }'

# Step 3: Configure facet options separately
curl -X PATCH "https://ac.cnstrc.com/v1/facets/brand/options?key=YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "options": [
      {"value": "Nike", "display_name": "Nike", "position": 1},
      {"value": "Adidas", "display_name": "Adidas", "position": 2}
    ]
  }'

FAQ

Do I need to create a searchability before creating a facet in v2?

No. In v2, the facet configuration is self-contained: path_in_metadata directly references the metadata field. However, you still need searchabilities if you want a field to be searchable (fuzzy or exact) or displayable in search results.

What happens to my existing facet options during migration?

Facet options are preserved. They are just no longer returned inline with facet configurations. Use the /v1/facets/{name}/options endpoint to manage them.

What if I have facet configurations that don't match any searchability?

During migration, facet configurations without a corresponding facetable searchability will be deleted along with their options. These configurations were already being ignored during index builds, so this should not affect your search results.

Will any searchabilities be removed during migration?

Yes. Searchabilities that existed solely to enable faceting (i.e., they are not fuzzy searchable, not exact searchable, and not displayable) will be removed automatically. Since the facetable property no longer exists in v2, these searchabilities would have no remaining function.

Can I test v2 before my production index is migrated?

Yes. Contact your customer manager to migrate a test index first. You can run your updated pipelines against the test index to verify everything works before migrating production.

How do I know which path_in_metadata to use for my facets?

The path_in_metadata value corresponds to the path in the metadata of each item where the facet data is present. It should map to metadata_name value of /v1 searchability. For example, if your items have {"facets": {"brand": "Nike"}}, then path_in_metadata would be facets.brand. During automatic migration, these paths are filled based on your existing facetable searchabilities.

Is the path_in_metadata field unique?

Yes. Each path_in_metadata value must be unique within your index. Attempting to create or update a facet with a duplicate path returns 409 Conflict.

Why can't I rename facets in v2?

Facet names serve as the resource identifier in the URL (e.g., /v2/facets/brand). Renaming would break this contract. If you need to rename a facet, delete it and create a new one with the desired name.

Is there a way to revert index back to v1?

Yes, it's supported. Please ask your Customer Success representative.