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

Quick source jumps:

- facade: [ebay_client/client.py](../ebay_client/client.py)
- notification wrapper: [ebay_client/notification/client.py](../ebay_client/notification/client.py)
- inventory wrapper: [ebay_client/inventory/client.py](../ebay_client/inventory/client.py)
- fulfillment wrapper: [ebay_client/fulfillment/client.py](../ebay_client/fulfillment/client.py)
- account wrapper: [ebay_client/account/client.py](../ebay_client/account/client.py)
- feed wrapper: [ebay_client/feed/client.py](../ebay_client/feed/client.py)
- media wrapper: [ebay_client/media/client.py](../ebay_client/media/client.py)
- FastAPI webhook example: [examples/fastapi_notification_webhook.py](../examples/fastapi_notification_webhook.py)
- media workflows example: [examples/media_workflows.py](../examples/media_workflows.py)
- inventory publish example: [examples/inventory_publish_flow.py](../examples/inventory_publish_flow.py)
- feed task example: [examples/feed_task_flow.py](../examples/feed_task_flow.py)

### 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](../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](../examples/media_workflows.py).

## 5. End-to-End Recipes

These are the shortest practical paths through the client for the most common seller tasks.

### Recipe: read existing listed inventory data

Use this when you mainly want to inspect what is already listed or staged in eBay.

```python
from ebay_client import EbayClient
from ebay_client.core.auth.models import EbayOAuthConfig

client = EbayClient(
    EbayOAuthConfig(
        client_id="your-client-id",
        client_secret="your-client-secret",
        default_scopes=["https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"],
    )
)

items = client.inventory.get_inventory_items(limit=50)
offers = client.inventory.get_offers(limit=50)
```

Methods involved:

- `get_inventory_items()`
- `get_offers()`
- optionally `get_inventory_item()` and `get_offer()` for detail reads

### Recipe: create an inventory item, create an offer, and publish it

Use this when you want the standard listing flow through the Inventory API.

```python
from ebay_client import EbayClient
from ebay_client.core.auth.models import EbayOAuthConfig
from ebay_client.generated.inventory.models import InventoryItem, OfferDetailsWithKeys

client = EbayClient(
    EbayOAuthConfig(
        client_id="your-client-id",
        client_secret="your-client-secret",
        default_scopes=["https://api.ebay.com/oauth/api_scope/sell.inventory"],
    )
)

client.inventory.create_or_replace_inventory_item(
    "SKU-123",
    InventoryItem(),
    content_language="en-US",
)

offer = client.inventory.create_offer(OfferDetailsWithKeys())
client.inventory.publish_offer(offer.offerId)
```

Operational notes:

- you typically need business policy IDs and location data in place before the offer is valid
- the wrapper requires `content_language` for the item write call because eBay expects it on several write endpoints
- the exact request model fields depend on your marketplace and listing format

### Recipe: receive Notification webhook events in FastAPI

Use the built-in webhook helpers instead of hand-parsing the signature format.

Flow:

1. create a normal `EbayClient`
2. build `WebhookPublicKeyResolver` from `client.notification.get_public_key`
3. build `WebhookSignatureValidator`
4. use `WebhookRequestHandler` for both GET challenge and POST notification handling

Working example: [examples/fastapi_notification_webhook.py](../examples/fastapi_notification_webhook.py).

### Recipe: upload a document or video asset for listings

Use the Media wrapper helpers when you want the client to handle the create, upload, and polling loop for you.

```python
from pathlib import Path

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"],
    ),
    Path("./manual.pdf"),
    timeout_seconds=60.0,
)

print(result.document_id)
print(result.document.documentStatus)
```

Working example: [examples/media_workflows.py](../examples/media_workflows.py).

### Recipe: create a Feed task, upload its file, and download the result

Use this when the operation is file-based and asynchronous.

Flow:

1. create the task with `create_task()`
2. extract the returned `resource_id`
3. upload the task input file with `upload_file()`
4. poll `get_task()` until the task reaches the state you expect
5. download the result with `get_result_file()`

This wrapper already provides typed helpers for the file downloads through `FeedFileDownload`.

Working example: [examples/feed_task_flow.py](../examples/feed_task_flow.py).

### Recipe: dispute handling and evidence upload

Use the Fulfillment dispute methods only when you have the dispute-specific scope.

Flow:

1. read the dispute with `get_payment_dispute()`
2. upload the binary evidence file with `upload_evidence_file()`
3. attach or update the evidence metadata with `add_evidence()` or `update_evidence()`
4. contest or accept with `contest_payment_dispute()` or `accept_payment_dispute()`

Caveat: these calls go through the alternate `apiz.ebay.com` fulfillment base URL inside the wrapper.

### Recipe: use the included examples directly

The repository now includes these runnable example scripts:

- [examples/inventory_publish_flow.py](../examples/inventory_publish_flow.py): create or replace an inventory item, create an offer, and publish it
- [examples/feed_task_flow.py](../examples/feed_task_flow.py): create a feed task, upload an input file, poll its status, and optionally save the result file
- [examples/media_workflows.py](../examples/media_workflows.py): image, document, and video asset workflows
- [examples/fastapi_notification_webhook.py](../examples/fastapi_notification_webhook.py): FastAPI-based challenge and notification handling

## 6. 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.<api>.models` package.

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

## 8. 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/<api>/models.py`.
