# Japan Used Watch Market — Search, Stats, History & Alerts (`keiya_dev/japan-used-watch-market`) Actor

Search, benchmark and monitor Japan's used watch shops (WATCHNIAN, Hard Off) in one schema: canonical brands, reference numbers, normalized grades, box & warranty card, JPY/USD. Median prices per model, daily price history, NEW / PRICE_DROP alerts to Slack, Discord, n8n.

- **URL**: https://apify.com/keiya_dev/japan-used-watch-market.md
- **Developed by:** [Keiya](https://apify.com/keiya_dev) (community)
- **Categories:** E-commerce, Other
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Japan Used Watch Market: Grand Seiko, Rolex & more — Search, Stats, History & Alerts

### Quick start

1. Click **Try for free** / open the Actor in Apify Console.
2. Keep the prefilled input (`{"mode": "search", "sources": ["watchnian", "offmall"], "keywords": ["Seiko"], "maxItems": 10}`) or type your own: a brand ("Grand Seiko"), a model ("Speedmaster") or a reference number ("SBGA211", "16233").
3. Press **Start**. Listings appear in the Output tab in under a minute, with **reference number, condition grade, box & warranty card, and price in JPY and USD**.

Japan has the deepest second-hand watch market in the world, and some of its lowest prices: Grand Seiko, vintage Seiko, Rolex, Omega and Cartier, all graded, serviced and sold with a shop warranty. The listings are only in Japanese, and each shop has its own grading. This Actor turns them into one clean dataset you can pipe into Google Sheets, n8n, Slack, Discord or your own pricing model.

### Who uses it

| You are… | You use… |
|---|---|
| **A Chrono24 / eBay seller** sourcing from Japan | `search` for a reference with "box + warranty card" and a grade floor; `monitor` to be first on new stock |
| **A Grand Seiko or vintage Seiko collector** | `monitor` on "Grand Seiko" / "King Seiko" / a ref, with alerts to Slack or Discord |
| **An overseas dealer benchmarking Japanese prices** | `stats` (median per reference, by shop and grade) and `history` (how the Tokyo median moves) |

### Shops

| Shop | Id | Listings | What you get |
|---|---|---|---|
| **WATCHNIAN** (ウォッチニアン, formerly 一風騎士 / Ippuukishi) | `watchnian` | ~3,900 used watches | brand, ref (from the shop's own data), grade N / S / SA / A / AB / B / C / Q, men's/women's, price (after any reduction), **box & warranty card** (from the product page), image |
| **Hard Off Off Mall** (ハードオフ オフモール, stores nationwide) | `offmall` | ~24,000 wristwatches on sale | brand, ref (from the shop's model code), grade N / S / A / B / C / D / ジャンク, price, image. Off Mall has no accessories field |

Want a shop added? Open an issue on the Actor page. If it's publicly crawlable, it goes on the roadmap.

### What you can do

| Mode | What it does |
|---|---|
| **search** | Listings matching keywords / brands / grades / price. Keywords match brand + title in English or Japanese, **and reference numbers**. For WATCHNIAN listings in the result, the product page is read once to add **box / warranty card / warranty date**. |
| **stats** | Per-model summary of in-stock listings: count, **min / median / max** in JPY and USD, broken down by shop and grade, plus the cheapest listing. Models are grouped by **brand + reference number**, so the same watch groups across shops. Use the median: watch prices spread widely. |
| **monitor** | Run on a schedule. It keeps a snapshot and emits **only what changed**: `NEW`, `PRICE_DROP`, `PRICE_UP`, `SOLD_OUT`, with box / warranty card on new WATCHNIAN listings. An optional webhook goes to Slack / Discord / n8n / Make / Zapier. |
| **history** | Daily min / median / count per model, by shop and grade, from stored daily snapshots. There is no crawling, so it returns in seconds. Needs `keywords`, `brands` or `models`. |

### Brands, references and model keys

- **Brands** are canonical English names, whatever the shop prints: オメガ / OMEGA → `Omega`, タグ・ホイヤー / TAG HEUER → `TAG Heuer`, チューダー(チュードル) → `Tudor`.
- **Sub-brands are separated:**
  - **Grand Seiko** (from the title, or a ref starting `SBG…`) is never lumped in with Seiko.
  - **Casio G-SHOCK** (from the title, or refs like `GW-`, `GA-`, `DW-`, `GMW-`, `MTG-`) is separate from Casio.
- **`ref`** is the reference number. It comes from the shop's structured data when there is one (WATCHNIAN "Ref.3510.50"), otherwise from the title or model code.
- **`model`** is `"brand ref"` (e.g. `omega 3510.50`, `rolex 126233g`). Without a ref, it is the brand plus the cleaned title.

### Condition grades, normalized

`NEW` > `UNUSED` > `SA` (like new) > `A` > `AB` > `B` > `C` > `JUNK` (not working / parts). `ANTIQUE` is an ungraded antique piece. The shop's own label is always kept in `conditionGradeRaw`.

| Shop | Raw → normalized |
|---|---|
| WATCHNIAN | N 新品 → NEW · S 未使用 → UNUSED · SA 新品同様 → SA · A → A · AB → AB · B → B · C (may need repair) → C · Q アンティーク → ANTIQUE |
| Off Mall | N 新品・未開封 → NEW · S 新品同様・廃盤 → SA · A 使用回数の少ない美品 → A · B 日常使用・傷汚少 → AB · C 使用感・傷汚れ多 → B · D ダメージ多 → C · ジャンク → JUNK |

### Box & warranty card (`accessories`)

Overseas buyers ask about these first. For WATCHNIAN listings returned by `search` or alerted by `monitor`, the product page is read and its "付属品" line is normalized:

```json
"accessories": {
  "box": true, "warrantyCard": true, "bodyOnly": false,
  "warrantyDate": "2019-12",
  "summary": "box + warranty card",
  "raw": "メーカー保証書( 2019年12月購入) ／ メーカー箱"
}
```

`summary` is one of `box + warranty card`, `warranty card, no box`, `box, no warranty card`, `watch only`, `other accessories only`. Each returned WATCHNIAN listing costs one extra request. `stats` and `history` don't read product pages, so `accessories` is `null` there, and always `null` for Off Mall.

### Input

```json
{
  "mode": "search",
  "sources": ["watchnian", "offmall"],
  "keywords": ["Grand Seiko"],
  "grades": ["UNUSED", "SA", "A"],
  "maxPriceJpy": 800000,
  "maxItems": 30
}
```

Monitor example (schedule it hourly):

```json
{
  "mode": "monitor",
  "sources": ["watchnian"],
  "keywords": ["Grand Seiko", "King Seiko"],
  "alertWebhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
  "alertEvents": ["NEW", "PRICE_DROP"]
}
```

The first monitor run only builds the snapshot and emits nothing. Each monitor keeps one snapshot per shop, keyed by its filters. Give each schedule a distinct `monitorId` if several use the same filters.

**Request budget:**

- Every request is one listing page (WATCHNIAN 60 watches, Off Mall 30), read newest-first.
- An hourly light monitor is normally **2 requests**, plus one product page per new WATCHNIAN listing.
- `"fullScan": true` reads everything: **WATCHNIAN ≈ 66 pages (~3 min), Off Mall ≈ 800 pages (~27 min)**. A good setup is a frequent light monitor plus a daily full scan of WATCHNIAN only.

### Price history since 2026-11-05

The Actor's own scheduled full scans store the complete inventory of each shop: **WATCHNIAN every day**, **Off Mall once a week, on Sundays** (~800 pages). `history` mode reads those snapshots:

```json
{ "mode": "history", "models": ["omega 3510.50", "grand seiko sbga211"], "days": 90 }
```

Each row is one model × day: `count`, `minJpy`, `medianJpy`, `maxJpy`, plus `byShop`, `byGrade` and `byShopGrade`.

- **History is still being collected.** Treat the first 30 days as indicative only.
- Off Mall numbers appear on Sunday rows only, so compare them week to week.

### Output

```json
{
  "source": "watchnian",
  "sourceName": "WATCHNIAN (Ippuukishi)",
  "sourceItemId": "ik-00-0743891",
  "url": "https://watchnian.com/shop/g/gik-00-0743891/",
  "title": "スピードマスター オートマティック 3510.50 ブラック",
  "brand": "Omega",
  "brandJa": "オメガ",
  "ref": "3510.50",
  "model": "omega 3510.50",
  "gender": "men",
  "conditionGrade": "A",
  "conditionGradeRaw": "A",
  "accessories": { "box": false, "warrantyCard": true, "bodyOnly": false, "warrantyDate": "2004-12", "summary": "warranty card, no box", "raw": "メーカー保証書( 2004年12月国内購入)" },
  "priceJpy": 550000,
  "priceUsd": 3474.6,
  "fxJpyPerUsd": 158.29,
  "inStock": true,
  "category": "used",
  "scrapedAt": "2026-11-04T06:00:00.000Z"
}
```

`stats` rows and monitor events have the same shape as in the [camera Actor](https://apify.com/keiya_dev/japan-used-camera-market): stats adds `count`, `minJpy`, `medianJpy`, `maxJpy`, `byShop`, `byGrade`; events add `eventType` and the price change.

### Pricing

Pay per event, no subscription:

| Event | Price |
|---|---|
| `listing` (search mode, per listing) | $0.003 |
| `model-stats` (stats mode, per model row) | $0.005 |
| `alert-event` (monitor mode, per NEW / PRICE_DROP / PRICE_UP / SOLD_OUT) | $0.01 |
| `history-row` (history mode, per model × day row) | $0.002 |

Higher Apify plans get progressively lower prices (Bronze / Silver / Gold discounts).

### Integrations

- **Google Sheets**: Apify → Integrations → Google Sheets.
- **Slack / Discord**: paste an incoming-webhook URL into `alertWebhookUrl`.
- **n8n / Make / Zapier**: the Apify node, or point `alertWebhookUrl` at a webhook trigger.
- **AI agents**: see the next section.

### AI agents (MCP)

A dedicated MCP server, [`@keiya-dev/japan-used-market-mcp`](https://github.com/Enu27/japan-used-market-mcp) (open source, MIT), gives Claude, Cursor and other MCP clients tools backed by this Actor. Pass `"market": "watch"` (the server also covers used cameras, its default):

| Tool | Mode |
|---|---|
| `search_listings` | search, with reference number, gender and box / warranty card |
| `price_stats` | stats |
| `price_history` | history |
| `start_monitor` | creates a scheduled monitor (Task + Schedule) in your Apify account |
| `list_markets` | shops and grades per market (no run) |

**Claude Desktop**: add this to `claude_desktop_config.json`. On macOS it is `~/Library/Application Support/Claude/claude_desktop_config.json`; on Windows, `%APPDATA%\Claude\claude_desktop_config.json`. Then restart the app:

```json
{
  "mcpServers": {
    "japan-used-market": {
      "command": "npx",
      "args": ["-y", "@keiya-dev/japan-used-market-mcp"],
      "env": { "APIFY_TOKEN": "apify_api_..." }
    }
  }
}
```

Get the token at Apify Console → Settings → API & Integrations. Runs are billed to your Apify account, as with any other run.

Then just ask: *"Find a Grand Seiko SBGA211 in SA or better with box and papers in Japan, with the USD price"*, *"What's the median price of a Rolex 16233 in Japan, by shop?"*, *"Alert me on Discord when a new King Seiko appears."*

**Claude Code**: `claude mcp add japan-used-market -e APIFY_TOKEN=apify_api_... -- npx -y @keiya-dev/japan-used-market-mcp`

**Remote / HTTP clients**: run `npx -y @keiya-dev/japan-used-market-mcp --http --port 3000`. The endpoint is `/mcp` (Streamable HTTP); send your token as `Authorization: Bearer <APIFY_TOKEN>`.

The generic [Apify MCP server](https://mcp.apify.com) also works: add the Actor `keiya_dev/japan-used-watch-market` as a tool.

### Limitations & fair use

- Prices are the shops' tax-included asking prices (税込), not sold prices. USD is a mid-market conversion for convenience.
- Only public listing pages and, for WATCHNIAN, the product pages of returned or alerted listings are read. There is no login, no search endpoint and no sort parameter: both shops' robots.txt forbid those.
- Both shops list watches on sale only. A sold watch disappears, and `SOLD_OUT` for it is detected on full scans.
- Serial numbers are never collected.
- If a shop's bot protection challenges a run, a page can't be loaded or a shop changes its layout, the run fails with a message saying what to do. It writes no partial data, charges nothing and leaves the monitor snapshot unchanged.
- Not affiliated with any of the shops.

### Changelog

- **0.1**: WATCHNIAN (all used watches) and Hard Off Off Mall (wristwatches). Search / stats / monitor / history, box & warranty card, brand + ref model keys.
- **2026-11-04**: Off Mall refs drop the case / movement / serial suffix where the brand needs it (Grand Seiko "SBGX063-9F62-0ABO" → "SBGX063", TAG Heuer "CAR2A11-EAH0983" → "CAR2A11"), so they group with WATCHNIAN in stats and history. Other brands' refs are unchanged.

### Related

- [Japan Used Camera Market](https://apify.com/keiya_dev/japan-used-camera-market): the same four modes for used cameras and lenses (Fujiya Camera, Sanpou Camera, Naniwa, Hard Off). It uses the same grading idea and the same output shape.

***

If this saved you a trip to Ginza, a review on the Store helps others find it.

# Actor input Schema

## `mode` (type: `string`):

search = return listings. stats = model stats of in-stock listings (count, min / median / max by shop & condition grade). monitor = compare against the last run and emit only NEW / PRICE_DROP / PRICE_UP / SOLD_OUT events (use with a Schedule). history = daily min / median / count per model, by shop and condition grade, from stored daily snapshots (no crawling; needs keywords, brands or models). History is collected since 2026-11-05 (WATCHNIAN daily, Off Mall weekly) — treat the first 30 days as indicative.

## `sources` (type: `array`):

Which shops to search. WATCHNIAN (Ippuukishi, ~3,900 used watches, with box / warranty-card info from product pages) and Hard Off Off Mall (~24,000 wristwatches on sale, nationwide stores).

## `keywords` (type: `array`):

Only keep listings whose brand + title contains any of these (case-insensitive, English or Japanese; reference numbers work too). Leave empty to get everything. Examples: "Grand Seiko", "SBGA211", "Speedmaster", "16233", "G-SHOCK".

## `brands` (type: `array`):

Optional exact brand filter (canonical English names, e.g. Rolex, Omega, Grand Seiko, Seiko, Casio G-SHOCK, Citizen, Cartier, TAG Heuer).

## `models` (type: `array`):

History mode: only these exact model keys, as shown in the "model" field of stats mode ("brand ref", e.g. "omega 3510.50", "grand seiko sbga211", "rolex 16233").

## `days` (type: `integer`):

History mode: how many days back to return, counting today (JST).

## `minPriceJpy` (type: `integer`):

Drop listings cheaper than this.

## `maxPriceJpy` (type: `integer`):

Drop listings more expensive than this.

## `grades` (type: `array`):

Keep only these normalized grades. NEW > UNUSED > SA (like new) > A > AB > B > C > JUNK (not working / parts); ANTIQUE = ungraded antique pieces.

## `includeSoldOut` (type: `boolean`):

Search mode: also return listings marked sold out. Both shops list watches on sale only, so this rarely changes anything.

## `fullScan` (type: `boolean`):

Monitor mode only. Off (default): lists are read newest-first and stop after 20 already-known listings in a row (normally 1 request per shop per run). On: read every page (WATCHNIAN ≈ 66 pages, ~3 min; Off Mall ≈ 800 pages, ~27 min) to catch every PRICE_DROP / PRICE_UP / SOLD_OUT. Tip: frequent light runs plus a daily full scan of WATCHNIAN only.

## `monitorId` (type: `string`):

Optional. Each monitor keeps its own snapshot, keyed by its filters (keywords, brands, grades, price range, includeSoldOut). Set a monitorId to separate several schedules that use the same filters, or to keep the snapshot when you edit the filters. Letters, digits and - \_ . only.

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

Stop after this many matching listings across all selected shops (0 = no limit). WATCHNIAN has ~3,900 used watches, Off Mall ~24,000. In search mode each returned WATCHNIAN listing also costs one product-page request (for box / warranty card). Ignored in monitor mode.

## `jpyPerUsd` (type: `number`):

Exchange rate used for priceUsd. Leave empty to fetch the current rate automatically.

## `alertWebhookUrl` (type: `string`):

Optional. In monitor mode every batch of monitor events is POSTed as JSON to this URL. Works with Slack/Discord incoming webhooks, n8n, Make, Zapier.

## `alertEvents` (type: `array`):

Monitor mode: only these event types are written to the dataset, charged, and sent to the webhook.

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

Off by default (direct connection). Datacenter proxies are more likely to be challenged by the shop's bot protection; switch to residential proxy only if runs get blocked.

## Actor input object example

```json
{
  "mode": "search",
  "sources": [
    "watchnian",
    "offmall"
  ],
  "keywords": [
    "Seiko"
  ],
  "days": 90,
  "includeSoldOut": false,
  "fullScan": false,
  "maxItems": 10,
  "alertEvents": [
    "NEW",
    "PRICE_DROP"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset items. Shape depends on mode — see the dataset schema (listing / model stats row / monitor event / history row).

## `overview` (type: `string`):

No description

## `stats` (type: `string`):

No description

## `history` (type: `string`):

No description

# 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 = {
    "sources": [
        "watchnian",
        "offmall"
    ],
    "keywords": [
        "Seiko"
    ],
    "maxItems": 10,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("keiya_dev/japan-used-watch-market").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 = {
    "sources": [
        "watchnian",
        "offmall",
    ],
    "keywords": ["Seiko"],
    "maxItems": 10,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("keiya_dev/japan-used-watch-market").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 '{
  "sources": [
    "watchnian",
    "offmall"
  ],
  "keywords": [
    "Seiko"
  ],
  "maxItems": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call keiya_dev/japan-used-watch-market --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,keiya_dev/japan-used-watch-market"
        }
    }
}
```

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/CgcQvPKxfqVK46Ufy/builds/Kzb8kVksFsZLwBmKC/openapi.json
