Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a483623
feat(catalog): Catalog capability for product discovery
igrigorik Jan 15, 2026
d06a5ae
Extract search filters to standalone schemas
igrigorik Jan 15, 2026
1a2e634
add custom words for spellcheck
igrigorik Jan 18, 2026
b5a42dd
rebase on main
igrigorik Jan 29, 2026
f471167
split into search and lookup capabilities
igrigorik Jan 30, 2026
eae79de
clarify context & market assignment
igrigorik Jan 30, 2026
6878142
feat(context): add language field for content localization
igrigorik Jan 30, 2026
e606d3d
Add POST /catalog/lookup endpoint + context query params for GET
igrigorik Jan 30, 2026
f92b9db
security hardening, currency support, schema refinements
igrigorik Jan 30, 2026
2d2f096
fix linter issues
igrigorik Jan 30, 2026
7894a60
rename `price` fields on Product for clarity
igrigorik Feb 1, 2026
8bbe148
extensible category schema with taxonomy support
igrigorik Feb 2, 2026
99615b9
Merge branch 'main' into feat/catalog-capability
igrigorik Feb 2, 2026
46d1e48
refactor to POST-only batch lookup API
igrigorik Feb 4, 2026
a816a7b
Merge branch 'main' into feat/catalog-capability
igrigorik Feb 4, 2026
828ed00
Remove stale spec/ directory (schemas live in source/)
igrigorik Feb 6, 2026
40369ff
update category examples with multi-taxonomy response
igrigorik Feb 6, 2026
269eefa
fix examples to show price_range on Product
igrigorik Feb 6, 2026
90ecd9d
extract description into reusable type
igrigorik Feb 6, 2026
1db9268
feat: category search filter as an array
igrigorik Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"ignoreRegExpList": [
"src\\s*=\\s*(\"[^\"]*\"|'[^']*')",
"(\"token\"|token)\\s*:\\s*(\"[^\"]*\"|'[^']*')",
"(\"auth_jwt\"|auth_jwt)\\s*:\\s*(\"[^\"]*\"|'[^']*')"
"(\"auth_jwt\"|auth_jwt)\\s*:\\s*(\"[^\"]*\"|'[^']*')",
"(\"cursor\"|cursor)\\s*:\\s*(\"[^\"]*\"|'[^']*')"
],
"dictionaryDefinitions": [
{
Expand Down
11 changes: 11 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ Shopee
Splitit
Streamable
Stripe
Talla
Target
UCP
Ulta
Visa
Variante
Wayfair
Worldpay
Zapatillas
Zalando
adyen
agentic
amortiguación
atok
backorder
checkout
Expand All @@ -59,10 +63,12 @@ gpay
ingestions
inlinehilite
lifecycles
ligeras
linenums
llms
llmstxt
mastercard
midsole
mkdocs
mtok
openapi
Expand All @@ -74,14 +80,19 @@ preorders
proto
protobuf
pymdownx
reactiva
renderable
repudiable
schemas
sdjwt
shopify
superfences
talla
tracción
upsell
upsells
variante
vulnz
yaml
zapatillas
yml
232 changes: 232 additions & 0 deletions docs/specification/catalog/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
<!--
Copyright 2026 UCP Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

# Catalog Capability

## Overview

The Catalog capability allows platforms to search and browse business product catalogs.
This enables product discovery before checkout, supporting use cases like:

* Free-text product search
* Category and filter-based browsing
* Batch product/variant retrieval by identifier
* Price comparison across variants

## Capabilities

| Capability | Description |
| :--- | :--- |
| [`dev.ucp.shopping.catalog.search`](search.md) | Search for products using query text and filters. |
| [`dev.ucp.shopping.catalog.lookup`](lookup.md) | Retrieve products or variants by identifier. |

## Key Concepts

* **Product**: A catalog item with title, description, media, and one or more
variants.
* **Variant**: A purchasable item with specific option selections (e.g., "Blue /
Large"), price, and availability.
* **Price**: Price values include both amount (in minor currency units) and
currency code, enabling multi-currency catalogs.

### Relationship to Checkout

Catalog operations return product and variant IDs that can be used directly in
checkout `line_items[].item.id`. The variant ID from catalog retrieval should match
the item ID expected by checkout.

## Shared Entities

### Context

Location and market context for catalog operations. All fields are optional
hints for relevance and localization. Platforms MAY geo-detect context from
request headers.

Context signals are provisional—not authorization. Businesses SHOULD use these
values when authoritative data (e.g., shipping address) is absent, and MAY
ignore or down-rank them if inconsistent with stronger signals (authenticated
account, fraud rules, export controls). Eligibility and policy enforcement
MUST occur at checkout/order time with authoritative data.

Businesses determine market assignment—including currency—based on context
signals. Price filter values are interpreted in the business's assigned
currency; response prices include explicit currency codes.

{{ schema_fields('types/context', 'catalog') }}

### Product

A catalog item representing a sellable item with one or more purchasable variants.

`media` and `variants` are ordered arrays. Businesses SHOULD return the most
relevant variant and image first—default for lookups, best match based on query
and context for search. Platforms SHOULD treat the first element as featured.

{{ schema_fields('types/product', 'catalog') }}

### Variant

A purchasable item with specific option selections, price, and availability.

`media` is an ordered array. Businesses SHOULD return the featured variant image
as the first element. Platforms SHOULD treat the first element as featured.

{{ schema_fields('types/variant', 'catalog') }}

### Price

{{ schema_fields('types/price', 'catalog') }}

### Price Range

{{ schema_fields('types/price_range', 'catalog') }}

### Media

{{ schema_fields('types/media', 'catalog') }}

### Product Option

{{ schema_fields('types/product_option', 'catalog') }}

### Option Value

{{ schema_fields('types/option_value', 'catalog') }}

### Selected Option

{{ schema_fields('types/selected_option', 'catalog') }}

### Rating

{{ schema_fields('types/rating', 'catalog') }}

## Messages and Error Handling

All catalog responses include an optional `messages` array that allows businesses
to provide context about errors, warnings, or informational notices.

### Message Types

Messages communicate business outcomes and provide context:

| Type | When to Use | Example Codes |
| :--- | :--- | :--- |
| `error` | Business-level errors | `NOT_FOUND`, `OUT_OF_STOCK`, `REGION_RESTRICTED` |
| `warning` | Important conditions affecting purchase | `DELAYED_FULFILLMENT`, `FINAL_SALE`, `AGE_RESTRICTED` |
| `info` | Additional context without issues | `PROMOTIONAL_PRICING`, `LIMITED_AVAILABILITY` |

**Note**: All catalog errors use `severity: "recoverable"` - agents handle them programmatically (retry, inform user, show alternatives).

#### Message (Error)

{{ schema_fields('types/message_error', 'catalog') }}

#### Message (Warning)

{{ schema_fields('types/message_warning', 'catalog') }}

#### Message (Info)

{{ schema_fields('types/message_info', 'catalog') }}

### Common Scenarios

#### Empty Search

When search finds no matches, return an empty array without messages.

```json
{
"ucp": {...},
"products": []
}
```

This is not an error - the query was valid but returned no results.

#### Backorder Warning

When a product is available but has delayed fulfillment, return the product with
a warning message. Use the `path` field to target specific variants.

```json
{
"ucp": {...},
"products": [
{
"id": "prod_xyz789",
"title": "Professional Chef Knife Set",
"description": { "plain": "Complete professional knife collection." },
"price_range": {
"min": { "amount": 29900, "currency": "USD" },
"max": { "amount": 29900, "currency": "USD" }
},
"variants": [
{
"id": "var_abc",
"title": "12-piece Set",
"description": { "plain": "Complete professional knife collection." },
"price": { "amount": 29900, "currency": "USD" },
"availability": { "available": true }
}
]
}
],
"messages": [
{
"type": "warning",
"code": "delayed_fulfillment",
"path": "$.products[0].variants[0]",
"content": "12-piece set on backorder, ships in 2-3 weeks"
}
]
}
```

Agents can present the option and inform the user about the delay. The `path`
field uses RFC 9535 JSONPath to target specific components.

#### Identifiers Not Found

When requested identifiers don't exist, return success with the found products
(if any). The response MAY include informational messages indicating which
identifiers were not found.

```json
{
"ucp": {...},
"products": [],
"messages": [
{
"type": "info",
"code": "not_found",
"content": "prod_invalid"
}
]
}
```

Agents correlate results by matching fields in returned products against
requested identifiers.

## Transport Bindings

The capabilities above are bound to specific transport protocols:

* [REST Binding](rest.md): RESTful API mapping.
* [MCP Binding](mcp.md): Model Context Protocol mapping via JSON-RPC.
79 changes: 79 additions & 0 deletions docs/specification/catalog/lookup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!--
Copyright 2026 UCP Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

# Catalog Lookup Capability

* **Capability Name:** `dev.ucp.shopping.catalog.lookup`

Retrieves products or variants by identifier. Use this when you already have
identifiers (e.g., from a saved list, deep links, or cart validation).

## Operation

| Operation | Description |
| :--- | :--- |
| **Lookup Catalog** | Retrieve products or variants by identifier. |

### Supported Identifiers

The `ids` parameter accepts an array of identifiers. Implementations MUST support
lookup by product ID and variant ID. Implementations MAY additionally support
secondary identifiers such as SKU or handle, provided these are also fields on
the returned product object.

Duplicate identifiers in the request MUST be deduplicated. When an identifier
matches multiple products (e.g., a SKU shared across variants), all matching
products MUST be returned. When multiple identifiers resolve to the same product,
it MUST be returned once. The `products` array may contain fewer or more items
than requested identifiers.

### Client Correlation

The response does not guarantee order or provide explicit identifier-to-product
mapping. Clients correlate results by matching fields in the returned products
(e.g., `id`, `sku`, `handle`) against the requested identifiers.

### Batch Size

Implementations SHOULD accept at least 10 identifiers per request. Implementations
MAY enforce a maximum batch size and MUST reject requests exceeding their limit
with an appropriate error (HTTP 400 `request_too_large` for REST, JSON-RPC
`-32602` for MCP).

### Resolution Behavior

For each identifier, the response returns the parent product with full context
(title, description, media, options):

* **Product ID lookup**: `variants` MAY contain a representative set.
* **Variant ID lookup**: `variants` MUST contain only the requested variant.

When the variant set is large, a representative set MAY be returned based on
buyer context or other criteria. This ensures agents always have product context
for display while getting exactly what they requested.

### Request

{{ extension_schema_fields('catalog_lookup.json#/$defs/lookup_request', 'catalog') }}

### Response

{{ extension_schema_fields('catalog_lookup.json#/$defs/lookup_response', 'catalog') }}

## Transport Bindings

* [REST Binding](rest.md#post-cataloglookup): `POST /catalog/lookup`
* [MCP Binding](mcp.md#lookup_catalog): `lookup_catalog` tool
Loading