diff --git a/.tmp_notification_models.py b/.tmp_notification_models.py
new file mode 100644
index 0000000..e911f3d
--- /dev/null
+++ b/.tmp_notification_models.py
@@ -0,0 +1,323 @@
+# generated by datamodel-codegen:
+# filename: commerce_notification_v1_oas3.yaml
+# timestamp: 2026-04-07T06:52:28+00:00
+
+from __future__ import annotations
+
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel, Field
+
+
+class Config(BaseModel):
+ alertEmail: Optional[str] = Field(
+ None,
+ description='This field is used to add or modify an email address that will be used for Notification API alerts associated with the application. getConfig can be used to get the email address currently being used for alerts.',
+ )
+
+
+class CreateSubscriptionFilterRequest(BaseModel):
+ filterSchema: Optional[Dict[str, Dict[str, Any]]] = Field(
+ None,
+ description='The content of a subscription filter as a valid JSON Schema Core document (version 2020-12 or later). The filterSchema provided must describe the subscription\'s notification payload such that it supplies valid criteria to filter the subscription\'s notifications.
Note: Not all topics can have filters applied to them. Use getTopic and getTopics requests to determine if a specific topic is filterable. Filterable topics have the boolean filterable returned as true in the response.
Note: If the JSON supplied as a subscription filter specifies a field that does not exist in the notifications for a topic, or if the topic is not filterable, the filter will be rejected and become DISABLED. If it is valid, however, the filter will move from PENDING status to ENABLED status.
Initially, when the createSubscriptionFilter request has been made, if the request has a valid JSON body a 201 Created is returned. After that, the validation of the filterSchema happens. See Creating a subscription filter for a topic for additional information.',
+ )
+
+
+class DeliveryConfig(BaseModel):
+ endpoint: Optional[str] = Field(
+ None,
+ description='The endpoint for this destination.
Note: The provided endpoint URL should use the HTTPS protocol, and it should not contain an internal IP address or localhost in its path.',
+ )
+ verificationToken: Optional[str] = Field(
+ None,
+ description='The verification token associated with this endpoint.
Note: The provided verification token must be between 32 and 80 characters. Allowed characters include alphanumeric characters, underscores (_), and hyphens (-); no other characters are allowed.',
+ )
+
+
+class Destination(BaseModel):
+ deliveryConfig: Optional[DeliveryConfig] = Field(
+ None, description='The configuration associated with this destination.'
+ )
+ destinationId: Optional[str] = Field(
+ None, description='The unique identifier for the destination.'
+ )
+ name: Optional[str] = Field(
+ None, description='The name associated with this destination.'
+ )
+ status: Optional[str] = Field(
+ None,
+ description='The status for this destination.
Note: The MARKED_DOWN value is set by eBay systems and cannot be used in a create or update call by applications.
Valid values:
ENABLEDDISABLEDMARKED_DOWN
For implementation help, refer to eBay API documentation',
+ )
+
+
+class DestinationRequest(BaseModel):
+ deliveryConfig: Optional[DeliveryConfig] = Field(
+ None,
+ description='This container is used to specify the destination endpoint and verification token associated with this endpoint.',
+ )
+ name: Optional[str] = Field(
+ None, description='The seller-specified name for the destination endpoint.'
+ )
+ status: Optional[str] = Field(
+ None,
+ description='This field sets the status for the destination endpoint as ENABLED or DISABLED.
Note: The MARKED_DOWN value is set by eBay systems and cannot be used in a create or update call by applications. For implementation help, refer to eBay API documentation',
+ )
+
+
+class DestinationSearchResponse(BaseModel):
+ destinations: Optional[List[Destination]] = Field(
+ None, description='An array that contains the destination details.'
+ )
+ href: Optional[str] = Field(
+ None,
+ description='The path to the call URI that produced the current page of results.',
+ )
+ limit: Optional[int] = Field(
+ None,
+ description='The number of records to show in the current response.
Default: 20',
+ )
+ next: Optional[str] = Field(
+ None,
+ description='The URL to access the next set of results. This field includes a continuation_token. No prev field is returned, but this value is persistent during the session so that you can use it to return to the next page.
This field is not returned if fewer records than specified by the limit field are returned.',
+ )
+ total: Optional[int] = Field(
+ None, description='The total number of matches for the search criteria.'
+ )
+
+
+class ErrorParameter(BaseModel):
+ name: Optional[str] = Field(None, description='The object of the error.')
+ value: Optional[str] = Field(None, description='The value of the object.')
+
+
+class PayloadDetail(BaseModel):
+ deliveryProtocol: Optional[str] = Field(
+ None,
+ description="The supported delivery protocols. For implementation help, refer to eBay API documentation",
+ )
+ deprecated: Optional[bool] = Field(None, description='A deprecation indicator.')
+ format: Optional[List[str]] = Field(
+ None,
+ description='The supported format. Presently, JSON is the only supported format.',
+ )
+ schemaVersion: Optional[str] = Field(
+ None, description='The supported schema version.'
+ )
+
+
+class PublicKey(BaseModel):
+ algorithm: Optional[str] = Field(
+ None,
+ description='The algorithm associated with the public key that is returned, such as Elliptic Curve Digital Signature Algorithm (ECDSA).',
+ )
+ digest: Optional[str] = Field(
+ None,
+ description='The digest associated with the public key that is returned, such as Secure Hash Algorithm 1 (SHA1).',
+ )
+ key: Optional[str] = Field(
+ None,
+ description='The public key that is returned for the specified key ID.
This value is used to validate the eBay push notification message payload.',
+ )
+
+
+class SubscriptionFilter(BaseModel):
+ creationDate: Optional[str] = Field(
+ None, description='The creation date for this subscription filter.'
+ )
+ filterId: Optional[str] = Field(
+ None, description='The unique identifier for this subscription filter.'
+ )
+ filterSchema: Optional[Dict[str, Dict[str, Any]]] = Field(
+ None,
+ description='The content of this subscription filter as a valid JSON Schema Core document (version 2020-12 or later). The filterSchema provided must describe the subscription\'s notification payload such that it supplies valid criteria to filter the subscription\'s notifications.',
+ )
+ filterStatus: Optional[str] = Field(
+ None,
+ description="The status of this subscription filter. For implementation help, refer to eBay API documentation",
+ )
+ subscriptionId: Optional[str] = Field(
+ None, description='The unique identifier for the subscription.'
+ )
+
+
+class SubscriptionPayloadDetail(BaseModel):
+ deliveryProtocol: Optional[str] = Field(
+ None,
+ description='The supported delivery protocol of the notification topic.
Note: HTTPS is currently the only supported delivery protocol of all notification topics. For implementation help, refer to eBay API documentation',
+ )
+ format: Optional[str] = Field(
+ None,
+ description='The supported data format of the payload.
Note: JSON is currently the only supported format for all notification topics. For implementation help, refer to eBay API documentation',
+ )
+ schemaVersion: Optional[str] = Field(
+ None,
+ description='The supported schema version for the notification topic. See the supportedPayloads.schemaVersion field for the topic in getTopics or getTopic response.',
+ )
+
+
+class Topic(BaseModel):
+ authorizationScopes: Optional[List[str]] = Field(
+ None,
+ description='The authorization scopes required to subscribe to this topic.',
+ )
+ context: Optional[str] = Field(
+ None,
+ description="The business context associated with this topic. For implementation help, refer to eBay API documentation",
+ )
+ description: Optional[str] = Field(
+ None, description='The description of the topic.'
+ )
+ filterable: Optional[bool] = Field(
+ None, description='The indicator of whether this topic is filterable or not.'
+ )
+ scope: Optional[str] = Field(
+ None,
+ description="The scope of this topic. For implementation help, refer to eBay API documentation",
+ )
+ status: Optional[str] = Field(
+ None,
+ description="The status of this topic. For implementation help, refer to eBay API documentation",
+ )
+ supportedPayloads: Optional[List[PayloadDetail]] = Field(
+ None, description='The supported payloads for this topic.'
+ )
+ topicId: Optional[str] = Field(
+ None, description='The unique identifier for the topic.'
+ )
+
+
+class TopicSearchResponse(BaseModel):
+ href: Optional[str] = Field(
+ None,
+ description='The path to the call URI that produced the current page of results.',
+ )
+ limit: Optional[int] = Field(
+ None,
+ description='The value of the limit parameter submitted in the request, which is the maximum number of items to return per page, from the result set. A result set is the complete set of results returned by the method.
Note: Though this parameter is not required to be submitted in the request, the parameter defaults to 20 if omitted.',
+ )
+ next: Optional[str] = Field(
+ None,
+ description='The URL to access the next set of results. This field includes a continuation_token. No prev field is returned, but this value is persistent during the session so that you can use it to return to the next page.
This field is not returned if fewer records than specified by the limit field are returned.',
+ )
+ topics: Optional[List[Topic]] = Field(
+ None, description='An array of topics that match the specified criteria.'
+ )
+ total: Optional[int] = Field(
+ None, description='The total number of matches for the search criteria.'
+ )
+
+
+class UpdateSubscriptionRequest(BaseModel):
+ destinationId: Optional[str] = Field(
+ None,
+ description='The unique identifier of the destination endpoint that will receive notifications associated with this subscription. Use getDestinations to retrieve destination IDs.',
+ )
+ payload: Optional[SubscriptionPayloadDetail] = Field(
+ None, description='The payload associated with this subscription.'
+ )
+ status: Optional[str] = Field(
+ None,
+ description="Set the status of the subscription being updated to ENABLED or DISABLED. For implementation help, refer to eBay API documentation",
+ )
+
+
+class CreateSubscriptionRequest(BaseModel):
+ destinationId: Optional[str] = Field(
+ None,
+ description='The unique identifier of the destination endpoint that will receive notifications associated with this subscription. Use the getDestinations method to retrieve destination IDs.',
+ )
+ payload: Optional[SubscriptionPayloadDetail] = Field(
+ None,
+ description='The payload associated with the notification topic. Use getTopics or getTopic to get the supported payload for the topic.',
+ )
+ status: Optional[str] = Field(
+ None,
+ description="Set the status of the subscription to ENABLED or DISABLED. For implementation help, refer to eBay API documentation",
+ )
+ topicId: Optional[str] = Field(
+ None,
+ description='The unique identifier of the notification topic to subscribe to. Use getTopics to get topic IDs.',
+ )
+
+
+class Error(BaseModel):
+ category: Optional[str] = Field(None, description='Identifies the type of erro.')
+ domain: Optional[str] = Field(
+ None,
+ description='Name for the primary system where the error occurred. This is relevant for application errors.',
+ )
+ errorId: Optional[int] = Field(
+ None, description='A unique number to identify the error.'
+ )
+ inputRefIds: Optional[List[str]] = Field(
+ None,
+ description='An array of request elements most closely associated to the error.',
+ )
+ longMessage: Optional[str] = Field(
+ None, description='A more detailed explanation of the error.'
+ )
+ message: Optional[str] = Field(
+ None,
+ description="Information on how to correct the problem, in the end user's terms and language where applicable.",
+ )
+ outputRefIds: Optional[List[str]] = Field(
+ None,
+ description='An array of request elements most closely associated to the error.',
+ )
+ parameters: Optional[List[ErrorParameter]] = Field(
+ None,
+ description='An array of name/value pairs that describe details the error condition. These are useful when multiple errors are returned.',
+ )
+ subdomain: Optional[str] = Field(
+ None,
+ description='Further helps indicate which subsystem the error is coming from. System subcategories include: Initialization, Serialization, Security, Monitoring, Rate Limiting, etc.',
+ )
+
+
+class Subscription(BaseModel):
+ creationDate: Optional[str] = Field(
+ None, description='The creation date for this subscription.'
+ )
+ destinationId: Optional[str] = Field(
+ None,
+ description='The unique identifier for the destination associated with this subscription.',
+ )
+ filterId: Optional[str] = Field(
+ None,
+ description='The unique identifier for the filter associated with this subscription.',
+ )
+ payload: Optional[SubscriptionPayloadDetail] = Field(
+ None, description='The payload associated with this subscription.'
+ )
+ status: Optional[str] = Field(
+ None,
+ description="The status of this subscription. For implementation help, refer to eBay API documentation",
+ )
+ subscriptionId: Optional[str] = Field(
+ None, description='The unique identifier for the subscription.'
+ )
+ topicId: Optional[str] = Field(
+ None,
+ description='The unique identifier for the topic associated with this subscription.',
+ )
+
+
+class SubscriptionSearchResponse(BaseModel):
+ href: Optional[str] = Field(
+ None,
+ description='The path to the call URI that produced the current page of results.',
+ )
+ limit: Optional[int] = Field(
+ None,
+ description='The value of the limit parameter submitted in the request, which is the maximum number of items to return per page, from the result set. A result set is the complete set of results returned by the method.
Note: Though this parameter is not required to be submitted in the request, the parameter defaults to 20 if omitted.
Default: 20',
+ )
+ next: Optional[str] = Field(
+ None,
+ description='The URL to access the next set of results. This field includes a continuation_token. No prev field is returned, but this value is persistent during the session so that you can use it to return to the next page.
This field is not returned if fewer records than specified by the limit field are returned.',
+ )
+ subscriptions: Optional[List[Subscription]] = Field(
+ None, description='The subscriptions that match the search criteria.'
+ )
+ total: Optional[int] = Field(
+ None, description='The total number of matches for the search criteria.'
+ )
diff --git a/ebay_client/account/client.py b/ebay_client/account/client.py
index f6303a1..19e7abf 100644
--- a/ebay_client/account/client.py
+++ b/ebay_client/account/client.py
@@ -11,6 +11,7 @@ from ebay_client.generated.account.models import (
ACCOUNT_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.account"
ACCOUNT_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.account.readonly"
+ACCOUNT_READ_SCOPE_OPTIONS = [[ACCOUNT_READ_SCOPE], [ACCOUNT_SCOPE]]
class AccountClient:
@@ -22,7 +23,7 @@ class AccountClient:
FulfillmentPolicyResponse,
"GET",
"/sell/account/v1/fulfillment_policy",
- scopes=[ACCOUNT_READ_SCOPE],
+ scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
params={"marketplace_id": marketplace_id},
)
@@ -31,7 +32,7 @@ class AccountClient:
PaymentPolicyResponse,
"GET",
"/sell/account/v1/payment_policy",
- scopes=[ACCOUNT_READ_SCOPE],
+ scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
params={"marketplace_id": marketplace_id},
)
@@ -40,7 +41,7 @@ class AccountClient:
ReturnPolicyResponse,
"GET",
"/sell/account/v1/return_policy",
- scopes=[ACCOUNT_READ_SCOPE],
+ scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
params={"marketplace_id": marketplace_id},
)
@@ -49,7 +50,7 @@ class AccountClient:
SellingPrivileges,
"GET",
"/sell/account/v1/privilege",
- scopes=[ACCOUNT_SCOPE],
+ scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
)
def get_opted_in_programs(self) -> Programs:
@@ -57,5 +58,5 @@ class AccountClient:
Programs,
"GET",
"/sell/account/v1/program/get_opted_in_programs",
- scopes=[ACCOUNT_READ_SCOPE],
+ scope_options=ACCOUNT_READ_SCOPE_OPTIONS,
)
diff --git a/ebay_client/feed/client.py b/ebay_client/feed/client.py
index 1718810..2ed0db6 100644
--- a/ebay_client/feed/client.py
+++ b/ebay_client/feed/client.py
@@ -10,6 +10,16 @@ from ebay_client.generated.feed.models import (
FEED_INVENTORY_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.inventory"
FEED_FULFILLMENT_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.fulfillment"
+FEED_MARKETING_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.marketing"
+FEED_CATALOG_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly"
+FEED_ANALYTICS_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.analytics.readonly"
+FEED_READ_SCOPE_OPTIONS = [
+ [FEED_INVENTORY_SCOPE],
+ [FEED_FULFILLMENT_SCOPE],
+ [FEED_MARKETING_SCOPE],
+ [FEED_CATALOG_READ_SCOPE],
+ [FEED_ANALYTICS_READ_SCOPE],
+]
class FeedClient:
@@ -21,7 +31,7 @@ class FeedClient:
TaskCollection,
"GET",
"/sell/feed/v1/task",
- scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
+ scope_options=FEED_READ_SCOPE_OPTIONS,
params={"feed_type": feed_type},
)
@@ -30,7 +40,7 @@ class FeedClient:
Task,
"GET",
f"/sell/feed/v1/task/{task_id}",
- scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
+ scope_options=FEED_READ_SCOPE_OPTIONS,
)
def get_schedule_templates(self) -> ScheduleTemplateCollection:
@@ -38,7 +48,7 @@ class FeedClient:
ScheduleTemplateCollection,
"GET",
"/sell/feed/v1/schedule_template",
- scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
+ scope_options=FEED_READ_SCOPE_OPTIONS,
)
def get_schedules(self) -> UserScheduleCollection:
@@ -46,5 +56,5 @@ class FeedClient:
UserScheduleCollection,
"GET",
"/sell/feed/v1/schedule",
- scopes=[FEED_INVENTORY_SCOPE, FEED_FULFILLMENT_SCOPE],
+ scope_options=FEED_READ_SCOPE_OPTIONS,
)
diff --git a/ebay_client/fulfillment/client.py b/ebay_client/fulfillment/client.py
index f05b110..f019f30 100644
--- a/ebay_client/fulfillment/client.py
+++ b/ebay_client/fulfillment/client.py
@@ -8,7 +8,9 @@ from ebay_client.generated.fulfillment.models import (
ShippingFulfillmentPagedCollection,
)
+FULFILLMENT_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.fulfillment"
FULFILLMENT_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"
+FULFILLMENT_READ_SCOPE_OPTIONS = [[FULFILLMENT_READ_SCOPE], [FULFILLMENT_SCOPE]]
class FulfillmentClient:
@@ -20,7 +22,7 @@ class FulfillmentClient:
Order,
"GET",
f"/sell/fulfillment/v1/order/{order_id}",
- scopes=[FULFILLMENT_READ_SCOPE],
+ scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
)
def get_orders(self, *, limit: int | None = None, offset: int | None = None) -> OrderSearchPagedCollection:
@@ -28,7 +30,7 @@ class FulfillmentClient:
OrderSearchPagedCollection,
"GET",
"/sell/fulfillment/v1/order",
- scopes=[FULFILLMENT_READ_SCOPE],
+ scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
params={"limit": limit, "offset": offset},
)
@@ -37,7 +39,7 @@ class FulfillmentClient:
ShippingFulfillmentPagedCollection,
"GET",
f"/sell/fulfillment/v1/order/{order_id}/shipping_fulfillment",
- scopes=[FULFILLMENT_READ_SCOPE],
+ scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
)
def get_shipping_fulfillment(self, order_id: str, fulfillment_id: str) -> ShippingFulfillment:
@@ -45,5 +47,5 @@ class FulfillmentClient:
ShippingFulfillment,
"GET",
f"/sell/fulfillment/v1/order/{order_id}/shipping_fulfillment/{fulfillment_id}",
- scopes=[FULFILLMENT_READ_SCOPE],
+ scope_options=FULFILLMENT_READ_SCOPE_OPTIONS,
)
diff --git a/ebay_client/inventory/client.py b/ebay_client/inventory/client.py
index 3abf891..4de7ad1 100644
--- a/ebay_client/inventory/client.py
+++ b/ebay_client/inventory/client.py
@@ -8,7 +8,9 @@ from ebay_client.generated.inventory.models import (
Offers,
)
+INVENTORY_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.inventory"
INVENTORY_READ_SCOPE = "https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"
+INVENTORY_READ_SCOPE_OPTIONS = [[INVENTORY_READ_SCOPE], [INVENTORY_SCOPE]]
class InventoryClient:
@@ -20,7 +22,7 @@ class InventoryClient:
InventoryItemWithSkuLocaleGroupid,
"GET",
f"/sell/inventory/v1/inventory_item/{sku}",
- scopes=[INVENTORY_READ_SCOPE],
+ scope_options=INVENTORY_READ_SCOPE_OPTIONS,
)
def get_inventory_items(self, *, limit: int | None = None, offset: int | None = None) -> InventoryItems:
@@ -28,7 +30,7 @@ class InventoryClient:
InventoryItems,
"GET",
"/sell/inventory/v1/inventory_item",
- scopes=[INVENTORY_READ_SCOPE],
+ scope_options=INVENTORY_READ_SCOPE_OPTIONS,
params={"limit": limit, "offset": offset},
)
@@ -37,7 +39,7 @@ class InventoryClient:
OfferResponseWithListingId,
"GET",
f"/sell/inventory/v1/offer/{offer_id}",
- scopes=[INVENTORY_READ_SCOPE],
+ scope_options=INVENTORY_READ_SCOPE_OPTIONS,
)
def get_offers(self, *, limit: int | None = None, offset: int | None = None, sku: str | None = None) -> Offers:
@@ -45,6 +47,6 @@ class InventoryClient:
Offers,
"GET",
"/sell/inventory/v1/offer",
- scopes=[INVENTORY_READ_SCOPE],
+ scope_options=INVENTORY_READ_SCOPE_OPTIONS,
params={"limit": limit, "offset": offset, "sku": sku},
)
diff --git a/tests/test_public_wrappers.py b/tests/test_public_wrappers.py
index 9b3bdd3..d97da13 100644
--- a/tests/test_public_wrappers.py
+++ b/tests/test_public_wrappers.py
@@ -49,6 +49,23 @@ def build_transport() -> ApiTransport:
return ApiTransport(base_url="https://api.ebay.com", oauth_client=DummyOAuthClient())
+class RecordingOAuthClient:
+ def __init__(self) -> None:
+ self.last_scopes: list[str] | None = None
+ self.last_scope_options: list[list[str]] | None = None
+
+ def get_valid_token(
+ self,
+ *,
+ scopes: list[str] | None = None,
+ scope_options: list[list[str]] | None = None,
+ ) -> OAuthToken:
+ self.last_scopes = scopes
+ self.last_scope_options = scope_options
+ resolved_scopes = scopes or (scope_options[0] if scope_options else [])
+ return OAuthToken(access_token="recorded-token", scope=" ".join(resolved_scopes))
+
+
def test_notification_wrapper_returns_pydantic_model(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
method="GET",
@@ -267,4 +284,79 @@ def test_feed_wrapper_returns_task_collection_model(httpx_mock: HTTPXMock) -> No
assert isinstance(result, TaskCollection)
assert result.total == 1
- assert result.tasks and result.tasks[0].taskId == "TASK-1"
\ No newline at end of file
+ assert result.tasks and result.tasks[0].taskId == "TASK-1"
+
+
+def test_inventory_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
+ oauth_client = RecordingOAuthClient()
+ transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
+ httpx_mock.add_response(
+ method="GET",
+ url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
+ json={"sku": "SKU-1"},
+ )
+
+ InventoryClient(transport).get_inventory_item("SKU-1")
+
+ assert oauth_client.last_scopes is None
+ assert oauth_client.last_scope_options == [
+ ["https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"],
+ ["https://api.ebay.com/oauth/api_scope/sell.inventory"],
+ ]
+
+
+def test_fulfillment_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
+ oauth_client = RecordingOAuthClient()
+ transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
+ httpx_mock.add_response(
+ method="GET",
+ url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1",
+ json={"orderId": "ORDER-1"},
+ )
+
+ FulfillmentClient(transport).get_order("ORDER-1")
+
+ assert oauth_client.last_scopes is None
+ assert oauth_client.last_scope_options == [
+ ["https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"],
+ ["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
+ ]
+
+
+def test_account_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
+ oauth_client = RecordingOAuthClient()
+ transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
+ httpx_mock.add_response(
+ method="GET",
+ url="https://api.ebay.com/sell/account/v1/privilege",
+ json={},
+ )
+
+ AccountClient(transport).get_privileges()
+
+ assert oauth_client.last_scopes is None
+ assert oauth_client.last_scope_options == [
+ ["https://api.ebay.com/oauth/api_scope/sell.account.readonly"],
+ ["https://api.ebay.com/oauth/api_scope/sell.account"],
+ ]
+
+
+def test_feed_wrapper_accepts_any_documented_feed_scope_option(httpx_mock: HTTPXMock) -> None:
+ oauth_client = RecordingOAuthClient()
+ transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
+ httpx_mock.add_response(
+ method="GET",
+ url="https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT",
+ json={"tasks": [{"taskId": "TASK-1"}], "total": 1},
+ )
+
+ FeedClient(transport).get_tasks(feed_type="LMS_ORDER_REPORT")
+
+ assert oauth_client.last_scopes is None
+ assert oauth_client.last_scope_options == [
+ ["https://api.ebay.com/oauth/api_scope/sell.inventory"],
+ ["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
+ ["https://api.ebay.com/oauth/api_scope/sell.marketing"],
+ ["https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly"],
+ ["https://api.ebay.com/oauth/api_scope/sell.analytics.readonly"],
+ ]
\ No newline at end of file