From 2c6bd35ebbed11bc46c9db6c4a7abd475a172377 Mon Sep 17 00:00:00 2001 From: claudi Date: Tue, 7 Apr 2026 11:23:09 +0200 Subject: [PATCH] Add usage guide for eBay REST Client to enhance documentation --- README.md | 2 + docs/usage-guide.md | 367 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+) create mode 100644 docs/usage-guide.md diff --git a/README.md b/README.md index 0b1ccf2..7ae5473 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Implemented API domains: The public entry point is `EbayClient`, which wires all six wrappers behind one shared transport and one shared token store. +For a workflow-oriented guide with setup patterns, scope planning, and common usage recipes, see [docs/usage-guide.md](docs/usage-guide.md). + ## Project Layout - `ebay_client/client.py`: top-level `EbayClient` facade diff --git a/docs/usage-guide.md b/docs/usage-guide.md new file mode 100644 index 0000000..3fa0482 --- /dev/null +++ b/docs/usage-guide.md @@ -0,0 +1,367 @@ +# eBay REST Client Usage Guide + +This guide is the practical companion to the project README. Use it when you want to answer one of these questions quickly: + +- Which scopes do I need for a workflow? +- Should I use `EbayClient` directly or `EbayOAuthClient` too? +- Which wrapper method matches the eBay operation I want? +- What are the implementation caveats around webhooks, disputes, uploads, or refunds? + +## 1. Choose the Right Auth Model + +There are two supported patterns. + +### Pattern A: Wrapper-first usage + +Use this when you want to call the REST wrappers and let the transport fetch tokens automatically. + +```python +from ebay_client import EbayClient +from ebay_client.core.auth.models import EbayOAuthConfig + +oauth_config = EbayOAuthConfig( + client_id="your-client-id", + client_secret="your-client-secret", + default_scopes=[ + "https://api.ebay.com/oauth/api_scope/sell.inventory", + ], +) + +client = EbayClient(oauth_config) +items = client.inventory.get_inventory_items(limit=25) +``` + +This is the simplest mode. The transport asks the OAuth layer for a token whenever a wrapper call needs one. + +### Pattern B: Shared OAuth and wrapper usage + +Use this when you need authorization-code flow, refresh tokens, or a shared persistent token store. + +```python +from ebay_client import EbayClient +from ebay_client.core.auth.models import EbayOAuthConfig +from ebay_client.core.auth.oauth import EbayOAuthClient +from ebay_client.core.auth.store import InMemoryTokenStore + +store = InMemoryTokenStore() + +oauth_config = EbayOAuthConfig( + client_id="your-client-id", + client_secret="your-client-secret", + redirect_uri="https://your-app.example/callback", + default_scopes=[ + "https://api.ebay.com/oauth/api_scope", + "https://api.ebay.com/oauth/api_scope/commerce.notification.subscription", + ], +) + +oauth = EbayOAuthClient(oauth_config, token_store=store) +authorization_url = oauth.build_authorization_url(state="csrf-token") + +client = EbayClient(oauth_config, token_store=store) +``` + +Key point: if you want the wrapper layer and your own OAuth flow to see the same token state, use the same `TokenStore` instance for both. + +## 2. Scope Planning + +The wrappers already encode the required scopes for each endpoint, but it still helps to know the scope families when you prepare credentials or interactive consent. + +### Core scope families + +- Notification management: `https://api.ebay.com/oauth/api_scope` +- Notification subscriptions: `https://api.ebay.com/oauth/api_scope/commerce.notification.subscription` +- Notification subscription readonly: `https://api.ebay.com/oauth/api_scope/commerce.notification.subscription.readonly` +- Inventory write: `https://api.ebay.com/oauth/api_scope/sell.inventory` +- Inventory readonly: `https://api.ebay.com/oauth/api_scope/sell.inventory.readonly` +- Fulfillment write: `https://api.ebay.com/oauth/api_scope/sell.fulfillment` +- Fulfillment readonly: `https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly` +- Account write: `https://api.ebay.com/oauth/api_scope/sell.account` +- Account readonly: `https://api.ebay.com/oauth/api_scope/sell.account.readonly` +- Refunds: `https://api.ebay.com/oauth/api_scope/sell.finances` +- Payment disputes: `https://api.ebay.com/oauth/api_scope/sell.payment.dispute` +- Media workflows: `https://api.ebay.com/oauth/api_scope/sell.inventory` +- Feed endpoints accept one of several allowed scope families depending on the business domain behind the feed + +### How the client behaves + +- Read wrappers often accept either the readonly scope or the broader write scope. +- If a broader valid token is already cached, it is reused. +- Write wrappers request the stronger scope directly. +- If the current token does not satisfy the endpoint, the OAuth layer fetches a new token for the required scope set. +- `default_scopes` are mainly the fallback used when you explicitly call OAuth methods without passing scopes yourself. + +## 3. Top-Level Client Map + +`EbayClient` exposes these subclients: + +- `client.notification` +- `client.inventory` +- `client.fulfillment` +- `client.account` +- `client.feed` +- `client.media` + +Use the generated model packages for request and response types: + +- `ebay_client.generated.notification.models` +- `ebay_client.generated.inventory.models` +- `ebay_client.generated.fulfillment.models` +- `ebay_client.generated.account.models` +- `ebay_client.generated.feed.models` +- `ebay_client.generated.media.models` + +## 4. Common Workflows + +This section maps typical seller workflows to wrapper methods. + +### Notification: manage webhook subscriptions + +Typical flow: + +1. inspect available topics with `get_topics()` +2. create or update a destination with `create_destination()` or `update_destination()` +3. create the subscription with `create_subscription()` +4. optionally attach filters with `create_subscription_filter()` +5. validate delivery with `test_subscription()` + +Use when you need: + +- destination CRUD +- subscription CRUD +- enable or disable subscriptions +- lookup of eBay notification public keys + +### Notification: receive and verify inbound events + +Inbound notifications do not use bearer-token auth. They use the `X-EBAY-SIGNATURE` header and eBay's public-key verification flow. + +Recommended server flow: + +1. answer the GET challenge with `WebhookRequestHandler.handle_challenge()` +2. resolve the public key through `WebhookPublicKeyResolver` +3. verify the POST body with `WebhookSignatureValidator` +4. dispatch the parsed `WebhookEventEnvelope` + +Reference implementation: `examples/fastapi_notification_webhook.py`. + +### Inventory: read listings and inventory records + +Use these methods when you want to inspect the seller's inventory state: + +- `get_inventory_item()` +- `get_inventory_items()` +- `bulk_get_inventory_item()` +- `get_offer()` +- `get_offers()` +- `get_listing_fees()` +- `get_inventory_location()` +- `get_inventory_locations()` + +This is the best starting point if your main use case is reading the data of already listed items. + +### Inventory: create or update listings + +The common progression is: + +1. create or replace the inventory item with `create_or_replace_inventory_item()` +2. configure location data if needed with `create_inventory_location()` or `update_inventory_location()` +3. create the offer with `create_offer()` +4. publish the offer with `publish_offer()` + +Bulk variants are available for larger batch flows: + +- `bulk_create_or_replace_inventory_item()` +- `bulk_create_offer()` +- `bulk_publish_offer()` +- `bulk_update_price_quantity()` + +Caveat: several write methods require `content_language=...` and the wrapper enforces that explicitly. + +### Inventory: multi-variation and mapping support + +Use these methods for additional listing structures: + +- inventory item groups: `get_inventory_item_group()`, `create_or_replace_inventory_item_group()`, `delete_inventory_item_group()` +- product compatibility: `get_product_compatibility()`, `create_or_replace_product_compatibility()`, `delete_product_compatibility()` +- location mapping: `get_sku_location_mapping()`, `create_or_replace_sku_location_mapping()`, `delete_sku_location_mapping()` +- listing migration: `bulk_migrate_listing()` + +### Fulfillment: orders and shipping fulfillments + +Typical order handling flow: + +1. read orders with `get_orders()` or `get_order()` +2. create shipment details with `create_shipping_fulfillment()` +3. inspect resulting records with `get_shipping_fulfillments()` or `get_shipping_fulfillment()` + +Helper returned by create: + +- `CreatedShippingFulfillment`, which includes the `Location` header value and the extracted fulfillment ID + +### Fulfillment: refunds + +Use `issue_refund()` with `IssueRefundRequest` when the seller workflow is eligible for the standard OAuth-backed refund path. + +Caveats: + +- this method uses the `sell.finances` scope instead of the normal fulfillment scope +- eBay documents additional Digital Signatures requirements for some refund scenarios, especially for some EU and UK sellers +- that Digital Signatures flow is not implemented in this client yet + +### Fulfillment: payment disputes + +Dispute support is separated from regular fulfillment because eBay serves it from a different host and scope. + +Methods: + +- reads: `get_payment_dispute()`, `get_payment_dispute_summaries()`, `get_payment_dispute_activities()` +- decisions: `contest_payment_dispute()`, `accept_payment_dispute()` +- evidence files: `upload_evidence_file()`, `fetch_evidence_content()` +- evidence metadata: `add_evidence()`, `update_evidence()` + +Caveats: + +- dispute methods use the `sell.payment.dispute` scope +- dispute methods target `https://apiz.ebay.com/sell/fulfillment/v1/...` +- the shared transport already supports absolute URLs, so this is handled inside the wrapper + +### Account: business policies and seller capabilities + +Use the Account wrapper for policy management and seller-level configuration reads. + +Business policy coverage: + +- fulfillment policies: list, get, create, update, delete, get-by-name +- payment policies: list, get, create, update, delete, get-by-name +- return policies: list, get, create, update, delete, get-by-name + +Seller capability reads: + +- `get_privileges()` +- `get_opted_in_programs()` + +This wrapper is a good fit when you need to prepare the policy IDs referenced by Inventory offers. + +### Feed: task-based and scheduled bulk operations + +Use the Feed wrapper when the workflow is file-driven rather than single-resource CRUD. + +Task flow: + +1. create a task with `create_task()` +2. upload its input file with `upload_file()` +3. inspect the task with `get_task()` +4. download results with `get_result_file()` + +Schedule flow: + +1. inspect schedule templates with `get_schedule_templates()` or `get_schedule_template()` +2. create the recurring schedule with `create_schedule()` +3. inspect or update it with `get_schedule()` or `update_schedule()` +4. download the latest result file with `get_latest_result_file()` + +Helpers: + +- `CreatedFeedResource` +- `FeedFileDownload` +- `extract_feed_resource_id()` + +### Media: image, document, and video workflows + +Use Media when you need hosted assets for listings. + +Images: + +- `create_image_from_file()` +- `create_image_from_path()` +- `create_image_from_url()` +- `get_image()` + +Documents: + +- `create_document()` +- `upload_document()` +- `upload_document_from_path()` +- `get_document()` +- `wait_for_document()` +- `create_upload_and_wait_document()` +- `create_upload_and_wait_document_from_path()` +- `create_document_from_url_and_wait()` + +Videos: + +- `create_video()` +- `upload_video()` +- `get_video()` +- `wait_for_video()` +- `create_upload_and_wait_video()` +- `create_upload_and_wait_video_from_path()` + +Helpers: + +- `extract_resource_id()` +- `guess_media_content_type()` +- `VideoWorkflowResult` +- `DocumentWorkflowResult` + +Reference implementation: `examples/media_workflows.py`. + +## 5. Working With Generated Models + +The wrappers are intentionally thin. Request payloads and response types come from the generated Pydantic model packages. + +Example pattern: + +```python +from ebay_client import EbayClient +from ebay_client.core.auth.models import EbayOAuthConfig +from ebay_client.generated.media.models import CreateDocumentRequest + +client = EbayClient( + EbayOAuthConfig( + client_id="your-client-id", + client_secret="your-client-secret", + default_scopes=["https://api.ebay.com/oauth/api_scope/sell.inventory"], + ) +) + +result = client.media.create_upload_and_wait_document_from_path( + CreateDocumentRequest( + documentType="USER_GUIDE_OR_MANUAL", + languages=["en-US"], + ), + "./manual.pdf", +) +``` + +For any wrapper method that takes a payload, look up the corresponding generated request model in the matching `ebay_client.generated..models` package. + +## 6. Operational Notes + +- The default token store is process-local and in-memory only. +- Empty `202` and `204` responses are normalized by the shared transport so wrapper methods can return `None` cleanly for no-body success cases. +- Multipart uploads are already handled by the transport and wrappers for Media, Feed, and dispute evidence uploads. +- Absolute URL handling is already built into the transport so wrappers can call alternate eBay hosts when required. + +## 7. Validation and Regeneration + +Run tests: + +```powershell +& .\.venv\Scripts\python.exe -m pytest +``` + +Regenerate model packages: + +```powershell +& .\.venv\Scripts\python.exe .\scripts\generate_clients.py +``` + +Generate just one API package: + +```powershell +& .\.venv\Scripts\python.exe .\scripts\generate_clients.py --api media +``` + +The generated models are written to `ebay_client/generated//models.py`. \ No newline at end of file