Overview
Every endpoint of the Scrapely API with its parameters, request and response examples in cURL, Python and Node.js, generated from the OpenAPI document. Start here for the base URL, authentication and the conventions every endpoint shares.
Every endpoint of the Scrapely API with its parameters, request and response examples in cURL, Python and Node.js, generated from the OpenAPI document. Start here for the base URL, authentication and the conventions every endpoint shares.
Base URL
https://api.scrapelyserp.com/v1Every endpoint lives under /v1, over HTTPS. The machine-readable description is https://api.scrapelyserp.com/v1/openapi.json (OpenAPI 3.1): import it into Postman, Insomnia or a client generator. New fields and endpoints are added without notice, so ignore fields you do not know; breaking changes ship as a new version.
Authentication
Send an API key from Dashboard → Developers → API keys with every call. The samples read it from the SCRAPELY_API_KEY environment variable.
How | Example | Notes |
|---|---|---|
Bearer header |
| Recommended. |
API key header |
| For clients that reserve |
Query parameter |
| Accepted for clients that cannot set headers. Prefer a header: URLs end up in logs. |
Request
- cURL
- Python
- Node.js
curl "https://api.scrapelyserp.com/v1/account" \
-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/account",
headers=HEADERS,
timeout=30,
)
r.raise_for_status()
data = r.json()
print(data["credit_balance"])const res = await fetch('https://api.scrapelyserp.com/v1/account', {
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();
console.log(data.credit_balance);GET /v1/platforms and GET /v1/openapi.json need no key. A missing, unknown or revoked key is a 401 UNAUTHORIZED. More: Authentication.
Requests and responses
POST, PUT and PATCH take a JSON body (
Content-Type: application/json); GET and DELETE take query parameters.Request parameters and resource fields are
snake_case. Product fields inside item results, snapshots and changes arecamelCase(inStock,newMinPrice), the same names everywhere.Timestamps are ISO 8601 in UTC. Prices are numbers in the marketplace currency, named beside them (
currency).A field the retailer does not show is left out rather than sent empty;
nullmeans "known to be nothing".
Every response carries these headers (the credit headers on item calls):
Header | Meaning |
|---|---|
| Unique per request. Quote it to support. |
| Credits this request cost (0 when refunded). |
| Your credit balance after this request. |
| Calls allowed per window for this key. |
| Calls left in the current window. |
| Unix time (seconds) when the window frees a call. |
| On a |
|
|
The item envelope
POST /v1/products, POST /v1/offers, GET /v1/products/{id}/offers and POST /v1/offers/check answer one shape. A well-formed call is always 200, even when every item failed: read each result's status.
Field | Type | Description |
|---|---|---|
| string | The platform that was read. |
| string | The marketplace that was read; every price is in its currency. |
| string | The tier that answered ( |
| string[] | Echoes the request's |
| object[] | One per unique requested id, in request order. Every id appears exactly once. |
| string | The item id as the platform knows it (the ASIN on Amazon). |
| string |
|
| object | — |
| integer | Unique items after de-dup. |
| integer | Answered in full (charged). |
| integer | Answered with fields missing (refunded). Always 0 where an answer cannot be partial (offers). |
| integer | The platform says the item does not exist (charged). |
| integer | No answer this time (refunded; retry later). |
| integer | How long the call took, in milliseconds. |
| object | lite + full. |
| integer | Credits charged for the call (also |
Item statuses (ok, partial, not_found, unavailable) and what each is charged: Responses & statuses.
Lists and pagination
List endpoints answer { "object": "list", "data": [...], "has_more": bool } and take limit.
Endpoint |
| Paging |
|---|---|---|
| 1–100, default 100 | Newest first. At most 100 lists per account, so one page holds them all. |
| 1–1,000, default 100 | In id order. Pass the previous page's |
| 1–100, default 20 | Newest first; the newest 500 runs are kept. |
| — | Every endpoint (at most 10). |
Idempotency
A client that times out on a create cannot tell whether it happened. POST /v1/watchlists therefore accepts an Idempotency-Key header (or an idempotency_key body field) of up to 255 printable characters, ideally a UUID, remembered for 24 hours:
A repeat after success returns the original response with
Idempotent-Replayed: true, and creates nothing.A repeat while the first request is still running is a
409 CONFLICT: retry in a few seconds.The same key with a different body is a
422withINVALID_PARAMandparam: "idempotency_key".A repeat after a failure (any non-2xx: nothing was created) runs again.
Item calls need no key: they create nothing, and a retried call is billed only for what it answers. POST /v1/webhook_endpoints deliberately has none: a replayable response would be a stored secret.
Errors
Every error has one shape. Branch on code; message is for humans and may be reworded; param names the parameter at fault; docs links to the code's entry.
{
"error": {
"code": "INVALID_PARAM",
"message": "ids[1] (\"12\") is not a TCIN: expected the numeric Target item number (TCIN), 6 to 10 digits, e.g. 83887639.",
"param": "ids[1]",
"docs": "/docs/errors#invalid_param"
}
}Code | HTTP | Meaning | What to do |
|---|---|---|---|
400 | A required parameter was not sent. | Add the parameter. JSON bodies need | |
400 | A parameter was sent but its value is not accepted: wrong type, out of range, an unsupported marketplace, a private webhook URL, an entry in the id list that is not a valid id on that platform ( | Read | |
401 | No API key was sent, or the key is unknown or revoked. | Send your key as | |
403 | The key belongs to the public, read-only demo workspace. It can be seen in the dashboard but cannot make requests or spend credits. | Create an account (5,000 free credits) and use your own API key. | |
402 | Your credit balance cannot cover this request. Nothing was charged. A call is all-or-nothing: every item in it must be affordable. | Buy a credit pack (credits never expire), or enable auto top-up in Billing. | |
404 | The endpoint, watchlist, webhook endpoint or structured endpoint does not exist, or belongs to another account. | Check the id and the path. The API reference lists every endpoint. | |
410 | The endpoint was retired and will not come back. The generic URL scraper ( | Use the product API, | |
409 | The request conflicts with the resource's current state: a request with the same | Retry idempotency and run conflicts after a few seconds; for limits, delete something first. | |
429 | All of your concurrent request slots are busy. The request was refused immediately and nothing was charged. | Retry with backoff. Concurrency grows with lifetime credits purchased. | |
429 | Too many API calls with this key in a short window. Separate from concurrency: this counts calls, including cheap ones such as listing watchlist changes. | Wait | |
502 | We could not get a usable answer from the retailer after our retries: it blocked us, rate-limited us, errored, or never responded. You were not charged. | Retry with backoff. Items that fail inside a successful call come back | |
503 | A dependency on our side is down or overloaded. Nothing was charged. | Retry with exponential backoff. Status updates at /status. | |
501 | The platform, marketplace, endpoint or option is announced but not available yet. It answers with | Use a live platform and marketplace ( | |
500 | Something failed on our side. Nothing was charged, and the failure was logged with your | Retry. If it persists, contact support with the |
Coming-soon platforms, marketplaces and endpoints add "coming_soon": true to their 501. When to retry: Errors & retries.
Every endpoint
Method | Path | Does |
|---|---|---|
|
|
Method | Path | Does |
|---|---|---|
|
| |
|
| |
|
|
Method | Path | Does |
|---|---|---|
|
|
Method | Path | Does |
|---|---|---|
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
|
Method | Path | Does |
|---|---|---|
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
|
Method | Path | Does |
|---|---|---|
|
| |
|
| |
|
| |
|
|
Method | Path | Does |
|---|---|---|
|
| |
|
|
What Scrapely POSTs to your endpoints: watchlist.product.changed, watchlist.product.matched, watchlist.product.delisted, watchlist.run.completed, webhook.test.