Shopify stores
Any Shopify store's products by hostname and handle: lowest price, compare-at price, variants with their availability, vendor, type and tags, about 1.3 million products per credit. Plus a page-by-page catalog of the store. Prices carry no currency.
Any Shopify store's products by hostname and handle: lowest price, compare-at price, variants with their availability, vendor, type and tags, about 1.3 million products per credit. Plus a page-by-page catalog of the store. Prices carry no currency.
At a glance
Property | Value |
|---|---|
|
|
Item id | The store's hostname, a slash and the product handle: |
Marketplaces | Live: |
Tier |
|
Ids per call | 100, from any mix of stores. |
Typical call | 5–30 s for 100 products, longer when the store throttles |
Catalog |
|
Prices have no currency
A store sets its own currency and does not say which in the data we read, so every amount comes back as { "amount": 120, "currency": null }. The currency is the one the store sells in. price is the lowest variant price; shopify.listPrice is the compare-at (crossed-out) price of that variant, only when it is higher, else null.
Availability is a flag, not a quantity
availability.inStock is true when any variant is available, false when none is, and absent when the store gives no flag. shopify.variants[].available is the store's own flag per variant. Quantities are never returned, even where a store happens to show one.
Example
- cURL
- Python
- Node.js
curl -X POST "https://api.scrapelyserp.com/v1/products" \
-H "Authorization: Bearer $SCRAPELY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "shopify",
"ids": [
"allbirds.com/mens-tree-runners",
"https://kith.com/products/vanvn000z02gr8"
]
}'import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SCRAPELY_API_KEY']}"}
r = requests.post(
"https://api.scrapelyserp.com/v1/products",
headers=HEADERS,
json={
"platform": "shopify",
"ids": [
"allbirds.com/mens-tree-runners",
"https://kith.com/products/vanvn000z02gr8",
],
},
timeout=30,
)
r.raise_for_status()
data = r.json()
for p in data["results"]:
print(p["id"], p["status"], (p.get("price") or {}).get("amount"), (p.get("availability") or {}).get("inStock"))const res = await fetch('https://api.scrapelyserp.com/v1/products', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SCRAPELY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
platform: 'shopify',
ids: [
'allbirds.com/mens-tree-runners',
'https://kith.com/products/vanvn000z02gr8',
],
}),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const data = await res.json();
for (const p of data.results) console.log(p.id, p.status, p.price?.amount, p.availability?.inStock);Full reference → POST /v1/products
Shopify fields
Besides the common fields, each result carries a shopify object.
Field | Description |
|---|---|
| The lowest variant price, as a number, with |
|
|
|
|
| The compare-at price of the lowest-priced variant, when higher than |
| Free text the merchant set; |
| When the product was published (ISO 8601, UTC). |
| The product link on the store, and its first picture. |
Not given | Quantities, ratings and reviews, descriptions, and the store's currency. |
Which stores work
Stores that publish their product list. A hostname that is not a Shopify store, a store that hides its products, or one behind a password page has nothing to read: its products are not_found (or unavailable for a password page), and a failed request is not charged. Give the hostname as the store spells it (www. and a bare name are different stores to us until the store redirects one to the other).
Catalog
A page of a store's products, GET /v1/structured/shopify/catalog?domain=allbirds.com, up to 250 per page in the store's own order, with collection (a collection handle) to list one collection and page to go on. Every row has the product id that POST /v1/products takes. Shopify has no keyword search: query keeps only the products of the page you fetched whose title, vendor, product type or tags contain every word, so walk the pages to search a whole store. total_results is always null, and next_page is set only when the page was full. An unknown store or collection is a free 404.
Parameter | Type | Default | Description |
|---|---|---|---|
| string | — | The store hostname in lowercase, e.g. |
| string | — | A collection handle (the part after /collections/ in the store's link): list that collection instead of the whole store. An unknown collection is a 404. |
| integer |
| Catalog page, 1–1000 (250 products each). An empty page is the end. |
| integer |
| Products per page, 1–250. |
| string | — | Keep only the products of this page whose title, vendor, product type or tags contain every word. It filters one page, not the store. |
Full reference → GET /v1/structured/shopify/catalog
Statuses on Shopify stores
partial: not used; a product is either read or it is not.not_found: the store has no product with that handle, or the hostname is not a Shopify store that publishes its products.unavailable: no usable answer after retries, or the store is closed behind a password.reasonsays which:throttled(the store is limiting requests),closedorunreachable. Not charged; ask again. Lookups may take longer when a store throttles: we retry on fresh connections before giving up, and a throttled item costs nothing and can simply be asked for again.