About Curio
Commerce IQ / Developer field guide

Inside the Curio harness

How we turn Commerce IQ’s structured product data into a personal shopping experience—without asking an AI model to invent the catalog.

Implementation walkthroughGrounded in returned productsCurio Shopping · October 2026

Commerce IQ supplies the grounding. Curio supplies the orchestration. The harness is the layer between a shopper’s request and the experience they see: it resolves context, plans retrieval, evaluates candidates, preserves product identity, and explains the result.

This is a walkthrough of Curio’s implementation, not an official API specification or a promise of catalog coverage. Request shapes and limits below describe our adapter and current application choices; examples are illustrative, not live inventory.

01 / Engineering notes

A small API surface, a richer experience

We keep the commerce adapter separate from planning, ranking, and presentation. That separation lets us change a beta response mapping, a scoring heuristic, or the interface without rewriting the other layers.

  1. 1. Understand

    Query + market + consented context

  2. 2. Retrieve

    One plan → parallel Commerce IQ searches

  3. 3. Evaluate

    Deterministic filters + semantic re-ranking

  4. 4. Present

    Groups, variants, and comparable store offers

Curio’s module boundaries
shopping.functions.ts          Search orchestration & public server functions
commerce-client.server.ts      Commerce IQ request/response adapter
query-expand.server.ts         Intent, fan-out plan & follow-ups
recipient.ts / locale.ts       Target shopper & market normalization
harness.server.ts             Filtering, scores, AI ranking & reasons
popularity.server.ts          Swappable popularity estimator
cluster.server.ts / cluster.ts AI grouping + deterministic validation
offer-confidence.ts           Store-offer comparison heuristics
ProductSheet.tsx              Selected variant → retailer offers

02 / Engineering notes

Normalize once, keep the catalog grounded

Our server-side adapter calls two commerce endpoints using x-apikey. Search uses ads3poff,enproductids; the latter requests identifiers needed for follow-on offer lookup. Keys never travel to the browser.

Commerce IQ search · server-side request
POST https://api.microsoft.ai/v3/search/commerce?features=ads3poff,enproductids
x-apikey: $COMMERCE_IQ_API_KEY
content-type: application/json

{
  "query": "wireless noise cancelling headphones",
  "maxResults": 50,
  "language": "en",
  "region": "US"
}

Search returns products with offers. We use the first offer as the search-card representative and map its name, seller, image, price, specifications, condition, and click URL into NormalizedProduct. Product, SKU, and offer IDs are retained independently; a missing SKU must not prevent store comparison.

  • Prices: convert minor units using the currency’s fraction digits, rather than always dividing by 100. Keep currency alongside every price.
  • Missing fields: preserve unknown values as null; do not fabricate a review, image, stock status, or specification.
  • Identity: the returned offer ID anchors the search result. Preserve the provider IDs through ranking, clustering, and detail views.
  • Schema isolation: only the commerce adapter needs to understand the provider’s response shape.

03 / Engineering notes

Resolve who the query is for before personalizing

Signed-in searches can load market settings, a shopping profile, a size guide, and search history in parallel. Public searches use an empty profile. A personalization opt-out removes the saved profile from the ranking context.

The important ordering decision is recipient first, preferences second. A gift or family query should not inherit the purchaser’s clothing sizes. We resolve the target from explicit query details and recorded household facts, then build a temporary scoped profile. It does not overwrite the saved profile.

Illustrative recipient precedence
Query: "rain jacket for my 9-year-old child"

Explicit query details → resolved recipient → scoped profile

Keep:  requested product, stated age, market, explicit constraints
Omit:  purchaser's adult size and unrelated lifestyle context
Never: translate age 9 into clothing size 9 or M

Apply the same target to every generated query and ranking call.

For child apparel, absence of an adult label is not enough: a listing needs positive child evidence. Explicit department and size choices in the query take precedence over inferred preferences. Manual size-guide entries take precedence over receipt-derived entries.

04 / Engineering notes

One planner, multiple shopping intents

One primary AI planning step classifies intent and decides between a single product search and grouped retrieval. It also proposes alternative search phrasings and short follow-up refinements. This avoids separate AI calls for intent detection, category expansion, and suggestions on the normal path.

The intent vocabulary includes use case, feature, comparison, specific product, budget, category + brand, category, retailer, availability, promotion, and review. A question starting with “what” does not automatically become a kit.

Illustrative plan · a multi-category request
{
  "intent": "use_case",
  "mode": "group",
  "terms": ["Tents", "Sleeping Bags", "Sleeping Pads", "Camp Cookware"],
  "variants": [],
  "followups": ["Lighter gear", "Under $300", "Add rain protection"]
}

Group plans accept up to six terms; single plans accept up to two alternative queries. Named comparisons use the exact alternatives as groups. The planner is instructed to preserve explicit models, brands, budgets, stores, and features—not turn a phone search into an accessory kit.

Locale enters the plan as well as the API request. Terms should use local retail language, while follow-ups use the shopper’s query language. An explicit currency is not silently converted. Malformed responses or unavailable AI fall back to intent heuristics and expansion/refinement helpers.

05 / Engineering notes

Retrieve in parallel, rank within each category

For a single product type, the primary query is department- or size-qualified when appropriate. Primary and alternative queries run concurrently, results merge by offer ID, and the candidate pool is capped at 120. Single-query calls request 50 results; grouped category calls request 20.

Orchestration sketch · simplified, not a drop-in SDK
const results = await Promise.all(
  uniqueQueries.map((query, index) =>
    index === 0
      ? search(query)                       // primary failure surfaces
      : search(query).catch(() => null)     // optional variant may fail
  )
);
const candidates = dedupeByOfferId(results.flatMap(productsOrEmpty));

// Grouped mode: rank categories independently, concurrently.
const [overall, categories] = await Promise.all([
  rank(allCandidates, { limit: 8 }),         // overall summary
  Promise.all(groups.map(g => rank(g.products, { limit: 8 })))
]);

Why separate category rankings? A global score can fill a camping list with tents and crowd out cookware. Independent rankings preserve the useful outline of the request. The response carries groups: [{ label, productIds }] so presentation does not have to reconstruct it from model-written prose. A repeated offer belongs to the first category that retrieved it; final products follow category order. The response’s ranking mode describes the overall summary ranker, not necessarily every category’s path.

Grouped retrieval tolerates a failed category and keeps successful ones. Optional query variants can fail without discarding the primary search. That is graceful degradation, but also a trade-off: partial coverage needs monitoring, because an empty category is not proof that no products exist.

Single-product searches also run bounded variant discovery alongside ranking when listings identify an exact model. Up to four optional searches seek finishes and evidenced color/capacity combinations, retaining the original query and locale. Each call has a six-second timeout. Exact-model and shared eligibility checks retain diverse sibling configurations separately as clusterProducts, so the top-24 cut does not erase them. This is not an exhaustive manufacturer variant lookup. Raw searches skip discovery; clicking Cluster in Raw mode explicitly discovers siblings for that view only, without ranking or changing the ordinary Raw results.

06 / Engineering notes

Deterministic judgment, then semantic re-ranking

runHarness first removes untitled products, excluded brands, and recipient-incompatible listings. It then computes a weighted score over product facts and preferences. A known department can exclude opposite-department apparel unless the query overrides it.

Current deterministic scoring coefficients
SignalWeightWhat Curio measures
Relevance0.30Query-token overlap with title, brand, category, and description
Category preference0.15Preferred and favorite category matches
Brand affinity0.15Preferred brands and a brand-tier nudge
Price fit0.10Price relative to candidate median and shopper sensitivity
Popularity0.12Review and interaction-derived heuristic
Quality0.12Rating with review-count confidence; preference adjustments
Availability0.06Recognized in-stock/out-of-stock text; neutral if unknown
Size fit0.10Saved size evidenced in title or specifications

These coefficients total 1.10: the score is an ordering prior, not a normalized probability. Price fit is not a hard budget guarantee. Popularity is pluggable; today it uses review evidence, optional API popularity values, merchant presence, and an aggregate brand-interaction prior. Offer count is retained by normalization but is not currently consumed by this estimator.

Size evidence starts neutral when no applicable size is known. If at least eight listings confirm the saved size, the harness drops listings with a size-fit score below 0.5. This protects fit without aggressively emptying a thin catalog.

The semantic step receives the top 50 scored candidates in a compact representation, plus derived shopping preferences, the scoped recipient, market, and language. It returns a summary, ordered product IDs, explanations, and badges—not new product objects.

Grounded ranking contract · illustrative
{
  "summary": "A short shopping summary grounded in these candidates.",
  "rankedProductIds": ["existing-offer-id-2", "existing-offer-id-1"],
  "explanations": [{
    "productId": "existing-offer-id-2",
    "reason": "A concise reason using supplied facts.",
    "badges": ["Matches your brand preference"]
  }]
}

Validate IDs against candidates → remove duplicate IDs → map to real products
Default output: up to 24 products; grouped output: up to 8 per category

Unknown IDs are ignored. Too few valid IDs trigger the deterministic fallback. An explicitly empty AI ranking is respected as a rejection of irrelevant matches—it is not refilled. Unconfirmed size/fit badges are removed, and confirmed size matches retain a factual badge.

07 / Engineering notes

Make the orchestration visible in the right places

The search response separates products, groups, intent, summary, follow-ups, candidate count, data mode, and ranking mode. That lets the interface present results without another AI call to reinterpret the answer.

  • Groups: category-by-category navigation keeps a kit or comparison scannable.
  • Explanations: each pick carries a short reason rather than a wall of generated shopping advice.
  • Refinements: suggested follow-ups become standalone queries using conversation history; the history travels with the search URL.
  • Memberships: the interface keeps retailer identity separate from membership benefits. Derived benefits are not provider-returned price guarantees.
  • Inspectability: owner-only Raw mode runs one Commerce IQ search, preserving normalized API order while bypassing planning, fan-out, scoring, and AI re-ranking. It is not the original response JSON.

Purchase imports are a separate enrichment path: receipts help derive preferences and size evidence, while Commerce IQ can add a missing product photo or link. WebIQ image search is a fallback for missing imagery—not evidence that a particular variant is available.

08 / Engineering notes

Cluster product identity, not just similar words

Grouping is on demand; variant discovery happens during normal product search and only on Cluster click in Raw mode. Raw clustering reuses existing members, adds evidenced exact-model siblings with query and condition checks, and caches the enriched plan separately; toggling back restores the untouched Raw list. The Cluster control requests an AI grouping plan for the top picks plus retained exact-model siblings, capped at 60 members; a deterministic fallback is available if that step fails. buildClusters validates the plan against actual members. It does not fetch or invent additional products. Known-model checks are conservative; ambiguous products remain on the ordinary retrieval path.

A model is a parent; a configuration is a variant. Distinct iPhone generations and base, Pro, Pro Max, Air, Duo, or e models remain separate when evidenced in listings. Storage, color, and carrier/unlocked may become axes within one model. Cases and other accessories do not belong inside a phone cluster.

One-off bundles sharing an evidenced core model can form a separate bundle parent. Each included accessory set becomes a Bundle choice, while storage and color remain real listing configurations. Bundles never merge into standalone parents or span different core models; uncertain identities remain separate listings.

Illustrative model boundary · only if listings provide this evidence
iPhone 18 Pro                  iPhone 18 Pro Max
├─ Blue / 256 GB / Unlocked     ├─ Blue / 256 GB / Unlocked
├─ Silver / 256 GB / Unlocked   └─ Silver / 512 GB / Unlocked
└─ Silver / 512 GB / Carrier

Two parents. Not one parent with a "Model" selector.
No listing evidence → no invented model or configuration.

Selection resolves to a concrete returned member. Changing color preserves storage if that combination exists, even if a secondary option must change. A color unavailable at the chosen storage is dimmed but remains selectable; choosing it selects an available storage configuration and signals the adjustment.

The detail sheet reuses the selected member’s identifiers and refetches its offers. Matching alternate sellers belong under store comparisons, not a “variant” axis. Stable IDs, scoped caches, and stale-request guards prevent a late response from switching the shopper’s selection. Configuration compatibility means a matching returned listing exists—not that current stock has been independently verified. iPhone model boundaries have additional deterministic guards; equivalent checks are not yet universal across product families.

09 / Engineering notes

Do not let the cheapest outlier become the best price

Opening a product or changing a variant calls the offers endpoint with available product, SKU, or offer identifiers. Offer ID alone can work when search omitted a SKU. No identifiers means no lookup.

Commerce IQ store-offer lookup · illustrative
POST https://api.microsoft.ai/v3/fetch/commerce/offers
x-apikey: $COMMERCE_IQ_API_KEY
content-type: application/json

{
  "offerId": "selected-member-offer-id",
  "language": "en",
  "region": "US"
}

// Include productId and/or skuId too when available.

A browser-safe helper examines product identity evidence and price anomalies before presentation. Likely comparable offers occupy the primary tier; uncertain product matches and suspicious price outliers remain in a secondary tier. Only the primary tier supports a best-price claim.

For example, one $8 offer against five roughly $20 offers should be visible for inspection, not automatically promoted as the bargain. A retailer’s identity alone is not evidence of mismatch. Missing data limits confidence; this layer is a heuristic, not another provider verification call.

The helper considers title overlap, model-like tokens, condition, currency, and consensus pricing. Price screening requires at least three comparable prices and a majority cluster around the median before flagging unusually low or high prices. It does not establish verified SKU equality or compare every variant axis. The selected listing stays visible as a fallback, so a single displayed store is not proof of an exhaustive successful comparison.

10 / Engineering notes

Spend latency where it changes the answer

After loading context, search-history writing, aggregate brand-prior loading, and the query plan run concurrently. Commerce queries fan out in parallel. Group-level rankings run alongside the small overall ranking used for the summary, rather than waiting behind it.

Planner cache
10 minutes in worker memory, keyed by query, profile context, history signals, and locale.
Brand prior
5 minutes per worker; aggregate interaction counts, not another user’s profile.
Homepage examples
Account-scoped, 24-hour persistent cache with background refresh.
Cluster plans
Scoped cached plans with stale-request protection; clustering is paid for only on demand.

Search and store-offer views avoid focus-triggered refetches so a browser tab switch does not reset a product panel. Shared account-keyed shopping context fingerprints preferences, market, language and consent; saves invalidate that context. Store-offer requests and cache keys carry the selected locale. Homepage warming uses one worker and cancels unused requests on navigation, while selected suggestions persist for reloads. Category retrieval and ranking use a six-call invocation pool without dropping categories or candidates. Aggregate brand-prior misses are coalesced; non-result history learning owns a separate awaited request after results. Optional onboarding dialogs load only when active or resuming.

Deterministic scoring precomputes query tokens and category preferences once per candidate set without changing coefficients or eligibility. Receipt discovery carries account-scoped, ID-only checkpoints between bounded batches instead of relisting the mailbox every round; failed extraction remains retryable, and paginated evidence reads avoid silent truncation. Purchase rows are visible before optional photo matching finishes. These reduce repeated work; they do not guarantee lower provider latency. No tighter ranking deadlines, smaller pools or longer cache lifetimes are used to manufacture a speed improvement.

11 / Engineering notes

Design failure modes as deliberately as the happy path

  • AI unavailable or invalid: return deterministic ranking and reasons. Clustering similarly has a deterministic fallback.
  • Commerce authentication failure: surface a configuration problem. A development build without a commerce key can use marked mock data; production does not silently substitute it.
  • Rate limiting: recognize 429 and return an appropriate failure. Search does not implement a universal automatic retry loop; optional variants and groups can degrade independently.
  • Store lookup failure: keep the product sheet usable without claiming offers exist. Rate-limit and lookup errors do not manufacture a comparison.
  • Coverage gaps: empty or partial market results cannot establish provider coverage. Keep live QA evidence and investigate provider-side gaps separately.

The ranker receives derived shopping context and compact product facts, not raw receipt emails or account credentials. Derived does not mean anonymous: household names, location, free-text notes, queries, and search history can enter AI prompts. Export analysis is a separate path that sends submitted text to AI to extract preferences. Gmail access is per user; connection keys are encrypted and kept server-side. Profile and purchase reads are user-scoped. No provider-wide retention promise follows from these code boundaries.

A personalization opt-out currently suppresses the saved profile used by the harness; it is not an assertion that all search-history collection or learning stops. Distinguish controls accurately, and verify each data path before making broader privacy promises.

12 / Engineering notes

A practical blueprint for your own harness

  1. Start with grounded retrieval. Build a server-side adapter, normalize nullable fields and currency, preserve IDs, and inspect unranked results.
  2. Add a deterministic baseline. Separate hard exclusions from soft scoring; measure relevance and false exclusions before adding AI.
  3. Introduce one structured planner. Bound fan-out, preserve explicit constraints, include locale, and keep a fallback.
  4. Re-rank by ID. Give the model compact facts and derived preferences; validate the returned IDs against the candidates.
  5. Protect coverage. Rank categories independently, log partial failures, and avoid treating an empty result as a proven absence.
  6. Add identity-aware interaction. Cluster only evidenced models and configurations; resolve selection before fetching store offers.
  7. Earn price confidence. Separate questionable offers before choosing a best price. Keep uncertainty visible.
  8. Test the boundaries. Make recipient, locale, variant, and degradation cases repeatable—not just happy-path screenshots.

Curio’s regression suite covers harness ranking, recipient resolution, locale and currency, query intent, offer confidence, variant clustering, size-dependent selection, and account-scoped homepage examples. A separate multilingual golden-query runner checks live retrieval across markets without mixing test searches into normal shopping memory.

High-value scenarios to keep in your own QA suite
Adult profile + child gift        → no inherited adult size
Explicit department in query      → beats saved department
Camping kit                      → each useful category survives ranking
Unknown or duplicate AI IDs       → ignored / deduplicated
AI failure                       → deterministic results remain
Different phone model            → different parent cluster
Color change, storage available  → storage stays selected
Color change, storage unavailable→ dimmed option + signaled adjustment
One very cheap store outlier      → secondary comparison tier
Different account or locale       → audit cache reuse and offer-market inputs

The reusable idea is not a giant prompt. It is a set of explicit boundaries: facts from commerce, intent from planning, constraints from code, judgment from ranking, and clarity from the interface. Commerce IQ gives the harness something real to reason over; the harness makes that grounding useful to a shopper.