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.