Every product on a Shopee category, search, brand, shop or collection page, as JSON. Send the page URL from your browser, with its sort and filters, and get up to 60 products per request with price, sold counts and rating, page after page up to about 1,020 products per listing URL (500 when sorted by sales, price or newest). You pay only for pages that return products.
The Listings API returns the products of one Shopee listing page: a category, a search, a brand, a shop or a collection. You send the page URL exactly as your browser shows it, with its sort order and filters, and get a job_id back at once. You poll the job until it is completed; the products of that page are then in items, as Shopee's own listing cards or, with slim: true, as compact uniform rows. Each completed page tells you whether more pages follow and gives you the URL of the next one, so you walk a listing page by page.
Category, search, brand, shop and collection URLs with the website's own sort and filter parameters. No IDs to look up and no parameters to translate.
One request is one listing page, the website's page size. Walk up to 17 pages (about 1,020 products) per URL, or 9 pages (500) when sorted by sales, price or newest.
One charge per completed page that returned at least one product. Empty pages, pages past the end of a listing and failed jobs are free.
A parameter or filter value the API cannot apply returns 400 naming it, so you never get, or pay for, a broader listing than the one you asked for.
Markets: all eight: Brazil, Taiwan, Vietnam, Singapore, Thailand, Indonesia, Malaysia and the Philippines (Markets). For a single product's full page (stock, variants, vouchers), use the Shopee Product API with the same API key.
Base URL: https://shopee-multi-region.fastscraping.com. Replace YOUR_API_KEY with your API key.
Open the category, search, brand, shop or collection page on the Shopee website, apply the sort and filters you want, and copy the address bar.
POST /cbc with {"url": "…"}. Add "slim": true for compact rows. The answer carries the job_id.
GET /cbc/{job_id} every 3–5 seconds until status is completed or failed. While has_more is true, submit next_page_url as the next job.
The cURL sample submits page 0, polls it and submits page 1. The Python and Node.js samples walk a whole search listing this way and de-duplicate the products by itemid.
# 1. submit page 0 of a listing: the answer carries job_id and "status": "pending" curl -s -X POST "https://shopee-multi-region.fastscraping.com/cbc" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales","slim":true}' # 2. poll every 3-5 seconds until "status" is "completed" or "failed" curl -s "https://shopee-multi-region.fastscraping.com/cbc/JOB_ID" -H "X-API-Key: YOUR_API_KEY" # 3. completed and "has_more": true? submit next_page_url as a new job curl -s -X POST "https://shopee-multi-region.fastscraping.com/cbc" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales&page=1","slim":true}'
import time import requests BASE = "https://shopee-multi-region.fastscraping.com" HEAD = {"X-API-Key": "YOUR_API_KEY"} def fetch_page(url, slim=True, timeout_s=900): """Submit one listing page and wait until it is completed or failed.""" r = requests.post(f"{BASE}/cbc", headers=HEAD, json={"url": url, "slim": slim}, timeout=30) if r.status_code == 400: raise ValueError(r.json()["detail"]) # names the unsupported URL or filter r.raise_for_status() job_id = r.json()["job_id"] deadline = time.time() + timeout_s while time.time() < deadline: time.sleep(4) job = requests.get(f"{BASE}/cbc/{job_id}", headers=HEAD, timeout=30).json() if job["status"] in ("completed", "failed"): return job return None # still retrying: poll it later or re-submit def walk_listing(url, slim=True): seen, rows = set(), [] while url: job = fetch_page(url, slim) if not job or job["status"] != "completed": print("stopped at", url, job and job["error"]) # free; re-submit this URL later break for p in job["items"]: # slim rows; full cards: see card_shape if p["itemid"] not in seen: seen.add(p["itemid"]) rows.append(p) url = job["next_page_url"] if job["has_more"] else None return rows rows = walk_listing("https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales") for p in rows[:5]: print(p["itemid"], p["name"], p["price"], p["historical_sold"]) print(len(rows), "products")
// Node.js 18+ (built-in fetch). Save as listing.mjs and run: node listing.mjs const BASE = "https://shopee-multi-region.fastscraping.com"; const HEAD = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function fetchPage(url, slim = true, timeoutS = 900) { const res = await fetch(`${BASE}/cbc`, { method: "POST", headers: HEAD, body: JSON.stringify({ url, slim }) }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); // 400 names the unsupported URL or filter const { job_id } = await res.json(); const deadline = Date.now() + timeoutS * 1000; while (Date.now() < deadline) { await sleep(4000); const job = await (await fetch(`${BASE}/cbc/${job_id}`, { headers: HEAD })).json(); if (job.status === "completed" || job.status === "failed") return job; } return null; // still retrying: poll it later or re-submit } async function walkListing(url, slim = true) { const seen = new Set(); const rows = []; while (url) { const job = await fetchPage(url, slim); if (!job || job.status !== "completed") { console.log("stopped at", url, job?.error); // free; re-submit this URL later break; } for (const p of job.items) { if (!seen.has(p.itemid)) { seen.add(p.itemid); rows.push(p); } } url = job.has_more ? job.next_page_url : null; } return rows; } const rows = await walkListing("https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales"); for (const p of rows.slice(0, 5)) console.log(p.itemid, p.name, p.price, p.historical_sold); console.log(rows.length, "products");
Every request needs your API key in the X-API-Key header (Authorization: Bearer YOUR_API_KEY works too). It is the same key as for the Shopee Product API.
X-API-Key: YOUR_API_KEY
A missing key returns 401 Missing API key; an unknown or disabled key returns 401 Invalid or inactive API key. Jobs belong to the key that created them: reading another key's job returns 403. Keep the key on your server; do not put it in client-side code.
Creates a job for one page of a listing and answers at once with 200 and the job in status pending. Submitting is free; a job is charged only when it completes with products.
| Field | Type | Description |
|---|---|---|
urlrequired | string | The listing page URL as the browser shows it: a Shopee market host (shopee.com.br, shopee.tw, … with or without www.) and a listing path. Sort, filters and the page number go in its query string. See Supported URLs and Sort & filters. |
regionoptional | string | Lowercase market code. It is taken from the URL host, so you can leave it out. If you send it, it must match the host (br for shopee.com.br), otherwise 400. |
slimoptional | boolean | true returns compact, uniform product rows instead of Shopee's full listing cards. Default false. See Products in items. |
Any other field in the body is rejected with 422. There is no page field: one request is always one page, picked by the page parameter of the URL (0-based, missing = 0).
POST /cbc
Content-Type: application/json
X-API-Key: YOUR_API_KEY
{
"url": "https://shopee.com.br/Mouses-cat.11059977.11060050.11061451?sortBy=sales"
}
The job in status pending, in the same shape as GET /cbc/{job_id}: the result fields are null (counts 0) until the page is read, and a note explains how to poll.
{
"job_id": "f3e60f8ec3a6403fae81492caa1c43d6",
"status": "pending",
"region": "br",
"url": "https://shopee.com.br/Mouses-cat.11059977.11060050.11061451?sortBy=sales",
"page": 0,
"slim": false,
"created_ts": 1791203395,
"attempts": 0,
"pages_fetched": 0,
"items_returned": 0,
"total_count": null,
"total_pages": null,
"has_more": null,
"next_page": null,
"next_page_url": null,
"stop": null,
"card_shape": null,
"billable_pages": 0,
"billing_error": null,
"elapsed_s": null,
"error": null,
"kind": "category",
"warnings": null,
"note": "Poll GET /cbc/f3e60f8ec3a6403fae81492caa1c43d6 until status is 'completed' or 'failed' …"
}Possible errors: 400 not a Shopee listing URL, unsupported host, unsupported filter, a search URL without keyword or brands, or an invalid page (the message names the problem); 401 key problem; 422 an unknown field in the body; 429 a per-key limit; 503 temporarily unavailable. See Errors.
Returns the job. Poll every 3–5 seconds while status is pending or processing. Once it is completed, the answer also carries items, the products of the page in listing order. Polling is free and not rate-limited. Both final states, completed and failed, return HTTP 200.
| Field | Type | Meaning |
|---|---|---|
job_id | string | 32 hex characters. Quote it when you contact support. |
status | string | pending, processing, completed or failed. See Job lifecycle. |
region | string | Market code taken from the URL host (br, tw, vn, sg …). |
url, slim | string · boolean | What you submitted, unchanged. |
page | integer | The 0-based page this job reads: the URL's page, 0 when it has none. |
created_ts | integer | Submission time, Unix seconds. |
attempts | integer | Failed attempts so far; a page is retried automatically, up to 4 attempts in all. Informational. |
pages_fetched | integer | 1 once the page has been read; 0 before that, and for a page beyond the reachable range. |
items_returned | integer | Products on this page, 0–60. |
total_count | integer · null | Shopee's own total for the listing, at most 2500. It can be larger than what is reachable with your sort; use total_pages. |
total_pages | integer · null | Pages you can actually fetch for this URL and sort, already capped (see Listing depth). |
has_more | boolean · null | true when another page follows; false on the last page. null until the job completes, and on failed jobs. |
next_page, next_page_url | integer · string · null | The next page number, and your URL with page set to it (every other parameter and the #fragment kept). Submit next_page_url as a new job. |
stop | string · null | Why the page ended: max_pages (more pages follow), nomore, total_reached, empty_page or end_of_range (last page), api_error=<code> on a failed job. Informational; branch on has_more. |
card_shape | string · null | item_basic or item_data: the shape of the full cards in items. null when the page has no products. |
billable_pages | integer | 1 when this job is charged (a completed page with products), otherwise 0. |
billing_error | boolean · null | true only if recording the charge failed on our side; it is reconciled later. Otherwise null. |
elapsed_s | number · null | Seconds spent reading the page (the last attempt). |
error | string · null | Why a failed job failed. See Errors. |
kind | string | category, search (brand pages too), shop or collection. |
warnings | array · null | Notices about the request, for example a filter Shopee does not apply on this listing type. |
note | string | Only on the POST answer (how to poll) and on an expired result. Free text; do not branch on it. |
items | array | The products, in listing order. Only on completed jobs: [] for a page without products or an expired result. See Products in items. |
Real answers from 5 October 2026, trimmed: full, a Vietnam category by sales (full cards); slim, a Brazil search with slim: true; shop, a Singapore shop page (item_data cards, single page); retry, a job waiting for an automatic retry. The failed tab shows the format of a failed job.
{
"job_id": "182989554db944e78eda8d8c3104cda4",
"status": "completed",
"region": "vn",
"url": "https://shopee.vn/x-cat.11035567?sortBy=sales&page=0",
"page": 0,
"slim": false,
"created_ts": 1791201099,
"attempts": 0,
"pages_fetched": 1,
"items_returned": 60,
"total_count": 2500,
"total_pages": 9,
"has_more": true,
"next_page": 1,
"next_page_url": "https://shopee.vn/x-cat.11035567?sortBy=sales&page=1",
"stop": "max_pages",
"card_shape": "item_basic",
"billable_pages": 1,
"billing_error": null,
"elapsed_s": 5.2,
"error": null,
"kind": "category",
"warnings": null,
"items": [
{
"item_basic": {
"itemid": 25922633167,
"shopid": 205054223,
"name": "Tất cầu lông Yonex cổ trung, Vớ yonex thể thao phiên bản kỷ niệm 75th, 3 sợi dày dặn chất liệu cotton chống trơn trượt",
"currency": "VND",
"price": 2150000000,
"price_min": 2150000000,
"price_max": 2390000000,
"price_before_discount": 3800000000,
"discount": "44%",
"historical_sold": 60000,
"sold": 2000,
"global_sold_count": 60000,
"item_card_display_sold_count": {
"display_sold_count": 60000,
"rounded_local_monthly_sold_count": 2000,
"local_monthly_sold_count_text": "2k+",
"rounded_display_sold_count": 60000,
"display_sold_count_text": "60k+"
},
"item_rating": {
"rating_star": 4.8787771062922145,
"rating_count": [5626, 42, 21, 78, 295, 5190]
},
"catid": 100011,
"brand": "",
"shop_name": "NgocAnh.Sport, Tất Vớ Thể Thao",
"shop_location": "Thành phố Hà Nội",
"image": "vn-11134207-7r98o-ltrynttxquj181",
"ctime": 1712398559,
"item_status": "normal",
… about 40 more Shopee fields (images, tier_variations, liked_count, cmt_count …)
},
"itemid": 25922633167,
"shopid": 205054223,
… Shopee's tracking fields
},
{
"item_basic": {
"itemid": 4649188271,
"shopid": 259681663,
"name": "Quần tây nam hàn quốc JBAGY dáng baggy vải co giãn dày dặn dáng suông ống rộng, màu đen, kem JA0101",
"price": 30900000000,
"price_before_discount": 45000000000,
"historical_sold": 400000,
"sold": 2000,
…
},
…
},
… 58 more cards
]
}
{
"job_id": "0d44269ec64f4cbd88189b0448eb18d3",
"status": "completed",
"region": "br",
"url": "https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales",
"page": 0,
"slim": true,
"created_ts": 1791203403,
"attempts": 3,
"pages_fetched": 1,
"items_returned": 60,
"total_count": 193,
"total_pages": 4,
"has_more": true,
"next_page": 1,
"next_page_url": "https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales&page=1",
"stop": "max_pages",
"card_shape": "item_basic",
"billable_pages": 1,
"billing_error": null,
"elapsed_s": 5.9,
"error": null,
"kind": "search",
"warnings": null,
"items": [
{
"itemid": 58210951565,
"shopid": 677762919,
"name": "Hidra Mel Cola Fitagem + Creme de Pentear Bomba Cachos",
"price": 2590000,
"price_before_discount": 7990000,
"historical_sold": 2000,
"sold": 1000,
"monthly_sold": 1000,
"display_sold_count": 2000,
"rating_star": 4.837423312883436,
"catid": 100630,
"brand": "Hidralise",
"shop_location": "Rio de Janeiro",
"image": "sg-11134201-822zz-mofhomhugpos6a",
"ctime": 1779197804,
"item_status": "normal"
},
{
"itemid": 23399168859,
"shopid": 389924031,
"name": "KIT Mel Cola Fitagem 500G + Creme Bomba 1KG Cachos Ativador de Pentear Fixação Cachos Definição",
"price": 2990000,
"price_before_discount": 5990000,
"historical_sold": 4000,
"sold": 774,
"monthly_sold": 774,
"display_sold_count": 4000,
"rating_star": 4.811948404616429,
"catid": 100630,
"brand": "Hidralise",
"shop_location": "Rio de Janeiro",
"image": "br-11134201-820l5-msq69wy9ips474",
"ctime": 1763577803,
"item_status": "normal"
},
… 58 more rows
]
}
{
"job_id": "c2ea20d98feb443bb287dc0640fb2e5c",
"status": "completed",
"region": "sg",
"url": "https://shopee.sg/shop/167068287",
"page": 0,
"slim": false,
"created_ts": 1791201473,
"attempts": 0,
"pages_fetched": 1,
"items_returned": 56,
"total_count": 56,
"total_pages": 1,
"has_more": false,
"next_page": null,
"next_page_url": null,
"stop": "nomore",
"card_shape": "item_data",
"billable_pages": 1,
"billing_error": null,
"elapsed_s": 4.6,
"error": null,
"kind": "shop",
"warnings": null,
"items": [
{
"item_data": {
"itemid": 2589354905,
"shopid": 167068287,
"catid": 100013,
"ctime": 1564646618,
"item_status": "normal",
"item_card_display_price": {
"price": 300000,
"strikethrough_price": 2999000,
"discount": 90,
…
},
"item_card_display_sold_count": {
"historical_sold_count": 286701,
"monthly_sold_count": 5697,
"historical_sold_count_text": "200k+ sold",
"monthly_sold_count_text": "5k+ Sold/Month"
},
"item_rating": {
"rating_star": 4.782765075054135,
"rating_count": [54496, 1133, 549, 1377, 2912, 48525]
},
"shop_data": { "shop_name": "ShieldMonster" },
"global_brand": { "brand_id": 1766928, "display_name": "Shieldmonster" },
… more fields; there is no name here
},
"item_card_displayed_asset": {
"name": "ShieldMonster Screen Protector Tempered Glass for iPhone 18 Pro Max/17/16E/15/14/13 Plus Clear Privacy Matte Blue Light",
"image": "sg-11134207-8259y-msxhrq5qpfd022",
"shop_location": "SG",
"display_price": { "price": 300000 },
"sold_count": { "text": "200k+ sold" },
"rating": { "rating_text": "4.8" },
…
},
"itemid": 2589354905,
"shopid": 167068287,
… Shopee's tracking fields
},
… 55 more cards
]
}
{
"job_id": "0d44269ec64f4cbd88189b0448eb18d3",
"status": "pending",
"region": "br",
"url": "https://shopee.com.br/search?keyword=kit%20mel%20cola&sortBy=sales",
"page": 0,
"slim": true,
"created_ts": 1791203403,
"attempts": 3,
"pages_fetched": 0,
"items_returned": 0,
"total_count": null,
"total_pages": null,
"has_more": null,
"next_page": null,
"next_page_url": null,
"stop": null,
"card_shape": null,
"billable_pages": 0,
"billing_error": null,
"elapsed_s": null,
"error": null,
"kind": "search",
"warnings": null
}
{
"job_id": "8d1e4b7c2a9f40e6b3c5d7f9a1b2c3d4",
"status": "failed",
"region": "br",
"url": "https://shopee.com.br/no-such-shop-2026",
"page": 0,
"slim": false,
"created_ts": 1791203512,
"attempts": 0,
"pages_fetched": 0,
"items_returned": 0,
"total_count": null,
"total_pages": null,
"has_more": null,
"next_page": null,
"next_page_url": null,
"stop": null,
"card_shape": null,
"billable_pages": 0,
"billing_error": null,
"elapsed_s": null,
"error": "shop 'no-such-shop-2026' not found",
"kind": "shop",
"warnings": null
}
A full page of 60 cards is about 250 KB to 1 MB of JSON; search pages with ads are the largest. The same page with slim: true is about 30 KB. Possible errors: 404 unknown or expired job, 403 the job belongs to another key, 401 key problem.
Send the page URL from the browser. The host picks the market, the path picks the listing, and the query string carries the sort, the filters and the page.
| Listing | URL form |
|---|---|
| Category | /<slug>-cat.<id>, also with sub-categories: -cat.<id>.<id>…e.g. https://shopee.com.br/Mouses-cat.11059977.11060050.11061451?sortBy=sales · kind: category |
| Search | /search?keyword=… (spaces as %20)e.g. https://shopee.com.br/search?keyword=kit%20mel%20cola · kind: search |
| Brand | /search?brands=<id>[,<id>], optionally with keyword=e.g. https://shopee.com.br/search?brands=2385189 · kind: search |
| Shop | /<shop-username> or /shop/<shopid>, optionally with searchKeyword= to search inside the shope.g. https://shopee.com.br/krwbikes or https://shopee.sg/shop/167068287 · kind: shop |
| Collection | /collections/<id>e.g. https://shopee.com.br/collections/19875226 · kind: collection |
-cat. is the category; the text before -cat. is not used./search URL needs keyword or brands, otherwise 400 search url needs ?keyword=... or ?brands=.... Several brand IDs return products of any of them.failed with shop '<name>' not found, free of charge.www. and m. in front of the host are fine.Not accepted (400): product pages (…-i.<shopid>.<itemid>), short or share links (s.shopee…, shp.ee), Shopee Mall pages (/mall/…), other site paths, a URL without a listing path, and hosts outside the eight markets. Send the normal page URL from the browser.
The market comes from the URL host. Prices in the price filters are whole units of the local currency.
| Market | Host | region | Currency | Price filter example | Status |
|---|---|---|---|---|---|
| Brazil | shopee.com.br | br | BRL | minPrice=20& | available |
| Taiwan | shopee.tw | tw | TWD | minPrice=100& | available |
| Vietnam | shopee.vn | vn | VND | minPrice=50000& | available |
| Singapore | shopee.sg | sg | SGD | minPrice=5& | available |
| Thailand | shopee.co.th | th | THB | minPrice=100& | available |
| Indonesia | shopee.co.id | id | IDR | minPrice=10000& | available |
| Malaysia | shopee.com.my | my | MYR | minPrice=10& | available |
| Philippines | shopee.ph | ph | PHP | minPrice=100& | available |
The API accepts URLs of all eight hosts. A job that cannot be served ends failed, which is never charged. For large daily volumes in any market, tell us in advance so the capacity is ready; a large burst for one market waits longer in pending (Job lifecycle). Other Shopee hosts return 400 unsupported Shopee host/region.
Use the website's own query parameters. The simplest way is to apply the sort and filters on the website and copy the resulting URL. Invalid values return 400; the API's message starts with cannot parse listing url: followed by the text shown under each parameter (cut at 120 characters).
| Parameter | Description |
|---|---|
sortBy | relevancy, pop (popular), sales (top sales), ctime (newest) or price. Without it, shop pages sort by pop and every other listing by relevancy, as on the website.400 unsupported sortBy=x (use ctime|sales|price|relevancy|pop) |
order | asc or desc, default desc. With sortBy=price, asc starts with the cheapest.400 order must be asc|desc |
minPrice, maxPrice | Price range in whole local-currency units, digits only: minPrice=20& in Brazil, minPrice=50000 in Vietnam. Either side can be used alone.400 minPrice/maxPrice must be whole numbers in local currency (got '4.50') |
keyword | Search term on /search URLs; on a shop URL it searches inside the shop. Write spaces as %20.400 keyword= is not supported on category urls (use /search?keyword=...) |
searchKeyword | Search term inside a shop, as the website writes it on shop pages. It has no effect on other listings. |
brands | One Shopee brand ID, or several comma-separated (products of any of them). /search URLs only.400 brands= filter is only supported on /search urls |
ratingFilter | Minimum star rating, 1–5.400 ratingFilter must be 1..5 |
locations | Shop locations exactly as the website of that market writes them, comma-separated for several (any of them). In Brazil, for example, São Paulo, Nacional (sellers in Brazil) or Internacional (sellers abroad). A name the website does not use returns no products: an empty page, not charged. |
withDiscount | true keeps only discounted products; false means no filter.400 withDiscount must be true|false |
fe_filter_options | The website's newer filter list: a JSON list such as [{"group_name":, URL-encoded in the URL as the website writes it. Supported groups: LOCATIONS, RATING (1–5), CONDITION (NEW_ITEM, USED_ITEM), SERVICE_AND_ (WITH_DISCOUNT) and PRICE_RANGE (min▶◀max in whole units; either side may be empty).400 unsupported fe_filter_options group X, unsupported CONDITION value X, RATING values must be 1..5, PRICE_RANGE value must look like 'min▶◀max' (whole local-currency units), fe_filter_options is not valid JSON, fe_filter_options must be a JSON list, fe_filter_options entries need group_name/values, unsupported SERVICE_AND_PROMOTION value X (for example FREE_SHIPPING) |
page | 0-based page number, default 0. See Walking a listing.400 page must be an integer (0 = first page), page must be >= 0 |
| Parameter | Category | Search / brand | Shop | Collection |
|---|---|---|---|---|
sortBy, order, page | yes | yes | yes | yes |
minPrice, maxPrice | yes | yes | yes | yes |
ratingFilter | yes | yes | yes | yes |
locations | yes | yes | yes | yes |
withDiscount | yes | yes | yes | yes |
keyword | 400 | yes | in shop | 400 |
searchKeyword | no effect | no effect | in shop | no effect |
brands | 400 | yes | 400 | 400 |
fe_filter_options: LOCATIONS, RATING, PRICE_RANGE, WITH_DISCOUNT | yes | yes | yes | yes |
fe_filter_options: CONDITION | yes | yes | yes | not applied |
warnings says sono effect accepted and ignoredA filter is never dropped silently. Any other query parameter, filter group or value returns 400 unsupported filter param(s): … naming it (the list of supported names after it may be cut short), so you never get, or pay for, a broader listing than the one you asked for. Website filters that are not supported yet include shipping options such as free shipping, Shopee Mall or official-shop filters, a shop's own category tabs (shopCollection) and attribute filters (facet). Two exceptions: Shopee does not apply CONDITION on collection pages, so such a job still runs and its warnings say the results are not filtered by condition; and searchKeyword only works on shop pages (use keyword on /search).
Ignored, because they do not change the listing: tracking parameters such as utm_*, gclid, fbclid, ref, entryPoint, tab, itemidlist, trackingId, itemId, noCorrection, and the #fragment. Parameters the website puts after the fragment (/<shop>#product_list?page=1) are read as normal parameters.
Shopee's own total_count stops at 2,500, but Shopee serves only 500 or about 1,020 of those products page by page, depending on the sort. total_pages already reflects these limits, so you can use it directly: the Brazil category in the submit example, sorted by sales, answers total_count: 2500 and total_pages: 9.
sortBy | Products reachable | Pages | Share of 2,500 |
|---|---|---|---|
sales, price, ctime | 500 | 0–8 (page 8 has 20) | |
pop, relevancy | about 1,020 | 0–16 |
These are the limits Shopee applies today, the same in every market we checked. A page beyond the reachable range (page 9 or later by sales, price or newest; page 17 or later otherwise) completes at once with 0 products and stop: "end_of_range", and is not charged:
{
"job_id": "646088864e664e3b8f66ddb20ccd4c9b",
"status": "completed",
"url": "https://shopee.com.br/x-cat.11059998?sortBy=sales&page=9",
"page": 9,
"items_returned": 0,
"has_more": false,
"stop": "end_of_range",
"billable_pages": 0,
… the other fields as in GET /cbc/{job_id}
"items": []
}To cover more of a large category, split it rather than going deeper: price ranges (minPrice/maxPrice), sub-categories (a longer -cat. URL), locations, or several sorts of the same listing, then de-duplicate by itemid.
Each page is its own job. When a job completes, has_more, next_page and next_page_url tell you how to continue.
has_more: true: submit next_page_url (your URL with page=N+1, everything else unchanged) as a new job. Setting page yourself works the same way.has_more: false: this was the last page (stop is nomore, total_reached, empty_page or end_of_range).total_pages, you can also submit the other pages directly with page=1 … page=total_pages-1 instead of one after the other.itemid. A listing changes between requests (new sales, new listings), so a product can move from one page to the next.itemsBy default items holds Shopee's listing cards exactly as Shopee returns them, in listing order, in one of two shapes. card_shape tells you which; read it rather than inferring the shape from the URL.
card_shape | Listing pages | Where the main fields are |
|---|---|---|
item_basic | Category, search and brand pages; usually collections | item_basic (about 62 fields): itemid, shopid, name, price, price_before_discount, historical_sold, sold, item_rating., brand, shop_name, shop_location, image, ctime … |
item_data | Shop pages; collections can use it too | item_data (about 22 fields, no name): itemid, shopid, item_card_display_price., item_card_display_sold_count. / monthly_sold_count, item_rating., global_brand. … and next to it item_card_displayed_asset: name, image, shop_location |
Search cards can also carry Shopee's own ad tracking fields (adsid, campaignid, ads_keyword) next to item_basic; they are passed through as Shopee sends them.
slim: true)With slim: true, every page returns the same compact row, whatever the card shape. A value the card does not carry is null.
| Field | Type | Meaning |
|---|---|---|
itemid, shopid | integer | The product and shop IDs. |
name | string | Listing title, in the local language. |
price, price_before_ | integer | Current price and price before discount, as Shopee integers: divide by 100,000. On shop pages, price can be 0 for a product in a promotion (the card's discount is 100); price_before_ then holds the crossed-out price. Treat a 0 price as not shown. |
historical_sold | integer | Lifetime units sold, as the card shows it: rounded for bigger sellers on item_basic cards, exact on shop pages (see below). |
sold | integer · null | Shopee's 30-day sold figure on item_basic cards. null on shop pages. |
monthly_sold | integer · null | Units sold in the last 30 days: rounded on item_basic cards, exact on shop pages. |
display_sold_count | integer · null | On item_basic cards, the number behind the card's sold label (10000 for "10k+"). On shop pages, the exact lifetime count, the same as historical_sold. |
rating_star | number · null | Average rating, 0 to 5. |
catid | integer · null | Shopee's global category ID of the product, such as 100011. It uses different numbers from the -cat. IDs in website URLs: the Vietnam category -cat.11035567 comes back as catid 100011 or 100009. |
brand | string · null | Brand name; null when the product has none. |
shop_location | string · null | Seller's location as the website shows it. |
image | string · null | Shopee image ID of the main picture. |
ctime | integer · null | Listing creation time, Unix seconds. |
item_status | string · null | "normal" for a live listing. |
Sold counts on category and search pages are the card's figures. On item_basic cards, historical_sold and sold carry the rounded number behind the label the website shows: in the Vietnam example, historical_sold is 60000 where the card says "60k+"; small counts such as 529 are exact. Shop pages carry the exact lifetime count: in the Singapore shop example, historical_sold_count is 286701 where the card says "200k+ sold". For exact per-product sold counts, see the Shopee Item Sold API.
Prices are Shopee integers: divide by 100,000 for the amount in local currency. 2150000000 in Vietnam is 21,500 ₫; 2590000 in Brazil is R$ 25.90; 300000 in Singapore is S$ 3.00. Prices in Indonesia and Vietnam run into the billions, so store them as 64-bit integers. Price filters in the URL are whole units, not ×100,000.
Listing cards carry what the listing page shows. For stock, variants, vouchers and the full product page, use the Shopee Product API.
| Status | Meaning | Charged |
|---|---|---|
pending | Accepted and waiting, or waiting for an automatic retry (attempts > 0). | no |
processing | The page is being read. | no |
completed | Final. items is in the answer ([] for a page without products) and the paging fields are set. | 1 if it has products |
failed | Final. error says why. Re-submit the URL if the error says so. | no |
elapsed_s); some take a minute or more.pending for their turn without using up attempts. A large burst for one market stays pending longer.pending and is retried automatically after 30, 60 and 90 seconds, up to 4 attempts in all; attempts counts them. A job can therefore stay pending for several minutes. Keep polling for up to 15 minutes before you give up on it.failed with an error ending in re-submit the URL, most often listings temporarily unavailable for this region after 4 attempts - re-submit the URL. A URL that Shopee itself rejects, and an unknown shop, fail at once without retries.completed, with items: [] and the note result expired (2h after completion) - re-submit the URL. The job itself (status, counts, paging fields) is kept for 24 hours after completion (its last status change), then 404.On request
Each charged page counts as one charged result on your key, at your key's rate. For a quote or volume pricing, ask support@fastscraping.com.
1 / page
A job is charged once when it completes with at least one product. billable_pages shows it: 1 or 0.
$0
Empty pages, pages beyond the end of a listing, failed jobs, automatic retries, submissions, polling and every error (4xx and 5xx).
billable_pages can read 0 for a moment right after a job completes, while the charge is recorded. billing_error: true means recording the charge failed on our side; it is reconciled later.| Limit | Default | Notes |
|---|---|---|
| Products per request | 60 | One listing page, the website's page size. |
| Pages per request | 1 | Pick it with page= in the URL. |
| Reachable depth | 500 · ≈1,020 | By sort; Shopee's own limits. See Listing depth. |
| Rate limits | per key | The per-minute, per-hour and per-day limits and the quota of your key, checked on POST /cbc. Each charged listing page counts as one charged result, together with your charged product results. The concurrency limit of product jobs does not apply to /cbc. Above a limit you get 429 naming it. |
| Polling | 3–5 s | GET /cbc/{job_id} is free and not rate-limited; there is no benefit in polling faster. |
| Products kept | 2 hours | After completion. Then items: [] with a note. |
| Job kept | 24 hours | After completion (its last status change). Then 404. |
Errors return a JSON body with a detail field. No error is charged. The detail text is meant for people and may change, so branch on the HTTP status code.
HTTP/1.1 400 Bad Request
{ "detail": "cannot parse listing url: unsupported sortBy=bestseller (use ctime|sales|price|relevancy|pop)" }| Code | Meaning | What to do |
|---|---|---|
| 400 | The URL cannot be served as asked: url must be a Shopee listing URL (category / search / brand / shop / collection page), unsupported Shopee host/region - covered: br, id, my, ph, sg, th, tw, vn, region 'xx' does not match the url host (br), search url needs ?keyword=... or ?brands=..., or cannot parse listing url: … with the reason, cut at 120 characters (see Supported URLs and Sort & filters). | Fix the URL. Retrying unchanged returns the same error. |
| 401 | Missing API key or Invalid or inactive API key. | Send a valid key in X-API-Key. |
| 403 | not your job: the job was created with another key. | Read it with the key that created it. |
| 404 | cbc job not found (or expired after 24h). | Check the ID, or submit the URL again. |
| 422 | The body does not match the schema: url missing, an unknown field, or not JSON. detail is a list. | Send only url, region and slim. |
| 429 | Rate limit exceeded (per-minute), (per-hour), (per-day) or Quota exceeded (lifetime). | Retry later with back-off, for example 30 s, 60 s, 120 s. For the quota, contact support. |
| 503 | Service temporarily unavailable - please retry later. | Retry after 30–60 seconds. |
A job that was accepted but could not be served ends with status: "failed" and the reason in error. Failed jobs are never charged.
error | Meaning | What to do |
|---|---|---|
shop '<name>' not found | The shop username in the URL does not exist in that market. | Check the URL. |
shopee error <code> … | Shopee rejected the request for this listing; Shopee's error code follows. | Check the URL and its filters; retrying it unchanged returns the same. |
listings temporarily unavailable for this region after N attempts - re-submit the URL | The market could not be served during the automatic retries. | Submit the same URL again later. |
Any other error ending in re-submit the URL | A temporary problem while reading the page, for example … after N attempts - re-submit the URL. | Submit the same URL again. |
internal error | Something went wrong on our side. | Submit the URL again. If it repeats, contact support and quote the job_id. |
400 tells you if one cannot be applied.pending job for up to 15 minutes, since automatic retries can hold it for a few minutes.next_page_url until has_more is false, and de-duplicate products by itemid.slim: true when you need names, prices and sold counts: about 30 KB per page instead of 250 KB to 1 MB.job_id. It lets us trace any page you ask about.