ebay-mcp-remote-edition 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,6 +48,10 @@ This is an open-source project provided "as is" without warranty of any kind. Th
48
48
  - [Deploy to Render](#deploy-to-render)
49
49
  - [OAuth flows](#oauth-flows)
50
50
  - [MCP endpoints](#mcp-endpoints)
51
+ - [Validation architecture](#validation-architecture)
52
+ - [Validation endpoints and auth model](#validation-endpoints-and-auth-model)
53
+ - [Diagnostics and health endpoints](#diagnostics-and-health-endpoints)
54
+ - [Validation provider behavior and limitations](#validation-provider-behavior-and-limitations)
51
55
  - [Remote client configuration](#remote-client-configuration)
52
56
  - [Available tools](#available-tools)
53
57
  - [Development](#development)
@@ -295,6 +299,32 @@ UPSTASH_REDIS_REST_TOKEN=
295
299
  ADMIN_API_KEY= # required for admin session endpoints
296
300
  OAUTH_START_KEY= # optional; protects /oauth/start with a shared secret
297
301
 
302
+ # Validation runner identity (required for hosted /validation/* routes)
303
+ # Reuses a stored refresh-token-backed user in the existing multi-user auth store.
304
+ # Use the env-specific values when sandbox and production need different runner users.
305
+ VALIDATION_RUNNER_USER_ID=
306
+ VALIDATION_RUNNER_USER_ID_SANDBOX=
307
+ VALIDATION_RUNNER_USER_ID_PRODUCTION=
308
+
309
+ # Temporary sold-data enrichment provider for validation.
310
+ # This is an interim external abstraction and will be replaced by an internal
311
+ # sales-data implementation without changing the validation orchestration route.
312
+ SOLD_ITEMS_API_URL=
313
+ SOLD_ITEMS_API_KEY=
314
+
315
+ # Future orchestration-side historical research provider.
316
+ # Currently only used to enable the placeholder research contract.
317
+ PERPLEXITY_API_KEY=
318
+
319
+ # Optional phase-1 social-signal providers used by hosted validation.
320
+ # These signals are supportive only and should not be treated as authoritative
321
+ # automated buy triggers on their own.
322
+ TWITTER_BEARER_TOKEN=
323
+ YOUTUBE_API_KEY=
324
+ REDDIT_CLIENT_ID=
325
+ REDDIT_CLIENT_SECRET=
326
+ REDDIT_USER_AGENT=
327
+
298
328
  # Session TTL (optional; default 30 days)
299
329
  SESSION_TTL_SECONDS=2592000
300
330
 
@@ -401,7 +431,7 @@ POST/GET/DELETE /mcp # resolves environment from ?env= or EBAY_DEFAULT_EN
401
431
 
402
432
  ```
403
433
  GET /health # Server health check (no auth required)
404
- GET /whoami # Session identity; requires Bearer token
434
+ GET /whoami # Session identity; requires Bearer session token
405
435
  GET /admin/session/:sessionToken # View session; requires X-Admin-API-Key
406
436
  POST /admin/session/:sessionToken/revoke # Revoke session
407
437
  DELETE /admin/session/:sessionToken # Delete session
@@ -419,6 +449,186 @@ DELETE /admin/session/:sessionToken # Delete session
419
449
  }
420
450
  ```
421
451
 
452
+ `/whoami` is the quickest hosted-session debugging check when an MCP client appears authenticated but requests still fail. It confirms which stored user session is active, which environment it is bound to, and whether the session has expired or been revoked.
453
+
454
+ ### Validation architecture
455
+
456
+ The hosted backend now includes a deployment-oriented validation pipeline for non-MCP server-side execution. The route handlers live in [`src/server-http.ts`](src/server-http.ts), while the validation module lives under [`src/validation/`](src/validation).
457
+
458
+ Current module layout:
459
+
460
+ - [`src/validation/types.ts`](src/validation/types.ts) — request/response contracts for validation runs, decision payloads, debug payloads, and provider signal types
461
+ - [`src/validation/run-validation.ts`](src/validation/run-validation.ts) — orchestration entrypoint that validates input, queries providers, merges signals, and returns writes/decision/debug output
462
+ - [`src/validation/recommendation.ts`](src/validation/recommendation.ts) — recommendation and automation decision logic
463
+ - [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts) — live eBay browse-market snapshot provider using the server's existing user-scoped eBay API client
464
+ - [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts) — temporary sold-data provider backed by an external API via `SOLD_ITEMS_API_URL` and `SOLD_ITEMS_API_KEY`
465
+ - [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts) — stable Terapeak/eBay research contract provider for current-market and previous-POB metrics; currently a placeholder contract, not a live authenticated research integration yet
466
+ - [`src/validation/providers/query-utils.ts`](src/validation/providers/query-utils.ts) — shared multi-tier query candidate and fallback helpers used by browse and sold providers
467
+ - [`src/validation/providers/social.ts`](src/validation/providers/social.ts) — phase-1 social provider for recent Twitter/X activity, YouTube view-rate proxy data, and Reddit recent-post counts with graceful degradation
468
+ - [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts) — chart-signal stub reserved for later implementation
469
+ - [`src/validation/providers/research.ts`](src/validation/providers/research.ts) — stable previous-comeback research contract provider for orchestration-side historical inference; currently a placeholder contract with optional future `PERPLEXITY_API_KEY` support
470
+
471
+ Current provider domains called by [`runValidation()`](src/validation/run-validation.ts:106):
472
+
473
+ - **browse/current-market** via [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts)
474
+ - **sold enrichment** via [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts)
475
+ - **Terapeak / eBay research contract** via [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts)
476
+ - **social support signals** via [`src/validation/providers/social.ts`](src/validation/providers/social.ts)
477
+ - **chart support signals** via [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts)
478
+ - **previous comeback research inference** via [`src/validation/providers/research.ts`](src/validation/providers/research.ts)
479
+
480
+ Architecturally, the validation stack is split into two practical classes of providers:
481
+
482
+ - **Server-side authenticated providers** — these run with the hosted backend's stored eBay user context and are the right place for authenticated marketplace retrieval. Today that means the live browse/current-market provider in [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts), the sold enrichment layer in [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts), and the Terapeak/eBay research contract in [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts).
483
+ - **Orchestration-side research providers** — these run as supporting inference layers inside orchestration rather than as part of the user-scoped eBay API client surface. Today that means previous comeback resolution and external historical-research inference in [`src/validation/providers/research.ts`](src/validation/providers/research.ts), plus non-authoritative support providers such as [`src/validation/providers/social.ts`](src/validation/providers/social.ts) and [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts).
484
+
485
+ Operationally, validation works like this:
486
+
487
+ 1. An admin caller invokes an environment-scoped validation route.
488
+ 2. The server resolves the environment (`sandbox` or `production`) from the mounted route tree.
489
+ 3. The route looks up the configured validation runner user ID for that environment.
490
+ 4. The server loads that user's stored refresh-token-backed credentials from the existing hosted auth store.
491
+ 5. The validation orchestrator calls all six provider domains and gathers browse/current-market, sold enrichment, Terapeak/research contract data, social support signals, chart stub output, and previous-comeback research output.
492
+ 6. [`runValidation()`](src/validation/run-validation.ts:106) deterministically merges the provider outputs into normalized field writes.
493
+ 7. The response returns those writes, a conservative buy/track decision block, and provider debug metadata for downstream systems.
494
+
495
+ The validation contract is intentionally split between stable route orchestration and swappable providers. That is why the current sold-data source can be replaced later without changing downstream orchestration or the hosted route contract implemented in [`src/validation/run-validation.ts`](src/validation/run-validation.ts).
496
+
497
+ #### Deterministic merge precedence
498
+
499
+ The current merge order is fixed in [`runValidation()`](src/validation/run-validation.ts:106) so downstream systems can treat the writes as predictable rather than provider-order dependent:
500
+
501
+ - **Watchers / preorder count / shipping / competition** prefer Terapeak contract output when available, then fall back to the browse/current-market provider.
502
+ - **Market price** prefers Terapeak contract output, then the sold provider's median sold price, then the browse/current-market provider.
503
+ - **Sold day buckets** (`day1Sold` through `day5Sold`, plus `daysTracked`) prefer the sold provider and only fall back to browse-derived values when sold enrichment is missing.
504
+ - **Previous POB metrics** (`previousPobAvgPriceUsd`, `previousPobSellThroughPct`) are written from the Terapeak contract output when available.
505
+ - **Previous comeback first-week sales** (`previousComebackFirstWeekSales`) is written from the orchestration-side research provider when available.
506
+ - **Supportive social fields** are only written when a value is actually resolved, so the pipeline avoids blanking previously stored downstream data.
507
+
508
+ The validation signal contracts in [`TerapeakValidationSignals`](src/validation/types.ts:142) and [`PreviousComebackResearchSignals`](src/validation/types.ts:164) also back the new write fields in [`ValidationWrites`](src/validation/types.ts:176): `previousPobAvgPriceUsd`, `previousPobSellThroughPct`, and `previousComebackFirstWeekSales`.
509
+
510
+ ### Validation endpoints and auth model
511
+
512
+ Use the environment-scoped hosted routes for validation:
513
+
514
+ ```
515
+ POST /sandbox/validation/run
516
+ POST /production/validation/run
517
+
518
+ GET /sandbox/validation/health
519
+ GET /production/validation/health
520
+ ```
521
+
522
+ Both routes require the admin key:
523
+
524
+ ```
525
+ X-Admin-API-Key: YOUR_ADMIN_API_KEY
526
+ ```
527
+
528
+ Auth model summary:
529
+
530
+ - Validation routes are **hosted HTTP backend routes**, not MCP tool endpoints.
531
+ - They do **not** use MCP client auth for execution.
532
+ - They reuse the existing stored refresh-token-backed hosted user architecture.
533
+ - The caller authenticates with `X-Admin-API-Key`, and the server then impersonates the configured validation runner user for the target environment.
534
+ - Validation runner identity comes from `VALIDATION_RUNNER_USER_ID`, `VALIDATION_RUNNER_USER_ID_SANDBOX`, or `VALIDATION_RUNNER_USER_ID_PRODUCTION`.
535
+ - The validation runner must already have stored hosted tokens in the configured token store backend.
536
+
537
+ #### `POST /validation/run`
538
+
539
+ Runs the validation pipeline for the target environment.
540
+
541
+ - Uses the configured validation runner user ID for that environment
542
+ - Requires that stored refresh-token-backed eBay credentials already exist for that user
543
+ - Returns either:
544
+ - `status: "ok"` with `writes`, `decision`, and `debug`, or
545
+ - `status: "error"` with `errorCode`, `message`, `retryable`, and `nextCheckAt`
546
+
547
+ The request/response contract is defined in [`src/validation/types.ts`](src/validation/types.ts), and the orchestration behavior is implemented in [`src/validation/run-validation.ts`](src/validation/run-validation.ts).
548
+
549
+ The `writes` payload is intentionally non-destructive for supportive and placeholder-backed optional fields: if a social, Terapeak placeholder, or research placeholder provider cannot resolve data, the orchestration omits those optional writes instead of overwriting existing downstream values with empty placeholders.
550
+
551
+ #### `GET /validation/health`
552
+
553
+ Checks whether the validation runner is operational in the target environment.
554
+
555
+ This endpoint is intended for deployment diagnostics and returns:
556
+
557
+ - configured environment
558
+ - configured validation runner user ID
559
+ - whether stored tokens are present
560
+ - whether token refresh/authentication succeeded
561
+ - token status from the user-scoped eBay API client
562
+ - `authDebug` diagnostics including token endpoint resolution and credential presence
563
+ - provider availability summary
564
+
565
+ The diagnostics are especially useful after the OAuth token endpoint fix in [`getOAuthTokenBaseUrl()`](src/config/environment.ts:373) and the debug additions in [`getAuthDebugInfo()`](src/auth/oauth.ts:282). If the validation runner cannot refresh tokens, `/validation/health` shows the resolved token endpoint and any captured upstream response status/body excerpt.
566
+
567
+ ### Diagnostics and health endpoints
568
+
569
+ Use these endpoints together when validating a hosted deployment:
570
+
571
+ ```
572
+ GET /health
573
+ GET /whoami
574
+ GET /sandbox/validation/health
575
+ GET /production/validation/health
576
+ ```
577
+
578
+ Recommended debugging flow:
579
+
580
+ 1. Call `/health` to confirm the HTTP service is up.
581
+ 2. Call `/whoami` with a Bearer hosted session token to confirm the active hosted user session, bound environment, expiry, and revocation status.
582
+ 3. Call the matching env-scoped `/validation/health` route with `X-Admin-API-Key` to confirm the validation runner user is configured, stored tokens exist, and token refresh succeeds.
583
+
584
+ `/whoami` is especially useful when an operator wants to verify which hosted session is currently active before registering or troubleshooting the validation runner user. Validation routes themselves still authenticate with the admin key and a stored hosted runner identity, not with MCP auth.
585
+
586
+ The validation health response is also the main place to verify the OAuth token-endpoint derivation fix from [`getOAuthTokenBaseUrl()`](src/config/environment.ts:373). If a refresh fails, the `authDebug` block exposes the resolved endpoint, credential-presence flags, and captured upstream response excerpts.
587
+
588
+ ### Validation provider behavior and limitations
589
+
590
+ Current backend status:
591
+
592
+ - eBay live market snapshot support is implemented and wired into orchestration.
593
+ - Sold-data enrichment is implemented through a **temporary external provider** abstraction.
594
+ - Terapeak/eBay research and previous-comeback research are both wired into orchestration as **stable placeholder contracts**.
595
+ - Social support signals are implemented in phase 1.
596
+ - Chart data remains a stub.
597
+ - Validation is currently an **admin-operated hosted backend workflow**, not an MCP tool surface.
598
+
599
+ Provider behavior:
600
+
601
+ - **Browse/eBay provider:** [`src/validation/providers/ebay.ts`](src/validation/providers/ebay.ts) uses the eBay Browse API plus shared query fallback logic from [`src/validation/providers/query-utils.ts`](src/validation/providers/query-utils.ts). It walks multiple query candidates, records the selected query and tier in debug output, and uses heuristic matching rather than a strict catalog identity join.
602
+ - **Browse debug semantics:** validation debug now keeps browse candidate generation, selected query/tier, browse-specific sample size, and per-candidate result counts separate from sold-provider result counts so operators can tell whether the browse layer contributed a field, fell back to a weaker query, or returned no usable match.
603
+ - **Sold provider:** [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts) uses a temporary external sold-data source configured by `SOLD_ITEMS_API_URL` and `SOLD_ITEMS_API_KEY`. It uses the same query-fallback strategy as the browse provider and returns sold-price ranges, sample sold items, and recent sold-velocity buckets when available.
604
+ - **Terapeak / eBay research provider:** [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts) is currently a stable placeholder contract. It already defines the query/debug shape and output fields used by orchestration, including `previousPobAvgPriceUsd` and `previousPobSellThroughPct`, but live authenticated Terapeak/eBay research retrieval is not implemented yet.
605
+ - **Social provider:** [`src/validation/providers/social.ts`](src/validation/providers/social.ts) supports phase-1 Twitter/X recent activity, YouTube average-daily-views proxy data exposed through the `youtubeViews24hMillions` field, and Reddit recent post counts. These signals degrade gracefully on provider/API failure and are used as supportive indicators rather than authoritative demand truth.
606
+ - **Chart provider:** [`src/validation/providers/chart.ts`](src/validation/providers/chart.ts) is still a stub and does not currently contribute chart-based metrics.
607
+ - **Previous comeback research provider:** [`src/validation/providers/research.ts`](src/validation/providers/research.ts) is currently a stable placeholder contract for orchestration-side historical research. It returns the future-facing `previousComebackFirstWeekSales` field shape, and it documents `PERPLEXITY_API_KEY` for a later external-research implementation, but it does not yet perform live historical lookup.
608
+
609
+ Recommendation behavior:
610
+
611
+ - [`src/validation/recommendation.ts`](src/validation/recommendation.ts) now accepts Terapeak and research inputs alongside browse, sold, social, and chart signals.
612
+ - The decisioning remains intentionally conservative: Terapeak and research data can improve monitoring notes and confidence context, but the system still avoids aggressive automatic buy-state changes from partial or proxy signals alone.
613
+
614
+ Known limitations in the current implementation:
615
+
616
+ - The sold-data provider depends on external configuration via `SOLD_ITEMS_API_URL` and `SOLD_ITEMS_API_KEY`.
617
+ - If those sold-data variables are missing, validation still runs but sold enrichment degrades to an unavailable/error state rather than providing full historical-sales signals.
618
+ - The sold-data provider is temporary and intended to be replaced by an internal implementation later.
619
+ - The Terapeak provider contract is present, but there is no live authenticated Terapeak/eBay research integration yet.
620
+ - The previous-comeback research provider contract is present, but no live historical inference or Perplexity-backed lookup is implemented yet even when `PERPLEXITY_API_KEY` is configured.
621
+ - The browse provider still relies on heuristic query selection and fallback matching.
622
+ - The YouTube-backed `youtubeViews24hMillions` field is currently an **average daily views proxy**, not a true trailing 24-hour delta.
623
+ - Social signals are supportive/proxy data only and should not be presented as decisive automated buy logic.
624
+ - eBay-derived metrics are intentionally practical rather than exhaustive; for example, watchers are not yet populated by the live eBay provider.
625
+
626
+ ### Roadmap note: provider maturation
627
+
628
+ - The current sold-data implementation is explicitly interim. It is isolated behind [`src/validation/providers/ebay-sold.ts`](src/validation/providers/ebay-sold.ts) so we can replace the external-provider-backed implementation with our own internal sales-data system later **without changing downstream validation orchestration or the hosted validation route contract**.
629
+ - The Terapeak/eBay research layer is intentionally isolated behind [`src/validation/providers/terapeak.ts`](src/validation/providers/terapeak.ts) so a future authenticated research integration can drop in without changing the route contract or downstream writes.
630
+ - The orchestration-side historical research layer is intentionally isolated behind [`src/validation/providers/research.ts`](src/validation/providers/research.ts) so future previous-comeback resolution or external inference providers can be added without rewriting the validation runner.
631
+
422
632
  ### Remote client configuration
423
633
 
424
634
  Replace `https://your-server.com` with your actual `PUBLIC_BASE_URL`.
@@ -566,6 +776,16 @@ curl https://your-server.com/health
566
776
  # Verify a session token
567
777
  curl -H "Authorization: Bearer <session-token>" https://your-server.com/whoami
568
778
 
779
+ # Verify validation runner health for sandbox
780
+ curl https://your-server.com/sandbox/validation/health \
781
+ -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY"
782
+
783
+ # Run a hosted validation job
784
+ curl -X POST https://your-server.com/sandbox/validation/run \
785
+ -H "Content-Type: application/json" \
786
+ -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY" \
787
+ -d '{"validationId":"example-123","runType":"manual","cadence":"Daily","timestamp":"2026-03-31T00:00:00.000Z","item":{"recordId":"1","name":"Example Item","variation":[],"itemType":[],"releaseType":[],"releaseDate":null,"releasePeriod":[],"availability":[],"wholesalePrice":null,"supplierNames":[],"canonicalArtists":[],"relatedAlbums":[]},"validation":{"validationType":"default","buyDecision":"Hold","automationStatus":"Manual","autoCheckEnabled":false,"dDay":null,"artistTier":"unknown","initialBudget":null,"reserveBudget":null,"currentMetrics":{"avgWatchersPerListing":null,"preOrderListingsCount":null,"twitterTrending":false,"youtubeViews24hMillions":null,"redditPostsCount7d":null,"marketPriceUsd":null,"avgShippingCostUsd":null,"competitionLevel":null,"marketPriceTrend":"Stable","day1Sold":null,"day2Sold":null,"day3Sold":null,"day4Sold":null,"day5Sold":null,"daysTracked":null}}}'
788
+
569
789
  # Test MCP endpoint returns auth challenge when no token is provided
570
790
  curl -X POST https://your-server.com/sandbox/mcp \
571
791
  -H "Content-Type: application/json" \
@@ -610,6 +830,25 @@ curl -X POST https://your-server.com/admin/session/<token>/revoke \
610
830
  -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY"
611
831
  ```
612
832
 
833
+ ### Validation health is degraded
834
+
835
+ Start with the environment-scoped health endpoint:
836
+
837
+ ```bash
838
+ curl https://your-server.com/sandbox/validation/health \
839
+ -H "X-Admin-API-Key: YOUR_ADMIN_API_KEY"
840
+ ```
841
+
842
+ Common causes:
843
+
844
+ - `VALIDATION_RUNNER_USER_ID` or the env-specific override is missing
845
+ - the validation runner user has no stored refresh-token-backed credentials in the hosted token store
846
+ - the refresh token is expired or revoked upstream
847
+ - `SOLD_ITEMS_API_URL` or `SOLD_ITEMS_API_KEY` is missing, causing sold enrichment to degrade
848
+ - one or more social-provider credentials are absent, which causes the related supportive signal to degrade gracefully instead of failing the entire run
849
+
850
+ If `authDebug.tokenEndpoint` or the captured upstream response looks wrong, verify the environment-specific OAuth configuration and token-base resolution.
851
+
613
852
  ### Security checklist
614
853
 
615
854
  - Do not commit `.env` or session tokens to version control
@@ -37,7 +37,7 @@ export class AnalyticsApi {
37
37
  return await this.client.get(`${this.basePath}/traffic_report`, params);
38
38
  }
39
39
  catch (error) {
40
- throw new Error(`Failed to get traffic report: ${error instanceof Error ? error.message : 'Unknown error'}`);
40
+ throw new Error(`Failed to get traffic report: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
41
41
  }
42
42
  }
43
43
  /**
@@ -50,7 +50,7 @@ export class AnalyticsApi {
50
50
  return await this.client.get(`${this.basePath}/seller_standards_profile`);
51
51
  }
52
52
  catch (error) {
53
- throw new Error(`Failed to find seller standards profiles: ${error instanceof Error ? error.message : 'Unknown error'}`);
53
+ throw new Error(`Failed to find seller standards profiles: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
54
54
  }
55
55
  }
56
56
  /**
@@ -70,7 +70,7 @@ export class AnalyticsApi {
70
70
  return await this.client.get(`${this.basePath}/seller_standards_profile/${program}/${cycle}`);
71
71
  }
72
72
  catch (error) {
73
- throw new Error(`Failed to get seller standards profile: ${error instanceof Error ? error.message : 'Unknown error'}`);
73
+ throw new Error(`Failed to get seller standards profile: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
74
74
  }
75
75
  }
76
76
  /**
@@ -96,7 +96,7 @@ export class AnalyticsApi {
96
96
  return await this.client.get(`${this.basePath}/customer_service_metric/${customerServiceMetricType}/${evaluationType}`, params);
97
97
  }
98
98
  catch (error) {
99
- throw new Error(`Failed to get customer service metric: ${error instanceof Error ? error.message : 'Unknown error'}`);
99
+ throw new Error(`Failed to get customer service metric: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
100
100
  }
101
101
  }
102
102
  }
@@ -68,14 +68,14 @@ export class TradingApiClient {
68
68
  }
69
69
  catch (error) {
70
70
  const message = error instanceof Error ? error.message : 'Unknown HTTP error';
71
- throw new Error(`Trading API ${callName} request failed: ${message}`);
71
+ throw new Error(`Trading API ${callName} request failed: ${message}`, { cause: error });
72
72
  }
73
73
  let parsed;
74
74
  try {
75
75
  parsed = this.parser.parse(response.data);
76
76
  }
77
77
  catch (e) {
78
- throw new Error(`Failed to parse Trading API ${callName} response: ${e instanceof Error ? e.message : String(e)}`);
78
+ throw new Error(`Failed to parse Trading API ${callName} response: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
79
79
  }
80
80
  const result = (parsed[responseTag] || parsed);
81
81
  // Log warnings without failing
@@ -37,7 +37,7 @@ export class FeedbackApi {
37
37
  return await this.client.get(`${this.basePath}/awaiting_feedback`, params);
38
38
  }
39
39
  catch (error) {
40
- throw new Error(`Failed to get awaiting feedback: ${error instanceof Error ? error.message : 'Unknown error'}`);
40
+ throw new Error(`Failed to get awaiting feedback: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
41
41
  }
42
42
  }
43
43
  /**
@@ -55,7 +55,7 @@ export class FeedbackApi {
55
55
  });
56
56
  }
57
57
  catch (error) {
58
- throw new Error(`Failed to get feedback: ${error instanceof Error ? error.message : 'Unknown error'}`);
58
+ throw new Error(`Failed to get feedback: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
59
59
  }
60
60
  }
61
61
  /**
@@ -68,7 +68,7 @@ export class FeedbackApi {
68
68
  return await this.client.get(`${this.basePath}/feedback_rating_summary`);
69
69
  }
70
70
  catch (error) {
71
- throw new Error(`Failed to get feedback rating summary: ${error instanceof Error ? error.message : 'Unknown error'}`);
71
+ throw new Error(`Failed to get feedback rating summary: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
72
72
  }
73
73
  }
74
74
  /**
@@ -84,7 +84,7 @@ export class FeedbackApi {
84
84
  return await this.client.post(`${this.basePath}/feedback`, feedbackData);
85
85
  }
86
86
  catch (error) {
87
- throw new Error(`Failed to leave feedback: ${error instanceof Error ? error.message : 'Unknown error'}`);
87
+ throw new Error(`Failed to leave feedback: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
88
88
  }
89
89
  }
90
90
  /**
@@ -106,7 +106,7 @@ export class FeedbackApi {
106
106
  });
107
107
  }
108
108
  catch (error) {
109
- throw new Error(`Failed to respond to feedback: ${error instanceof Error ? error.message : 'Unknown error'}`);
109
+ throw new Error(`Failed to respond to feedback: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
110
110
  }
111
111
  }
112
112
  /**
@@ -21,7 +21,7 @@ export class MessageApi {
21
21
  return await this.client.post(`${this.basePath}/bulk_update_conversation`, updateData);
22
22
  }
23
23
  catch (error) {
24
- throw new Error(`Failed to bulk update conversation: ${error instanceof Error ? error.message : 'Unknown error'}`);
24
+ throw new Error(`Failed to bulk update conversation: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
25
25
  }
26
26
  }
27
27
  /**
@@ -53,7 +53,7 @@ export class MessageApi {
53
53
  return await this.client.get(`${this.basePath}/conversation`, params);
54
54
  }
55
55
  catch (error) {
56
- throw new Error(`Failed to get conversations: ${error instanceof Error ? error.message : 'Unknown error'}`);
56
+ throw new Error(`Failed to get conversations: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
57
57
  }
58
58
  }
59
59
  /**
@@ -69,7 +69,7 @@ export class MessageApi {
69
69
  return await this.client.get(`${this.basePath}/conversation/${conversationId}`);
70
70
  }
71
71
  catch (error) {
72
- throw new Error(`Failed to get conversation: ${error instanceof Error ? error.message : 'Unknown error'}`);
72
+ throw new Error(`Failed to get conversation: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
73
73
  }
74
74
  }
75
75
  /**
@@ -85,7 +85,7 @@ export class MessageApi {
85
85
  return await this.client.post(`${this.basePath}/send_message`, messageData);
86
86
  }
87
87
  catch (error) {
88
- throw new Error(`Failed to send message: ${error instanceof Error ? error.message : 'Unknown error'}`);
88
+ throw new Error(`Failed to send message: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
89
89
  }
90
90
  }
91
91
  /**
@@ -101,7 +101,7 @@ export class MessageApi {
101
101
  return await this.client.post(`${this.basePath}/update_conversation`, updateData);
102
102
  }
103
103
  catch (error) {
104
- throw new Error(`Failed to update conversation: ${error instanceof Error ? error.message : 'Unknown error'}`);
104
+ throw new Error(`Failed to update conversation: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
105
105
  }
106
106
  }
107
107
  /**
@@ -37,7 +37,7 @@ export class NegotiationApi {
37
37
  return await this.client.get(`${this.basePath}/find_eligible_items`, params);
38
38
  }
39
39
  catch (error) {
40
- throw new Error(`Failed to find eligible items: ${error instanceof Error ? error.message : 'Unknown error'}`);
40
+ throw new Error(`Failed to find eligible items: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
41
41
  }
42
42
  }
43
43
  /**
@@ -53,7 +53,7 @@ export class NegotiationApi {
53
53
  return await this.client.post(`${this.basePath}/send_offer_to_interested_buyers`, offerData);
54
54
  }
55
55
  catch (error) {
56
- throw new Error(`Failed to send offer to interested buyers: ${error instanceof Error ? error.message : 'Unknown error'}`);
56
+ throw new Error(`Failed to send offer to interested buyers: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
57
57
  }
58
58
  }
59
59
  /**
@@ -91,7 +91,7 @@ export class NegotiationApi {
91
91
  return await this.client.get(`${this.basePath}/offer/${offerId}`);
92
92
  }
93
93
  catch (error) {
94
- throw new Error(`Failed to get offer: ${error instanceof Error ? error.message : 'Unknown error'}`);
94
+ throw new Error(`Failed to get offer: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
95
95
  }
96
96
  }
97
97
  }
@@ -20,7 +20,7 @@ export class NotificationApi {
20
20
  return await this.client.get(`${this.basePath}/public_key/${publicKeyId}`);
21
21
  }
22
22
  catch (error) {
23
- throw new Error(`Failed to get public key: ${error instanceof Error ? error.message : 'Unknown error'}`);
23
+ throw new Error(`Failed to get public key: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
24
24
  }
25
25
  }
26
26
  /**
@@ -32,7 +32,7 @@ export class NotificationApi {
32
32
  return await this.client.get(`${this.basePath}/config`);
33
33
  }
34
34
  catch (error) {
35
- throw new Error(`Failed to get config: ${error instanceof Error ? error.message : 'Unknown error'}`);
35
+ throw new Error(`Failed to get config: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
36
36
  }
37
37
  }
38
38
  /**
@@ -47,7 +47,7 @@ export class NotificationApi {
47
47
  return await this.client.put(`${this.basePath}/config`, config);
48
48
  }
49
49
  catch (error) {
50
- throw new Error(`Failed to update config: ${error instanceof Error ? error.message : 'Unknown error'}`);
50
+ throw new Error(`Failed to update config: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
51
51
  }
52
52
  }
53
53
  /**
@@ -64,7 +64,7 @@ export class NotificationApi {
64
64
  return await this.client.get(`${this.basePath}/destination`, params);
65
65
  }
66
66
  catch (error) {
67
- throw new Error(`Failed to get destinations: ${error instanceof Error ? error.message : 'Unknown error'}`);
67
+ throw new Error(`Failed to get destinations: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
68
68
  }
69
69
  }
70
70
  /**
@@ -79,7 +79,7 @@ export class NotificationApi {
79
79
  return await this.client.get(`${this.basePath}/destination/${destinationId}`);
80
80
  }
81
81
  catch (error) {
82
- throw new Error(`Failed to get destination: ${error instanceof Error ? error.message : 'Unknown error'}`);
82
+ throw new Error(`Failed to get destination: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
83
83
  }
84
84
  }
85
85
  /**
@@ -94,7 +94,7 @@ export class NotificationApi {
94
94
  return await this.client.post(`${this.basePath}/destination`, destination);
95
95
  }
96
96
  catch (error) {
97
- throw new Error(`Failed to create destination: ${error instanceof Error ? error.message : 'Unknown error'}`);
97
+ throw new Error(`Failed to create destination: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
98
98
  }
99
99
  }
100
100
  /**
@@ -112,7 +112,7 @@ export class NotificationApi {
112
112
  return await this.client.put(`${this.basePath}/destination/${destinationId}`, destination);
113
113
  }
114
114
  catch (error) {
115
- throw new Error(`Failed to update destination: ${error instanceof Error ? error.message : 'Unknown error'}`);
115
+ throw new Error(`Failed to update destination: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
116
116
  }
117
117
  }
118
118
  /**
@@ -127,7 +127,7 @@ export class NotificationApi {
127
127
  return await this.client.delete(`${this.basePath}/destination/${destinationId}`);
128
128
  }
129
129
  catch (error) {
130
- throw new Error(`Failed to delete destination: ${error instanceof Error ? error.message : 'Unknown error'}`);
130
+ throw new Error(`Failed to delete destination: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
131
131
  }
132
132
  }
133
133
  /**
@@ -153,7 +153,7 @@ export class NotificationApi {
153
153
  return await this.client.get(`${this.basePath}/subscription`, params);
154
154
  }
155
155
  catch (error) {
156
- throw new Error(`Failed to get subscriptions: ${error instanceof Error ? error.message : 'Unknown error'}`);
156
+ throw new Error(`Failed to get subscriptions: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
157
157
  }
158
158
  }
159
159
  /**
@@ -169,7 +169,7 @@ export class NotificationApi {
169
169
  return await this.client.post(`${this.basePath}/subscription`, subscription);
170
170
  }
171
171
  catch (error) {
172
- throw new Error(`Failed to create subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
172
+ throw new Error(`Failed to create subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
173
173
  }
174
174
  }
175
175
  /**
@@ -185,7 +185,7 @@ export class NotificationApi {
185
185
  return await this.client.get(`${this.basePath}/subscription/${subscriptionId}`);
186
186
  }
187
187
  catch (error) {
188
- throw new Error(`Failed to get subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
188
+ throw new Error(`Failed to get subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
189
189
  }
190
190
  }
191
191
  /**
@@ -204,7 +204,7 @@ export class NotificationApi {
204
204
  return await this.client.put(`${this.basePath}/subscription/${subscriptionId}`, subscription);
205
205
  }
206
206
  catch (error) {
207
- throw new Error(`Failed to update subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
207
+ throw new Error(`Failed to update subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
208
208
  }
209
209
  }
210
210
  /**
@@ -220,7 +220,7 @@ export class NotificationApi {
220
220
  return await this.client.delete(`${this.basePath}/subscription/${subscriptionId}`);
221
221
  }
222
222
  catch (error) {
223
- throw new Error(`Failed to delete subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
223
+ throw new Error(`Failed to delete subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
224
224
  }
225
225
  }
226
226
  /**
@@ -236,7 +236,7 @@ export class NotificationApi {
236
236
  return await this.client.post(`${this.basePath}/subscription/${subscriptionId}/disable`, {});
237
237
  }
238
238
  catch (error) {
239
- throw new Error(`Failed to disable subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
239
+ throw new Error(`Failed to disable subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
240
240
  }
241
241
  }
242
242
  /**
@@ -252,7 +252,7 @@ export class NotificationApi {
252
252
  return await this.client.post(`${this.basePath}/subscription/${subscriptionId}/enable`, {});
253
253
  }
254
254
  catch (error) {
255
- throw new Error(`Failed to enable subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
255
+ throw new Error(`Failed to enable subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
256
256
  }
257
257
  }
258
258
  /**
@@ -268,7 +268,7 @@ export class NotificationApi {
268
268
  return await this.client.post(`${this.basePath}/subscription/${subscriptionId}/test`, {});
269
269
  }
270
270
  catch (error) {
271
- throw new Error(`Failed to test subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
271
+ throw new Error(`Failed to test subscription: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
272
272
  }
273
273
  }
274
274
  /**
@@ -284,7 +284,7 @@ export class NotificationApi {
284
284
  return await this.client.get(`${this.basePath}/topic/${topicId}`);
285
285
  }
286
286
  catch (error) {
287
- throw new Error(`Failed to get topic: ${error instanceof Error ? error.message : 'Unknown error'}`);
287
+ throw new Error(`Failed to get topic: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
288
288
  }
289
289
  }
290
290
  /**
@@ -310,7 +310,7 @@ export class NotificationApi {
310
310
  return await this.client.get(`${this.basePath}/topic`, params);
311
311
  }
312
312
  catch (error) {
313
- throw new Error(`Failed to get topics: ${error instanceof Error ? error.message : 'Unknown error'}`);
313
+ throw new Error(`Failed to get topics: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
314
314
  }
315
315
  }
316
316
  /**
@@ -329,7 +329,7 @@ export class NotificationApi {
329
329
  return await this.client.post(`${this.basePath}/subscription/${subscriptionId}/filter`, filter);
330
330
  }
331
331
  catch (error) {
332
- throw new Error(`Failed to create subscription filter: ${error instanceof Error ? error.message : 'Unknown error'}`);
332
+ throw new Error(`Failed to create subscription filter: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
333
333
  }
334
334
  }
335
335
  /**
@@ -348,7 +348,7 @@ export class NotificationApi {
348
348
  return await this.client.get(`${this.basePath}/subscription/${subscriptionId}/filter/${filterId}`);
349
349
  }
350
350
  catch (error) {
351
- throw new Error(`Failed to get subscription filter: ${error instanceof Error ? error.message : 'Unknown error'}`);
351
+ throw new Error(`Failed to get subscription filter: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
352
352
  }
353
353
  }
354
354
  /**
@@ -367,7 +367,7 @@ export class NotificationApi {
367
367
  return await this.client.delete(`${this.basePath}/subscription/${subscriptionId}/filter/${filterId}`);
368
368
  }
369
369
  catch (error) {
370
- throw new Error(`Failed to delete subscription filter: ${error instanceof Error ? error.message : 'Unknown error'}`);
370
+ throw new Error(`Failed to delete subscription filter: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
371
371
  }
372
372
  }
373
373
  }