Search the mock-shop directory

Use the directory to find a complete reference catalog for a business. Search returns published shops that contain products, ranked by their names, descriptions, taxonomy categories, collection titles, and a sample of product titles.

Start with the products you need

Search before choosing sample data. Replace pet supplies in variables.query with the products requested by the task. GraphQL variables keep the search text separate from the query, and the quoted heredoc preserves the JSON unchanged:

curl --fail-with-body -sS https://mock.shop/api/public/graphql \
  -H 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "query": "query FindMockShops($query: String!) { shopDirectory { search(query: $query) { subdomain name shortDescription categories collectionTitles productCount storefrontApiUrl } } }",
  "variables": { "query": "pet supplies" }
}
JSON

Inspect the returned descriptions, categories, collection titles, and product counts. Choose a catalog that fits the task and use its storefrontApiUrl for product queries and in the generated app. Read the chosen host's /llms.txt for catalog details. Inspect the shortlist rather than automatically choosing its first entry.

Clients that ask for a store domain, such as Hydrogen's PUBLIC_STORE_DOMAIN, need the selected URL's hostname without https:// or /api. For a new custom Shopify storefront, continue with the Hydrogen build guide to connect that catalog to the existing mock.shop starter.

demostore is the default apparel sample; select it when its products fit. The bare https://mock.shop/api endpoint serves that same sample. Its query responses include optional extensions.mockShop metadata with directory links and a search request. hydrogenGuideUrl links to the Hydrogen build guide; the requested Storefront API data remains in data. Named catalogs also include a compact Hydrogen starter hint with that catalog's PUBLIC_STORE_DOMAIN and the build guide. These hints accompany queries; cart mutations retain their existing response metadata.

GET /api returns HTTP 405 with instructions to use POST. On mock.shop hosts, its extensions.mockShop also links to the catalog's /llms.txt and includes the directory search request. Bare-apex URLs point to demostore's catalog instructions; named-store URLs point to that host's instructions if it exists.

If search returns an error, retry or browse the published-store directory. An empty list means no match was found. Browse the directory or explain the missing catalog instead of relabeling unrelated products.

Request

Send GraphQL to POST https://mock.shop/api/public/graphql with Content-Type: application/json. The directory is public: no credentials, API key, or employee login are required. Staging uses https://mock-shop-staging.shopify.io/api/public/graphql and is anonymous too. Browser clients can call it from any origin. Each selected shop's catalog API also needs no token for products and collections.

Existing callers of /api/corpus/graphql can keep using its authenticated shopDirectory fields. To switch to the public endpoint, change the URL and remove the Authorization header; the query and response fields stay the same.

query FindMockShops($query: String!, $limit: Int = 3) {
  shopDirectory {
    search(query: $query, limit: $limit) {
      id
      subdomain
      name
      shortDescription
      categories
      collectionTitles
      productCount
      collectionCount
      currencyCode
      storefrontUrl
      storefrontApiUrl
    }
  }
}

Example variables:

{ "query": "dog accessories", "limit": 3 }

query must contain a letter or number and be 1–500 characters after trimming. limit defaults to 3 and must be an integer from 1 to 10. Invalid values return a GraphQL error with extensions.code: "BAD_USER_INPUT"; inspect GraphQL errors even when the HTTP response is 200. Send one search per request. The public schema limits query complexity to 100, charges 50 plus selected fields for each search, and rejects excessive queries before executing any search. It also limits depth to 8 and query tokens to 5,000. Schema introspection is excluded from the depth and complexity limits so standard GraphQL clients can discover the API. The public mock-shop API currently exposes only directory queries; the corpus API continues to require HTTP Basic credentials.

Ranking and results

Search ranks shops by semantic similarity. Each shop has a precomputed gemini-embedding-2 embedding of its name, descriptions, categories, collections, and up to 12 product titles sampled across categories. The query is embedded through Shopify's AI Proxy and compared against the shop vectors using Postgres/pgvector cosine distance. No product/image corpus vectors are mixed into this search. The existing Gemini embedding client is reused with Google's retrieval format: title: {name} | text: {document} for shops and task: search result | query: {query} for searches.

Exact normalized names or subdomains always rank first. Other indexed matches must have cosine similarity of at least 0.59 and be within 0.08 of the best match. When at least 10 current shop vectors exist, the best score must also exceed the median score by at least 0.055; this filters queries that have uniformly high similarity to unrelated catalogs. Smaller directories use only the absolute and best-match cutoffs. Exact names bypass these filters, using case-, accent-, and punctuation-insensitive matching without stemming or synonyms. All other results rank by cosine similarity. Ties sort by subdomain. These are retrieval heuristics, not probabilities or guarantees that a shop meets every requested constraint. Search returns up to the requested limit and can return an empty list.

For example, supplies for a new puppy can find pet catalogs without the word “puppy” appearing in their summaries. comfortable outfits for sleeping finds loungewear; candles still places the exact candle shop first.

Query vectors are cached for one hour per process. A query embedding request has a five-second response timeout, a two-second connection timeout, and no foreground retries. If a query embedding cannot be obtained because credentials are missing, AI Proxy fails, or the provider returns an invalid vector, the endpoint returns a GraphQL error with extensions.code: "SEARCH_UNAVAILABLE" and extensions.retryable: true. Inspect GraphQL errors even with HTTP 200; an unavailable search is not a successful empty result. There is no keyword scoring or fallback.

Search checks document digests against current catalog metadata. Missing or changed embeddings enqueue a background refresh. Those catalogs can only match an exact name or subdomain until the job finishes; stale vectors are never used. If no current shop vectors exist, only exact matches can be returned, and the query makes no embedding API call. Refresh requests are deduplicated with a five-minute database lease; failed jobs retry, and an expired lease lets a subsequent search recover a lost job. Workers use the existing embeddings_backfill queue. Publishing or editing a catalog does not make a synchronous AI call: its next search detects the change. The materializer does not currently enqueue an embedding at publication time. Run the refresh task after a batch of new shops to prepare them before their first directory search.

Each result includes:

Field Meaning
id, subdomain The mock-shop database identity and canonical source slug. Use the slug to identify the chosen catalog.
name, shortDescription Name and nullable short description for presenting the choice.
categories, collectionTitles Taxonomy summaries and all collection titles; either list may be empty. Categories are omitted when they do not represent at least half the catalog.
productCount, collectionCount Catalog sizes at search time; variant counts are separate from product counts.
currencyCode Source catalog currency; compare it with the destination before interpreting prices.
storefrontUrl Browser preview on the renderer's host family.
storefrontApiUrl Public Storefront GraphQL endpoint for fetching the catalog.

Use the returned URLs. Production API hosts are <subdomain>.mock.shop, while browser previews use <subdomain>.hydrogen.mock.shop. The default catalog's slug is demostore and its API URL is https://demostore.mock.shop/api. A request through the mock.shopify.com alias still returns canonical mock.shop API URLs. Development and staging return their own configured host families.

Existing list and exact lookup

shopDirectory.shops still lists every published shop ordered by subdomain, including shops with no products. shopDirectory.shop(subdomain: "pets") still returns an exact published shop or null for an invalid, unknown, or unpublished slug. Those fields retain id, subdomain, name, and shortDescription; the richer metadata belongs to search results.

Recheck the selected shop before importing: a shop can be unpublished after a search. The public catalog endpoint then returns 404. Search does not create or modify any Shopify resources.

Rollout and validation

Deploy the schema-only mock_shops_gem2_v1 migration before deploying the search code. Then populate existing catalogs outside migrations:

shadowenv exec -- bin/rails mock_shop:refresh_directory_embeddings

The command is idempotent and skips re-embedding unchanged documents. The small directory uses an exact cosine scan over one full 3072-dimensional vector per shop; it needs no HNSW index, partitions, or separate vector database. The encoder is pinned to Gemini Embeddings 2 in code, with no per-row model column. A future encoder change must bump the version in SearchDocument and regenerate the vectors; the changed digests exclude older embeddings during that rebuild.

For a local tophat with the current public catalogs and real AI Proxy calls:

dev clone-prod all
shadowenv exec -- bin/rails mock_shop:refresh_directory_embeddings
shadowenv exec -- bin/rails runner script/evaluate_shop_directory.rb

The evaluator uses the committed natural-language and no-match examples in test/fixtures/files/shop_directory_queries.json, prints semantic shortlists and elapsed times, and exits nonzero on a missed expected catalog or an unexpected no-match result. Expected shops should be reviewed when the published catalog changes. This is a small relevance regression set, not a comprehensive quality benchmark. Unit and endpoint tests run offline, including stale vectors, queue/provider failures, authentication, exact matches, and limits.