# TikTok Shop API: 52 products over 3 credits (https://www.socialcrawl.dev/blog/tiktok-shop-api-catalogues-reviews) > One TikTok Shop API walk returned 52 of 52 products over 3 credits, plus 60 of 1,125 reviews. Same product object from search and shop. Live 2026-09-08. On 8 September 2026, a TikTok Shop API walk of the Goli Nutrition storefront returned **52 of 52 products over 3 pages for 3 credits** (20 + 20 + 12). The same harvest pulled **60 distinct reviews over 6 pages for 6 credits** from a Potaroma cat-nail-clipper listing, out of **1,125** reported on page 1. `has_more` was still true. Sixteen production calls billed **13 credits**. Google's top results for that query are official — and the wrong API. [Research Query TikTok Shop Info](https://developers.tiktok.com/docs/en/research-api-specs-query-tiktok-shop-info) is EU shop *metadata*, max **10**, scope `research.data.basic` — not a catalogue. The [Partner / TTS API](https://partner.tiktokshop.com/docv2/page/tts-api-concepts-overview) is seller-gated OAuth for **authorized** shops — the catalogues you already own. This post is the measured public catalogue and reviews walk on the SocialCrawl API. For Partner versus public, see [which TikTok Shop API](/blog/best-tiktok-shop-apis-2026). **Stack:** Python 3.10+ · `requests` · `GET https://www.socialcrawl.dev/v1/tiktokshop/…` · header `x-api-key`. Print `len(items)` and `credits_used`. Pagination is one billed page per call. Free plan: [100 credits, no card](/pricing). Live catalogue: [`/platforms/tiktokshop`](/platforms/tiktokshop). Goli Nutrition, SocialCrawl GET, 8 September 2026: **52 of 52** products over **3 credits** (20 + 20 + 12). Reviews: **60** distinct of **1,125** reported over **6 credits**, not exhausted. Same product object from search and shop listing. Seller description on detail: **1,288** characters. One US shop, one review product. Three cream product-card sheets overflowing blank tiles with three coins at the base, a TikTok Shop API catalogue walk billed one credit a page. ## Does a TikTok Shop API page a whole shop catalogue? Call `GET /v1/tiktokshop/products` with the shop URL and keep passing `pagination.next_cursor` as `cursor` until `has_more` is false. Page size is not a caller `limit`. Print `len(items)` and `credits_used` on every page. Live on the documented Goli Nutrition storefront (`https://www.tiktok.com/shop/store/goli-nutrition/7495794203056835079`), 2026-09-08, 00:17–00:21 UTC: | Page | Items | `data.total` | `dropped` | `has_more` | Credits | `request_id` | | ---: | ----: | -----------: | --------: | ---------- | ------: | ------------ | | 1 | **20** | 52 | 0 | true | 1 | `req-icWAu9rbauLx3sRS` | | 2 | **20** | 52 | 0 | true | 1 | `req-kj0NIF4mF9guocdL` | | 3 | **12** | 52 | 0 | **false** | 1 | `req-gqF6spgOxKoRGtpH` | | **Walk** | **52 unique / 0 dupes** | 52 | 0 | stopped | **3** | | Seller on every row: `"Goli Nutrition"`. Page size is 20 until the last page (12). Shop `data.total` was stable at **52**. Labeled before (2026-09-07 ship note, not re-measured today): this call used to stop at 20 and report that as the whole shop. Live it pages. A first-page-only read is incomplete after even a few days of official TTS quota. New shops sit at a [100 product uploads per day](https://partner.tiktokshop.com/docv2/page/products-api-overview) cap; after graduation it is 1,000. Commerce lists are walks: Shopify REST iterates [`rel=next` until it disappears](https://shopify.dev/docs/api/admin-rest/usage/pagination). This fixture is the documented example storefront, not a random sample. Do not generalise "every shop has 52 products". List rows do **not** carry the long description, stock, shipping, or SKU list. On the first listing row, `description`, `brand`, and `availability` were **null** — those fields live on product detail. Worked row (page 1, first product): id `1729527313880355335`, seller `Goli Nutrition`, price `{current: 14.98, original: 19, currency: "USD"}`, rating 4.5 / 94,634, `ext.sold_count` 1,301,493, `shop_products_count` 52, `shop_sold_count` 5,726,901. ```bash curl "https://www.socialcrawl.dev/v1/tiktokshop/products?url=https://www.tiktok.com/shop/store/goli-nutrition/7495794203056835079" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` Trimmed first item (`request_id` `req-icWAu9rbauLx3sRS`). Envelope: 20 items, `credits_used=1`, `has_more=true`. ```json { "product": { "id": "1729527313880355335", "url": "https://www.tiktok.com/shop/pdp/1729527313880355335", "title": "Goli Ashwagandha & Vitamin D Gummy - Mixed Berry, KSM-66, Vegan, Plant Based, Non-GMO, Gluten-Free & Gelatin Free. America's #1 Ashwagandha Brand", "description": null, "seller": "Goli Nutrition", "brand": null, "price": { "current": 14.98, "original": 19, "currency": "USD" }, "rating": { "average": 4.5, "count": 94634 }, "availability": null, "reviews_count": 94634, "ext": { "sold_count": 1301493, "tiktokshop": { "shop_name": "Goli Nutrition", "shop_rating": 4.6, "shop_products_count": 52, "shop_sold_count": 5726901, "shop_region": "US" } } } } ``` The Python cursor loop lives in the paging section below. A stack of blank review cards with a handful already turned aside, the TikTok Shop reviews API paging ten reviews per credit through a larger corpus. ## How many reviews does a TikTok Shop reviews API return per credit? **10** reviews a page, 1 credit. That is a walk, not a 10-row window presented as the corpus. `GET /v1/tiktokshop/product/reviews` on `https://www.tiktok.com/shop/pdp/cat-nail-clipper-by-potaroma-adjustable-sizes-built-in-file-safe-for-kittens-cats/1731578642912612516`, walked `pagination.next_cursor` as `cursor`, stopped after 6 pages on purpose: | Page | Items | Distinct | `data.total` | `has_more` | Credits | `request_id` | | ---: | ----: | -------: | -----------: | ---------- | ------: | ------------ | | 1 | 10 | 10 | **1125** | true | 1 | `req-mgfNFIiO6IXbbMmx` | | 2 | 10 | 20 | 1120 | true | 1 | `req-zeo0Q70FPbXMpQ2R` | | 3 | 10 | 30 | **924** | true | 1 | `req-4KcmN8zLWhtWN8rc` | | 4 | 10 | 40 | 1124 | true | 1 | `req-X0b6p9VGinYHaKKc` | | 5 | 10 | 50 | 1125 | true | 1 | `req-UXNB7lOdfdeedfWl` | | 6 | 10 | **60** | 1125 | **true** (stopped) | 1 | `req-FO6sRQUwCzBvD9i2` | | **Walk** | **60** | **60 unique, 0 dupes** | use **1,125** | not exhausted | **6** | | That is **60 distinct reviews over 6 pages, 6 credits, out of 1,125 reported.** The walk did not finish the corpus — `has_more` was still true on page 6. `data.total` moved between pages: 1125, 1120, **924**, 1124, 1125, 1125. Use **1,125** from page 1 as the reported corpus. The live count is not a frozen integer. Shop `data.total` was stable at 52; reviews were not. Labeled before (2026-09-07 note): this path used to stop at 10, no paging. Official Research [Query TikTok Shop Reviews](https://developers.tiktok.com/docs/en/research-api-specs-query-tiktok-shop-reviews) pages at `page_size` max **10**, EU-only, rating enum `"FIVE"`. That is not a measured walk of a four-digit listing. On all 60 reviews this run: `review.language` = `null`, `review.original_language` = `null`, `author.location` = `"US"`, `verified` = true, `ext.tiktokshop.is_incentivized_review` = false. Country lives on the reviewer. Language is not a country code, and it is not English — live is `null`. Worked review (page 1, first): id `7667988866325907213`, rating `{value: 5, max: 5}`, 336-character text starting "These nail clippers are really nice and work great.", author Ash, `published_at` `2026-07-29T16:33:12.322Z`. One product, US region. Do not generalise "every product has 1,125 reviews". ```bash curl "https://www.socialcrawl.dev/v1/tiktokshop/product/reviews?url=https://www.tiktok.com/shop/pdp/cat-nail-clipper-by-potaroma-adjustable-sizes-built-in-file-safe-for-kittens-cats/1731578642912612516" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` Trimmed first review. Live `review.text` is 336 characters; the string below is cut after the opening sentence. ```json { "review": { "id": "7667988866325907213", "entity_id": "1731578642912612516", "rating": { "value": 5, "max": 5 }, "text": "These nail clippers are really nice and work great. …", "author": { "name": "Ash", "location": "US" }, "language": null, "verified": true, "published_at": "2026-07-29T16:33:12.322Z", "ext": { "tiktokshop": { "is_incentivized_review": false } } } } ``` A single cream product cube at the meeting of two paths, the same TikTok Shop product API object returned from search and from the shop listing. ## Does a TikTok Shop product API return the same object from search and shop listing? Same canonical shape. Not identical density. Product `1729587769570529799` (3 Bottles of Goli Ashwagandha & Vitamin D Gummy) sat on shop catalogue page 1 **and** on `GET /v1/tiktokshop/search?query=goli ashwagandha` (11 hits, 1 credit, `req-bOgwplfdH63zwU6l`, 2026-09-08). Shared keys and types: `id`, `url`, `title`, `description`, `seller`, `brand`, `price` `{current, original, currency}`, `rating` `{average, count}`, `image_urls`, `availability`, `reviews_count`, `ext`. That is the TikTok Shop product data you can parse once. [JSON:API](https://jsonapi.org/format/#document-resource-objects) treats the same resource as the same object whichever collection it appeared in; clients join on that identity. A parser that already handles the shop-list product should reuse it on search. Official TTS already keys products by one product ID across Get Product List and Get Product Detail ([Product API overview](https://partner.tiktokshop.com/docv2/page/products-api-overview)). | Field | Shop listing | Search hit | | --- | --- | --- | | `id` | `1729587769570529799` | `1729587769570529799` | | `title` | 3 Bottles of Goli Ashwagandha & Vitamin D Gummy - Mixed Berry, KSM-66, Vegan, Plant Based, Non-GMO, Gluten & Gelatin Free | same string | | `seller` | `Goli Nutrition` | `Goli Nutrition` | | `price` | `{current: 44.98, original: 57, currency: "USD"}` | `{current: 44.98, original: 57, currency: "USD"}` | | `rating.average` | 4.7 | 4.7 | | `rating.count` | 71730 | `null` | | `reviews_count` | 71730 | `null` | | `ext.sold_count` | 864108 | 864108 | | `ext.sku_id` | `1729587788239049223` | `null` | Not every field is populated on every lane. The object is the same shape under the unified schema. Search adds merchandising (`trust_label`, `promotion_labels`, `category_breadcrumb`, `video_url`, `discount_percent` under `ext.tiktokshop`). The shop list adds the shop profile (id, name, url, rating, region, followers, product count). Review count is populated on the shop row and was **null** on this search hit. Search used to return raw extra fields, not the product object. Creator showcase is a different shape entirely — `{post, computed}`, commerce on `post.ext.commerce`. Do not treat showcase rows as this object. Second search, page-size check, 2026-09-08: `query=phone case` → **34** items, `has_more: true`, 1 credit, `req-0Vcs8H2Scf1HPTYw`, first seller `"TECHGODAS"`, first price `{current: 11.99, original: 21.99, currency: "USD"}`. Same canonical keys as the shop list. ```bash curl "https://www.socialcrawl.dev/v1/tiktokshop/search?query=goli%20ashwagandha" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` ## What extra fields does a TikTok Shop product now carry? On the listing row, description is null. On product detail, it is the seller's copy. `GET /v1/tiktokshop/product?product_id=1729587769570529799` (no `url`) returned HTTP 200, 1 credit, cache miss, 4.54 s, `req-lr9dtYDvozKSKMOz`, 2026-09-08. Lookup by `product_id` works. Reviews already accepted both identifiers. `String(data.product.description).length` = **1288**. 184 words, 8 lines. It starts "Goli Nutrition has sold over 90 Million bottles of gummy vitamins." That is seller copy, not a one-word category label. Labeled before: description used to be `"Health"` / `"Beauty"`. `ext.tiktokshop.category` is still `"Health"` as a separate field — the category did not disappear. Also on this detail call: `availability: "in_stock"` · `discount_percent: 21` · `shipping_fee: 0` · `free_shipping: true` · delivery `"Sep 11 - Sep 14"` (days 4–7) · `variations` length **1** (`id` `1729587788239049223`, title `Specifications: Default`) · 8 `specifications` (Brand, Container type, Flavor, Product form, Region of origin, Age warning, Manufacturer, Ingredients) · seller location `"United States of America"` · seller rating **4.6** · `seller_sold_count` **5,726,901** · `stock` 237,896 · brand `"Goli"` · 6 `image_urls`. Credits unchanged at 1; fields richer. This product has **one** SKU in `variations` — the field exists; do not read a variant matrix out of this capture. A catalogue walk does not give you the 1,288-character copy. Product-detail region is US-only (documented); a GB-only listing would 404 — not probed this run. ```bash curl "https://www.socialcrawl.dev/v1/tiktokshop/product?product_id=1729587769570529799" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` Trimmed detail. Full `description` is 1,288 characters; the string below is the opening sentence only. On this endpoint `ext` sits on the product. ```json { "product": { "id": "1729587769570529799", "description": "Goli Nutrition has sold over 90 Million bottles of gummy vitamins. …", "seller": "Goli Nutrition", "brand": "Goli", "availability": "in_stock", "reviews_count": 71730, "variations": [ { "id": "1729587788239049223", "title": "Specifications: Default" } ], "ext": { "sold_count": 864108, "tiktokshop": { "category": "Health", "discount_percent": 21, "stock": 237896, "free_shipping": true, "shipping_fee": 0, "delivery_estimate": "Sep 11 - Sep 14", "delivery_days_min": 4, "delivery_days_max": 7, "seller_location": "United States of America", "seller_rating": 4.6, "seller_sold_count": 5726901 } } } } ``` ## How do I page a TikTok Shop catalogue from Python? A TikTok Shop Python API walk is a `requests` loop that prints `len(items)` and `credits_used` across pages. One billed page per call. Pass `pagination.next_cursor` back as `cursor`. Stop when `has_more` is false. Auth is `x-api-key`. Limits: 600 requests/minute, 50 concurrent ([pricing](/pricing)). No TikTok developer app, no OAuth, no `research.data.basic`. ```python BASE = "https://www.socialcrawl.dev" SHOP = "https://www.tiktok.com/shop/store/goli-nutrition/7495794203056835079" headers = {"x-api-key": os.environ["SOCIALCRAWL_API_KEY"]} cursor = None seen = set() credits = 0 while True: params = {"url": SHOP} if cursor: params["cursor"] = cursor r = requests.get( f"{BASE}/v1/tiktokshop/products", params=params, headers=headers, timeout=60, ) r.raise_for_status() payload = r.json() items = payload["data"]["items"] pag = payload.get("pagination") or {} credits += payload.get("credits_used") or 0 for row in items: seen.add(row["product"]["id"]) print(len(items), payload.get("credits_used"), len(seen), pag.get("has_more")) if not pag.get("has_more"): break cursor = pag.get("next_cursor") if not cursor: break print("unique", len(seen), "credits", credits) # 20 1 20 True # 20 1 40 True # 12 1 52 False # unique 52 credits 3 ``` Expected on this fixture, 2026-09-08: 20 + 20 + 12 = **52** unique, **3** credits. The same cursor pattern works on reviews. Reviews and search also accept `page`. Do not loop 1,125 reviews in the snippet — cap pages, or stop on `has_more`. This harvest stopped reviews at 6 pages on purpose (`has_more` still true, 60 of 1,125). ## Do missing handles and bad regions bill a credit? No. Three captured misses on 2026-09-08, all unbilled: | Call | HTTP | Latency | Credits | `request_id` | | --- | ---: | ---: | ---: | --- | | showcase `handle=this-handle-should-not-exist-xyz123` | **404** `RESOURCE_NOT_FOUND` | **1.21 s** | **0** | `req-5inAB63a9hVESzZr` | | reviews `region=ZZ` | **400** `INVALID_REQUEST` | **66 ms** | **0** | `req-nRDOu4aMNZMD2z0h` | | product with neither `url` nor `product_id` | **400** `INVALID_REQUEST` | **0.32 s** | **0** | `req-bJRt7cq1Qj77MkCB` | Verbatim messages: - Bad region: `Invalid value for 'region': 'ZZ'. Allowed values: US, GB, DE, FR, IT, ID, MY, MX, PH, SG, ES, TH, VN, BR, JP, IE.` - Missing params: `Missing required parameter(s): one of url, product_id. This endpoint accepts: one of (url, product_id).` - Missing handle: `The requested resource was not found on the platform.` [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found) treats 404 as a missing resource, not an empty list. The same showcase path with real handle `mrtiktokreviews` billed 1 credit (20 items, `has_more: true`). A missing handle is not an empty 200. Labeled before (2026-09-07 note, not re-measured today): missing handle used to empty-200; a bad region on reviews used to retry 4× then 500. Live it rejects before billing. ```bash curl -i "https://www.socialcrawl.dev/v1/tiktokshop/user/showcase?handle=this-handle-should-not-exist-xyz123" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` ```json { "success": false, "error": { "type": "RESOURCE_NOT_FOUND", "message": "The requested resource was not found on the platform.", "status": 404 } } ``` ## How do you start using this? 1. Get a key. The free plan is [100 credits, no card](/pricing). Send it as `x-api-key`. One key covers the shop catalogue path, reviews, search, and product detail. 2. Run the Goli Nutrition shop GET from the first example. Print `len(items)` and `credits_used`. Try the same GET in the [visual explorer](/explorer) before writing a single line. 3. Then open the live catalogue at [`/platforms/tiktokshop`](/platforms/tiktokshop), the endpoint docs at [`/docs/tiktokshop`](/docs/tiktokshop), and the [which-API comparison](/blog/best-tiktok-shop-apis-2026) if you landed here looking for a tiktok shop scraper, a scrape tiktok shop recipe, or a tiktok shop data api roundup. Creator showcase is already paged and was unchanged this wave. `GET /v1/tiktokshop/user/showcase?handle=mrtiktokreviews` returned **20** items, `has_more: true`, 1 credit, `req-RiOpn1XxifwZ3XE6`, 2026-09-08. Item shape is `{post, computed}` — a PostList, not the ProductList used by shop and search. First commerce sits on `post.ext.commerce`: price 15 / 30 USD, rating 4.4 / 35,223, sold 524,359. One page is enough; not walked further. Same-wave family, not Shop: [150 TikTok followers per credit](/blog/tiktok-api-more-per-credit). Reproduce it: with any SocialCrawl key, `GET https://www.socialcrawl.dev/v1/tiktokshop/products?url=https://www.tiktok.com/shop/store/goli-nutrition/7495794203056835079`, then follow `pagination.next_cursor`. On 2026-09-08 that walk was 52 of 52 products, 3 credits, 1 credit a page. Try the same GET in the visual explorer. ## Frequently asked questions ### Can a TikTok Shop API return a whole shop catalogue? Yes on this fixture. `GET /v1/tiktokshop/products` on Goli Nutrition returned **52 of 52** products over **3 pages for 3 credits** (20 + 20 + 12), `data.total=52`, `has_more` false on page 3. Live 2026-09-08. One shop, not a promise that every shop is 52 products. Official Research shop-info is metadata, max 10, not a catalogue. ### How many products does one TikTok Shop API credit return? **20** on a full shop-list page this run (last page **12**), live 2026-09-08. 1 credit a page. Three pages = 52 unique products, 3 credits. Page size is not a caller `limit`. Print `len(items)`. ### How many TikTok Shop reviews can I pull per credit? **10** reviews a page, 1 credit. This harvest pulled **60 distinct over 6 pages, 6 credits**, out of **1,125** reported on page 1. `has_more` was still true — the corpus was not exhausted. Official Research reviews max 10 per page and EU-only. ### Do I need a TikTok Shop Partner account to read public products? No. Partner / TTS API is seller-gated OAuth for authorized shops. This walk used `x-api-key` on public shop and product URLs. For Partner-versus-public and the vendor table, see [which TikTok Shop API](/blog/best-tiktok-shop-apis-2026). ### Can I look up a TikTok Shop product by product_id, not just URL? Yes. `GET /v1/tiktokshop/product?product_id=1729587769570529799` (no URL) returned 200, 1 credit, 2026-09-08. Description **1,288** chars, `in_stock`, 21% off, shipping 0, delivery Sep 11–14. Reviews already accepted both identifiers. ### Do search and shop listing return the same product object? Same canonical shape. Product `1729587769570529799` matched on id, title, seller, and price `{current: 44.98, original: 57, currency: "USD"}` from `query=goli ashwagandha` and from the Goli shop list, live 2026-09-08. Density is not identical: `reviews_count` was 71730 on the shop row and null on that search hit. ### What happens if the TikTok Shop handle does not exist? **404** `RESOURCE_NOT_FOUND` in **1.21 s, 0 credits**. A malformed `region=ZZ` is **400** in **66 ms, 0 credits**, naming the 16 accepted regions. A missing handle is not an empty 200.