# eBay Product Scraper - Prices, Auctions & Buy It Now (`harvestlab/ebay-scraper`) Actor

Scrape active eBay listings across 13 markets: prices, shipping, sellers, auction bids, Buy It Now offers, fallback listing recovery, and diagnostics.

- **URL**: https://apify.com/harvestlab/ebay-scraper.md
- **Developed by:** [harvestlab](https://apify.com/harvestlab) (community)
- **Categories:** E-commerce, AI
- **Features:** Limited permissions
- **Stats:** 231 total users, 13 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 product scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## eBay Product Scraper - Prices, Auctions & Buy It Now

Use this eBay product scraper to extract active listings across 13 country markets, including prices, shipping, seller data, auction bids, Buy It Now listings, images, item IDs, and diagnostics.

> **Access status - October 25, 2026:** owner cloud checks using residential and direct connections recovered no priced listings. eBay and the priced fallback returned HTTP 403; an earlier direct run returned only a price-less discovery row. Do not use these checks as evidence of a working price feed. Verify one qualified result on your own authorized access path before scheduling or scaling. Diagnostics have no product or dataset-item fee; startup charges still apply.

> **Scheduled billing change:** from **November 8, 2026 at 17:56 Europe/Amsterdam (16:56 UTC)**, customers pay Apify platform usage in addition to the existing event fees. All event prices stay the same. Until then, platform usage remains included in event pricing. After the change, platform usage and startup charges can apply even when a run is blocked or returns no listings.

**Best first run**

```json
{
  "searchQuery": "sony wh-1000xm5",
  "ebayDomain": "ebay.com",
  "maxProducts": 1,
  "requirePrice": true,
  "fetchDetails": false,
  "enableAiAnalysis": false
}
```

Use this actor when you need an eBay product scraper, active listing monitor, auction intelligence feed, price-band dataset, or cross-market e-commerce monitor and you can tolerate eBay's current anti-bot variability. Start with an active keyword run; treat direct item URLs and `soldOnly: true` as advanced proxy-dependent JSON modes until your path proves it can reach item or completed-sale pages.

Related commerce workflows: use [Amazon Scraper](https://apify.com/harvestlab/amazon-scraper) for ASIN and Best Sellers Rank tracking.

> **No cookies, no OAuth, no API keys (for the scraping)** - runs on public data only. Hosted AI providers require your own API key when enabled; self-hosted Ollama needs no key. Provider token charges are separate from Actor report events.

Scrape eBay product listings with prices, shipping, seller ratings, condition, and bid counts across all 13 eBay country domains. If active search pages are blocked, the actor attempts fallback listing recovery with a reduced but billable active-listing field set. Direct item URLs and sold-listings intelligence are advanced proxy-path-dependent JSON modes. Auction + Buy It Now + optional AI market analysis.

### eBay Product Scraper

Search eBay by keyword to extract item IDs, titles, prices, shipping, seller names, feedback scores, condition, bids, time left, listing type, sold count, image URL, location, currency, and marketplace domain. Use it for resale research, competitor monitoring, auction tracking, marketplace pricing, and cross-border e-commerce analysis.

### Sold Listings Mode Status

`soldOnly: true` is an advanced JSON-only mode, hidden from the standard Console form because eBay blocks completed-sale pages on many Apify-hosted paths. Only enable it after a tiny test proves your own proxy path can reach those pages. Blocked sold-listings runs emit diagnostics and do not charge listing or summary events. Active-listing access does not prove sold-listing access.

### What It Does

eBay Scraper extracts structured product data from all 13 eBay country domains - US, UK, Germany, France, Italy, Spain, Australia, Canada, Netherlands, Austria, Switzerland, Ireland, and Poland. Provide a keyword and the actor returns 25+ fields per result: title, price, currency, shipping, condition, seller, feedback score, bid count, time left, listing type, sold count, and image URL. Advanced JSON inputs also exist for direct listing URLs and sold-listings tests when your proxy path supports them.

**Operating modes:**

**1. Active-listings mode** (default) - attempts to extract active auction and Buy It Now listings matching your keyword. Returns up to 100 results per run. Primary eBay search supports sort order, price range, condition, and listing-type filters. Charges $0.003 per qualifying priced listing.

Fallback indexes do not verify item condition or preserve eBay sort order. They are used only with `itemCondition: "any"` and `sortBy: "best_match"`. When a filtered primary search fails, the actor stops with a diagnostic instead of returning broader, differently ordered results. It never silently removes your filters. Fallback rows carry `source: "picclick_fallback"` or `source: "search_index_fallback"`; indexed prices are not a guarantee that the listing remains available or that its current checkout price matches. `scraped_at` records retrieval time, not the index's last update.

Fallback price-range filters require an explicit price in the selected marketplace's currency. Different or unverified currencies are excluded; the actor does not perform exchange-rate conversion.

If eBay and the full-listing fallback both challenge a live search, the actor can return clearly marked `data_completeness: "discovery_only"` rows from a public search index. Those recovery rows contain the verified eBay item ID, canonical URL, title, and indexed description snippet; price, shipping, seller, and auction fields remain empty rather than being guessed.

Discovery-only and partial rows do not incur the $0.003 `product-scraped` event; Apify's standard $0.00001 default-dataset item event may still apply.

For price workflows, set `requirePrice: true`. This excludes incomplete and discovery-only listings from the default dataset and skips the price-less search-index fallback. It requires a valid item identity, title, positive price with matching currency evidence, and a supported listing type. It does not verify that an indexed listing remains available. Missing results are not zero prices, sales, removals, or price changes.

**2. Advanced direct item URL mode** (`productUrls`, JSON input only) - accepts known `/itm/` URLs and tries the primary eBay item page first, then fallback item recovery when eBay blocks the page. This mode is proxy-path dependent. Charges $0.003 only when a listing is recovered.

**3. Advanced sold-listings mode** (`soldOnly=true`, JSON input only) - switches to eBay's completed-sale filter (`LH_Sold=1&LH_Complete=1`, ~90-day window). This mode is currently proxy-path dependent and may return diagnostics when eBay blocks completed-sale pages. Sold-listing and summary event fees apply only when qualifying sold rows or summaries are emitted.

**AI analysis** (`enableAiAnalysis=true`) - sends scraped listings to OpenRouter, Anthropic Claude, Google Gemini, OpenAI GPT, or self-hosted Ollama for a market overview, price-bracket analysis, seller-landscape summary, and buying recommendations. Charges $0.05 per analysis.

By default, runs try HTTP extraction with browser recovery, polite 1-2.5 second delays, and cross-page deduplication. HTTP-only execution is optional. Current eBay HTTP 403 blocks trigger active-listing fallback where permitted by the input filters, then diagnostics if no usable listing data can be recovered.

**Supported eBay Domains:**

| Domain | Country | Currency |
|--------|---------|----------|
| `ebay.com` | United States | USD |
| `ebay.co.uk` | United Kingdom | GBP |
| `ebay.de` | Germany | EUR |
| `ebay.fr` | France | EUR |
| `ebay.it` | Italy | EUR |
| `ebay.es` | Spain | EUR |
| `ebay.com.au` | Australia | AUD |
| `ebay.ca` | Canada | CAD |
| `ebay.nl` | Netherlands | EUR |
| `ebay.at` | Austria | EUR |
| `ebay.ch` | Switzerland | CHF |
| `ebay.ie` | Ireland | EUR |
| `ebay.pl` | Poland | PLN |

### Features

- **Active listing fallback recovery** - active keyword runs try a fallback listing index when eBay returns HTTP 403
- **Advanced sold-listings mode** (JSON-only watchlist) - completed-sale records and summaries when your proxy path can reach eBay completed-sale pages
- **13 eBay country domains** - US, UK, DE, FR, IT, ES, AU, CA, NL, AT, CH, IE, PL with automatic currency resolution
- **Auction + Buy It Now** - both listing types in a single run, filter by either or scrape all
- **25+ fields per listing** - title, price, currency, MSRP/list_price, shipping, condition, seller, feedback score, bids, time-left, sold count, sold date, sponsored flag, best-offer flag, images, location
- **Detail-page enrichment** (`fetchDetails=true`) - seller information, full item specifics, and complete description from individual listing pages
- **Flexible filtering** - price range, item condition (new/used/refurbished/open box), listing type, sort order
- **5 AI providers** - OpenRouter (recommended, 300+ models), Anthropic Claude, Google Gemini, OpenAI GPT, Ollama (self-hosted)
- **Pay-per-event pricing** - qualified listing and completed-analysis events, plus startup and default-dataset item fees; platform usage is billed separately from November 8, 2026 at 17:56 Europe/Amsterdam
- **Cross-page deduplication** - `maxProducts` counts unique listings, not raw slots

### Use Cases

- **Resellers & Arbitrage** - monitor active prices, auction bids, and Buy It Now offers across multiple eBay domains.
- **Price Monitoring & Alerts** - schedule daily active-listing runs to track asking-price changes for specific SKUs across marketplaces.
- **Market Research** - analyze pricing distribution, seller landscape, and demand signals (bids, sold counts) for any product category. AI analysis adds structured price-bracket breakdowns and buying recommendations.
- **Competitive Intelligence** - monitor competitor pricing and seller strategies across multiple regions. Track how prices differ between ebay.com, ebay.co.uk, and ebay.de for the same product.
- **Auction Intelligence** - filter to auctions only, sort by ending soonest, and track bid counts and time-left for active listings. Identify underpriced auctions before they close.
- **E-commerce Sourcing** - search for wholesale or bulk offers, then inspect seller and shipping data when available. The primary search supports Buy It Now and condition filters; there is no free-shipping input filter.
- **Vintage & Collectibles Pricing** - compare active asking prices and auction pressure; use sold-listings intelligence only after a small completed-sale test run succeeds on your proxy path.
- **Academic & Policy Research** - export structured datasets for price-index research, consumer behavior studies, or e-commerce market analysis.

### Input

#### Search Parameters

- `searchQuery` - Keyword to search for on eBay (e.g., `"laptop"`, `"vintage Rolex"`, `"pokemon cards"`). Also accepts CLI aliases `query`, `q`, `search`, `keyword`, and `searchTerm` for compatibility with existing workflows.
- `productUrls` - Hidden from the standard Console form. Advanced JSON-only list of direct eBay listing URLs (e.g., `https://www.ebay.com/itm/123456789`). Direct item pages and fallback item lookups are proxy-path dependent; use `searchQuery` for production runs.
- `ebayDomain` - eBay country domain. Options: `ebay.com` (US, default), `ebay.co.uk` (UK), `ebay.de` (Germany), `ebay.fr` (France), `ebay.it` (Italy), `ebay.es` (Spain), `ebay.com.au` (Australia), `ebay.ca` (Canada), `ebay.nl` (Netherlands), `ebay.at` (Austria), `ebay.ch` (Switzerland), `ebay.ie` (Ireland), `ebay.pl` (Poland). Hidden API aliases `domain` and legacy `ebaySite` are also accepted.
- `listingType` - Filter by listing format: `all` (default), `auction`, or `buy_it_now`.
- `sortBy` - Sort results: `best_match` (default), `price_low`, `price_high`, `ending_soonest`, `newly_listed`.
- `itemCondition` - Filter by condition: `any` (default), `new`, `open_box`, `refurbished`, `used`.
- `minPrice` / `maxPrice` - Price range filter in the domain's local currency.
- `maxProducts` - Maximum number of results per run (1-100, default 25). Advanced `soldOnly=true` is capped at 50 for bill safety.
- `fetchDetails` - Visit each listing page for seller info, full description, and item specifics (slower, ~2-3x run time).

#### Execution And Result Quality

- `useBrowser` - Default `true`, preserving the existing recovery path and requiring at least 4096 MB. Set `false` for HTTP-only search and detail requests; no browser is launched. HTTP-only runs accept allocations from 512 MB. Smaller allocations also provide less CPU, so they may time out sooner and are not guaranteed to cost less overall. Proxy settings are respected in both modes; a failed configured proxy never silently switches to a direct connection.
- `requirePrice` - Default `false` for compatibility. Set `true` for qualified priced listings only. Excluded rows request neither product nor default-dataset item fees. If no row qualifies, inspect Diagnostics. A successful run status alone is not a successful price check.

Both inputs must be JSON booleans, not strings. Start with one result, details and AI disabled, and a short timeout. Keep browser mode at 4096 MB; a lower allocation is rejected with an input diagnostic. The default allocation remains 4096 MB. A smaller-memory run is not evidence of reliable access, complete data, or profitability.

#### Advanced Sold-Listings Intelligence

- `soldOnly` - Hidden from the standard Console form. Set `true` in JSON input only after a test proves completed-sale pages are reachable for your proxy path. Default: `false`.
- `soldDateLookback` - Window in days for velocity calculation (30-365, default 90). eBay retains ~90 days of sold data.

#### AI Analysis

- `enableAiAnalysis` - Enable AI market analysis. Default: `false`. Hosted providers require your own API key; self-hosted Ollama needs no key.
- `llmProvider` - AI backend: `openrouter` (default), `anthropic`, `google`, `openai`, or `ollama`.
- `llmModel` - Override the provider default model. Leave blank to use the recommended default.
- `openrouterApiKey` - OpenRouter API key ([openrouter.ai/keys](https://openrouter.ai/keys)).
- `anthropicApiKey` - Anthropic API key ([console.anthropic.com](https://console.anthropic.com/settings/keys)).
- `googleApiKey` - Google AI API key ([aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey)).
- `openaiApiKey` - OpenAI API key ([platform.openai.com/api-keys](https://platform.openai.com/api-keys)).
- `ollamaBaseUrl` - Ollama base URL (default: `http://localhost:11434`). No API key needed for self-hosted.

#### Proxy Settings

- `proxyConfiguration` - Apify Proxy settings. eBay currently blocks many Apify-hosted runs, including some residential exits. Active searches have fallback listing recovery, but sold-listings mode still needs eBay completed-sale pages to be reachable. If you bring a premium proxy, match its country to the selected eBay domain and validate with a tiny run before scaling.

### Output

The default dataset contains buyer-facing listing, sold-summary, and optional AI-analysis records. Blocked or zero-result runs write `diagnostic_item: true` records only to the separate `diagnostics` dataset, available through the run's Diagnostics output link. Diagnostics have no product or dataset-item fee. The existing Actor startup fee still applies, as does separately billed platform usage from November 8, 2026 at 17:56 Europe/Amsterdam.

#### Listing Item Fields

| Field | Description |
|-------|-------------|
| `itemId` | eBay item ID (the numeric string from the `/itm/` URL path) |
| `title` | Product listing title |
| `url` | Direct link to the eBay listing |
| `price` | Numeric asking price observed in the stated source; indexed prices may be stale |
| `price_text` | Price as displayed (e.g., `"$29.99"`) |
| `currency` | ISO 4217 currency code (e.g., `USD`, `GBP`, `EUR`) |
| `list_price` | Strikethrough / MSRP numeric price if discounted, else `null` |
| `list_price_text` | Strikethrough price as displayed (e.g., `"$299.99"`) |
| `shipping_cost` | Shipping cost as float (0.0 = free, null = unknown) |
| `shipping_text` | Shipping info as displayed |
| `free_shipping` | Bool - `true` when `shipping_cost == 0` |
| `condition` | Item condition (New, Used, Refurbished, Open Box, etc.) |
| `seller` | Combined seller string, e.g. `"logitech 99.6% positive (20.7K)"` |
| `seller_username` | Bare seller username |
| `seller_feedback_percentage` | Seller feedback percentage as float |
| `seller_feedback_count` | Feedback count as integer (parsed from `"(20.7K)"` -> `20700`) |
| `bids` | Number of bids (auction items only) |
| `end_time` | Auction end date/time in ISO 8601 format (auction items only, else `null`) |
| `time_left` | Time remaining for auctions as human-readable text (e.g., `"2d 4h"`) |
| `sold_price` | Final sale price for completed auction listings in `soldOnly` mode (else `null`) |
| `listing_type` | `auction`, `buy_it_now`, or `buy_it_now_or_best_offer` |
| `best_offer` | Bool - `true` when seller accepts offers |
| `is_sponsored` | Bool - `true` for promoted/sponsored listings |
| `image_url` | Product image URL |
| `sold_info` | Raw sold/watching badge text (e.g., `"184+ sold"`) |
| `sold_count` | Integer parsed from `sold_info` (`"2.1K+ sold"` -> `2100`) |
| `sold_date` | ISO-8601 date when listing sold - populated only in `soldOnly` mode |
| `sold_date_text` | Raw sold-date caption as displayed |
| `is_sold` | Bool - `true` for completed-sale listings |
| `location` | Seller location |
| `domain` | eBay domain used for scraping |
| `source` | Retrieval source, including `ebay_search`, `ebay_detail`, `picclick_fallback`, or `search_index_fallback` |
| `data_completeness` | Listing qualification; incomplete and discovery-only rows are excluded when `requirePrice=true` |
| `search_query` | Original search term |
| `scraped_at` | ISO 8601 timestamp of extraction |

#### Sample Listing Item

```json
{
    "itemId": "295854732101",
    "title": "Pokemon TCG Scarlet & Violet Booster Box - 36 Packs - Sealed",
    "url": "https://www.ebay.com/itm/295854732101",
    "price": 129.99,
    "price_text": "$129.99",
    "currency": "USD",
    "list_price": 159.99,
    "list_price_text": "$159.99",
    "shipping_cost": 0.0,
    "shipping_text": "Free shipping",
    "free_shipping": true,
    "condition": "New",
    "seller": "pokemon_central 99.8% positive (14.2K)",
    "seller_username": "pokemon_central",
    "seller_feedback_percentage": 99.8,
    "seller_feedback_count": 14200,
    "bids": null,
    "end_time": null,
    "time_left": null,
    "sold_price": null,
    "listing_type": "buy_it_now",
    "best_offer": false,
    "is_sponsored": false,
    "image_url": "https://i.ebayimg.com/images/g/abc123/s-l1600.webp",
    "sold_info": "184+ sold",
    "sold_count": 184,
    "location": "Los Angeles, CA",
    "domain": "ebay.com",
    "search_query": "pokemon cards",
    "scraped_at": "2026-04-18T10:15:00Z"
}
```

#### Sold-Listings Summary Item

When `soldOnly=true`, each run also emits a single `sold_listings_summary` item:

```json
{
    "type": "sold_listings_summary",
    "search_query": "pokemon booster box",
    "domain": "ebay.com",
    "lookback_days": 90,
    "n_sold_listings": 100,
    "currency": "USD",
    "recommended_list_price": 129.95,
    "median_price": 119.50,
    "mean_price": 128.30,
    "p10_p90_range": {"p10": 65.00, "p25": 89.99, "p75": 149.99, "p90": 199.00},
    "price_range": {"min": 24.99, "max": 349.00},
    "median_days_to_sell": 18,
    "p90_days_to_sell": 62,
    "market_velocity": "hot",
    "confidence_score": 0.87,
    "date_coverage_ratio": 0.92,
    "sold_listings_trend": {
        "has_history": true,
        "price_delta_7d_pct": -2.1,
        "price_delta_30d_pct": 5.4,
        "volume_delta_pct": 12.0,
        "velocity_delta": "steady -> hot",
        "velocity_rank_delta": 1,
        "direction": "up",
        "sample_count_history": 14,
        "previous_recommended_list_price": 124.50
    },
    "generated_at": "2026-04-22T23:00:00Z",
    "methodology": "Sold-listings aggregation over eBay LH_Sold=1&LH_Complete=1 completed-sale filter"
}
```

#### Pricing

The event fees below are unchanged. **From November 8, 2026 at 17:56 Europe/Amsterdam (16:56 UTC), Apify platform usage is billed separately in addition to these fees.** Until that time, platform usage remains included in event pricing. Usage varies with runtime, memory, storage, data transfer and proxy use; it can be charged even if the run is blocked or returns no listings. Check the current and upcoming pricing in Apify Console before running.

| Event | Price | Description |
|-------|-------|-------------|
| `product-scraped` | $0.003 | Per live listing (auction or BIN) in default mode |
| `sold-listing-scraped` | $0.005 | Per completed-sale listing in `soldOnly` mode |
| `sold-summary-generated` | $0.05 | Once per run when the `sold_listings_summary` is emitted |
| `sold-trend-detected` | $0.03 | Once per run when `sold_listings_trend` is populated from history (free on first run) |
| `ai-analysis-completed` | $0.05 | Per AI market analysis report |

**Listing-event examples, excluding startup, default-dataset fees and any separately billed platform usage**: 50 qualified active listings = $0.15. When completed-sale pages are reachable, 50 sold listings + summary (first run) = $0.30. Same with trend (subsequent runs) = $0.33. Diagnostic-only blocked runs do not charge listing or summary events. The existing startup event is $0.00005 per GB (minimum one event) and the default-dataset item event is $0.00001 per item. Owner test usage is not buyer revenue.

### Quick Start

Test access with a single active-listing request:

```json
{
    "searchQuery": "mechanical keyboard",
    "maxProducts": 1,
    "requirePrice": true,
    "ebayDomain": "ebay.com"
}
```

Run this in Apify Console, then inspect the result's source, price and currency. An empty default dataset is not a successful price check; open Diagnostics for the explanation. Increase the limit only after useful output is confirmed.

**Advanced sold-listings test (JSON input only):**

```json
{
    "searchQuery": "pokemon booster box",
    "ebayDomain": "ebay.com",
    "soldOnly": true,
    "maxProducts": 3,
    "soldDateLookback": 90
}
```

Use this only after you have a proxy path that can reach eBay completed-sale pages. When reachable, it returns completed-sale records plus a `sold_listings_summary`; when blocked, it returns a diagnostic item instead.

**Filtered auction search:**

```json
{
    "searchQuery": "vintage Rolex watch",
    "ebayDomain": "ebay.com",
    "listingType": "auction",
    "sortBy": "ending_soonest",
    "minPrice": 500,
    "maxPrice": 5000,
    "maxProducts": 50
}
```

**Used electronics on eBay UK:**

```json
{
    "searchQuery": "MacBook Pro",
    "ebayDomain": "ebay.co.uk",
    "itemCondition": "used",
    "sortBy": "price_low",
    "maxProducts": 30
}
```

**With AI market analysis:**

```json
{
    "searchQuery": "Nintendo Switch OLED",
    "maxProducts": 50,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-..."
}
```

**Advanced direct product URL test (JSON input only):**

```json
{
    "productUrls": [
        "https://www.ebay.com/itm/123456789",
        "https://www.ebay.com/itm/987654321"
    ]
}
```

> **Proxy note**: eBay is currently anti-bot sensitive from cloud and datacenter IPs. Start with `maxProducts: 1-3`, use a residential or premium proxy path, and match the exit country to the selected eBay domain before scaling.

#### MCP Quickstart - call this actor from Claude / Cursor / ChatGPT

Open Apify's hosted MCP server with this actor selected:

```text
https://mcp.apify.com?tools=harvestlab/ebay-scraper
```

Then prompt the agent:

> "Use the harvestlab/ebay-scraper actor on Apify to find active vintage Polaroid camera listings on ebay.co.uk with prices, shipping, seller fields, and item URLs. Push the results back as JSON."

Through Apify MCP, the agent can generate the right input, run the actor, and pipe the typed output back into your conversation.

### Use with AI agents

ebay-scraper returns structured active-listing records with auction vs. Buy It Now status, bid count, seller feedback, currency, prices, and item URLs across 13 marketplaces. Use it as a single tool inside resale-arbitrage, price-discovery, or marketplace-monitoring workflows.

#### LangChain single-query tool

This optional runner is for your own **already proven, authorized access path**.
The current owner checks have not recovered a useful eBay price feed; an SDK
example does not change that status or establish source API approval. Start with
one listing. Each invocation starts one paid Actor run, even if eBay blocks it.
Startup charges apply; separately billed platform usage begins November 8, 2026
at 17:56 Europe/Amsterdam. The $0.10 limit below is an event-charge cap, not an
all-in quote. Keep all existing access and billing warnings above.

The example uses typed `apify-client` 3.3/3.4 results and your existing LangChain
installation. It accepts one query and an explicit canonical `ebayDomain`, bounds
results to 1–10, and leaves sold listings, browser rendering, details and AI off.
Supply only your own previously validated proxy configuration; none is invented
or assumed to restore access.

```python
from datetime import timedelta
from decimal import Decimal
import os
from apify_client import ApifyClient
from langchain_core.tools import tool

EBAY_DOMAINS = {"ebay." + suffix for suffix in (
    "com co.uk de fr it es com.au ca nl at ch ie pl"
).split()}
client = ApifyClient(os.environ["APIFY_TOKEN"])

@tool
def fetch_ebay_priced_listings(searchQuery: str, ebayDomain: str = "ebay.com",
                               maxProducts: int = 1, proxyConfiguration: dict | None = None) -> list[dict]:
    """Run one bounded active-listing search only after your access path is proven."""
    if not searchQuery.strip() or len(searchQuery) > 200:
        raise ValueError("Provide one nonempty query of at most 200 characters")
    if ebayDomain not in EBAY_DOMAINS or not 1 <= maxProducts <= 10:
        raise ValueError("Use a supported canonical ebayDomain and 1-10 results")
    run_input = {"searchQuery": searchQuery.strip(), "ebayDomain": ebayDomain,
                 "maxProducts": maxProducts, "requirePrice": True,
                 "useBrowser": False, "fetchDetails": False,
                 "soldOnly": False, "enableAiAnalysis": False}
    if proxyConfiguration is not None:
        run_input["proxyConfiguration"] = proxyConfiguration
    run = client.actor("harvestlab/ebay-scraper").call(
        run_input=run_input, memory_mbytes=4096,
        run_timeout=timedelta(seconds=120), max_total_charge_usd=Decimal("0.10"),
        restart_on_error=False,
    )
    if run is None or run.status != "SUCCEEDED":
        raise RuntimeError("No completed observation; retain your last accepted data")
    rows = list(client.dataset(run.default_dataset_id).iterate_items(limit=100))
    if not rows:
        raise RuntimeError(f"No priced listings in run {run.id}; inspect its Diagnostics output")
    return rows

## This would start one paid run; do not invoke until your own access is proven:
## listings = fetch_ebay_priced_listings.invoke({"searchQuery": "vintage polaroid sx-70",
##     "ebayDomain": "ebay.co.uk", "maxProducts": 1, "proxyConfiguration": your_validated_proxy})
```

`requirePrice=true` excludes partial and discovery-only rows; it does not prove
that an indexed price is current or that inventory remains available. Empty
Listings output is not a successful price check. Read the same run's Diagnostics
output and preserve your last accepted data; do not automatically retry, scale or
silently switch domains. AI is off and this wrapper sends no notifications.

#### LangGraph asking-price comparison

This graph performs **no Actor calls**. Give it listing rows from already
completed, accepted `requirePrice=true` exports for the products you chose to
compare. It checks basic listing identity, source, positive price and timestamp
fields, then compares marketplace medians only when all accepted rows use the
same currency and at least two marketplaces are represented. It does not
reproduce the Actor's full qualification, verify source freshness or establish
that different listings are comparable products.

The threshold is a **percentage gap in observed asking prices**. Shipping, taxes,
condition, fees, exchange rates and realized profit are not included. Mixed
currencies or too little evidence produce an explicit no-signal result, never an
arbitrage or profit claim. This independent block needs only LangGraph; it does
not need an Apify token, SDK client or the runner block above.

```python
from datetime import datetime
from decimal import Decimal, InvalidOperation
import re
from statistics import median
from typing import TypedDict
from urllib.parse import urlsplit
from langgraph.graph import StateGraph, START, END

class PriceGapState(TypedDict, total=False):
    observations: list[dict]
    spread_threshold_pct: float
    comparison: dict

DOMAINS = {"ebay." + suffix for suffix in (
    "com co.uk de fr it es com.au ca nl at ch ie pl"
).split()}

def accepted_listing(row: dict) -> dict | None:
    try:
        if (not isinstance(row, dict) or row.get("diagnostic_item") or row.get("block_detected")
                or row.get("result_type") or row.get("error") or row.get("type")
                or row.get("data_completeness") or row.get("is_sold") or row.get("sold_date")
                or row.get("sold_date_text") or row.get("source") not in {"ebay_search", "ebay_detail", "picclick_fallback"}):
            return None
        if row["source"] == "ebay_detail" and row.get("listing_status") != "active":
            return None
        domain, currency = row["domain"], row["currency"]
        item_id = str(row.get("item_id") or row.get("itemId") or "")
        parsed = urlsplit(row["url"])
        match = re.fullmatch(r"/itm/(?:[^/]+/)?([0-9]{9,15})/?", parsed.path)
        price = Decimal(str(row["price"]))
        observed = datetime.fromisoformat(row["scraped_at"].replace("Z", "+00:00"))
        if (domain not in DOMAINS or (parsed.hostname or "").removeprefix("www.") != domain
                or parsed.scheme not in {"http", "https"} or parsed.username or parsed.password or parsed.port
                or not match or match.group(1) != item_id or not str(row.get("title") or "").strip()
                or not re.fullmatch(r"[A-Z]{3}", currency) or not price.is_finite() or price <= 0
                or observed.utcoffset() is None or row.get("listing_type") not in {
                    "auction", "buy_it_now", "buy_it_now_or_best_offer"}):
            return None
        return {"key": domain + ":" + item_id, "domain": domain, "currency": currency,
                "price": price, "observed": observed}
    except (KeyError, TypeError, ValueError, AttributeError, InvalidOperation):
        return None

def compare_prices(state: PriceGapState) -> dict:
    threshold = Decimal(str(state.get("spread_threshold_pct", 25)))
    rows = state.get("observations", [])
    if not threshold.is_finite() or threshold <= 0 or not isinstance(rows, list) or len(rows) > 1000:
        raise ValueError("Use a positive finite percentage and at most 1000 accepted rows")
    result = {"status": "insufficient_data", "currency": None, "medians": {},
              "price_gap_pct": None, "price_gap_alert": False, "accepted_listings": 0}
    # Select newest timestamps first; older conflicts cannot veto newer evidence.
    candidates = {}
    for item in rows:
        row = accepted_listing(item)
        if row is None:
            continue
        group = candidates.get(row["key"])
        if group is None or row["observed"] > group[0]["observed"]:
            candidates[row["key"]] = [row]
        elif row["observed"] == group[0]["observed"]:
            group.append(row)
    latest = {}
    for key, group in candidates.items():
        if len({(row["price"], row["currency"]) for row in group}) > 1:
            return {"comparison": dict(result, status="conflicting_observations")}
        latest[key] = group[0]
    if len({row["currency"] for row in latest.values()}) > 1:
        return {"comparison": dict(result, status="currency_conflict")}
    grouped = {}
    for row in latest.values():
        grouped.setdefault(row["domain"], []).append(row["price"])
    if len(grouped) < 2:
        return {"comparison": result}
    medians = {domain: median(values) for domain, values in grouped.items()}
    gap = (max(medians.values()) - min(medians.values())) * 100 / min(medians.values())
    return {"comparison": dict(result, status="observed", currency=next(iter(latest.values()))["currency"],
        medians={domain: str(value) for domain, value in medians.items()}, price_gap_pct=str(gap),
        price_gap_alert=gap >= threshold, accepted_listings=len(latest))}

builder = StateGraph(PriceGapState)
builder.add_node("compare", compare_prices)
builder.add_edge(START, "compare")
builder.add_edge("compare", END)
price_gap_graph = builder.compile()

## Replace this empty list with rows from your existing accepted exports.
## Empty input intentionally returns status="insufficient_data", not a price signal.
saved_listings = []
comparison = price_gap_graph.invoke({"observations": saved_listings, "spread_threshold_pct": 25})
```

`price_gap_alert` is only a same-currency asking-price gap for your review. The
graph neither schedules work nor writes to a store, receiver or CRM. Currency
conflicts need manual review; no currency conversion provider is called. A
retrieval timestamp is not proof of an indexed listing's current checkout price.

### Troubleshooting

**Getting blocked or seeing empty results**
eBay's bot-detection is currently blocking many Apify-hosted search and item-page requests with HTTP 403. Active-listing runs try a fallback public listing index after the primary eBay fetch fails; sold-listings mode cannot use that fallback because completed-sale history is not exposed there. Try a premium residential/mobile proxy path, match the proxy country to the eBay domain, and validate with `maxProducts: 1-3` before scaling.

For blocked or zero-result runs, open the run's Diagnostics output link and read the final status message. The default dataset remains empty when no listing is recovered, and diagnostics incur no product or dataset-item fee. Startup charges still apply, along with separately billed platform usage from November 8, 2026 at 17:56 Europe/Amsterdam. Recovery stops after 25 seconds without a successfully saved qualified priced listing. Partial and discovery-only rows do not extend that window. Listings already fetched can still be saved, but no new optional detail request starts after the recovery window expires. The configured proxy is used for both page requests and listing recovery; proxy configuration failures do not silently switch to direct requests.

**Prices appear in the wrong currency**
eBay localizes prices based on the requesting IP's country. If you see EUR prices when you expected USD (or vice versa), your proxy exit node is landing in the wrong country. Set `proxyConfiguration.apifyProxyCountry` to the two-letter country code that matches your target domain - for example `US` for `ebay.com`, `GB` for `ebay.co.uk`, `DE` for `ebay.de`. The scraper maps each of the 13 supported domains to its canonical country automatically when you use Apify Proxy without a country override.

**Sold-listings results are empty or very sparse**
First, confirm `soldOnly: true` is set in your input - without it, the scraper runs in live-listing mode. Second, check whether the product has recent sold history: narrow or niche listings may have fewer than 5 completed sales in eBay's ~90-day window. Try broadening the search query. Third, ensure you are using the correct domain - a search on `ebay.de` for an English-language query will return far fewer sold listings than the same search on `ebay.com`.

**Seller information missing on auction listings**
Some auction cards omit seller information. `fetchDetails=true` attempts to add fields from the individual item page, at the cost of extra requests. Blocked or incomplete pages leave fields empty; enabling enrichment does not guarantee seller information.

**Filtered primary search fails**

Recovery is deliberately skipped for non-default condition or sort selections because the fallback indexes cannot honor those requirements. Keep the filters when they matter. Choose `itemCondition: "any"` and `sortBy: "best_match"` only when broader indexed results are acceptable. Detail pages declaring a different item or marketplace are discarded rather than merged into the requested listing.

**AI analysis returns an error about a missing API key**
OpenRouter API key: set `OPENROUTER_API_KEY` env var OR `openrouterApiKey` input. Get one at [openrouter.ai/keys](https://openrouter.ai/keys). Anthropic: set `ANTHROPIC_API_KEY` env var OR `anthropicApiKey` input. Google AI: set `GOOGLE_API_KEY` env var OR `googleApiKey` input. OpenAI: set `OPENAI_API_KEY` env var OR `openaiApiKey` input. Ollama: ensure Ollama is running and accessible at the configured `ollamaBaseUrl`.

**Cross-run trend block shows `has_history: false`**
This is normal on the first run for any `(searchQuery, domain)` pair. The first run seeds the history store; trend data (`price_delta_7d_pct`, `velocity_delta`, etc.) appears from the second run onward. The `sold-trend-detected` billing event ($0.03) fires only when history exists, so the first run is free on that dimension.

**Maximum 100 products per run**
The actor caps results at 100 per search for PPE bill safety. For larger datasets, run the actor multiple times with different search queries or price-range filters to cover complementary market segments.

***

### Scheduling and webhooks

Schedule daily price-watch runs in Apify Console to track eBay auction closing prices or BIN fluctuations for a target category. Wire a webhookUrl in n8n or Make to push deal alerts into a Slack channel or Telegram bot the moment a run finishes. Typical pipeline: "vintage Lego set 10179" daily eBay run -> n8n -> Slack alert when price drops below threshold.

***

### Legal and Compliance

This actor scrapes publicly available data from eBay. Users are responsible for:

- Complying with eBay's [Terms of Service](https://www.ebay.com/help/policies/member-behaviour-policies/user-agreement?id=4259)
- Adhering to applicable data protection laws (GDPR, CCPA, and local equivalents)
- Using extracted data in accordance with all relevant regulations
- Ensuring their use case does not violate any local or international laws

**eBay scraping policy**: eBay's User Agreement restricts automated access to its platform. Users are solely responsible for ensuring their use case complies with eBay's Terms of Service and any applicable API policies. This actor is provided as a technical tool; compliance obligations rest with the user.

**GDPR and seller personal data**: eBay seller profiles (username, feedback percentage, feedback count, location) constitute personal data under GDPR for EU-resident sellers. If you process data of individuals in the EU, you must have a lawful basis for processing, honor data-subject access and deletion requests, and avoid retaining seller personal data beyond your stated purpose.

**Request limits**: the actor inserts 1-2.5 second delays between pages, bounds individual fetches and stops a recovery phase after 25 seconds without a successfully saved qualified priced listing. Partial and discovery-only rows do not renew the recovery budget. The PicClick fallback stops after HTTP 403 or 429 instead of repeatedly retrying a rejected host. These limits do not establish permission to scrape or guarantee access.

**Data retention guidance**: scrape outputs may include seller usernames, feedback scores, and item locations. Establish a retention policy aligned with your use case - for price-monitoring workloads a 90-day rolling window is typically sufficient. Delete or anonymize data no longer needed for your stated purpose.

For questions or concerns, use this Actor's Issues tab in Apify Store. Include the run ID and a description of the missing or incorrect fields; do not post credentials.

#### Related Actors

- **[Amazon Scraper](https://apify.com/harvestlab/amazon-scraper)** - pair eBay sold-comp data with Amazon catalog prices across 11 marketplaces for cross-platform arbitrage signals.
- **[Product Hunt Scraper](https://apify.com/harvestlab/producthunt-scraper)** - surface early product trends on Product Hunt before they hit eBay reseller demand cycles.

# Actor input Schema

## `searchQuery` (type: `string`):

Keyword(s) to search for on eBay (e.g. 'vintage Rolex', 'pokemon cards', 'Nintendo Switch OLED')

## `query` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `q` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `search` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `keyword` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `searchTerm` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `productUrls` (type: `array`):

Advanced JSON-only mode for direct eBay listing URLs. Direct item pages and fallback item lookups are proxy-path dependent; active searchQuery runs are more reliable for production.

## `urls` (type: `array`):

CLI alias for productUrls. Hidden from Console form.

## `links` (type: `array`):

CLI alias for productUrls. Hidden from Console form.

## `ebayDomain` (type: `string`):

eBay country/regional domain to search. Prices and listings are localized per country - pick the market you want to scrape.

## `domain` (type: `string`):

CLI alias for ebayDomain - accepts the same country domain values. Hidden from the Console form; exposed so `apify call -i '{"domain":"ebay.de"}'` works for users copying JSON between portfolio scrapers.

## `ebaySite` (type: `string`):

Legacy CLI alias for ebayDomain. Hidden from Console form; kept so older API payloads that use `ebaySite` are not dropped by Apify schema validation.

## `listingType` (type: `string`):

Filter by sale format. Auction = bidding with end time; Buy It Now = fixed-price instant purchase; All = both.

## `sortBy` (type: `string`):

Primary eBay search ordering. Price sorts apply to post-shipping total on ebay.com. Fallback indexes cannot preserve this order, so recovery is available only with Best Match; other sorts stop with a diagnostic if primary search yields no listings.

## `minPrice` (type: `number`):

Minimum listing price in the domain's local currency (USD on ebay.com, GBP on ebay.co.uk, EUR on .de/.fr/.it/.es/.nl, AUD on .com.au, CAD on .ca). Example: 50 on ebay.com filters out listings under $50. Leave empty for no lower bound.

## `maxPrice` (type: `number`):

Filter results by maximum price in the selected domain's local currency (USD on ebay.com, GBP on ebay.co.uk, etc.).

## `itemCondition` (type: `string`):

Filter the primary eBay search by condition. Fallback indexes cannot verify condition, so recovery is available only with Any Condition; filtered searches stop with a diagnostic if primary search yields no listings.

## `maxProducts` (type: `integer`):

Maximum number of listings to scrape. Start with 1 to check access and inspect output before scaling. Capped at 100; advanced sold-listings mode is runtime-capped to 50. An empty dataset is not a successful price check.

## `useBrowser` (type: `boolean`):

Keep enabled for the existing browser recovery path (requires 4096 MB). Disable to use only bounded HTTP requests; this allows 512 MB runs but cannot recover pages that require browser rendering. Proxy settings are still respected.

## `requirePrice` (type: `boolean`):

Exclude partial and price-less discovery rows from the default dataset and their dataset-item fees. Skips the price-less search-index fallback. An empty run produces a separate diagnostic, not a price observation. Startup and platform usage still apply. Indexed priced rows retain their source; this does not verify current inventory.

## `fetchDetails` (type: `boolean`):

Visit each listing page to fetch full details (seller, full description, item specifics). Required for auction listings if you need `seller` field populated. Slower (~2-3x run time). Recommended for Buy It Now analysis too.

## `soldOnly` (type: `boolean`):

Advanced JSON-only mode for proven proxy paths. When eBay completed-sale pages are reachable, scrape completed sales (LH_Sold=1\&LH_Complete=1, ~90 day window) and emit a `sold_listings_summary`. When eBay blocks completed-sale pages, the actor emits diagnostics and does not charge sold listing or summary events.

## `soldDateLookback` (type: `integer`):

How far back to assume eBay's LH_Sold window covers when computing market_velocity (sold-per-day). eBay typically retains 90 days of completed-sale data. Only used when soldOnly=true. Values: 30-365, default 90.

## `maxItems` (type: `integer`):

CLI alias for maxProducts. Hidden from Console form.

## `enableAiAnalysis` (type: `boolean`):

Generate AI-powered market pricing analysis with price distribution, seller landscape, and buying recommendations. Hosted providers require your own API key; their token charges are separate from Actor report events. Self-hosted Ollama needs no API key.

## `llmProvider` (type: `string`):

Choose the provider for market/pricing analysis. Hosted providers require your own API key; their model and token charges are separate from Actor report events. Self-hosted Ollama needs no API key and uses your server.

## `llmModel` (type: `string`):

Specific model to use. Leave empty for the provider default (google/gemini-3.8-flash for OpenRouter, claude-sonnet-5-5 for Anthropic, gemini-3.8-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama).

## `openrouterApiKey` (type: `string`):

API key for OpenRouter (required if LLM Provider = OpenRouter). Get one at https://openrouter.ai/keys. Or set the OPENROUTER_API_KEY env var on the actor.

## `anthropicApiKey` (type: `string`):

API key for Anthropic Claude (required if LLM Provider = Anthropic). Get one at https://console.anthropic.com/settings/keys. Or set the ANTHROPIC_API_KEY env var on the actor.

## `googleApiKey` (type: `string`):

API key for Google AI / Gemini (required if LLM Provider = Google AI). Get one at https://aistudio.google.com/app/apikey. Or set the GOOGLE_API_KEY env var on the actor.

## `openaiApiKey` (type: `string`):

API key for OpenAI (required if LLM Provider = OpenAI). Get one at https://platform.openai.com/api-keys. Or set the OPENAI_API_KEY env var on the actor.

## `ollamaBaseUrl` (type: `string`):

Base URL for a self-hosted Ollama server (only used if LLM Provider = Ollama). Default: http://localhost:11434. Install Ollama at https://ollama.com/download.

## `proxyConfiguration` (type: `object`):

Proxy settings. eBay currently returns 403 blocks from many Apify exits, including some residential runs. Active searches try a fallback listing index when eBay blocks access; sold-listing mode still depends on eBay completed-sale page access. If you bring a premium proxy, match `apifyProxyCountry` to your eBay domain (US for ebay.com, GB for ebay.co.uk, DE for ebay.de, etc.).

## Actor input object example

```json
{
  "searchQuery": "pokemon cards",
  "ebayDomain": "ebay.com",
  "listingType": "all",
  "sortBy": "best_match",
  "itemCondition": "any",
  "maxProducts": 25,
  "useBrowser": true,
  "requirePrice": false,
  "fetchDetails": false,
  "soldOnly": false,
  "soldDateLookback": 90,
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

Dataset containing scraped eBay live listings, sold listings, per-run sold-listings summaries, and optional AI market-analysis reports.

## `diagnostics` (type: `string`):

Invalid-input, no-result, and block-detection diagnostics with no product or dataset-item fee. No diagnostic rows are written to the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "pokemon cards",
    "ebayDomain": "ebay.com",
    "maxProducts": 25,
    "soldOnly": false,
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/ebay-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "pokemon cards",
    "ebayDomain": "ebay.com",
    "maxProducts": 25,
    "soldOnly": False,
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/ebay-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "pokemon cards",
  "ebayDomain": "ebay.com",
  "maxProducts": 25,
  "soldOnly": false,
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call harvestlab/ebay-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,harvestlab/ebay-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/d7N1Z89xM4DWINcxF/builds/Zxuj5bXP0HozNjllM/openapi.json
