Watchlists
Up to 50,000 items of one platform and marketplace (ASINs, Walmart item ids, TCINs, SKUs…) re-checked on a cadence by a pipeline of scrapers and rules you choose (default on Amazon: the pulse → lite → full ladder; on one-tier retailers: a fetch and diff of every item); changes and matches pushed to a signed webhook. Billed as the calls each run makes.
Up to 50,000 items of one platform and marketplace (ASINs, Walmart item ids, TCINs, SKUs…) re-checked on a cadence by a pipeline of scrapers and rules you choose (default on Amazon: the pulse → lite → full ladder; on one-tier retailers: a fetch and diff of every item); changes and matches pushed to a signed webhook. Billed as the calls each run makes.
Endpoints
Method | Path | Does |
|---|---|---|
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| Add or remove items (legacy path) (deprecated) |
How it works, when to use what, and what it costs: Watchlists & pipelines. Base URL, authentication and errors: Overview.
POST /v1/watchlists
Create a watchlist.
Track up to 50,000 items of one platform and marketplace on a cadence (every) or a cron schedule in a time zone (cron + timezone) (at most 100 lists per account). Creating is free. Send the item ids in ids (every platform); asins is still accepted on Amazon as a legacy alias. Send a pipeline to choose the scrapers each run uses and how they chain; without one the platform default runs. The first run starts at once and reads every item at the baseline step (default on Amazon: full; no change events). With the default pipeline, on Amazon each later run sweeps at pulse, escalates to lite the ASINs whose offer count or lowest new price moved (plus those whose buy box sat above the lowest offer, and any owed retry), and every refresh_every_runs-th run sweeps everything at lite; one-tier retailers fetch and diff every item each run. Runs are billed exactly like the /v1/products calls they make; a run the balance cannot cover is skipped (insufficient_credits), and the third skip in a row pauses the list.
Headers
Name | Type | Required | Description |
|---|---|---|---|
| string | — | Retrying with the same key returns the original response ( |
Request body
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | Up to 100 characters. |
| string[] | yes | 1 to 50,000 item ids of |
| string[] | — | Deprecated. Amazon only, legacy: the same as |
| string | — | Cadence, start to start: whole minutes, hours or days, at least 5 minutes, at most 7 days. E.g. |
| string | — | Standard 5-field cron (minute hour day-of-month month day-of-week) read in |
| string | — | IANA time zone the |
| string | — | One of your registered webhook endpoints ( |
| string | — | Alternative to |
| integer | — | Default pipeline on Amazon: every Nth run sweeps every ASIN at |
| object | — | What each run does. Omitted: the platform default. |
| string | — | The step every run starts with. Default: the first step. |
| string or null | — | Where items never read before start instead. Default: |
| object or null | — | Every Nth run starts at this step instead. |
| integer | yes | From 1 to 1,000. |
| string | yes | — |
| object[] | yes | 1 to 10 items. |
| string | yes | — |
| string | yes | GET /v1/watchlists/scrapers lists them with their fields. One of: |
| string | — |
|
| object[] | — | At most 8 items. |
| string | yes | One of: |
| string | — | With |
| string | — | One of: |
| number or boolean or string | — | — |
| string | — | A step id to run next for the matching items. |
| boolean | — | Send the matching items ( |
| string | — | Which platform to read. Platforms: |
| string | — | The platform's regional storefront ( |
Request
- cURL
- Python
- Node.js
curl -X POST "https://api.scrapelyserp.com/v1/watchlists" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Consoles",
"platform": "target",
"marketplace": "us",
"ids": ["83887639", "1011697103"],
"every": "15m",
"webhook_url": "https://example.com/hooks/scrapely"
}'import os
import uuid
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.post(
"https://api.scrapelyserp.com/v1/watchlists",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={
"name": "Consoles",
"platform": "target",
"marketplace": "us",
"ids": ["83887639", "1011697103"],
"every": "15m",
"webhook_url": "https://example.com/hooks/scrapely",
},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
name: 'Consoles',
platform: 'target',
marketplace: 'us',
ids: ['83887639', '1011697103'],
every: '15m',
webhook_url: 'https://example.com/hooks/scrapely',
}),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 201 Created
- 400 Bad Request
{
"id": "wl_3f1c2a9e8d4b4c61",
"object": "watchlist",
"name": "Consoles",
"platform": "target",
"marketplace": "us",
"currency": "USD",
"status": "active",
"paused_reason": null,
"schedule": {
"type": "every",
"every": "15m",
"every_seconds": 900
},
"every": "15m",
"every_seconds": 900,
"refresh_every_runs": 8,
"webhook_url": "https://example.com/hooks/scrapely",
"item_count": 2,
"items_by_state": {
"new": 2,
"active": 0,
"delisted": 0
},
"asin_count": 2,
"asins_by_state": {
"new": 2,
"active": 0,
"delisted": 0
},
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "target.standard",
"routes": [
{
"when": "changed",
"notify": true
}
]
}
]
},
"default_pipeline": true,
"estimated_credits": {
"pulseRun": 0,
"refreshRun": 0,
"newAsins": 2
},
"run_count": 0,
"consecutive_skips": 0,
"last_run_at": null,
"last_run_status": null,
"next_run_at": "2026-10-16T09:15:04.120Z",
"created_at": "2026-10-16T09:15:04.120Z",
"updated_at": "2026-10-16T09:15:04.120Z",
"url": "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61"
}{
"error": {
"code": "INVALID_PARAM",
"message": "`every` must be a number of minutes, hours or days, e.g. \"5m\", \"15m\", \"1h\", \"6h\", \"24h\". Or send `cron` (e.g. \"0 9 * * 1-5\") with a `timezone`.",
"param": "every",
"docs": "/docs/errors#invalid_param"
}
}Responses
Status | Meaning |
|---|---|
| Created; the first run is due now. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist" | — |
| string | — |
| string | Whose item ids the list holds ( |
| string | Every run fetches the list here. |
| string | The currency of every price in its snapshots, changes and webhooks. |
| string | One of: |
| string or null | One of: |
| object or object | When the list runs: a cadence (start to start) or a cron schedule in a time zone. |
| string or null | The cadence of an every-list; |
| integer or null | The cadence in seconds; |
| integer | — |
| object | Up to 10 steps, each running one scraper over the items sent to it; its routes send the items on a branch to another step ( |
| string | The step every run starts with. Default: the first step. |
| string or null | Where items never read before start instead. Default: |
| object or null | Every Nth run starts at this step instead. |
| object[] | 1 to 10 items. |
| boolean | True when the list runs its platform default. |
| string or null | — |
| integer | Items on the list, every state. |
| object | — |
| integer | — |
| integer | — |
| integer | — |
| integer | Deprecated. Deprecated alias, same value as |
| object | Deprecated. Deprecated alias, same value as |
| integer | — |
| integer | — |
| integer | — |
| object | Up-front cost of the next runs: a sweep run with nothing changed (Amazon: pulse), a refresh run, and the one-off baseline fetch of |
| integer | — |
| integer | — |
| integer | — |
| integer | — |
| integer | Runs skipped in a row for credits; the third pauses the list. |
| string (date-time) or null | — |
| string or null | One of: |
| string (date-time) or null | null while paused. |
| string (date-time) | — |
| string (date-time) | — |
| string | — |
GET /v1/watchlists
List watchlists.
Your watchlists, newest first, with their schedule, pipeline and last run. Free.
Query parameters
Name | Type | Required | Description |
|---|---|---|---|
| integer | — | How many lists to return. Default |
Request
- cURL
- Python
- Node.js
curl -G "https://api.scrapelyserp.com/v1/watchlists" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-d limit=20import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.get(
"https://api.scrapelyserp.com/v1/watchlists",
headers=HEADERS,
params={"limit": "20"},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch(`https://api.scrapelyserp.com/v1/watchlists?${new URLSearchParams({ limit: '20' })}`, {
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
{
"object": "list",
"data": [
{
"id": "wl_3f1c2a9e8d4b4c61",
"object": "watchlist",
"name": "Consoles",
"platform": "target",
"marketplace": "us",
"currency": "USD",
"status": "active",
"paused_reason": null,
"schedule": {
"type": "every",
"every": "15m",
"every_seconds": 900
},
"every": "15m",
"every_seconds": 900,
"refresh_every_runs": 8,
"webhook_url": "https://example.com/hooks/scrapely",
"item_count": 2,
"items_by_state": {
"new": 0,
"active": 2,
"delisted": 0
},
"asin_count": 2,
"asins_by_state": {
"new": 0,
"active": 2,
"delisted": 0
},
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "target.standard",
"routes": [
{
"when": "changed",
"notify": true
}
]
}
]
},
"default_pipeline": true,
"estimated_credits": {
"pulseRun": 2,
"refreshRun": 2,
"newAsins": 0
},
"run_count": 42,
"consecutive_skips": 0,
"last_run_at": "2026-10-16T09:15:00.004Z",
"last_run_status": "completed",
"next_run_at": "2026-10-16T09:30:00.004Z",
"created_at": "2026-10-06T08:00:00.000Z",
"updated_at": "2026-10-16T09:15:21.101Z",
"url": "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61"
}
],
"has_more": false
}Responses
Status | Meaning |
|---|---|
| Newest first. |
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| "list" | — |
| object[] | — |
| string | — |
| "watchlist" | — |
| string | — |
| string | Whose item ids the list holds ( |
| string | Every run fetches the list here. |
| string | The currency of every price in its snapshots, changes and webhooks. |
| string | One of: |
| string or null | One of: |
| object or object | When the list runs: a cadence (start to start) or a cron schedule in a time zone. |
| string or null | The cadence of an every-list; |
| integer or null | The cadence in seconds; |
| integer | — |
| object | Up to 10 steps, each running one scraper over the items sent to it; its routes send the items on a branch to another step ( |
| boolean | True when the list runs its platform default. |
| string or null | — |
| integer | Items on the list, every state. |
| object | — |
| integer | Deprecated. Deprecated alias, same value as |
| object | Deprecated. Deprecated alias, same value as |
| object | Up-front cost of the next runs: a sweep run with nothing changed (Amazon: pulse), a refresh run, and the one-off baseline fetch of |
| integer | — |
| integer | Runs skipped in a row for credits; the third pauses the list. |
| string (date-time) or null | — |
| string or null | One of: |
| string (date-time) or null | null while paused. |
| string (date-time) | — |
| string (date-time) | — |
| string | — |
| boolean | — |
GET /v1/watchlists/{id}
Get a watchlist.
One list, with its item counts per state (items_by_state) and the credits its next runs should cost (estimated_credits). Free.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Request
- cURL
- Python
- Node.js
curl "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61" \
-H "Authorization: Bearer $SCRAPELY_API_KEY"import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.get(
"https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61",
headers=HEADERS,
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61', {
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 404 Not Found
{
"id": "wl_3f1c2a9e8d4b4c61",
"object": "watchlist",
"name": "Consoles",
"platform": "target",
"marketplace": "us",
"currency": "USD",
"status": "active",
"paused_reason": null,
"schedule": {
"type": "every",
"every": "15m",
"every_seconds": 900
},
"every": "15m",
"every_seconds": 900,
"refresh_every_runs": 8,
"webhook_url": "https://example.com/hooks/scrapely",
"item_count": 2,
"items_by_state": {
"new": 0,
"active": 2,
"delisted": 0
},
"asin_count": 2,
"asins_by_state": {
"new": 0,
"active": 2,
"delisted": 0
},
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "target.standard",
"routes": [
{
"when": "changed",
"notify": true
}
]
}
]
},
"default_pipeline": true,
"estimated_credits": {
"pulseRun": 2,
"refreshRun": 2,
"newAsins": 0
},
"run_count": 42,
"consecutive_skips": 0,
"last_run_at": "2026-10-16T09:15:00.004Z",
"last_run_status": "completed",
"next_run_at": "2026-10-16T09:30:00.004Z",
"created_at": "2026-10-06T08:00:00.000Z",
"updated_at": "2026-10-16T09:15:21.101Z",
"url": "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61"
}{
"error": {
"code": "NOT_FOUND",
"message": "No watchlist wl_3f1c2a9e8d4b4c61 on this account.",
"param": "id",
"docs": "/docs/errors#not_found"
}
}Responses
Status | Meaning |
|---|---|
| The watchlist, with item counts per state and credit estimates. |
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist" | — |
| string | — |
| string | Whose item ids the list holds ( |
| string | Every run fetches the list here. |
| string | The currency of every price in its snapshots, changes and webhooks. |
| string | One of: |
| string or null | One of: |
| object or object | When the list runs: a cadence (start to start) or a cron schedule in a time zone. |
| string or null | The cadence of an every-list; |
| integer or null | The cadence in seconds; |
| integer | — |
| object | Up to 10 steps, each running one scraper over the items sent to it; its routes send the items on a branch to another step ( |
| string | The step every run starts with. Default: the first step. |
| string or null | Where items never read before start instead. Default: |
| object or null | Every Nth run starts at this step instead. |
| object[] | 1 to 10 items. |
| boolean | True when the list runs its platform default. |
| string or null | — |
| integer | Items on the list, every state. |
| object | — |
| integer | — |
| integer | — |
| integer | — |
| integer | Deprecated. Deprecated alias, same value as |
| object | Deprecated. Deprecated alias, same value as |
| integer | — |
| integer | — |
| integer | — |
| object | Up-front cost of the next runs: a sweep run with nothing changed (Amazon: pulse), a refresh run, and the one-off baseline fetch of |
| integer | — |
| integer | — |
| integer | — |
| integer | — |
| integer | Runs skipped in a row for credits; the third pauses the list. |
| string (date-time) or null | — |
| string or null | One of: |
| string (date-time) or null | null while paused. |
| string (date-time) | — |
| string (date-time) | — |
| string | — |
PATCH /v1/watchlists/{id}
Update a watchlist.
Rename; switch between a cadence (every, counted from the last run) and a cron schedule (cron + timezone); change the webhook, the pipeline or the refresh interval; pause or resume. Send only the fields to change. Free.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Request body
Name | Type | Required | Description |
|---|---|---|---|
| string | — | Up to 100 characters. |
| string | — | Switch to (or change) a cadence; clears a cron schedule. Send this or |
| string | — | Switch to (or change) a cron schedule. Standard 5-field cron (minute hour day-of-month month day-of-week) read in |
| string | — | With |
| string or null | — | A registered endpoint URL; |
| string or null | — | Alternative to |
| integer | — | From 0 to 1,000. |
| object or null | — |
|
| string | — | The step every run starts with. Default: the first step. |
| string or null | — | Where items never read before start instead. Default: |
| object or null | — | Every Nth run starts at this step instead. |
| integer | yes | From 1 to 1,000. |
| string | yes | — |
| object[] | yes | 1 to 10 items. |
| string | yes | — |
| string | yes | GET /v1/watchlists/scrapers lists them with their fields. One of: |
| string | — |
|
| object[] | — | At most 8 items. |
| string | yes | One of: |
| string | — | With |
| string | — | One of: |
| number or boolean or string | — | — |
| string | — | A step id to run next for the matching items. |
| boolean | — | Send the matching items ( |
| string | — | Resuming clears an auto-pause and its skip count. One of: |
Request
- cURL
- Python
- Node.js
curl -X PATCH "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cron":"0 8 * * 1-5","timezone":"America/New_York"}'import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.patch(
"https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61",
headers=HEADERS,
json={"cron": "0 8 * * 1-5", "timezone": "America/New_York"},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ cron: '0 8 * * 1-5', timezone: 'America/New_York' }),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 400 Bad Request
{
"id": "wl_3f1c2a9e8d4b4c61",
"object": "watchlist",
"name": "Consoles",
"platform": "target",
"marketplace": "us",
"currency": "USD",
"status": "active",
"paused_reason": null,
"schedule": {
"type": "cron",
"cron": "0 8 * * 1-5",
"timezone": "America/New_York"
},
"every": null,
"every_seconds": null,
"refresh_every_runs": 8,
"webhook_url": "https://example.com/hooks/scrapely",
"item_count": 2,
"items_by_state": {
"new": 0,
"active": 2,
"delisted": 0
},
"asin_count": 2,
"asins_by_state": {
"new": 0,
"active": 2,
"delisted": 0
},
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "target.standard",
"routes": [
{
"when": "changed",
"notify": true
}
]
}
]
},
"default_pipeline": true,
"estimated_credits": {
"pulseRun": 2,
"refreshRun": 2,
"newAsins": 0
},
"run_count": 42,
"consecutive_skips": 0,
"last_run_at": "2026-10-16T09:15:00.004Z",
"last_run_status": "completed",
"next_run_at": "2026-10-19T12:00:00.000Z",
"created_at": "2026-10-06T08:00:00.000Z",
"updated_at": "2026-10-16T09:15:04.120Z",
"url": "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61"
}{
"error": {
"code": "INVALID_PARAM",
"message": "Send either `every` (a cadence) or `cron` (a schedule), not both.",
"param": "cron",
"docs": "/docs/errors#invalid_param"
}
}Responses
Status | Meaning |
|---|---|
| Updated. |
|
|
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist" | — |
| string | — |
| string | Whose item ids the list holds ( |
| string | Every run fetches the list here. |
| string | The currency of every price in its snapshots, changes and webhooks. |
| string | One of: |
| string or null | One of: |
| object or object | When the list runs: a cadence (start to start) or a cron schedule in a time zone. |
| string or null | The cadence of an every-list; |
| integer or null | The cadence in seconds; |
| integer | — |
| object | Up to 10 steps, each running one scraper over the items sent to it; its routes send the items on a branch to another step ( |
| string | The step every run starts with. Default: the first step. |
| string or null | Where items never read before start instead. Default: |
| object or null | Every Nth run starts at this step instead. |
| object[] | 1 to 10 items. |
| boolean | True when the list runs its platform default. |
| string or null | — |
| integer | Items on the list, every state. |
| object | — |
| integer | — |
| integer | — |
| integer | — |
| integer | Deprecated. Deprecated alias, same value as |
| object | Deprecated. Deprecated alias, same value as |
| integer | — |
| integer | — |
| integer | — |
| object | Up-front cost of the next runs: a sweep run with nothing changed (Amazon: pulse), a refresh run, and the one-off baseline fetch of |
| integer | — |
| integer | — |
| integer | — |
| integer | — |
| integer | Runs skipped in a row for credits; the third pauses the list. |
| string (date-time) or null | — |
| string or null | One of: |
| string (date-time) or null | null while paused. |
| string (date-time) | — |
| string (date-time) | — |
| string | — |
DELETE /v1/watchlists/{id}
Delete a watchlist.
Deletes the list, its items and its run history. A run already in progress finishes but sends no webhooks.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Request
- cURL
- Python
- Node.js
curl -X DELETE "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61" \
-H "Authorization: Bearer $SCRAPELY_API_KEY"import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.delete(
"https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61",
headers=HEADERS,
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 404 Not Found
{
"id": "wl_3f1c2a9e8d4b4c61",
"object": "watchlist",
"deleted": true
}{
"error": {
"code": "NOT_FOUND",
"message": "No watchlist wl_3f1c2a9e8d4b4c61 on this account.",
"param": "id",
"docs": "/docs/errors#not_found"
}
}Responses
Status | Meaning |
|---|---|
| Deleted. |
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist" | — |
| true | — |
POST /v1/watchlists/{id}/run
Run a watchlist now.
Starts a run at once (202). A cadence list (every) counts its next run from now; a cron list keeps its wall-clock times, skipping a fire time less than 5 minutes after the manual run. Billed like a scheduled run; works on a paused list. 409 while a run of this list is in progress.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Request
- cURL
- Python
- Node.js
curl -X POST "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/run" \
-H "Authorization: Bearer $SCRAPELY_API_KEY"import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.post(
"https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/run",
headers=HEADERS,
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/run', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 202 Accepted
- 409 Conflict
{
"id": "wlrun_5f0c8a1b2d3e4f56",
"object": "watchlist_run",
"watchlist_id": "wl_3f1c2a9e8d4b4c61",
"run_number": 42,
"trigger": "manual",
"kind": "sweep",
"status": "running",
"credits": 0,
"stats": null,
"error": null,
"started_at": "2026-10-16T09:15:04.120Z",
"finished_at": null,
"runs_url": "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/runs"
}{
"error": {
"code": "CONFLICT",
"message": "A run of this watchlist is already in progress. It will finish on its own; see GET …/runs.",
"param": "id",
"docs": "/docs/errors#conflict"
}
}Responses
Status | Meaning |
|---|---|
| Started. |
|
|
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist_run" | — |
| string | — |
| integer | — |
| string | One of: |
| string |
|
| string | One of: |
| integer | Charged, after refunds. |
| object or null | — |
| integer | — |
| string | The step the run started from. |
| integer | Items read for the first time. |
| object | Per step that ran, in run order. |
| integer | Items a notify rule matched on a branch other than |
| string | Deprecated. Runs before pipelines. Amazon: |
| object | Deprecated. Runs before pipelines, keyed by tier. |
| object | Deprecated. |
| integer | Step items postponed to the next run: the balance could not cover them. |
| integer | — |
| integer | — |
| integer | partial + unavailable items (free, retried next run, never a change). |
| integer | On |
| string or null | — |
| string (date-time) | — |
| string (date-time) or null | — |
| string | — |
GET /v1/watchlists/scrapers
List pipeline scrapers.
What a pipeline step can run on a platform: each scraper, what it reads, its credits per item, the fields its routes can test and whether it is available in the marketplace; plus ready-made templates. Free.
Query parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | — | The platform whose scrapers to list. Default |
| string | — | Marks each scraper |
Request
- cURL
- Python
- Node.js
curl -G "https://api.scrapelyserp.com/v1/watchlists/scrapers" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-d platform=walmart \
-d marketplace=usimport os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.get(
"https://api.scrapelyserp.com/v1/watchlists/scrapers",
headers=HEADERS,
params={"platform": "walmart", "marketplace": "us"},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch(`https://api.scrapelyserp.com/v1/watchlists/scrapers?${new URLSearchParams({ platform: 'walmart', marketplace: 'us' })}`, {
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 400 Bad Request
{
"object": "list",
"platform": "walmart",
"marketplace": "us",
"branches": ["new", "changed", "unchanged", "ok", "failed", "not_found", "always"],
"ops": ["increased", "decreased", "below", "above", "is", "is_not"],
"data": [
{
"id": "walmart.standard",
"name": "Product lookup",
"description": "Price, stock and the rest of what the retailer shows.",
"credits_per_item": "1",
"reads": "product",
"input": "item",
"fields": [
{
"key": "price",
"label": "Price",
"type": "money"
},
{
"key": "listPrice",
"label": "List price",
"type": "money"
},
{
"key": "inStock",
"label": "In stock",
"type": "boolean"
},
{
"key": "seller",
"label": "Seller",
"type": "text"
},
{
"key": "byPlatform",
"label": "Sold by Walmart",
"type": "boolean"
},
{
"key": "clearance",
"label": "Clearance",
"type": "boolean"
}
],
"available": true
}
],
"templates": [
{
"id": "default",
"name": "Price & stock monitor",
"description": "Notify when price, stock or seller changes.",
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "walmart.standard",
"routes": [
{
"when": "changed",
"notify": true
}
]
}
]
}
},
{
"id": "restock",
"name": "Back in stock",
"description": "Notify only when an item comes back in stock.",
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "walmart.standard",
"routes": [
{
"when": "changed",
"field": "inStock",
"op": "is",
"value": true,
"notify": true
}
]
}
]
}
},
{
"id": "price-drop",
"name": "Price drops",
"description": "Notify when the price goes down.",
"pipeline": {
"entry": "check",
"baseline": null,
"refresh": null,
"steps": [
{
"id": "check",
"scraper": "walmart.standard",
"routes": [
{
"when": "changed",
"field": "price",
"op": "decreased",
"notify": true
}
]
}
]
}
}
]
}{
"error": {
"code": "INVALID_PARAM",
"message": "Unknown platform \"wallmart\". Available: amazon, google, walmart, ebay, target, bestbuy, aliexpress.",
"param": "platform",
"docs": "/docs/errors#invalid_param"
}
}Responses
Status | Meaning |
|---|---|
| Scrapers and templates. |
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| "list" | — |
| string | — |
| string | — |
| string[] | — |
| string[] | — |
| object[] | — |
| string | The |
| string | — |
| string | — |
| string | A number, or a range such as |
| string | The kind of data it reads; steps of one kind compare with each other. One of: |
| string |
|
| object[] | What rules can test ( |
| object | Step options, when it has any: |
| boolean | Can run in |
| object[] | — |
| string | — |
| string | — |
| string | — |
| object | Up to 10 steps, each running one scraper over the items sent to it; its routes send the items on a branch to another step ( |
GET /v1/watchlists/{id}/runs
List runs.
The list's runs, newest first, with per-step counts and the credits each run cost. The newest 500 are kept. Free.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Query parameters
Name | Type | Required | Description |
|---|---|---|---|
| integer | — | How many runs to return. Default |
Request
- cURL
- Python
- Node.js
curl -G "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/runs" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-d limit=20import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.get(
"https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/runs",
headers=HEADERS,
params={"limit": "20"},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch(`https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/runs?${new URLSearchParams({ limit: '20' })}`, {
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 404 Not Found
{
"object": "list",
"data": [
{
"id": "wlrun_5f0c8a1b2d3e4f56",
"object": "watchlist_run",
"watchlist_id": "wl_3f1c2a9e8d4b4c61",
"run_number": 42,
"trigger": "schedule",
"kind": "sweep",
"status": "completed",
"credits": 2,
"stats": {
"tracked": 2,
"entry": "check",
"new_items": 0,
"steps": {
"check": {
"scraper": "target.standard",
"requested": 2,
"ok": 2,
"partial": 0,
"not_found": 0,
"unavailable": 0,
"credits": 2,
"skipped": 0
}
},
"matched": 0,
"deferred": 0,
"changed": 1,
"delisted": 0,
"retry_next_run": 0
},
"error": null,
"started_at": "2026-10-16T09:15:00.004Z",
"finished_at": "2026-10-16T09:15:02.310Z"
}
],
"has_more": true
}{
"error": {
"code": "NOT_FOUND",
"message": "No watchlist wl_3f1c2a9e8d4b4c61 on this account.",
"param": "id",
"docs": "/docs/errors#not_found"
}
}Responses
Status | Meaning |
|---|---|
| Runs. |
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| "list" | — |
| object[] | — |
| string | — |
| "watchlist_run" | — |
| string | — |
| integer | — |
| string | One of: |
| string |
|
| string | One of: |
| integer | Charged, after refunds. |
| object or null | — |
| string or null | — |
| string (date-time) | — |
| string (date-time) or null | — |
| boolean | — |
GET /v1/watchlists/{id}/items
List items.
The items on the list with their last snapshot, in id order. Each carries id (plus asin on Amazon lists). changed_since returns only items that changed or were delisted since then: the pull counterpart of the webhook.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Query parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | — | ISO 8601 or epoch milliseconds. |
| string | — | Only items in this state. One of: |
| integer | — | Items per page. Default |
| string | — |
|
Request
- cURL
- Python
- Node.js
curl -G "https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/items" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
--data-urlencode "changed_since=2026-10-15T09:00:00Z" \
-d limit=100import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.get(
"https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/items",
headers=HEADERS,
params={"changed_since": "2026-10-15T09:00:00Z", "limit": "100"},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch(`https://api.scrapelyserp.com/v1/watchlists/wl_3f1c2a9e8d4b4c61/items?${new URLSearchParams({ changed_since: '2026-10-15T09:00:00Z', limit: '100' })}`, {
headers: { Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}` },
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 400 Bad Request
{
"object": "list",
"platform": "target",
"marketplace": "us",
"data": [
{
"id": "83887639",
"state": "active",
"added_at": "2026-10-06T08:00:00.000Z",
"last_status": "ok",
"pulse": null,
"snapshot": {
"price": 349.99,
"regularPrice": 399.99,
"inStock": true,
"shipping": "IN_STOCK",
"pickup": "IN_STOCK",
"title": "Nintendo Switch - OLED Model with White Joy-Con",
"parentTcin": null
},
"snapshot_at": "2026-10-16T09:15:01.870Z",
"snapshot_tier": "standard",
"buybox_above_lowest": false,
"pending_standard": false,
"changed_at": "2026-10-16T09:15:01.870Z",
"last_change": {
"price": {
"before": 399.99,
"after": 349.99
}
},
"delisted_at": null
}
],
"has_more": false,
"next_starting_after": "83887639"
}{
"error": {
"code": "INVALID_PARAM",
"message": "`changed_since` must be an ISO 8601 timestamp (e.g. 2026-10-16T09:00:00Z) or epoch milliseconds.",
"param": "changed_since",
"docs": "/docs/errors#invalid_param"
}
}Responses
Status | Meaning |
|---|---|
| A page of items. |
|
|
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| "list" | — |
| string | — |
| string | — |
| object[] | — |
| string | The item's id on the list's platform: an ASIN on Amazon, an item id on Walmart, a TCIN on Target, a SKU on Best Buy. |
| string | Deprecated. Amazon lists only: the same value as |
| string |
|
| string (date-time) | — |
| string or null | One of: |
| object or null | — |
| object or null | The tracked fields from the last detail fetch, named as in |
| string (date-time) or null | — |
| string or null | The tier the snapshot came from (Amazon: |
| boolean | The buy box cost more than the lowest new offer at the last fetch, so every run checks it at lite. |
| boolean | A detail fetch (Amazon: lite) is owed: pulse moved, or the last one failed or was deferred. |
| string (date-time) or null | — |
| object or null | — |
| string (date-time) or null | — |
| boolean | — |
| string or null | The last |
POST /v1/watchlists/{id}/items
Add or remove items.
Item ids of the list's platform. Removals apply first. Added items get a baseline fetch on the next run (Amazon: full; no change event); a delisted item added again gets another chance. At most 50,000 items per list. Free.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Request body
Name | Type | Required | Description |
|---|---|---|---|
| string[] | — | Item ids to add. At most 50,000 items. |
| string[] | — | Item ids to remove. At most 50,000 items. |
Request
- cURL
- Python
- Node.js
curl -X POST "https://api.scrapelyserp.com/v1/watchlists/wl_9a8b7c6d5e4f3a21/items" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"add":["6505727"],"remove":["6418599"]}'import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.post(
"https://api.scrapelyserp.com/v1/watchlists/wl_9a8b7c6d5e4f3a21/items",
headers=HEADERS,
json={"add": ["6505727"], "remove": ["6418599"]},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists/wl_9a8b7c6d5e4f3a21/items', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ add: ['6505727'], remove: ['6418599'] }),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 400 Bad Request
{
"id": "wl_9a8b7c6d5e4f3a21",
"object": "watchlist_items",
"platform": "bestbuy",
"marketplace": "us",
"added": 1,
"reactivated": 0,
"removed": 1,
"unchanged": 0,
"item_count": 120,
"asin_count": 120
}{
"error": {
"code": "INVALID_PARAM",
"message": "add[0] (\"SKU-6505727\") is not a SKU: expected the numeric Best Buy SKU, e.g. 6505727.",
"param": "add[0]",
"docs": "/docs/errors#invalid_param"
}
}Responses
Status | Meaning |
|---|---|
| What changed. |
|
|
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist_items" | — |
| string | — |
| string | — |
| integer | — |
| integer | Delisted items added again. |
| integer | — |
| integer | Already present (add) or not present (remove). |
| integer | — |
| integer | Deprecated. Deprecated alias, same value as |
POST /v1/watchlists/{id}/asins
Add or remove items (legacy path).
Deprecated
Still answered exactly as documented, but kept only for older clients. New code should use the replacement named below.
The historical path of POST /api/v1/watchlists/{id}/items: same body and behaviour on every platform; only object differs. Item ids of the list's platform. Removals apply first. Added items get a baseline fetch on the next run (Amazon: full; no change event); a delisted item added again gets another chance. At most 50,000 items per list. Free.
Path parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | The watchlist id ( |
Request body
Name | Type | Required | Description |
|---|---|---|---|
| string[] | — | Item ids to add. At most 50,000 items. |
| string[] | — | Item ids to remove. At most 50,000 items. |
Request
- cURL
- Python
- Node.js
curl -X POST "https://api.scrapelyserp.com/v1/watchlists/wl_2b3c4d5e6f7a8b9c/asins" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"add":["B074PVTPBW"],"remove":["0060094664"]}'import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.post(
"https://api.scrapelyserp.com/v1/watchlists/wl_2b3c4d5e6f7a8b9c/asins",
headers=HEADERS,
json={"add": ["B074PVTPBW"], "remove": ["0060094664"]},
timeout=30,
)
r.raise_for_status()
data = r.json()const res = await fetch('https://api.scrapelyserp.com/v1/watchlists/wl_2b3c4d5e6f7a8b9c/asins', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ add: ['B074PVTPBW'], remove: ['0060094664'] }),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();Response
- 200 OK
- 404 Not Found
{
"id": "wl_2b3c4d5e6f7a8b9c",
"object": "watchlist_asins",
"platform": "amazon",
"marketplace": "us",
"added": 1,
"reactivated": 0,
"removed": 1,
"unchanged": 0,
"item_count": 2400,
"asin_count": 2400
}{
"error": {
"code": "NOT_FOUND",
"message": "No watchlist wl_2b3c4d5e6f7a8b9c on this account.",
"param": "id",
"docs": "/docs/errors#not_found"
}
}Responses
Status | Meaning |
|---|---|
| What changed. |
|
|
|
|
|
|
|
|
|
|
Response fields
Field | Type | Description |
|---|---|---|
| string | — |
| "watchlist_asins" | — |
| string | — |
| string | — |
| integer | — |
| integer | Delisted items added again. |
| integer | — |
| integer | Already present (add) or not present (remove). |
| integer | — |
| integer | Deprecated. Deprecated alias, same value as |