Getting Started
You supply the domain. Confirm it is a Shopify store, then read its catalog — the same two steps for every storefront on the web.
1. Get your API key
Create an account and generate a FetchLayer API key from your dashboard. There is no Shopify Partner account to register, no app to install on the store, and no per-store access token.
Create free account →2. Confirm the domain is a Shopify store
curl -X POST https://api.fetchlayer.dev/shopify-stores/detect-store \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"store":"allbirds.com"}'A domain that is not a Shopify store is a normal 200 with isShopify: false, not an error — so this is safe to run down a list of a thousand domains. Read the endpoints array on the response before going further: it says whether this particular store publishes its products, its collections and its metadata, and a store can switch any of them off.
Tip
permanentDomain its own markup reveals — pass that back as store. 3. Pull the catalog
curl -X POST https://api.fetchlayer.dev/shopify-stores/products \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"store":"allbirds.com","limit":250,"pages":5}'Each page walked counts as one request, so pages: 5 bills as five. When hasNextPage is true, pass the response's nextCursor back as cursor to continue where you stopped.
Read the notes array
notes array, and it is the only place a partial answer announces itself — a walk that stopped early, an upstream limit, a field this store does not publish. An empty notes means the result is as complete as the store publishes. 4. Go deeper on one product
A catalog walk returns every variant's price, sale price, SKU and availability, but not units on hand. /product-detail is the only endpoint that returns inventoryQuantity, and only for stores that track inventory — which is what makes per-product stock tracking over time possible.
curl -X POST https://api.fetchlayer.dev/shopify-stores/product-detail \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"product":"https://allbirds.com/products/mens-tree-runner-nz-natural-white"}'Tip
/resolve-url first — it tells you whether it is a product, a collection or the store, and hands back the domain, handle and variant id the other endpoints take. It touches no store and does not consume a credit. Error responses
Every error, on every route, is JSON in one shape: error (what went wrong, in words), status, code, retryable and requestId, which also comes as the X-Request-Id header. Branch on code and retryable rather than on the text; on a 5xx the text is generic. Quote requestId when reporting a problem.
{
"error": "Missing required parameter: url",
"status": 400,
"missing": ["url"],
"code": "invalid_input",
"retryable": false,
"requestId": "922874e3-c0d0-40d8-8866-d198af132420"
}| code | Status | Retryable | Meaning |
|---|---|---|---|
| invalid_input | 400 | no | The request needs fixing; error names the field. A missing required parameter also lists it in missing. |
| unauthorized | 401 | no | The API key is missing or not valid. |
| insufficient_credits | 402 | no | Nothing left to spend: no credits, a used-up free trial, or a monthly allowance and credit balance both spent. error says which. Waiting does not help; topping up does. |
| forbidden | 403 | no | This key or account is not allowed this request. |
| not_found | 404 | no | What the request names does not exist or is not public. |
| expired | 410 | no | What the request refers to is no longer available; ask for it afresh. |
| quota_exceeded | 429 | no | A request-rate limit was reached; error says which. Slow down before sending more. |
| cancelled | 499 | no | The caller closed or cancelled the request before it finished. The work was stopped and nothing was charged. |
| time_limit | 503 | yes | Your timeoutMs ran out before there was anything to return; ask again, or allow more time. |
| temporarily_unavailable | 503 | yes | Not available right now; ask again shortly. |
| store_unreachable | 503 | yes | Store routes (Shopify, WooCommerce): the store itself can’t be reached right now — it may be offline. Ask again later. Where a store route doesn’t say so yet, an offline store is temporarily_unavailable. |
| upstream_error | 502 | yes | The source answered in a way that could not be used; ask again later. |
| internal_error | 500 | yes | Something failed on our side; ask again later, and quote requestId if it persists. |
| tool_error | — | no | MCP only: a tool call failed in a way no other code describes; error says what. |
More codes may be added; treat one you do not recognise by its retryable flag. Over MCP, a tool error carries the same fields in structuredContent, and as a JSON text block beside the message.