From 031d901040b2376fb309de1b2394d27f5778b831 Mon Sep 17 00:00:00 2001 From: cusell-google Date: Wed, 18 Mar 2026 15:21:51 +0000 Subject: [PATCH 1/4] Add license metadata to pyproject.toml and update footer --- LICENSE | 2 +- pyproject.toml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d645695..e7371b3 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + 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. diff --git a/pyproject.toml b/pyproject.toml index d4d858a..bcf5f89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,9 +3,21 @@ name = "ucp-sdk" version = "0.1.0" description = "UCP Python SDK" readme = "README.md" +license = {file = "LICENSE"} authors = [ { name = "Florin Iucha", email = "fiucha@google.com" } ] +classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries :: Python Modules", +] requires-python = ">=3.10" dependencies = [ "pydantic>=2.5.0", From c3d789a27c77186fa3703700102f7a47f3e3c984 Mon Sep 17 00:00:00 2001 From: cusell-google Date: Wed, 18 Mar 2026 15:25:55 +0000 Subject: [PATCH 2/4] Update author to Enric Cusell in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bcf5f89..11193d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "UCP Python SDK" readme = "README.md" license = {file = "LICENSE"} authors = [ - { name = "Florin Iucha", email = "fiucha@google.com" } + { name = "Enric Cusell", email = "cusell@google.com" } ] classifiers = [ "License :: OSI Approved :: Apache Software License", From dabd6b9312874e1b3d0d29c5c05549dca3594836 Mon Sep 17 00:00:00 2001 From: cusell-google Date: Tue, 24 Mar 2026 14:57:29 +0100 Subject: [PATCH 3/4] build: keep Florin and add Enric to authors --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 11193d1..1ff7b35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "UCP Python SDK" readme = "README.md" license = {file = "LICENSE"} authors = [ + { name = "Florin Iucha", email = "fiucha@google.com" }, { name = "Enric Cusell", email = "cusell@google.com" } ] classifiers = [ From 4ff1676171708ae1c12503e1155652c387279a09 Mon Sep 17 00:00:00 2001 From: cusell-google Date: Tue, 31 Mar 2026 10:30:32 +0200 Subject: [PATCH 4/4] feat: sync UCP models and update SDK version to 0.3 Update the version of the SDK to 0.3. This version is larger than the previous one which was 0.1.0. --- pyproject.toml | 2 +- src/ucp_sdk/models/schemas/__init__.py | 1 + src/ucp_sdk/models/schemas/capability.py | 132 ++++++++-- src/ucp_sdk/models/schemas/common/__init__.py | 18 ++ .../models/schemas/common/identity_linking.py | 114 +++++++++ src/ucp_sdk/models/schemas/payment_handler.py | 26 ++ src/ucp_sdk/models/schemas/service.py | 28 ++- .../models/schemas/shopping/__init__.py | 1 + .../__init__.py} | 34 --- .../shopping/ap2_mandate/dev/__init__.py | 18 ++ .../shopping/ap2_mandate/dev/ucp/__init__.py | 18 ++ .../shopping/ap2_mandate/dev/ucp/shopping.py | 56 +++++ .../__init__.py} | 17 +- .../shopping/buyer_consent/dev/__init__.py | 18 ++ .../buyer_consent/dev/ucp/__init__.py | 18 ++ .../buyer_consent/dev/ucp/shopping.py | 38 +++ src/ucp_sdk/models/schemas/shopping/cart.py | 95 ++++++++ .../schemas/shopping/cart_create_request.py | 66 +++++ .../schemas/shopping/cart_update_request.py | 70 ++++++ .../models/schemas/shopping/catalog_lookup.py | 90 +++++++ .../models/schemas/shopping/catalog_search.py | 68 ++++++ .../models/schemas/shopping/checkout.py | 15 +- .../shopping/checkout_complete_request.py | 10 +- .../shopping/checkout_create_request.py | 2 + .../shopping/checkout_update_request.py | 2 + .../{discount.py => discount/__init__.py} | 32 ++- .../schemas/shopping/discount/dev/__init__.py | 18 ++ .../shopping/discount/dev/ucp/__init__.py | 18 ++ .../shopping/discount/dev/ucp/shopping.py | 47 ++++ .../schemas/shopping/fulfillment/__init__.py | 21 +- .../shopping/fulfillment/dev/__init__.py | 1 + .../shopping/fulfillment/dev/ucp/__init__.py | 1 + .../shopping/fulfillment/dev/ucp/shopping.py | 17 ++ src/ucp_sdk/models/schemas/shopping/order.py | 15 +- .../models/schemas/shopping/types/__init__.py | 1 + .../schemas/shopping/types/adjustment.py | 6 +- .../models/schemas/shopping/types/amount.py | 31 +++ .../types/available_payment_instrument.py | 41 ++++ .../shopping/types/card_payment_instrument.py | 25 +- .../models/schemas/shopping/types/category.py | 39 +++ .../models/schemas/shopping/types/context.py | 20 +- .../shopping/types/context_create_request.py | 23 +- .../shopping/types/context_update_request.py | 23 +- .../schemas/shopping/types/description.py | 43 ++++ .../schemas/shopping/types/error_code.py | 41 ++++ .../schemas/shopping/types/error_response.py | 59 +++++ .../fulfillment_method_update_request.py | 6 + .../shopping/types/input_correlation.py | 39 +++ .../models/schemas/shopping/types/item.py | 10 +- .../shopping/types/item_create_request.py | 2 +- .../shopping/types/item_update_request.py | 2 +- .../models/schemas/shopping/types/media.py | 51 ++++ .../schemas/shopping/types/message_error.py | 14 +- .../schemas/shopping/types/message_warning.py | 14 +- .../schemas/shopping/types/option_value.py | 35 +++ .../schemas/shopping/types/pagination.py | 71 ++++++ .../models/schemas/shopping/types/price.py | 41 ++++ .../schemas/shopping/types/price_filter.py | 41 ++++ .../schemas/shopping/types/price_range.py | 41 ++++ .../models/schemas/shopping/types/product.py | 97 ++++++++ .../schemas/shopping/types/product_option.py | 41 ++++ .../models/schemas/shopping/types/rating.py | 47 ++++ .../shopping/types/reverse_domain_name.py | 35 +++ .../reverse_domain_name_create_request.py | 35 +++ .../reverse_domain_name_update_request.py | 35 +++ .../schemas/shopping/types/search_filters.py | 38 +++ .../schemas/shopping/types/selected_option.py | 39 +++ .../models/schemas/shopping/types/signals.py | 39 +++ .../types/signals_complete_request.py | 39 +++ .../shopping/types/signals_create_request.py | 39 +++ .../shopping/types/signals_update_request.py | 39 +++ .../schemas/shopping/types/signed_amount.py | 31 +++ .../models/schemas/shopping/types/total.py | 25 +- .../shopping/types/total_create_request.py | 4 + .../shopping/types/total_update_request.py | 4 + .../models/schemas/shopping/types/totals.py | 64 +++++ .../models/schemas/shopping/types/variant.py | 226 ++++++++++++++++++ .../models/schemas/transports/__init__.py | 1 + .../schemas/transports/embedded_config.py | 6 + src/ucp_sdk/models/schemas/ucp.py | 156 +++++++++--- 80 files changed, 2697 insertions(+), 189 deletions(-) create mode 100644 src/ucp_sdk/models/schemas/common/__init__.py create mode 100644 src/ucp_sdk/models/schemas/common/identity_linking.py rename src/ucp_sdk/models/schemas/shopping/{ap2_mandate.py => ap2_mandate/__init__.py} (82%) create mode 100644 src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/__init__.py create mode 100644 src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/__init__.py create mode 100644 src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/shopping.py rename src/ucp_sdk/models/schemas/shopping/{buyer_consent.py => buyer_consent/__init__.py} (84%) create mode 100644 src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/__init__.py create mode 100644 src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/__init__.py create mode 100644 src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/shopping.py create mode 100644 src/ucp_sdk/models/schemas/shopping/cart.py create mode 100644 src/ucp_sdk/models/schemas/shopping/cart_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/cart_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/catalog_lookup.py create mode 100644 src/ucp_sdk/models/schemas/shopping/catalog_search.py rename src/ucp_sdk/models/schemas/shopping/{discount.py => discount/__init__.py} (78%) create mode 100644 src/ucp_sdk/models/schemas/shopping/discount/dev/__init__.py create mode 100644 src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/__init__.py create mode 100644 src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/shopping.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/amount.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/category.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/description.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/error_code.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/error_response.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/input_correlation.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/media.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/option_value.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/pagination.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/price.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/price_filter.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/price_range.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/product.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/product_option.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/rating.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/search_filters.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/selected_option.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signals.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/signed_amount.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/totals.py create mode 100644 src/ucp_sdk/models/schemas/shopping/types/variant.py diff --git a/pyproject.toml b/pyproject.toml index f55d058..20aae1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ucp-sdk" -version = "0.0.3" +version = "0.3" description = "UCP Python SDK" readme = "README.md" license = {file = "LICENSE"} diff --git a/src/ucp_sdk/models/schemas/__init__.py b/src/ucp_sdk/models/schemas/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/__init__.py +++ b/src/ucp_sdk/models/schemas/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/capability.py b/src/ucp_sdk/models/schemas/capability.py index 3e326ff..06c378c 100644 --- a/src/ucp_sdk/models/schemas/capability.py +++ b/src/ucp_sdk/models/schemas/capability.py @@ -33,6 +33,114 @@ class UcpCapability(RootModel[Any]): """ +class Extends(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends1Item(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + + +class Extends1(RootModel[list[Extends1Item]]): + model_config = ConfigDict( + frozen=True, + ) + root: list[Extends1Item] = Field(..., min_length=1) + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends2(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends3Item(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + + +class Extends3(RootModel[list[Extends3Item]]): + model_config = ConfigDict( + frozen=True, + ) + root: list[Extends3Item] = Field(..., min_length=1) + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends4(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends5Item(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + + +class Extends5(RootModel[list[Extends5Item]]): + model_config = ConfigDict( + frozen=True, + ) + root: list[Extends5Item] = Field(..., min_length=1) + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends6(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + +class Extends7Item(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + + +class Extends7(RootModel[list[Extends7Item]]): + model_config = ConfigDict( + frozen=True, + ) + root: list[Extends7Item] = Field(..., min_length=1) + """ + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. + """ + + class Version(RootModel[Any]): model_config = ConfigDict( frozen=True, @@ -64,11 +172,9 @@ class Base(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: str | None = Field( - None, pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" - ) + extends: Extends | Extends1 | None = None """ - Parent capability this extends. Present for extensions, absent for root capabilities. + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ @@ -100,11 +206,9 @@ class PlatformSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: str | None = Field( - None, pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" - ) + extends: Extends2 | Extends3 | None = None """ - Parent capability this extends. Present for extensions, absent for root capabilities. + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ @@ -136,11 +240,9 @@ class BusinessSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: str | None = Field( - None, pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" - ) + extends: Extends4 | Extends5 | None = None """ - Parent capability this extends. Present for extensions, absent for root capabilities. + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ @@ -172,9 +274,7 @@ class ResponseSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ - extends: str | None = Field( - None, pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$" - ) + extends: Extends6 | Extends7 | None = None """ - Parent capability this extends. Present for extensions, absent for root capabilities. + Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. """ diff --git a/src/ucp_sdk/models/schemas/common/__init__.py b/src/ucp_sdk/models/schemas/common/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/common/identity_linking.py b/src/ucp_sdk/models/schemas/common/identity_linking.py new file mode 100644 index 0000000..c331fcd --- /dev/null +++ b/src/ucp_sdk/models/schemas/common/identity_linking.py @@ -0,0 +1,114 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel + +from ..capability import BusinessSchema as BusinessSchema_1 +from ..capability import PlatformSchema as PlatformSchema_1 + + +class IdentityLinkingCapability(RootModel[Any]): + model_config = ConfigDict( + frozen=True, + ) + root: Any = Field(..., title="Identity Linking Capability") + """ + Schema for authenticating and establishing verified connections between platforms and businesses. + """ + + +class IdentityScope(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field( + ..., + pattern="^[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*\\.scopes\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*(\\.[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*$", + ) + + +class IdentityScopes(RootModel[list[IdentityScope]]): + """ + A custom UCP annotation placed at the root of a capability schema to declare the OAuth 2.0 scopes required to operate that capability. Scopes MUST use reverse DNS dot notation to prevent namespace collisions (e.g., 'dev.ucp.shopping.scopes.checkout_session' for UCP-defined scopes, 'com.example.scopes.my_capability' for third-party scopes). Platforms collect this annotation from every capability in the finalized negotiated intersection and use the union as the authorization scope set. This annotation is intentionally a plain JSON array so it is ignored by standard JSON Schema validators — it carries semantic meaning only to UCP-aware tooling. Absence of this annotation on a capability schema means that capability requires no dedicated scope. + """ + + model_config = ConfigDict( + frozen=True, + ) + root: list[IdentityScope] = Field( + ..., min_length=1, title="Identity Scopes Annotation" + ) + """ + A custom UCP annotation placed at the root of a capability schema to declare the OAuth 2.0 scopes required to operate that capability. Scopes MUST use reverse DNS dot notation to prevent namespace collisions (e.g., 'dev.ucp.shopping.scopes.checkout_session' for UCP-defined scopes, 'com.example.scopes.my_capability' for third-party scopes). Platforms collect this annotation from every capability in the finalized negotiated intersection and use the union as the authorization scope set. This annotation is intentionally a plain JSON array so it is ignored by standard JSON Schema validators — it carries semantic meaning only to UCP-aware tooling. Absence of this annotation on a capability schema means that capability requires no dedicated scope. + """ + + +class Mechanism(BaseModel): + """ + Base definition for any authentication mechanism. The 'type' field discriminates between known mechanism schemas (e.g., oauth2). Unknown types pass through with only the base requirement, enabling forward-compatible extensibility. Note: this open base schema does not enforce field requirements for known types — use $defs/oauth2 directly to validate an oauth2 mechanism object explicitly. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + The mechanism type discriminator. Known values: 'oauth2'. Specific mechanism schemas constrain this to a constant value. + """ + + +class Oauth2(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: Literal["oauth2"] + """ + OAuth 2.0 authentication mechanism. + """ + issuer: AnyUrl + """ + The authorization server URL, supporting RFC 8414 discovery. + """ + discovery_endpoint: AnyUrl | None = None + """ + Optional explicit URI to the authorization server's metadata (e.g., `https://auth.merchant.example.com/.well-known/openid-configuration`). If omitted, platforms construct discovery paths based on the `issuer`. + """ + + +class PlatformSchema(PlatformSchema_1): + model_config = ConfigDict( + extra="allow", + ) + + +class Config(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + supported_mechanisms: list[Mechanism] = Field(..., min_length=1) + + +class BusinessSchema(BusinessSchema_1): + model_config = ConfigDict( + extra="allow", + ) + config: Config diff --git a/src/ucp_sdk/models/schemas/payment_handler.py b/src/ucp_sdk/models/schemas/payment_handler.py index c51161c..1e3d4e5 100644 --- a/src/ucp_sdk/models/schemas/payment_handler.py +++ b/src/ucp_sdk/models/schemas/payment_handler.py @@ -22,6 +22,8 @@ from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel +from .shopping.types import available_payment_instrument + class PaymentHandler(RootModel[Any]): model_config = ConfigDict( @@ -64,6 +66,12 @@ class Base(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ + available_instruments: ( + list[available_payment_instrument.AvailablePaymentInstrument] | None + ) = Field(None, min_length=1) + """ + Instrument types this handler supports, with optional constraints. When absent, every instrument should be considered available. + """ class PlatformSchema(BaseModel): @@ -94,6 +102,12 @@ class PlatformSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ + available_instruments: ( + list[available_payment_instrument.AvailablePaymentInstrument] | None + ) = Field(None, min_length=1) + """ + Instrument types this handler supports, with optional constraints. When absent, every instrument should be considered available. + """ class BusinessSchema(BaseModel): @@ -124,6 +138,12 @@ class BusinessSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ + available_instruments: ( + list[available_payment_instrument.AvailablePaymentInstrument] | None + ) = Field(None, min_length=1) + """ + Instrument types this handler supports, with optional constraints. When absent, every instrument should be considered available. + """ class ResponseSchema(BaseModel): @@ -154,3 +174,9 @@ class ResponseSchema(BaseModel): """ Entity-specific configuration. Structure defined by each entity's schema. """ + available_instruments: ( + list[available_payment_instrument.AvailablePaymentInstrument] | None + ) = Field(None, min_length=1) + """ + Instrument types this handler supports, with optional constraints. When absent, every instrument should be considered available. + """ diff --git a/src/ucp_sdk/models/schemas/service.py b/src/ucp_sdk/models/schemas/service.py index 2a91c36..b571996 100644 --- a/src/ucp_sdk/models/schemas/service.py +++ b/src/ucp_sdk/models/schemas/service.py @@ -45,6 +45,10 @@ class Config(BaseModel): """ Delegations the business allows. At service-level, declares available delegations. In checkout responses, confirms accepted delegations for this session. """ + color_scheme: list[Literal["light", "dark"]] | None = None + """ + Color schemes the business supports. Hosts use ec_color_scheme query parameter to request a scheme from this list. + """ class Version(RootModel[Any]): @@ -126,7 +130,7 @@ class PlatformSchema(BaseModel): """ -class PlatformSchema5(BaseModel): +class PlatformSchema6(BaseModel): """ Full service declaration for platform-level discovery. Different transports require different fields. """ @@ -164,7 +168,7 @@ class PlatformSchema5(BaseModel): """ -class PlatformSchema6(BaseModel): +class PlatformSchema7(BaseModel): """ Full service declaration for platform-level discovery. Different transports require different fields. """ @@ -202,7 +206,7 @@ class PlatformSchema6(BaseModel): """ -class PlatformSchema7(BaseModel): +class PlatformSchema8(BaseModel): """ Full service declaration for platform-level discovery. Different transports require different fields. """ @@ -240,16 +244,16 @@ class PlatformSchema7(BaseModel): """ -class PlatformSchema3( +class PlatformSchema4( RootModel[ - PlatformSchema | PlatformSchema5 | PlatformSchema6 | PlatformSchema7 + PlatformSchema | PlatformSchema6 | PlatformSchema7 | PlatformSchema8 ] ): model_config = ConfigDict( frozen=True, ) root: ( - PlatformSchema | PlatformSchema5 | PlatformSchema6 | PlatformSchema7 + PlatformSchema | PlatformSchema6 | PlatformSchema7 | PlatformSchema8 ) = Field(..., title="Service (Platform Schema)") """ Full service declaration for platform-level discovery. Different transports require different fields. @@ -294,7 +298,7 @@ class BusinessSchema(BaseModel): """ -class BusinessSchema4(BaseModel): +class BusinessSchema5(BaseModel): """ Service binding for business/merchant configuration. May override platform endpoints. """ @@ -332,7 +336,7 @@ class BusinessSchema4(BaseModel): """ -class BusinessSchema5(BaseModel): +class BusinessSchema6(BaseModel): """ Service binding for business/merchant configuration. May override platform endpoints. """ @@ -370,7 +374,7 @@ class BusinessSchema5(BaseModel): """ -class BusinessSchema6(BaseModel): +class BusinessSchema7(BaseModel): """ Service binding for business/merchant configuration. May override platform endpoints. """ @@ -408,16 +412,16 @@ class BusinessSchema6(BaseModel): """ -class BusinessSchema2( +class BusinessSchema3( RootModel[ - BusinessSchema | BusinessSchema4 | BusinessSchema5 | BusinessSchema6 + BusinessSchema | BusinessSchema5 | BusinessSchema6 | BusinessSchema7 ] ): model_config = ConfigDict( frozen=True, ) root: ( - BusinessSchema | BusinessSchema4 | BusinessSchema5 | BusinessSchema6 + BusinessSchema | BusinessSchema5 | BusinessSchema6 | BusinessSchema7 ) = Field(..., title="Service (Business Schema)") """ Service binding for business/merchant configuration. May override platform endpoints. diff --git a/src/ucp_sdk/models/schemas/shopping/__init__.py b/src/ucp_sdk/models/schemas/shopping/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/__init__.py similarity index 82% rename from src/ucp_sdk/models/schemas/shopping/ap2_mandate.py rename to src/ucp_sdk/models/schemas/shopping/ap2_mandate/__init__.py index a76c1d8..5a87c76 100644 --- a/src/ucp_sdk/models/schemas/shopping/ap2_mandate.py +++ b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/__init__.py @@ -22,8 +22,6 @@ from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout import Checkout as Checkout_1 - class Ap2MandateExtension(RootModel[Any]): model_config = ConfigDict( @@ -91,24 +89,6 @@ class Ap2WithCheckoutMandate(BaseModel): """ -class Ap2(BaseModel): - """ - AP2 extension data including merchant authorization. - """ - - model_config = ConfigDict( - extra="allow", - ) - merchant_authorization: MerchantAuthorization | None = None - """ - Merchant's signature proving checkout terms are authentic. - """ - checkout_mandate: CheckoutMandate | None = None - """ - SD-JWT+kb proving user authorized this checkout. - """ - - class ErrorCode( RootModel[ Literal[ @@ -137,17 +117,3 @@ class ErrorCode( """ Error codes specific to AP2 mandate verification. """ - - -class Checkout(Checkout_1): - """ - Checkout extended with AP2 mandate support. - """ - - model_config = ConfigDict( - extra="allow", - ) - ap2: Ap2 | None = None - """ - AP2 extension data including merchant authorization. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/__init__.py b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/__init__.py b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/shopping.py b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/shopping.py new file mode 100644 index 0000000..312a17a --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/ap2_mandate/dev/ucp/shopping.py @@ -0,0 +1,56 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from ....checkout import Checkout as Checkout_1 +from ... import CheckoutMandate, MerchantAuthorization + + +class Ap2(BaseModel): + """ + AP2 extension data including merchant authorization. + """ + + model_config = ConfigDict( + extra="allow", + ) + merchant_authorization: MerchantAuthorization | None = None + """ + Merchant's signature proving checkout terms are authentic. + """ + checkout_mandate: CheckoutMandate | None = None + """ + SD-JWT+kb proving user authorized this checkout. + """ + + +class Checkout(Checkout_1): + """ + Checkout extended with AP2 mandate support. + """ + + model_config = ConfigDict( + extra="allow", + ) + ap2: Ap2 | None = None + """ + AP2 extension data including merchant authorization. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent/__init__.py similarity index 84% rename from src/ucp_sdk/models/schemas/shopping/buyer_consent.py rename to src/ucp_sdk/models/schemas/shopping/buyer_consent/__init__.py index 4162d93..c23994e 100644 --- a/src/ucp_sdk/models/schemas/shopping/buyer_consent.py +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent/__init__.py @@ -22,8 +22,7 @@ from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout import Checkout as Checkout_1 -from .types.buyer import Buyer as Buyer_1 +from ..types.buyer import Buyer as Buyer_1 class BuyerConsentExtension(RootModel[Any]): @@ -74,17 +73,3 @@ class Buyer(Buyer_1): """ Consent tracking fields. """ - - -class Checkout(Checkout_1): - """ - Checkout extended with consent tracking via buyer object. - """ - - model_config = ConfigDict( - extra="allow", - ) - buyer: Buyer | None = None - """ - Buyer with consent tracking. - """ diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/__init__.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/__init__.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/shopping.py b/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/shopping.py new file mode 100644 index 0000000..6b59298 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/buyer_consent/dev/ucp/shopping.py @@ -0,0 +1,38 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from ....checkout import Checkout as Checkout_1 +from ... import Buyer + + +class Checkout(Checkout_1): + """ + Checkout extended with consent tracking via buyer object. + """ + + model_config = ConfigDict( + extra="allow", + ) + buyer: Buyer | None = None + """ + Buyer with consent tracking. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/cart.py b/src/ucp_sdk/models/schemas/shopping/cart.py new file mode 100644 index 0000000..c7e5267 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/cart.py @@ -0,0 +1,95 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict + +from .. import ucp as ucp_1 +from .checkout import Checkout as Checkout_1 +from .types import buyer as buyer_1 +from .types import context as context_1 +from .types import line_item, link, message +from .types import signals as signals_1 +from .types import totals as totals_1 + + +class Cart(BaseModel): + """ + Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.UcpMetadata + id: str + """ + Unique cart identifier. + """ + line_items: list[line_item.LineItem] + """ + Cart line items. Same structure as checkout. Full replacement on update. + """ + context: context_1.Context | None = None + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + signals: signals_1.Signals | None = None + buyer: buyer_1.Buyer | None = None + """ + Optional buyer information for personalized estimates. + """ + currency: str + """ + ISO 4217 currency code. Determined by merchant based on context or geo-IP. + """ + totals: totals_1.Totals + """ + Estimated cost breakdown. May be partial if shipping/tax not yet calculable. + """ + messages: list[message.Message] | None = None + """ + Validation messages, warnings, or informational notices. + """ + links: list[link.Link] | None = None + """ + Optional merchant links (policies, FAQs). + """ + continue_url: AnyUrl | None = None + """ + URL for cart handoff and session recovery. Enables sharing and human-in-the-loop flows. + """ + expires_at: AwareDatetime | None = None + """ + Cart expiry timestamp (RFC 3339). Optional. + """ + + +class Checkout(Checkout_1): + """ + Checkout extended with cart capability. Adds cart_id to create_checkout for cart-to-checkout conversion. + """ + + model_config = ConfigDict( + extra="allow", + ) + cart_id: str | None = None + """ + Cart ID to convert to checkout. Business MUST use cart contents (line_items, context, buyer) and MUST ignore overlapping fields in checkout payload. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/cart_create_request.py b/src/ucp_sdk/models/schemas/shopping/cart_create_request.py new file mode 100644 index 0000000..10cd5db --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/cart_create_request.py @@ -0,0 +1,66 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from .checkout import Checkout as Checkout_1 +from .types import ( + buyer_create_request, + context_create_request, + line_item_create_request, + signals_create_request, +) + + +class CartCreateRequest(BaseModel): + """ + Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. + """ + + model_config = ConfigDict( + extra="allow", + ) + line_items: list[line_item_create_request.LineItemCreateRequest] + """ + Cart line items. Same structure as checkout. Full replacement on update. + """ + context: context_create_request.ContextCreateRequest | None = None + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + signals: signals_create_request.SignalsCreateRequest | None = None + buyer: buyer_create_request.BuyerCreateRequest | None = None + """ + Optional buyer information for personalized estimates. + """ + + +class Checkout(Checkout_1): + """ + Checkout extended with cart capability. Adds cart_id to create_checkout for cart-to-checkout conversion. + """ + + model_config = ConfigDict( + extra="allow", + ) + cart_id: str | None = None + """ + Cart ID to convert to checkout. Business MUST use cart contents (line_items, context, buyer) and MUST ignore overlapping fields in checkout payload. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/cart_update_request.py b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py new file mode 100644 index 0000000..938639d --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/cart_update_request.py @@ -0,0 +1,70 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from .checkout import Checkout as Checkout_1 +from .types import ( + buyer_update_request, + context_update_request, + line_item_update_request, + signals_update_request, +) + + +class CartUpdateRequest(BaseModel): + """ + Shopping cart with estimated pricing before checkout. Lightweight pre-purchase exploration with no payment info or complex status states. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Unique cart identifier. + """ + line_items: list[line_item_update_request.LineItemUpdateRequest] + """ + Cart line items. Same structure as checkout. Full replacement on update. + """ + context: context_update_request.ContextUpdateRequest | None = None + """ + Buyer signals for localization (country, region, postal_code). Merchant uses for pricing, availability, currency. Falls back to geo-IP if omitted. + """ + signals: signals_update_request.SignalsUpdateRequest | None = None + buyer: buyer_update_request.BuyerUpdateRequest | None = None + """ + Optional buyer information for personalized estimates. + """ + + +class Checkout(Checkout_1): + """ + Checkout extended with cart capability. Adds cart_id to create_checkout for cart-to-checkout conversion. + """ + + model_config = ConfigDict( + extra="allow", + ) + cart_id: str | None = None + """ + Cart ID to convert to checkout. Business MUST use cart contents (line_items, context, buyer) and MUST ignore overlapping fields in checkout payload. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py b/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py new file mode 100644 index 0000000..25470ad --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/catalog_lookup.py @@ -0,0 +1,90 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from .. import ucp as ucp_1 +from .types import context as context_1 +from .types import input_correlation, message +from .types import signals as signals_1 +from .types.product import Product as Product_1 +from .types.variant import Variant + + +class CatalogLookup(BaseModel): + """ + Product/variant lookup by identifier capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class LookupVariant(Variant): + """ + Variant with required correlation metadata for lookup responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + inputs: list[input_correlation.InputCorrelation] = Field(..., min_length=1) + """ + Which request identifiers resolved to this variant, and how. Each entry maps a request ID to its match type. + """ + + +class LookupRequest(BaseModel): + """ + Request body for catalog lookup. + """ + + model_config = ConfigDict( + extra="allow", + ) + ids: list[str] = Field(..., min_length=1) + """ + Identifiers to lookup. Implementations MUST support product ID and variant ID; MAY support secondary identifiers (SKU, handle, etc.). + """ + context: context_1.Context | None = None + signals: signals_1.Signals | None = None + + +class Product(Product_1): + model_config = ConfigDict( + extra="allow", + ) + variants: list[LookupVariant] | None = None + + +class LookupResponse(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.ResponseCatalogSchema + products: list[Product] + """ + Products matching the requested identifiers. May contain fewer items if some identifiers not found, or more if identifiers match multiple products. + """ + messages: list[message.Message] | None = None + """ + Errors, warnings, or informational messages about the requested items. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/catalog_search.py b/src/ucp_sdk/models/schemas/shopping/catalog_search.py new file mode 100644 index 0000000..6e65867 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/catalog_search.py @@ -0,0 +1,68 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from .. import ucp as ucp_1 +from .types import context as context_1 +from .types import message +from .types import pagination as pagination_1 +from .types import product, search_filters +from .types import signals as signals_1 + + +class CatalogSearch(BaseModel): + """ + Product catalog search capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class SearchRequest(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + query: str | None = None + """ + Free-text search query. + """ + context: context_1.Context | None = None + signals: signals_1.Signals | None = None + filters: search_filters.SearchFilters | None = None + pagination: pagination_1.Request | None = None + + +class SearchResponse(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + ucp: ucp_1.ResponseCatalogSchema + products: list[product.Product] + """ + Products matching the search criteria. + """ + pagination: pagination_1.Response | None = None + messages: list[message.Message] | None = None + """ + Errors, warnings, or informational messages about the search results. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout.py b/src/ucp_sdk/models/schemas/shopping/checkout.py index e2c95a2..102fdb2 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout.py @@ -18,15 +18,17 @@ from __future__ import annotations -from typing import Literal +from typing import Any, Literal -from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict +from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict, Field from .. import ucp as ucp_1 from . import payment as payment_1 from .types import buyer as buyer_1 from .types import context as context_1 -from .types import line_item, link, message, order_confirmation, total +from .types import line_item, link, message, order_confirmation +from .types import signals as signals_1 +from .types import totals as totals_1 class Checkout(BaseModel): @@ -51,6 +53,11 @@ class Checkout(BaseModel): Representation of the buyer. """ context: context_1.Context | None = None + signals: signals_1.Signals | None = None + risk_signals: dict[str, Any] | None = Field(None, deprecated=True) + """ + Deprecated. Use signals instead. Will be removed in the next version. + """ status: Literal[ "incomplete", "requires_escalation", @@ -66,7 +73,7 @@ class Checkout(BaseModel): """ ISO 4217 currency code reflecting the merchant's market determination. Derived from address, context, and geo IP—buyers provide signals, merchants determine currency. """ - totals: list[total.Total] + totals: totals_1.Totals """ Different cart totals. """ diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py index 84dab14..fdbd1e8 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_complete_request.py @@ -18,9 +18,12 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field from . import payment_complete_request +from .types import signals_complete_request class CheckoutCompleteRequest(BaseModel): @@ -31,4 +34,9 @@ class CheckoutCompleteRequest(BaseModel): model_config = ConfigDict( extra="allow", ) + signals: signals_complete_request.SignalsCompleteRequest | None = None + risk_signals: dict[str, Any] | None = Field(None, deprecated=True) + """ + Deprecated. Use signals instead. Will be removed in the next version. + """ payment: payment_complete_request.PaymentCompleteRequest diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py index 1747b92..e10e36a 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_create_request.py @@ -25,6 +25,7 @@ buyer_create_request, context_create_request, line_item_create_request, + signals_create_request, ) @@ -45,4 +46,5 @@ class CheckoutCreateRequest(BaseModel): Representation of the buyer. """ context: context_create_request.ContextCreateRequest | None = None + signals: signals_create_request.SignalsCreateRequest | None = None payment: payment_create_request.PaymentCreateRequest | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py index 4bc4d90..455c460 100644 --- a/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/checkout_update_request.py @@ -25,6 +25,7 @@ buyer_update_request, context_update_request, line_item_update_request, + signals_update_request, ) @@ -49,4 +50,5 @@ class CheckoutUpdateRequest(BaseModel): Representation of the buyer. """ context: context_update_request.ContextUpdateRequest | None = None + signals: signals_update_request.SignalsUpdateRequest | None = None payment: payment_update_request.PaymentUpdateRequest | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/discount.py b/src/ucp_sdk/models/schemas/shopping/discount/__init__.py similarity index 78% rename from src/ucp_sdk/models/schemas/shopping/discount.py rename to src/ucp_sdk/models/schemas/shopping/discount/__init__.py index 14e429b..8b2f9f3 100644 --- a/src/ucp_sdk/models/schemas/shopping/discount.py +++ b/src/ucp_sdk/models/schemas/shopping/discount/__init__.py @@ -22,7 +22,8 @@ from pydantic import BaseModel, ConfigDict, Field, RootModel -from .checkout import Checkout as Checkout_1 +from ..types import amount as amount_1 +from ..types import reverse_domain_name class DiscountExtension(RootModel[Any]): @@ -31,7 +32,7 @@ class DiscountExtension(RootModel[Any]): ) root: Any = Field(..., title="Discount Extension") """ - Extends Checkout with discount code support, enabling agents to apply promotional, loyalty, referral, and other discount codes. + Extends Cart and Checkout with discount support, including discount codes, automatic discounts, and eligibility-triggered provisional discounts. """ @@ -47,9 +48,9 @@ class Allocation(BaseModel): """ JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals.shipping'). """ - amount: int = Field(..., ge=0) + amount: amount_1.Amount """ - Amount allocated to this target in minor (cents) currency units. + Amount allocated to this target in ISO 4217 minor units. """ @@ -69,9 +70,9 @@ class AppliedDiscount(BaseModel): """ Human-readable discount name (e.g., 'Summer Sale 20% Off'). """ - amount: int = Field(..., ge=0) + amount: amount_1.Amount """ - Total discount amount in minor (cents) currency units. + Total discount amount in ISO 4217 minor units. """ automatic: bool | None = False """ @@ -85,6 +86,14 @@ class AppliedDiscount(BaseModel): """ Stacking order for discount calculation. Lower numbers applied first (1 = first). """ + provisional: bool | None = False + """ + True if this discount requires additional verification. + """ + eligibility: reverse_domain_name.ReverseDomainName | None = None + """ + The eligibility claim accepted by the Business for this discount. Corresponds to a value from context.eligibility. Omitted for code-based and non-eligibility automatic discounts. + """ allocations: list[Allocation] | None = None """ Breakdown of where this discount was allocated. Sum of allocation amounts equals total amount. @@ -107,14 +116,3 @@ class DiscountsObject(BaseModel): """ Discounts successfully applied (code-based and automatic). """ - - -class Checkout(Checkout_1): - """ - Checkout extended with discount capability. - """ - - model_config = ConfigDict( - extra="allow", - ) - discounts: DiscountsObject | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/discount/dev/__init__.py b/src/ucp_sdk/models/schemas/shopping/discount/dev/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/discount/dev/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/__init__.py b/src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/__init__.py new file mode 100644 index 0000000..421dc21 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/__init__.py @@ -0,0 +1,18 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/shopping.py b/src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/shopping.py new file mode 100644 index 0000000..23d4fcf --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/discount/dev/ucp/shopping.py @@ -0,0 +1,47 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict + +from ....cart import Cart as Cart_1 +from ....checkout import Checkout as Checkout_1 +from ... import DiscountsObject + + +class Cart(Cart_1): + """ + Cart extended with discount capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + discounts: DiscountsObject | None = None + + +class Checkout(Checkout_1): + """ + Checkout extended with discount capability. + """ + + model_config = ConfigDict( + extra="allow", + ) + discounts: DiscountsObject | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py index f926b0b..46fba84 100644 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/__init__.py @@ -22,9 +22,8 @@ from pydantic import ConfigDict, Field, RootModel -from ..checkout import Checkout as Checkout_1 -from ..types import fulfillment as fulfillment_1 from ..types import ( + fulfillment, fulfillment_available_method, fulfillment_group, fulfillment_method, @@ -72,22 +71,8 @@ class FulfillmentMethod(RootModel[fulfillment_method.FulfillmentMethod]): root: fulfillment_method.FulfillmentMethod -class Fulfillment(RootModel[fulfillment_1.Fulfillment]): +class Fulfillment(RootModel[fulfillment.Fulfillment]): model_config = ConfigDict( frozen=True, ) - root: fulfillment_1.Fulfillment - - -class Checkout(Checkout_1): - """ - Checkout extended with hierarchical fulfillment. - """ - - model_config = ConfigDict( - extra="allow", - ) - fulfillment: Fulfillment | None = None - """ - Fulfillment details. - """ + root: fulfillment.Fulfillment diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py index 5a804e5..2d58bb8 100644 --- a/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py +++ b/src/ucp_sdk/models/schemas/shopping/fulfillment/dev/ucp/shopping.py @@ -22,9 +22,26 @@ from pydantic import ConfigDict, RootModel +from ....checkout import Checkout as Checkout_1 +from ... import Fulfillment as Fulfillment_1 + class Fulfillment(RootModel[Any]): model_config = ConfigDict( frozen=True, ) root: Any + + +class Checkout(Checkout_1): + """ + Checkout extended with hierarchical fulfillment. + """ + + model_config = ConfigDict( + extra="allow", + ) + fulfillment: Fulfillment_1 | None = None + """ + Fulfillment details. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/order.py b/src/ucp_sdk/models/schemas/shopping/order.py index 2e60bb1..0b3b63d 100644 --- a/src/ucp_sdk/models/schemas/shopping/order.py +++ b/src/ucp_sdk/models/schemas/shopping/order.py @@ -21,13 +21,8 @@ from pydantic import AnyUrl, BaseModel, ConfigDict from .. import ucp as ucp_1 -from .types import ( - adjustment, - expectation, - fulfillment_event, - order_line_item, - total, -) +from .types import adjustment, expectation, fulfillment_event, order_line_item +from .types import totals as totals_1 class PlatformSchema(BaseModel): @@ -95,7 +90,11 @@ class Order(BaseModel): """ Append-only event log of money movements (refunds, returns, credits, disputes, cancellations, etc.) that exist independently of fulfillment. """ - totals: list[total.Total] + currency: str | None = None + """ + ISO 4217 currency code. MUST match the currency from the originating checkout session. + """ + totals: totals_1.Totals """ Different totals for the order. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/__init__.py b/src/ucp_sdk/models/schemas/shopping/types/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/__init__.py +++ b/src/ucp_sdk/models/schemas/shopping/types/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/shopping/types/adjustment.py b/src/ucp_sdk/models/schemas/shopping/types/adjustment.py index f28c0ea..6f27b06 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/adjustment.py +++ b/src/ucp_sdk/models/schemas/shopping/types/adjustment.py @@ -22,6 +22,8 @@ from pydantic import AwareDatetime, BaseModel, ConfigDict, Field +from . import amount as amount_1 + class LineItem(BaseModel): model_config = ConfigDict( @@ -65,9 +67,9 @@ class Adjustment(BaseModel): """ Which line items and quantities are affected (optional). """ - amount: int | None = None + amount: amount_1.Amount | None = None """ - Amount in minor units (cents) for refunds, credits, price adjustments (optional). + Amount in ISO 4217 minor units for refunds, credits, or price adjustments. """ description: str | None = None """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/amount.py b/src/ucp_sdk/models/schemas/shopping/types/amount.py new file mode 100644 index 0000000..ac8c453 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/amount.py @@ -0,0 +1,31 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field, RootModel + + +class Amount(RootModel[int]): + model_config = ConfigDict( + frozen=True, + ) + root: int = Field(..., ge=0, title="Amount") + """ + Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py b/src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py new file mode 100644 index 0000000..44ce88f --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/available_payment_instrument.py @@ -0,0 +1,41 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict + + +class AvailablePaymentInstrument(BaseModel): + """ + An instrument type available from a payment handler with optional constraints. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + The instrument type identifier (e.g., 'card', 'gift_card'). References an instrument schema's type constant. + """ + constraints: dict[str, Any] | None = None + """ + Constraints on this instrument type. Structure depends on instrument type and active capabilities. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py b/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py index e5cffa1..2233292 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py +++ b/src/ucp_sdk/models/schemas/shopping/types/card_payment_instrument.py @@ -20,8 +20,9 @@ from typing import Literal -from pydantic import AnyUrl, BaseModel, ConfigDict +from pydantic import AnyUrl, BaseModel, ConfigDict, Field +from .available_payment_instrument import AvailablePaymentInstrument from .payment_instrument import PaymentInstrument @@ -59,6 +60,28 @@ class Display(BaseModel): """ +class Constraints(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + brands: list[str] | None = Field(None, min_length=1) + """ + Limit to specific card brands (e.g., ['visa', 'mastercard', 'amex']). + """ + + +class AvailableCardPaymentInstrument(AvailablePaymentInstrument): + """ + Declares card instrument availability with card-specific constraints. + """ + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["card"] = "card" + constraints: Constraints | None = None + + class CardPaymentInstrument(PaymentInstrument): """ A basic card payment instrument with visible card details. Can be inherited by a handler's instrument schema to define handler-specific display details or more complex credential structures. diff --git a/src/ucp_sdk/models/schemas/shopping/types/category.py b/src/ucp_sdk/models/schemas/shopping/types/category.py new file mode 100644 index 0000000..128be48 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/category.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class Category(BaseModel): + """ + A product category with optional taxonomy identifier. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: str + """ + Category value or path (e.g., 'Apparel > Shirts', '1604'). + """ + taxonomy: str | None = None + """ + Source taxonomy. Well-known values: `google_product_category`, `shopify`, `merchant`. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context.py b/src/ucp_sdk/models/schemas/shopping/types/context.py index d501c00..0107938 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/context.py +++ b/src/ucp_sdk/models/schemas/shopping/types/context.py @@ -20,10 +20,12 @@ from pydantic import BaseModel, ConfigDict +from . import reverse_domain_name + class Context(BaseModel): """ - Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. Context can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. Platforms SHOULD progressively enhance context throughout the buyer journey. + Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. """ model_config = ConfigDict( @@ -41,3 +43,19 @@ class Context(BaseModel): """ The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + language: str | None = None + """ + Preferred language for content. Use IETF BCP 47 language tags (e.g., 'en', 'fr-CA', 'zh-Hans'). For REST, equivalent to Accept-Language header—platforms SHOULD fall back to Accept-Language when this field is absent; when provided, overrides Accept-Language. Businesses MAY return content in a different language if unavailable. + """ + currency: str | None = None + """ + Preferred currency (ISO 4217, e.g., 'EUR', 'USD'). Businesses determine presentment currency from context and authoritative signals; this hint MAY inform selection in multi-currency markets. Also serves as the denomination for price filter values — platforms SHOULD include this field when sending price filters. Response prices include explicit currency confirming the resolution. + """ + eligibility: list[reverse_domain_name.ReverseDomainName] | None = None + """ + Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py index 64035b4..6c86b1a 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/context_create_request.py @@ -20,10 +20,12 @@ from pydantic import BaseModel, ConfigDict +from . import reverse_domain_name_create_request + class ContextCreateRequest(BaseModel): """ - Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. Context can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. Platforms SHOULD progressively enhance context throughout the buyer journey. + Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. """ model_config = ConfigDict( @@ -41,3 +43,22 @@ class ContextCreateRequest(BaseModel): """ The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + language: str | None = None + """ + Preferred language for content. Use IETF BCP 47 language tags (e.g., 'en', 'fr-CA', 'zh-Hans'). For REST, equivalent to Accept-Language header—platforms SHOULD fall back to Accept-Language when this field is absent; when provided, overrides Accept-Language. Businesses MAY return content in a different language if unavailable. + """ + currency: str | None = None + """ + Preferred currency (ISO 4217, e.g., 'EUR', 'USD'). Businesses determine presentment currency from context and authoritative signals; this hint MAY inform selection in multi-currency markets. Also serves as the denomination for price filter values — platforms SHOULD include this field when sending price filters. Response prices include explicit currency confirming the resolution. + """ + eligibility: ( + list[reverse_domain_name_create_request.ReverseDomainNameCreateRequest] + | None + ) = None + """ + Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py index 77ee792..79610e9 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/context_update_request.py @@ -20,10 +20,12 @@ from pydantic import BaseModel, ConfigDict +from . import reverse_domain_name_update_request + class ContextUpdateRequest(BaseModel): """ - Provisional buyer signals for relevance and localization: product availability, pricing, currency, tax, shipping, payment methods, and eligibility (e.g., student or affiliation discounts). Businesses SHOULD use these values when authoritative data (e.g., address) is absent, and MAY ignore unsupported values without returning errors. Context can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. Platforms SHOULD progressively enhance context throughout the buyer journey. + Provisional buyer signals for relevance and localization—not authoritative data. Businesses SHOULD use these values when verified inputs (e.g., shipping address) are absent, and MAY ignore or down-rank them if inconsistent with higher-confidence signals (authenticated account, risk detection) or regulatory constraints (export controls). Eligibility and policy enforcement MUST occur at checkout time using binding transaction data. Context SHOULD be non-identifying and can be disclosed progressively—coarse signals early, finer resolution as the session progresses. Higher-resolution data (shipping address, billing address) supersedes context. """ model_config = ConfigDict( @@ -41,3 +43,22 @@ class ContextUpdateRequest(BaseModel): """ The postal code. For example, 94043. Optional hint for regional refinement—higher-resolution data (e.g., shipping address) supersedes this value. """ + intent: str | None = None + """ + Background context describing buyer's intent (e.g., 'looking for a gift under $50', 'need something durable for outdoor use'). Informs relevance, recommendations, and personalization. + """ + language: str | None = None + """ + Preferred language for content. Use IETF BCP 47 language tags (e.g., 'en', 'fr-CA', 'zh-Hans'). For REST, equivalent to Accept-Language header—platforms SHOULD fall back to Accept-Language when this field is absent; when provided, overrides Accept-Language. Businesses MAY return content in a different language if unavailable. + """ + currency: str | None = None + """ + Preferred currency (ISO 4217, e.g., 'EUR', 'USD'). Businesses determine presentment currency from context and authoritative signals; this hint MAY inform selection in multi-currency markets. Also serves as the denomination for price filter values — platforms SHOULD include this field when sending price filters. Response prices include explicit currency confirming the resolution. + """ + eligibility: ( + list[reverse_domain_name_update_request.ReverseDomainNameUpdateRequest] + | None + ) = None + """ + Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/description.py b/src/ucp_sdk/models/schemas/shopping/types/description.py new file mode 100644 index 0000000..18f6582 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/description.py @@ -0,0 +1,43 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class Description(BaseModel): + """ + Description content in one or more formats. At least one format must be provided. + """ + + model_config = ConfigDict( + extra="allow", + ) + plain: str | None = None + """ + Plain text content. + """ + html: str | None = None + """ + HTML-formatted content. Security: Platforms MUST sanitize before rendering—strip scripts, event handlers, and untrusted elements. Treat all rich text as untrusted input. + """ + markdown: str | None = None + """ + Markdown-formatted content. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_code.py b/src/ucp_sdk/models/schemas/shopping/types/error_code.py new file mode 100644 index 0000000..ac5ed8a --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/error_code.py @@ -0,0 +1,41 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field, RootModel + + +class ErrorCode(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field( + ..., + examples=[ + "out_of_stock", + "item_unavailable", + "address_undeliverable", + "payment_failed", + "eligibility_invalid", + ], + title="Error Code", + ) + """ + Error code identifying the type of error. Standard errors are defined in specification (see examples), and have standardized semantics; freeform codes are permitted. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/error_response.py b/src/ucp_sdk/models/schemas/shopping/types/error_response.py new file mode 100644 index 0000000..d6d4c60 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/error_response.py @@ -0,0 +1,59 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Literal + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field + +from ...ucp import Base +from . import message + + +class Ucp(Base): + """ + UCP protocol metadata. Status MUST be 'error' for error response. + """ + + model_config = ConfigDict( + extra="allow", + ) + status: Literal["error"] + + +class ErrorResponse(BaseModel): + """ + Generic error response when business logic prevents resource creation or failed to retrieve resource. Used when no valid resource can be established. + """ + + model_config = ConfigDict( + extra="allow", + ) + ucp: Ucp + """ + UCP protocol metadata. Status MUST be 'error' for error response. + """ + messages: list[message.Message] = Field(..., min_length=1) + """ + Array of messages describing why the operation failed. + """ + continue_url: AnyUrl | None = None + """ + URL for buyer handoff or session recovery. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py index e86694b..33b953f 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/fulfillment_method_update_request.py @@ -18,6 +18,8 @@ from __future__ import annotations +from typing import Literal + from pydantic import BaseModel, ConfigDict from . import ( @@ -38,6 +40,10 @@ class FulfillmentMethodUpdateRequest(BaseModel): """ Unique fulfillment method identifier. """ + type: Literal["shipping", "pickup"] + """ + Fulfillment method type. + """ line_item_ids: list[str] """ Line item IDs fulfilled via this method. diff --git a/src/ucp_sdk/models/schemas/shopping/types/input_correlation.py b/src/ucp_sdk/models/schemas/shopping/types/input_correlation.py new file mode 100644 index 0000000..d4707ea --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/input_correlation.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class InputCorrelation(BaseModel): + """ + Maps a request identifier to the variant it resolved to, with match semantics. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + The identifier from the lookup request that resolved to this variant. + """ + match: str | None = Field(None, examples=["exact", "featured"]) + """ + How the request identifier resolved to this variant. Well-known values: `exact` (input directly identifies this variant, e.g., variant ID, SKU), `featured` (server selected this variant as representative, e.g., product ID resolved to best match). Businesses MAY implement and provide additional resolution strategies. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item.py b/src/ucp_sdk/models/schemas/shopping/types/item.py index e2b1954..cfcc71c 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item.py @@ -18,7 +18,9 @@ from __future__ import annotations -from pydantic import AnyUrl, BaseModel, ConfigDict, Field +from pydantic import AnyUrl, BaseModel, ConfigDict + +from . import amount class Item(BaseModel): @@ -27,15 +29,15 @@ class Item(BaseModel): ) id: str """ - Should be recognized by both the Platform, and the Business. For Google it should match the id provided in the "id" field in the product feed. + The product identifier, often the SKU, required to resolve the product details associated with this line item. Should be recognized by both the Platform, and the Business. """ title: str """ Product title. """ - price: int = Field(..., ge=0) + price: amount.Amount """ - Unit price in minor (cents) currency units. + Unit price in ISO 4217 minor units. """ image_url: AnyUrl | None = None """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py index fd3f850..bbef8e8 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item_create_request.py @@ -27,5 +27,5 @@ class ItemCreateRequest(BaseModel): ) id: str """ - Should be recognized by both the Platform, and the Business. For Google it should match the id provided in the "id" field in the product feed. + The product identifier, often the SKU, required to resolve the product details associated with this line item. Should be recognized by both the Platform, and the Business. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py index 735b984..c957d16 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/item_update_request.py @@ -27,5 +27,5 @@ class ItemUpdateRequest(BaseModel): ) id: str """ - Should be recognized by both the Platform, and the Business. For Google it should match the id provided in the "id" field in the product feed. + The product identifier, often the SKU, required to resolve the product details associated with this line item. Should be recognized by both the Platform, and the Business. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/media.py b/src/ucp_sdk/models/schemas/shopping/types/media.py new file mode 100644 index 0000000..dc8338a --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/media.py @@ -0,0 +1,51 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field + + +class Media(BaseModel): + """ + Product media item (image, video, etc.). + """ + + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + Media type. Well-known values: `image`, `video`, `model_3d`. + """ + url: AnyUrl + """ + URL to the media resource. + """ + alt_text: str | None = None + """ + Accessibility text describing the media. + """ + width: int | None = Field(None, ge=1) + """ + Width in pixels (for images/video). + """ + height: int | None = Field(None, ge=1) + """ + Height in pixels (for images/video). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_error.py b/src/ucp_sdk/models/schemas/shopping/types/message_error.py index d1e6adb..e74117c 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_error.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_error.py @@ -22,6 +22,8 @@ from pydantic import BaseModel, ConfigDict +from . import error_code + class MessageError(BaseModel): model_config = ConfigDict( @@ -31,10 +33,7 @@ class MessageError(BaseModel): """ Message type discriminator. """ - code: str - """ - Error code. Possible values include: missing, invalid, out_of_stock, payment_declined, requires_sign_in, requires_3ds, requires_identity_linking. Freeform codes also allowed. - """ + code: error_code.ErrorCode path: str | None = None """ RFC 9535 JSONPath to the component the message refers to (e.g., $.items[1]). @@ -48,8 +47,11 @@ class MessageError(BaseModel): Human-readable message. """ severity: Literal[ - "recoverable", "requires_buyer_input", "requires_buyer_review" + "recoverable", + "requires_buyer_input", + "requires_buyer_review", + "unrecoverable", ] """ - Declares who resolves this error. 'recoverable': agent can fix via API. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules (checkout complete). Errors with 'requires_*' severity contribute to 'status: requires_escalation'. + Reflects the resource state and recommended action. 'recoverable': platform can resolve by modifying inputs and retrying via API. 'requires_buyer_input': merchant requires information their API doesn't support collecting programmatically (checkout incomplete). 'requires_buyer_review': buyer must authorize before order placement due to policy, regulatory, or entitlement rules. 'unrecoverable': no valid resource exists to act on, retry with new resource or inputs. Errors with 'requires_*' severity contribute to 'status: requires_escalation'. """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/message_warning.py b/src/ucp_sdk/models/schemas/shopping/types/message_warning.py index f5eee46..7025558 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/message_warning.py +++ b/src/ucp_sdk/models/schemas/shopping/types/message_warning.py @@ -20,7 +20,7 @@ from typing import Literal -from pydantic import BaseModel, ConfigDict +from pydantic import AnyUrl, BaseModel, ConfigDict class MessageWarning(BaseModel): @@ -47,3 +47,15 @@ class MessageWarning(BaseModel): """ Content format, default = plain. """ + presentation: str | None = "notice" + """ + Rendering contract for this warning. 'notice' (default): platform MUST display, MAY dismiss. 'disclosure': platform MUST display in proximity to the path-referenced component, MUST NOT hide or auto-dismiss. See specification for full contract. + """ + image_url: AnyUrl | None = None + """ + URL to a required visual element (e.g., warning symbol, energy class label). + """ + url: AnyUrl | None = None + """ + Reference URL for more information (e.g., regulatory site, registry entry, policy page). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/option_value.py b/src/ucp_sdk/models/schemas/shopping/types/option_value.py new file mode 100644 index 0000000..441a743 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/option_value.py @@ -0,0 +1,35 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class OptionValue(BaseModel): + """ + A selectable value for a product option. + """ + + model_config = ConfigDict( + extra="allow", + ) + label: str + """ + Display text for this option value (e.g., 'Small', 'Blue'). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/pagination.py b/src/ucp_sdk/models/schemas/shopping/types/pagination.py new file mode 100644 index 0000000..ca1b307 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/pagination.py @@ -0,0 +1,71 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class Pagination(BaseModel): + """ + Cursor-based pagination for list operations. + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class Request(BaseModel): + """ + Pagination parameters for requests. + """ + + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + Opaque cursor from previous response. + """ + limit: int | None = Field(10, ge=1) + """ + Requested page size. Implementations MAY clamp to a lower maximum. + """ + + +class Response(BaseModel): + """ + Pagination information in responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + Cursor to fetch the next page of results. MUST be present when has_next_page is true. + """ + has_next_page: bool + """ + Whether more results are available. + """ + total_count: int | None = Field(None, ge=0) + """ + Total number of matching items, if available. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/price.py b/src/ucp_sdk/models/schemas/shopping/types/price.py new file mode 100644 index 0000000..aff26a1 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/price.py @@ -0,0 +1,41 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from . import amount as amount_1 + + +class Price(BaseModel): + """ + Price with explicit currency. + """ + + model_config = ConfigDict( + extra="allow", + ) + amount: amount_1.Amount + """ + Amount in ISO 4217 minor units. Use 0 for free items. + """ + currency: str = Field(..., pattern="^[A-Z]{3}$") + """ + ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP'). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/price_filter.py b/src/ucp_sdk/models/schemas/shopping/types/price_filter.py new file mode 100644 index 0000000..7772e95 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/price_filter.py @@ -0,0 +1,41 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from . import amount + + +class PriceFilter(BaseModel): + """ + Price range filter denominated in context.currency. When context.currency matches the presentment currency, businesses apply the filter directly. When it differs, businesses SHOULD convert filter values to the presentment currency before applying; if conversion is not supported, businesses MAY ignore the filter and SHOULD indicate this via a message. When context.currency is absent, filter denomination is ambiguous and businesses MAY ignore it. + """ + + model_config = ConfigDict( + extra="allow", + ) + min: amount.Amount | None = None + """ + Minimum price in ISO 4217 minor units. + """ + max: amount.Amount | None = None + """ + Maximum price in ISO 4217 minor units. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/price_range.py b/src/ucp_sdk/models/schemas/shopping/types/price_range.py new file mode 100644 index 0000000..d3129db --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/price_range.py @@ -0,0 +1,41 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from . import price + + +class PriceRange(BaseModel): + """ + A price range representing minimum and maximum values (e.g., across product variants). + """ + + model_config = ConfigDict( + extra="allow", + ) + min: price.Price + """ + Minimum price in the range. + """ + max: price.Price + """ + Maximum price in the range. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/product.py b/src/ucp_sdk/models/schemas/shopping/types/product.py new file mode 100644 index 0000000..6fa39d0 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/product.py @@ -0,0 +1,97 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field + +from . import category +from . import description as description_1 +from . import media as media_1 +from . import price_range as price_range_1 +from . import product_option +from . import rating as rating_1 +from . import variant + + +class Product(BaseModel): + """ + A product in the catalog with variants and options. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Global ID (GID) uniquely identifying this product. + """ + handle: str | None = None + """ + URL-safe slug for SEO-friendly URLs (e.g., 'blue-runner-pro'). Use id for stable API references. + """ + title: str + """ + Product title. + """ + description: description_1.Description + """ + Product description in one or more formats. + """ + url: AnyUrl | None = None + """ + Canonical product page URL. + """ + categories: list[category.Category] | None = None + """ + Product categories with optional taxonomy identifiers. + """ + price_range: price_range_1.PriceRange + """ + Price range across all variants. + """ + list_price_range: price_range_1.PriceRange | None = None + """ + List price range before discounts (for strikethrough display). + """ + media: list[media_1.Media] | None = None + """ + Product media (images, videos, 3D models). First item is the featured media for listings. + """ + options: list[product_option.ProductOption] | None = None + """ + Product options (Size, Color, etc.). + """ + variants: list[variant.Variant] = Field(..., min_length=1) + """ + Purchasable variants of this product. First item is the featured variant for listings. + """ + rating: rating_1.Rating | None = None + """ + Aggregate product rating. + """ + tags: list[str] | None = None + """ + Product tags for categorization and search. + """ + metadata: dict[str, Any] | None = None + """ + Business-defined custom data extending the standard product model. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/product_option.py b/src/ucp_sdk/models/schemas/shopping/types/product_option.py new file mode 100644 index 0000000..297717e --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/product_option.py @@ -0,0 +1,41 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from . import option_value + + +class ProductOption(BaseModel): + """ + A product option such as size, color, or material. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Option name (e.g., 'Size', 'Color'). + """ + values: list[option_value.OptionValue] = Field(..., min_length=1) + """ + Available values for this option. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/rating.py b/src/ucp_sdk/models/schemas/shopping/types/rating.py new file mode 100644 index 0000000..79388fd --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/rating.py @@ -0,0 +1,47 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class Rating(BaseModel): + """ + Product rating aggregate. + """ + + model_config = ConfigDict( + extra="allow", + ) + value: float = Field(..., ge=0.0) + """ + Average rating value. + """ + scale_min: float | None = Field(1, ge=0.0) + """ + Minimum value on the rating scale (e.g., 1 for 1-5 stars). + """ + scale_max: float = Field(..., ge=1.0) + """ + Maximum value on the rating scale (e.g., 5 for 5-star). + """ + count: int | None = Field(None, ge=0) + """ + Number of reviews contributing to the rating. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py b/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py new file mode 100644 index 0000000..7bb3f46 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name.py @@ -0,0 +1,35 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field, RootModel + + +class ReverseDomainName(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field( + ..., + pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + title="Reverse Domain Name", + ) + """ + Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py new file mode 100644 index 0000000..d748408 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_create_request.py @@ -0,0 +1,35 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field, RootModel + + +class ReverseDomainNameCreateRequest(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field( + ..., + pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + title="Reverse Domain Name Create Request", + ) + """ + Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py new file mode 100644 index 0000000..8f95bf4 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/reverse_domain_name_update_request.py @@ -0,0 +1,35 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field, RootModel + + +class ReverseDomainNameUpdateRequest(RootModel[str]): + model_config = ConfigDict( + frozen=True, + ) + root: str = Field( + ..., + pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + title="Reverse Domain Name Update Request", + ) + """ + Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold'). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/search_filters.py b/src/ucp_sdk/models/schemas/shopping/types/search_filters.py new file mode 100644 index 0000000..6b35cd2 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/search_filters.py @@ -0,0 +1,38 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from . import price_filter + + +class SearchFilters(BaseModel): + """ + Filter criteria to narrow search results. All specified filters combine with AND logic. + """ + + model_config = ConfigDict( + extra="allow", + ) + categories: list[str] | None = None + """ + Filter by product categories (OR logic — matches products in any listed categories). Values match against the value field in product category entries. Valid values can be discovered from the categories field in search results, merchant documentation, or standard taxonomies that businesses may align with. + """ + price: price_filter.PriceFilter | None = None diff --git a/src/ucp_sdk/models/schemas/shopping/types/selected_option.py b/src/ucp_sdk/models/schemas/shopping/types/selected_option.py new file mode 100644 index 0000000..6e04ad0 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/selected_option.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class SelectedOption(BaseModel): + """ + A specific option selection on a variant (e.g., Size: Large). + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Option name (e.g., 'Size'). + """ + label: str + """ + Selected option label (e.g., 'Large'). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals.py b/src/ucp_sdk/models/schemas/shopping/types/signals.py new file mode 100644 index 0000000..d80f329 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signals.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class Signals(BaseModel): + """ + Environment data provided by the platform to support authorization and abuse prevention. Values MUST NOT be buyer-asserted claims — platforms provide signals based on direct observation or independently verifiable third-party attestations. All signal keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace. + """ + + model_config = ConfigDict( + extra="allow", + ) + dev_ucp_buyer_ip: str | None = Field(None, alias="dev.ucp.buyer_ip") + """ + Client's IP address (IPv4 or IPv6). + """ + dev_ucp_user_agent: str | None = Field(None, alias="dev.ucp.user_agent") + """ + Client's HTTP User-Agent header or equivalent. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py b/src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py new file mode 100644 index 0000000..436b901 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signals_complete_request.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class SignalsCompleteRequest(BaseModel): + """ + Environment data provided by the platform to support authorization and abuse prevention. Values MUST NOT be buyer-asserted claims — platforms provide signals based on direct observation or independently verifiable third-party attestations. All signal keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace. + """ + + model_config = ConfigDict( + extra="allow", + ) + dev_ucp_buyer_ip: str | None = Field(None, alias="dev.ucp.buyer_ip") + """ + Client's IP address (IPv4 or IPv6). + """ + dev_ucp_user_agent: str | None = Field(None, alias="dev.ucp.user_agent") + """ + Client's HTTP User-Agent header or equivalent. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py new file mode 100644 index 0000000..fd1630d --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signals_create_request.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class SignalsCreateRequest(BaseModel): + """ + Environment data provided by the platform to support authorization and abuse prevention. Values MUST NOT be buyer-asserted claims — platforms provide signals based on direct observation or independently verifiable third-party attestations. All signal keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace. + """ + + model_config = ConfigDict( + extra="allow", + ) + dev_ucp_buyer_ip: str | None = Field(None, alias="dev.ucp.buyer_ip") + """ + Client's IP address (IPv4 or IPv6). + """ + dev_ucp_user_agent: str | None = Field(None, alias="dev.ucp.user_agent") + """ + Client's HTTP User-Agent header or equivalent. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py new file mode 100644 index 0000000..09ea148 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signals_update_request.py @@ -0,0 +1,39 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class SignalsUpdateRequest(BaseModel): + """ + Environment data provided by the platform to support authorization and abuse prevention. Values MUST NOT be buyer-asserted claims — platforms provide signals based on direct observation or independently verifiable third-party attestations. All signal keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace. + """ + + model_config = ConfigDict( + extra="allow", + ) + dev_ucp_buyer_ip: str | None = Field(None, alias="dev.ucp.buyer_ip") + """ + Client's IP address (IPv4 or IPv6). + """ + dev_ucp_user_agent: str | None = Field(None, alias="dev.ucp.user_agent") + """ + Client's HTTP User-Agent header or equivalent. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/signed_amount.py b/src/ucp_sdk/models/schemas/shopping/types/signed_amount.py new file mode 100644 index 0000000..3ee19e1 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/signed_amount.py @@ -0,0 +1,31 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import ConfigDict, Field, RootModel + + +class SignedAmount(RootModel[int]): + model_config = ConfigDict( + frozen=True, + ) + root: int = Field(..., title="Signed Amount") + """ + Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). May be negative — the sign is intrinsic to the value (e.g., discounts are negative, charges are positive). + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/total.py b/src/ucp_sdk/models/schemas/shopping/types/total.py index 623c2dd..41c9ccc 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/total.py +++ b/src/ucp_sdk/models/schemas/shopping/types/total.py @@ -18,32 +18,25 @@ from __future__ import annotations -from typing import Literal +from pydantic import BaseModel, ConfigDict -from pydantic import BaseModel, ConfigDict, Field +from . import amount as amount_1 class Total(BaseModel): + """ + A cost breakdown entry with a category, amount, and optional display text. + """ + model_config = ConfigDict( extra="allow", ) - type: Literal[ - "items_discount", - "subtotal", - "discount", - "fulfillment", - "tax", - "fee", - "total", - ] + type: str """ - Type of total categorization. + Cost category. Well-known values: subtotal, items_discount, discount, fulfillment, tax, fee, total. Businesses MAY use additional values. """ display_text: str | None = None """ Text to display against the amount. Should reflect appropriate method (e.g., 'Shipping', 'Delivery'). """ - amount: int = Field(..., ge=0) - """ - If type == total, sums subtotal - discount + fulfillment + tax + fee. Should be >= 0. Amount in minor (cents) currency units. - """ + amount: amount_1.Amount diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py b/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py index 6318099..e3eb186 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/total_create_request.py @@ -22,6 +22,10 @@ class TotalCreateRequest(BaseModel): + """ + A cost breakdown entry with a category, amount, and optional display text. + """ + model_config = ConfigDict( extra="allow", ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py b/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py index e8ca037..0f58f46 100644 --- a/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py +++ b/src/ucp_sdk/models/schemas/shopping/types/total_update_request.py @@ -22,6 +22,10 @@ class TotalUpdateRequest(BaseModel): + """ + A cost breakdown entry with a category, amount, and optional display text. + """ + model_config = ConfigDict( extra="allow", ) diff --git a/src/ucp_sdk/models/schemas/shopping/types/totals.py b/src/ucp_sdk/models/schemas/shopping/types/totals.py new file mode 100644 index 0000000..6e1d7c7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/totals.py @@ -0,0 +1,64 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field, RootModel + +from . import signed_amount +from .total import Total as Total_1 + + +class Line(BaseModel): + """ + Sub-line entry. Additional metadata MAY be included. + """ + + model_config = ConfigDict( + extra="allow", + ) + display_text: str + """ + Human-readable label for this sub-line. + """ + amount: signed_amount.SignedAmount + + +class Total(Total_1): + model_config = ConfigDict( + extra="allow", + ) + amount: signed_amount.SignedAmount | None = None + lines: list[Line] | None = None + """ + Optional itemized breakdown. The parent entry is always rendered; lines are supplementary. Sum of line amounts MUST equal the parent entry amount. + """ + + +class Totals(RootModel[list[Total]]): + """ + Pricing breakdown provided by the business. MUST contain exactly one subtotal and one total entry. Detail types (tax, fee, discount, fulfillment) may appear multiple times for itemization. Platforms MUST render all entries in order using display_text and amount. + """ + + model_config = ConfigDict( + frozen=True, + ) + root: list[Total] = Field(..., title="Totals") + """ + Pricing breakdown provided by the business. MUST contain exactly one subtotal and one total entry. Detail types (tax, fee, discount, fulfillment) may appear multiple times for itemization. Platforms MUST render all entries in order using display_text and amount. + """ diff --git a/src/ucp_sdk/models/schemas/shopping/types/variant.py b/src/ucp_sdk/models/schemas/shopping/types/variant.py new file mode 100644 index 0000000..96e55a7 --- /dev/null +++ b/src/ucp_sdk/models/schemas/shopping/types/variant.py @@ -0,0 +1,226 @@ +# 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. + +# generated by datamodel-codegen +# pylint: disable=all +# pyformat: disable + +from __future__ import annotations + +from typing import Any + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field + +from . import amount as amount_1 +from . import category +from . import description as description_1 +from . import link +from . import media as media_1 +from . import price as price_1 +from . import rating as rating_1 +from . import selected_option + + +class Barcode(BaseModel): + model_config = ConfigDict( + extra="allow", + ) + type: str + """ + Barcode standard. Well-known values: UPC, EAN, ISBN, GTIN, JAN. + """ + value: str + """ + Barcode value. + """ + + +class Measure(BaseModel): + """ + Product quantity in packaging (e.g., 750ml bottle). + """ + + model_config = ConfigDict( + extra="allow", + ) + value: float + """ + Package quantity. + """ + unit: str + """ + Unit of measurement. + """ + + +class Reference(BaseModel): + """ + Denominator for unit price display (e.g., per 100ml, per 1kg). + """ + + model_config = ConfigDict( + extra="allow", + ) + value: int + """ + Reference quantity. + """ + unit: str + """ + Unit of measurement. + """ + + +class UnitPrice(BaseModel): + """ + Price per standard unit of measurement. MAY be omitted when unit pricing does not apply. + """ + + model_config = ConfigDict( + extra="allow", + ) + amount: amount_1.Amount + """ + Unit price in ISO 4217 minor units. Business MUST return precomputed unit price value: (variant.price / measure.value) * reference.value. + """ + currency: str = Field(..., pattern="^[A-Z]{3}$") + """ + ISO 4217 currency code. + """ + measure: Measure + """ + Product quantity in packaging (e.g., 750ml bottle). + """ + reference: Reference + """ + Denominator for unit price display (e.g., per 100ml, per 1kg). + """ + + +class Availability(BaseModel): + """ + Variant availability for purchase. + """ + + model_config = ConfigDict( + extra="allow", + ) + available: bool | None = None + """ + Whether this variant can be purchased. See status for fulfillment details. + """ + status: str | None = None + """ + Qualifies available with fulfillment state. Well-known values: `in_stock`, `backorder`, `preorder`, `out_of_stock`, `discontinued`. + """ + + +class Seller(BaseModel): + """ + Optional seller context for this variant. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + Seller display name. + """ + links: list[link.Link] | None = None + """ + Seller policy and information links. + """ + + +class Variant(BaseModel): + """ + A purchasable variant of a product with specific option selections. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: str + """ + Global ID (GID) uniquely identifying this variant. Used as item.id in checkout. + """ + sku: str | None = None + """ + Business-assigned identifier for inventory and fulfillment. + """ + barcodes: list[Barcode] | None = None + """ + Industry-standard product identifiers for cross-reference and correlation. + """ + handle: str | None = None + """ + URL-safe variant handle/slug. + """ + title: str + """ + Variant display title (e.g., 'Blue / Large'). + """ + description: description_1.Description + """ + Variant description in one or more formats. + """ + url: AnyUrl | None = None + """ + Canonical variant page URL. + """ + categories: list[category.Category] | None = None + """ + Variant categories with optional taxonomy identifiers. + """ + price: price_1.Price + """ + Current selling price. + """ + list_price: price_1.Price | None = None + """ + List price before discounts (for strikethrough display). + """ + unit_price: UnitPrice | None = None + """ + Price per standard unit of measurement. MAY be omitted when unit pricing does not apply. + """ + availability: Availability | None = None + """ + Variant availability for purchase. + """ + selected_options: list[selected_option.SelectedOption] | None = None + """ + Option selections that define this variant. + """ + media: list[media_1.Media] | None = None + """ + Variant media (images, videos, 3D models). First item is the featured media for listings. + """ + rating: rating_1.Rating | None = None + """ + Variant rating. + """ + tags: list[str] | None = None + """ + Variant tags for categorization and search. + """ + metadata: dict[str, Any] | None = None + """ + Business-defined custom data extending the standard variant model. + """ + seller: Seller | None = None + """ + Optional seller context for this variant. + """ diff --git a/src/ucp_sdk/models/schemas/transports/__init__.py b/src/ucp_sdk/models/schemas/transports/__init__.py index 1252d6b..421dc21 100644 --- a/src/ucp_sdk/models/schemas/transports/__init__.py +++ b/src/ucp_sdk/models/schemas/transports/__init__.py @@ -15,3 +15,4 @@ # generated by datamodel-codegen # pylint: disable=all # pyformat: disable + diff --git a/src/ucp_sdk/models/schemas/transports/embedded_config.py b/src/ucp_sdk/models/schemas/transports/embedded_config.py index 8f3cba7..5d998f0 100644 --- a/src/ucp_sdk/models/schemas/transports/embedded_config.py +++ b/src/ucp_sdk/models/schemas/transports/embedded_config.py @@ -18,6 +18,8 @@ from __future__ import annotations +from typing import Literal + from pydantic import BaseModel, ConfigDict @@ -33,3 +35,7 @@ class EmbeddedTransportConfig(BaseModel): """ Delegations the business allows. At service-level, declares available delegations. In checkout responses, confirms accepted delegations for this session. """ + color_scheme: list[Literal["light", "dark"]] | None = None + """ + Color schemes the business supports. Hosts use ec_color_scheme query parameter to request a scheme from this list. + """ diff --git a/src/ucp_sdk/models/schemas/ucp.py b/src/ucp_sdk/models/schemas/ucp.py index 9f98a49..cbc101d 100644 --- a/src/ucp_sdk/models/schemas/ucp.py +++ b/src/ucp_sdk/models/schemas/ucp.py @@ -18,11 +18,12 @@ from __future__ import annotations -from typing import Any +from typing import Any, Literal from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel from . import capability, payment_handler, service +from .shopping.types import reverse_domain_name class Version(RootModel[str]): @@ -35,13 +36,41 @@ class Version(RootModel[str]): """ -class ReverseDomainName(RootModel[str]): +class VersionConstraint(BaseModel): + """ + Version range requirement with minimum and optional maximum. + """ + model_config = ConfigDict( - frozen=True, + extra="allow", + ) + min: Version + """ + Minimum required version (inclusive). + """ + max: Version | None = None + """ + Maximum compatible version (inclusive). When absent, no upper bound. + """ + + +class Requires(BaseModel): + """ + Version requirements for extension schemas. Declares minimum (and optionally maximum) protocol and capability versions needed for correct operation. + """ + + model_config = ConfigDict( + extra="allow", ) - root: str = Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") + protocol: VersionConstraint | None = None + """ + Required protocol version. + """ + capabilities: ( + dict[reverse_domain_name.ReverseDomainName, VersionConstraint] | None + ) = None """ - Reverse-domain identifier (e.g., com.google.pay, dev.ucp.shopping.checkout) + Required capability versions, keyed by capability name. Keys must be a subset of the extension's $defs keys. """ @@ -75,13 +104,6 @@ class Entity(BaseModel): """ -class ResponseCartSchema(RootModel[Any]): - model_config = ConfigDict( - frozen=True, - ) - root: Any - - class Base(BaseModel): """ Base UCP metadata with shared properties for all schema types. @@ -91,16 +113,26 @@ class Base(BaseModel): extra="allow", ) version: Version - services: dict[ReverseDomainName, list[service.Base]] | None = None + status: Literal["success", "error"] | None = "success" + """ + Application-level status of the UCP operation. + """ + services: ( + dict[reverse_domain_name.ReverseDomainName, list[service.Base]] | None + ) = None """ Service registry keyed by reverse-domain name. """ - capabilities: dict[ReverseDomainName, list[capability.Base]] | None = None + capabilities: ( + dict[reverse_domain_name.ReverseDomainName, list[capability.Base]] + | None + ) = None """ Capability registry keyed by reverse-domain name. """ payment_handlers: ( - dict[ReverseDomainName, list[payment_handler.Base]] | None + dict[reverse_domain_name.ReverseDomainName, list[payment_handler.Base]] + | None ) = None """ Payment handler registry keyed by reverse-domain name. @@ -115,18 +147,25 @@ class PlatformSchema(Base): model_config = ConfigDict( extra="allow", ) - services: dict[ReverseDomainName, list[service.PlatformSchema3]] + services: dict[ + reverse_domain_name.ReverseDomainName, list[service.PlatformSchema4] + ] """ Service registry keyed by reverse-domain name. """ capabilities: ( - dict[ReverseDomainName, list[capability.PlatformSchema]] | None + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.PlatformSchema], + ] + | None ) = None """ Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - ReverseDomainName, list[payment_handler.PlatformSchema] + reverse_domain_name.ReverseDomainName, + list[payment_handler.PlatformSchema], ] """ Payment handler registry keyed by reverse-domain name. @@ -141,18 +180,29 @@ class BusinessSchema(Base): model_config = ConfigDict( extra="allow", ) - services: dict[ReverseDomainName, list[service.BusinessSchema2]] + supported_versions: dict[Version, AnyUrl] | None = None + """ + Previous protocol versions this business supports, mapped to profile URIs. Businesses that support older protocol versions SHOULD advertise each version and link to its profile. Each URI points to a complete, self-contained profile for that version. When omitted, only `version` is supported. + """ + services: dict[ + reverse_domain_name.ReverseDomainName, list[service.BusinessSchema3] + ] """ Service registry keyed by reverse-domain name. """ capabilities: ( - dict[ReverseDomainName, list[capability.BusinessSchema]] | None + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.BusinessSchema], + ] + | None ) = None """ Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - ReverseDomainName, list[payment_handler.BusinessSchema] + reverse_domain_name.ReverseDomainName, + list[payment_handler.BusinessSchema], ] """ Payment handler registry keyed by reverse-domain name. @@ -167,20 +217,28 @@ class ResponseCheckoutSchema(Base): model_config = ConfigDict( extra="allow", ) - services: dict[ReverseDomainName, list[service.ResponseSchema2]] | None = ( - None - ) + services: ( + dict[ + reverse_domain_name.ReverseDomainName, list[service.ResponseSchema2] + ] + | None + ) = None """ Service registry keyed by reverse-domain name. """ capabilities: ( - dict[ReverseDomainName, list[capability.ResponseSchema]] | None + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.ResponseSchema], + ] + | None ) = None """ Capability registry keyed by reverse-domain name. """ payment_handlers: dict[ - ReverseDomainName, list[payment_handler.ResponseSchema] + reverse_domain_name.ReverseDomainName, + list[payment_handler.ResponseSchema], ] """ Payment handler registry keyed by reverse-domain name. @@ -196,7 +254,51 @@ class ResponseOrderSchema(Base): extra="allow", ) capabilities: ( - dict[ReverseDomainName, list[capability.ResponseSchema]] | None + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.ResponseSchema], + ] + | None + ) = None + """ + Capability registry keyed by reverse-domain name. + """ + + +class ResponseCartSchema(Base): + """ + UCP metadata for cart responses. No payment handlers needed pre-checkout. + """ + + model_config = ConfigDict( + extra="allow", + ) + capabilities: ( + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.ResponseSchema], + ] + | None + ) = None + """ + Capability registry keyed by reverse-domain name. + """ + + +class ResponseCatalogSchema(Base): + """ + UCP metadata for catalog responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + capabilities: ( + dict[ + reverse_domain_name.ReverseDomainName, + list[capability.ResponseSchema], + ] + | None ) = None """ Capability registry keyed by reverse-domain name.