Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,20 @@ worktree
yaml
yml
zapatillas
jwks
keyid
reauth
redeemables
reprepare
sandboxing
fbclid
gclid
ttclid
Accor
Kogan
Petbarn
VTEX
geofence
geofencing
wifi
optionalities
2 changes: 1 addition & 1 deletion docs/specification/cart-rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ All REST endpoints **MUST** be served over HTTPS with minimum TLS version 1.3.
The following headers are defined for the HTTP binding and apply to all
operations unless otherwise noted.

{{ header_fields('create_cart', 'rest.openapi.json') }}
{{ header_fields('create_cart', 'shopping/rest.openapi.json') }}

### Specific Header Requirements

Expand Down
6 changes: 3 additions & 3 deletions docs/specification/catalog/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Businesses advertise REST transport availability through their UCP profile at

Maps to the [Catalog Search](search.md) capability.

{{ method_fields('search_catalog', 'rest.openapi.json', 'catalog/rest') }}
{{ method_fields('search_catalog', 'shopping/rest.openapi.json', 'catalog/rest') }}

#### Example

Expand Down Expand Up @@ -201,7 +201,7 @@ for supported identifiers, resolution behavior, and client correlation requireme
The request body contains an array of identifiers and optional context that
applies to all lookups in the batch.

{{ method_fields('lookup_catalog', 'rest.openapi.json', 'catalog/rest') }}
{{ method_fields('lookup_catalog', 'shopping/rest.openapi.json', 'catalog/rest') }}

#### Example: Batch Lookup with Context

Expand Down Expand Up @@ -353,7 +353,7 @@ messages indicating which identifiers were not found.
Maps to the [Catalog Lookup](lookup.md#get-product-get_product) capability. Returns a singular
`product` object (not an array) for full product detail page rendering.

{{ method_fields('get_product', 'rest.openapi.json', 'catalog/rest') }}
{{ method_fields('get_product', 'shopping/rest.openapi.json', 'catalog/rest') }}

#### Example: With Option Selection

Expand Down
2 changes: 1 addition & 1 deletion docs/specification/checkout-rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -1280,7 +1280,7 @@ place to set these expectations via `messages`.
The following headers are defined for the HTTP binding and apply to all
operations unless otherwise noted.

{{ header_fields('create_checkout', 'rest.openapi.json') }}
{{ header_fields('create_checkout', 'shopping/rest.openapi.json') }}

### Specific Header Requirements

Expand Down
10 changes: 5 additions & 5 deletions docs/specification/checkout.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ should accept an additional `cart_id` field for cart-to-checkout conversion. See
[Cart → Cart-to-Checkout Conversion](cart.md#cart-to-checkout-conversion) for
the field contract.

{{ method_fields('create_checkout', 'rest.openapi.json', 'checkout') }}
{{ method_fields('create_checkout', 'shopping/rest.openapi.json', 'checkout') }}

### Get Checkout

Expand All @@ -643,7 +643,7 @@ checkout.
The platform will honor the TTL provided by the business via `expires_at` at the
time of checkout session creation.

{{ method_fields('get_checkout', 'rest.openapi.json', 'checkout') }}
{{ method_fields('get_checkout', 'shopping/rest.openapi.json', 'checkout') }}

### Update Checkout

Expand All @@ -652,7 +652,7 @@ The platform is **REQUIRED** to send the entire checkout resource containing any
data updates to write-only data fields. The resource provided in the request
will replace the existing checkout session state on the business side.

{{ method_fields('update_checkout', 'rest.openapi.json', 'checkout') }}
{{ method_fields('update_checkout', 'shopping/rest.openapi.json', 'checkout') }}

### Complete Checkout

Expand All @@ -668,7 +668,7 @@ to construct the order representation (i.e. information like `line_items`,
After this call, other details will be updated through subsequent events
as the order, and its associated items, moves through the supply chain.

{{ method_fields('complete_checkout', 'rest.openapi.json', 'checkout') }}
{{ method_fields('complete_checkout', 'shopping/rest.openapi.json', 'checkout') }}

### Cancel Checkout

Expand All @@ -678,7 +678,7 @@ already canceled or completed), then businesses **SHOULD** send back an error
indicating the operation is not allowed. Any checkout session with a status
that is not equal to `completed` or `canceled` **SHOULD** be cancelable.

{{ method_fields('cancel_checkout', 'rest.openapi.json', 'checkout') }}
{{ method_fields('cancel_checkout', 'shopping/rest.openapi.json', 'checkout') }}

## Transport Bindings

Expand Down
4 changes: 2 additions & 2 deletions docs/specification/fulfillment.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ method.

{{ schema_fields('types/shipping_destination_resp', 'fulfillment') }}

#### Retail Location
#### Location

{{ schema_fields('types/retail_location_resp', 'fulfillment') }}
{{ schema_fields('types/location_resp', 'fulfillment') }}

#### Fulfillment Group

Expand Down
148 changes: 148 additions & 0 deletions docs/specification/location/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<!--
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.
-->

# Location Capability

The Location capability allows platforms to discover, search, and retrieve physical locations
(such as retail stores, restaurants, brand lockers) from businesses.

This is vertical-agnostic and enables key commerce flows such as:

* **Local Pickup Discovery**: Finding locations like retail stores or restaurant branches
nearby that support customer pickup and checking their operating hours & inventory availability
before selection.
* **Fulfillment Area Verification**: Checking if a specific location (e.g., utility depot, restaurant,
or local service provider) has delivery coverage for a buyer's address.

## Capabilities

| Capability | Description |
| :--- | :--- |
| [`dev.ucp.common.location.search`](search.md) | Search for locations using natural language queries and filters (hours, offerings, geofencing). |
| [`dev.ucp.common.location.lookup`](lookup.md) | Retrieve full details for one or more locations by identifier. |

## Key Concepts

* **Location**: A physical entity that can be found on a map. Defined by a display name,
address, operating hours, and **geographic context** (geographic coordinates and an
optional circular **geofence service radius** for delivery/service area checks).
* **Offerings**: Features, capabilities, and inventory provided by the location.
This is split into two distinct concepts to ensure tooling compatibility and semantic clarity:
* **Amenities**: Static features, services, or capabilities of the location
(e.g., `free_wifi`, `parking`, `outdoor_seating`, `curbside_pickup`).
* **Inventory**: Dynamic availability of goods (e.g., retail products or restaurant dishes).
* **Geofencing**: Locations can define a `geofence_radius` around their coordinates.
This is used to determine if a location can serve a specific user (e.g., delivery area check).
Clients can perform proximity searches (`distance` filter) or coverage checks (`geofence` filter) using the filter.
* **Operating Hours**: Weekly schedules (`hours`) and date-specific overrides
(`exception_hours` - e.g., holidays, temporary closures) associated with a timezone.

### Relationship to Other Capabilities

The Location capability provides the foundation for localized commerce by integrating tightly
other capabilities (like Catalog, Cart, and Checkout in Shopping):

1. **Stable Identifiers**: Location search/lookup operations return stable,
business-scoped `location.id` values. These IDs are referenced further in other requests & responses
(e.g., associating product variants to specific locations in Catalog filters, passed directly
in `selected_destination_id` to indicate pickup fulfillment mode).
2. **Inventory-Based Store Finder**: Platforms can use Location Search with the `offerings.inventory` filter
to locate nearby stores that have a specific item available, bridging the gap between online catalog
browsing and physical store visits.
3. **Provisional vs. Authoritative Boundaries**:
* *Discovery Phase (Provisional)*: Location responses based on operating hours, real-time inventory
availability, and amenities offerings represent the business's *current terms* at the
time of query. They are **provisional signals** (despite most, like hours & amenities, remain stable
overtime) and are not binding commitments.
* *Checkout Phase (Authoritative)*: Final transaction terms that depend on a location (e.g., pickup)
**MUST** be negotiated and finalized authoritatively. Discovery signals **SHOULD NOT** be cached
or reused across sessions without re-validation.

## Shared Entities

### Context

User location and market context for the operations. All fields are optional
hints for relevance and localization. Platforms **MAY** geo-detect context from
request headers.

Context signals are provisional—not authoritative data. Businesses **SHOULD** use
these values when verified inputs (e.g., coordinates as part of the request filter)
are absent, and **MAY** ignore or down-rank them if inconsistent with
higher-confidence signals (authenticated account, risk detection).

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

### Signals

Environment data provided by the platform to support authorization
and abuse prevention. Signal values **MUST NOT** be buyer-asserted claims. See
[Signals](../overview.md#signals) for details and privacy requirements.

{{ schema_fields('types/signals', 'location') }}

## Messages and Error Handling

All location 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 | `no_service_coverage` (geographic coordinates based filter) |
| `warning` | Important conditions affecting purchase | `permanently_closed`, `temporary_closure` |
| `info` | Additional context without issues | `not_found`, `holiday_hours_active` |

**Note**: Most 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.

<!-- ucp:example schema=common/location_search op=search -->
```json
{
"ucp": {...},
"locations": []
}
```

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

## 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.
76 changes: 76 additions & 0 deletions docs/specification/location/lookup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!--
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.
-->

# Location Lookup Capability

* **Capability Name:** `dev.ucp.common.location.lookup`

Retrieves physical locations by their unique identifiers.
Supports full-detail batch retrieval of multiple locations to provide optionalities
or retrieval of a single location (useful for a dedicated location detail page).

## Operation

| Operation | Description |
| :--------------------- | :-------------------------------------------- |
| **Lookup Location(s)** | Retrieve single or multiple locations by ID. |

## Supported Identifiers

The `ids` parameter accepts an array of identifiers. Implementations **MUST**
support lookup by the business's stable location ID.

Duplicate identifiers in the request **MUST** be deduplicated by the server.
When multiple identifiers resolve to the same physical location,
it **MUST** be returned only once in the response.

### Client Correlation

The response does not guarantee order. Clients correlate returned locations
simply by matching the returned `id` field against their requested `ids`.

### 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).

### Filters

Optional `filters` (hours, offerings/inventory, geo) are accepted
to narrow down the returned locations.
Filters use the same schema and AND semantics as [Search Filters](search.md#search-filters).

Filters apply **after** identifier resolution. For example, if a client requests
`["loc_downtown", "loc_uptown"]` with a filter of `hours.open_now: true`:

1. The server first resolves both identifiers to their respective locations.
2. The server then evaluates the `open_now` filter against each resolved location.
3. If `loc_uptown` is currently closed, it is excluded, and only `loc_downtown` is returned.

### Request

{{ extension_schema_fields('location_lookup.json#/$defs/lookup_request', 'location') }}

### Response

{{ extension_schema_fields('location_lookup.json#/$defs/lookup_response', 'location') }}

## Transport Bindings

* [REST Binding](rest.md#post-locationslookup): `POST /locations/lookup`
* [MCP Binding](mcp.md#lookup_locations): `lookup_locations` tool
Loading
Loading