Shopify API endpoint
Returns products from a public Shopify storefront. Valid empty result pages return `200` with an empty products array. `sortBy` and dynamic facet-filter query params (e.g. `fit`, `canonicalColour`) only take effect for headless storefronts (`transport_mode: "ssr_embedded"`) and return an invalid-param error if supplied against a classic store, since classic Shopify storefronts have no server-side sort or filter support. `sort_by`, `min_price`, `max_price`, `product_type`, `in_stock_only`, and `option_`-prefixed params (e.g. `option_size=Small,Medium`) drive a separate, independent mechanism -- Shopify's own native Storefront Filtering collection-page feature (`transport_mode: "storefront_filtered"`) -- which works for both classic and headless stores; supplying any of these takes precedence over sortBy/dynamic facet filters.
/shopify/productsQuery parameters:
url: required. Full Shopify storefront URL, using http or https. Localhost, private network, link-local, and credentialed URLs are rejected.page: optional integer. 1-based page number. Default: 1.limit: optional integer. Maximum products to return. Default: 50; maximum: 250.sortBy: optional string. ssr_embedded stores only (transport_mode: "ssr_embedded", see below) — applied as the storefront's own sort UI does. Allowed values: sortLTH (price low to high), sortHTL (price high to low), newest. Omit for the storefront's default relevancy order. Supplying sortBy against any other store returns a 400 invalid-param error (see below).ssr_embedded stores only): any other query param not listed above is treated as a storefront facet filter and applied as the storefront's own filter UI does — one query param per facet field, e.g. fit=regular. A field name matches a key in this endpoint's own facets response field (which varies per collection/store, for example fit, activities, canonicalColour, garmentRise, sizeInStock, features, collections, discountPercentage on a Gymshark leggings collection). Multiple values within one facet are comma-separated (fit=regular,tall); multiple distinct facets combine with & (fit=regular&canonicalColour=black). There is no fixed enum of filter field names since they vary per store/collection — the raw key/value is applied and the storefront's own response (a changed or unchanged total_items) signals whether it was meaningful. Filter keys/values are still sanitized (length caps, no control characters, at most 20 distinct fields). Supplying any facet filter against any other store returns a 400 invalid-param error.sort_by: optional string. Storefront Filtering (transport_mode: "storefront_filtered", see below) — a separate, independent mechanism from sortBy/dynamic facet filters above; works for all stores. Allowed values: manual (Featured/collection order), best-selling, title-ascending, title-descending, price-ascending, price-descending, created-ascending (oldest first), created-descending (newest first). Omit for the collection's own default order.min_price / max_price: optional numbers. Storefront Filtering. Minimum/maximum price (inclusive), in the storefront's own display currency's major unit (e.g. whole dollars for USD, not cents), matching the storefront's own Price range slider.product_type: optional string, comma-separated. Storefront Filtering. Exact, case-sensitive product-type display strings from the storefront's own Product Type facet checkboxes (e.g. Hats,T-Shirts) — see facets.product_type in an unfiltered response for the live value set per store.in_stock_only: optional boolean. Storefront Filtering. true restricts results to currently in-stock items only.option_<name>: optional string, comma-separated, dynamic key. Storefront Filtering. Filters by one of the storefront's own product variant options (lowercased option name after option_, e.g. option_size=Small,Medium, option_color=Black). The set of option names and their accepted values is dynamic per store/collection — see facets in an unfiltered response for the live option names/values. Supplying sort_by, min_price, max_price, product_type, in_stock_only, or any option_-prefixed param takes precedence over sortBy/dynamic facet filters and uses Storefront Filtering for every store.Notable response behavior:
200 with an empty products array.products roots return upstream errors.transport_mode: "ssr_embedded", total_items, and total_pages (the storefront's own result-count/page-count metadata), and each product may include additive fields only available from this source: colour, canonical_colour, discount_percentage, rating, rating_count, collection_tags, labels, and per-variant inventory_quantity. limit is still honored as an upper bound on returned items, but the underlying page size is fixed by the storefront (commonly 60) and cannot be requested larger than that. transport_mode, total_items, and total_pages are omitted entirely for other stores.transport_mode is ssr_embedded, the response may also include facets and facets_stats, mirroring the storefront's own filter sidebar for that listing: facets maps a facet field name exactly as the storefront names it (for example fit, activities, canonicalColour, sizeInStock) to its value-to-count buckets, and facets_stats gives min/max/avg for numeric-range fields such as price and discountPercentage. Both are omitted when the storefront's payload did not include facet data, and always omitted for other stores.sortBy and facet filters apply to ssr_embedded stores only. Rather than silently accepting and ignoring sortBy or a facet filter against other stores (which would look like it worked but wouldn't), this endpoint rejects the request with a 400 invalid-param error whenever sortBy or any facet-filter query param is supplied and the resolved store is not transport_mode: "ssr_embedded".ssr_embedded store, the response echoes back what was actually honored: sort (one of the enum values below) and filters (a map of facet field name to the array of values that were requested for it, after comma-splitting). Both are omitted when no sort/filter was supplied.hitsPerPage (the storefront's own listing page size, commonly 60) is unaffected by sort/filters; limit continues to apply as an upper bound on the returned page.sort_by/min_price/max_price/product_type/in_stock_only/option_*) is Shopify's own native collection-page filtering/sorting feature, distinct from the sortBy/dynamic-facet-filter pair above. It works for all stores. When any of these params is supplied, the response has transport_mode: "storefront_filtered" and echoes back the effective sort_by, min_price, max_price, product_type, in_stock_only, and variant_options that were applied. facets/facets_stats are populated from the storefront's own filter sidebar when present: facets maps a lowercased variant option name (e.g. size, color) or product_type to its value-to-count buckets, and facets_stats.price gives the Price slider's own min/max bounds. total_pages is populated only when the collection page itself renders numbered pagination links; a single-page result leaves it unset. Not every Shopify store has this feature enabled — a store without it returns the unfiltered/unsorted collection page, which this endpoint cannot distinguish from "the filter matched everything" without upstream signal.sortBy allowed values: sortLTH, sortHTL, newest.
sort_by allowed values: manual, best-selling, title-ascending, title-descending, price-ascending, price-descending, created-ascending, created-descending.
transport_mode allowed values: ssr_embedded, storefront_filtered (omitted entirely for the classic transport).
| Field | Filled in, of 40 checked | Values in the sample |
|---|---|---|
| id | 40 | |
| handle | 40 | |
| title | 40 | “Women's Allbirds Flip Flop - Dusty Pink” and 39 other values |
| url | 40 | allbirds.com |
| description | 35 | median 224 characters |
| vendor | 40 | Allbirds in all 40 |
| product_type | 40 | Shoes (37), Apparel (2), Socks (1) |
| published_at | 40 | 2026-09-25 |
| created_at | 40 | 2021-01-29 to 2026-04-14 |
| updated_at | 40 | 2026-10-13 |
| price | 40 | 12 to 140, median 77 |
| compare_at_price | 17 | 25 to 145, median 120 |
| available | 19 | true in 19 of 19 |
| options | 40 | median 1 entry |
| variants | 40 | median 13 entries |
| images | 40 | median 5 entries |
| featured_image | 40 | cdn.shopify.com |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| url | string | Yes | Shopify storefront URL | https://www.allbirds.com | |
| page | integer | No | 1 | 1-based page, defaults to 1 | 1 |
| limit | integer | No | 50 and supports up to 250 | Maximum products, defaults to 50 and supports up to 250 | 50 |
| sortBy | string | No | SSR-fallback transport only (transport_mode ssr_embedded). Allowed values: sortLTH, sortHTL, newest. Omit for the storefront's default relevancy order. Rejected as an invalid param for classic-transport stores. Allowed values: sortLTH, sortHTL, newest. | sortLTH | |
| sort_by | string | No | Storefront-filtering transport. Allowed values: manual, best-selling, title-ascending, title-descending, price-ascending, price-descending, created-ascending, created-descending. Omit for the collection's own default order. Allowed values: manual, best-selling, title-ascending, title-descending, price-ascending, price-descending, created-ascending, created-descending. | price-ascending | |
| min_price | number | No | Storefront-filtering transport. Minimum price (inclusive), in the storefront's display currency's major unit. | 10 | |
| max_price | number | No | Storefront-filtering transport. Maximum price (inclusive), in the storefront's display currency's major unit. | 100 | |
| product_type | string | No | Storefront-filtering transport, comma-separated. Exact product-type display strings from the storefront's own Product Type facet -- see facets.product_type in an unfiltered response for the live value set. | Hats,T-Shirts | |
| in_stock_only | boolean | No | Storefront-filtering transport. true restricts to currently in-stock items only. | true | |
| option_size | string | No | Storefront-filtering transport. Example dynamic variant-option filter: comma-separated exact display values for the storefront's own size option (e.g. option_size=Small,Medium). Any variant option name is accepted the same way (option_color, ...) -- see facets in an unfiltered response for the live option names/values per store. | Small,Medium | |
| x-api-key (header) | string | Yes | API key required |
curl -X GET "https://api.crawlora.net/api/v1/shopify/products?url=https%3A%2F%2Fwww.allbirds.com&page=1&limit=10&sortBy=sortLTH&sort_by=manual&in_stock_only=true" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"msg": "OK",
"data": {
"store_url": "https://www.allbirds.com",
"source_url": "https://www.allbirds.com",
"page": 1,
"limit": 2,
"products": [
{
"id": "7188363542608",
"handle": "mens-tree-runner-nz-natural-white",
"title": "Men's Tree Runner NZ - Natural White (Natural White Sole)",
"url": "https://www.allbirds.com/products/mens-tree-runner-nz-natural-white",
"price": 100,
"available": true
}
]
}
}Request schema
No body schema
Response schema
#/definitions/shopify.productsResponseDoc
| Field | Type | Required | Enum | Bounds | Example | Description |
|---|---|---|---|---|---|---|
| code | integer | No | 200 | |||
| data | shopify.ProductsResponse | No | ||||
| data.facets | object | No | Facets and FacetsStats are populated when TransportMode is ssr_embedded (from the storefront's own embedded search-index facet buckets) or storefront_filtered (from the storefront's own Storefront Filtering sidebar -- Size/Color/etc. option facets, Product Type, and a "price" FacetsStats entry for the price-range slider's own min/max bounds). | |||
| data.facets_stats | object | No | Lowest, highest and average value per numeric facet such as `price`. Present only when `transport_mode` is set. | |||
| data.filters | object | No | Facet filters applied, mapping each facet name to the values requested. Present only for headless storefronts. | |||
| data.in_stock_only | boolean | No | Whether only in-stock products were requested. Present only when `transport_mode` is `storefront_filtered`. | |||
| data.limit | integer | No | 50 | Maximum number of products returned per page. | ||
| data.max_price | number | No | Maximum price filter applied. Present only when `transport_mode` is `storefront_filtered`. | |||
| data.min_price | number | No | Minimum price filter applied. Present only when `transport_mode` is `storefront_filtered`. | |||
| data.page | integer | No | 1 | Page number returned. | ||
| data.product_type | array | No | Product types the results were filtered to. Present only when `transport_mode` is `storefront_filtered`. | |||
| data.products | array | No | Products on this page, one item per product. | |||
| data.products[].available | boolean | No | Whether at least one variant is available to buy. | |||
| data.products[].canonical_colour | string | No | black | Main colour name the storefront groups the product under, for example `black`. | ||
| data.products[].collection_tags | array | No | Collections the product is listed in, as the storefront labels them. | |||
| data.products[].colour | string | No | black | The fields below are only populated when the product was resolved via the embedded-SSR-JSON fallback transport (see ssr_fallback.go) and the source page exposed this data; they are additive and never populated for classic-store responses. | ||
| data.products[].compare_at_price | number | No | 120 | Original price before discount, in the store's currency. The highest compare-at price across variants. | ||
| data.products[].condition | string | No | Excellent | Condition is only populated for resale-marketplace search results (Fashionphile's AlgoliaSearch, Rebag's StorefrontSearch -- see search.go) whose catalog data exposes a condition grade. Never populated for any other brand or endpoint in this family. | ||
| data.products[].created_at | string | No | Time the record was created, as Shopify returns it. | |||
| data.products[].description | string | No | Product description as plain text. | |||
| data.products[].discount_percentage | number | No | 20 | Discount currently applied to the product, as a percentage. | ||
| data.products[].featured_image | string | No | URL of the product's main image. | |||
| data.products[].handle | string | No | mens-wool-runners | URL slug of the product, for example `mens-wool-runners`. | ||
| data.products[].id | string | No | 6616124981328 | Shopify product id. | ||
| data.products[].images | array | No | Images of the product, in the storefront's order. | |||
| data.products[].images[].alt | string | No | Product image | Alternative text of the image. | ||
| data.products[].images[].created_at | string | No | Time the record was created, as Shopify returns it. | |||
| data.products[].images[].height | integer | No | 1600 | Image height in pixels. | ||
| data.products[].images[].id | string | No | 36411373027408 | Shopify image id. | ||
| data.products[].images[].position | integer | No | 1 | Position of the image in the product's image list, starting at 1. | ||
| data.products[].images[].updated_at | string | No | Time the record was last updated, as Shopify returns it. | |||
| data.products[].images[].url | string | No | https://cdn.shopify.com/s/files/1/example.jpg | URL of the image file. | ||
| data.products[].images[].variant_ids | array | No | Ids of the variants this image is attached to. | |||
| data.products[].images[].width | integer | No | 1600 | Image width in pixels. | ||
| data.products[].labels | array | No | new | Badges the storefront shows on the product, for example `new`. | ||
| data.products[].options | array | No | Options the product varies by, such as size or colour. | |||
| data.products[].options[].name | string | No | Size | Option name, for example `Size`. | ||
| data.products[].options[].position | integer | No | 1 | Position of the option on the product, starting at 1. | ||
| data.products[].options[].values | array | No | Values the option can take, for example `S` or `M`. | |||
| data.products[].price | number | No | 98 | Current price in the store's currency. The lowest variant price when variants are listed. | ||
| data.products[].product_type | string | No | Shoes | Product type set by the merchant, for example `Shoes`. | ||
| data.products[].published_at | string | No | Time the record was published, as Shopify returns it. | |||
| data.products[].rating | number | No | 4.2 | Average customer rating shown by the storefront. | ||
| data.products[].rating_count | integer | No | 1549 | Number of customer ratings behind `rating`. | ||
| data.products[].tags | array | No | Tags the merchant attached to the product. | |||
| data.products[].title | string | No | Men's Wool Runners | Product name as shown on the storefront. | ||
| data.products[].updated_at | string | No | Time the record was last updated, as Shopify returns it. | |||
| data.products[].url | string | No | https://www.allbirds.com/products/mens-wool-runners | Storefront page URL of the product. | ||
| data.products[].variants | array | No | Purchasable variants of the product, such as each size and colour. | |||
| data.products[].variants[].available | boolean | No | Whether this variant is currently available to buy. | |||
| data.products[].variants[].barcode | string | No | Barcode of the variant, such as a UPC or EAN. | |||
| data.products[].variants[].compare_at_price | number | No | 120 | Original price before discount, in the store's currency. | ||
| data.products[].variants[].created_at | string | No | Time the record was created, as Shopify returns it. | |||
| data.products[].variants[].featured_image | string | No | URL of the image shown for this variant. | |||
| data.products[].variants[].grams | integer | No | 400 | Weight of the variant in grams. | ||
| data.products[].variants[].id | string | No | 34761278160941 | Shopify variant id. | ||
| data.products[].variants[].inventory_quantity | integer | No | 24 | InventoryQuantity is only populated via the embedded-SSR-JSON fallback transport (see ssr_fallback.go); classic-store responses never set it. | ||
| data.products[].variants[].option1 | string | No | Value of the product's first option for this variant, for example a size. | |||
| data.products[].variants[].option2 | string | No | Value of the product's second option for this variant. | |||
| data.products[].variants[].option3 | string | No | Value of the product's third option for this variant. | |||
| data.products[].variants[].position | integer | No | 1 | Position of the variant in the product's variant list, starting at 1. | ||
| data.products[].variants[].price | number | No | 98 | Current price of the variant in the store's currency. | ||
| data.products[].variants[].product_id | string | No | 6616124981328 | Shopify id of the product the variant belongs to. | ||
| data.products[].variants[].requires_shipping | boolean | No | Whether the variant is a physical item that needs shipping. | |||
| data.products[].variants[].sku | string | No | A123 | Merchant stock keeping unit code of the variant. | ||
| data.products[].variants[].taxable | boolean | No | Whether tax is charged on the variant. | |||
| data.products[].variants[].title | string | No | Natural Black / 10 | Variant name, for example `Natural Black / 10`. | ||
| data.products[].variants[].updated_at | string | No | Time the record was last updated, as Shopify returns it. | |||
| data.products[].vendor | string | No | Allbirds | Brand or vendor name set by the merchant. | ||
| data.sort | string | No | sortLTH, sortHTL, newest | sortLTH | Sort and Filters echo back the effective sortBy/facet-filter query params that were applied, present only when transport_mode is ssr_embedded and the caller supplied at least one. Always omitted for the classic transport (which rejects them as invalid params instead). | |
| data.sort_by | string | No | manual, best-selling, title-ascending, title-descending, price-ascending, price-descending, created-ascending, created-descending | price-ascending | SortBy, MinPrice, MaxPrice, ProductType, InStockOnly, and VariantOptions echo back the effective storefront-filtering params that were applied, present only when transport_mode is storefront_filtered. | |
| data.source_url | string | No | https://www.allbirds.com | URL the data was read from. | ||
| data.store_url | string | No | https://www.allbirds.com | Base URL of the Shopify storefront that was queried. | ||
| data.total_items | integer | No | 184 | TotalItems and TotalPages are populated only when TransportMode is ssr_embedded, from the storefront's own embedded search-result pagination metadata. | ||
| data.total_pages | integer | No | 4 | Total number of result pages. Present only for headless storefronts. | ||
| data.transport_mode | string | No | ssr_embedded, storefront_filtered | ssr_embedded | Set to `ssr_embedded` for headless storefronts, or `storefront_filtered` when sort, price, product type, in-stock or variant filters were applied. Omitted otherwise. | |
| data.variant_options | object | No | Variant option filters applied, mapping an option name such as `size` to the values requested. Present only when `transport_mode` is `storefront_filtered`. | |||
| msg | string | No | OK |
| Status | Description | Schema |
|---|---|---|
| 400 | Bad Request | #/definitions/app.Response |
| 429 | Too Many Requests | #/definitions/app.Response |
| 503 | Service Unavailable | #/definitions/app.Response |
Use environment variables for secrets and keep Crawlora API keys server-side.
curl -X GET "https://api.crawlora.net/api/v1/shopify/products?url=https%3A%2F%2Fwww.allbirds.com&page=1&limit=10&sortBy=sortLTH&sort_by=manual&in_stock_only=true" \
-H "x-api-key: $CRAWLORA_API_KEY"