# Shopify Products - Prices, Variants, SKUs & Availability (`kestrel/shopify-products`) Actor

Get Shopify products from store URLs or a collection. One row per product: prices, currency, variants, SKUs, availability, images, vendor and descriptions. Filter titles, vendors and available products before billing. Exclude known IDs for new products. No login or API key needed.

- **URL**: https://apify.com/kestrel/shopify-products.md
- **Developed by:** [Tedj MEABIOU](https://apify.com/kestrel) (community)
- **Categories:** E-commerce
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Shopify Products - Prices, Variants, SKUs & Availability

Shopify products returns prices and variants from public storefront catalogs for research and automation. It produces structured JSON that an agent can use without parsing a web page. Start with a small input, inspect a few rows, and then increase the result limit. The output has a stable identifier, source provenance and a fetch timestamp so another step can store, compare or cite the result.

Last verified working: 2026-10-16.

### Shopify product scraper API for agent workflows

Use this independent Apify actor to retrieve the public data documented below. It is not an official platform API or an account-management API. Pass the documented input JSON, keep result limits small for the first call, and use Apify run option `maxTotalChargeUsd` for the charge cap.

### Shopify 商品采集：价格、SKU、变体与库存状态

从已知店铺的公开商品目录获取 **Shopify 商品、产品价格、SKU、商品变体、图片和库存状态**，适合选品研究、竞品价格比较和 AI 智能体。`available` 仅表示至少一个变体可购买，**不提供精确库存数量**。不访问商家后台，不查找未知店铺，不读取订单或客户资料。

| 中文需求 | 正确参数或处理方式 |
|---|---|
| 采集一个店铺 | `storeUrls: ["https://www.allbirds.com"]` |
| 只保留可购买商品 | `onlyAvailable: true` |
| 最多返回 3 个商品 | `maxProductsPerStore: 3`，`maxResults: 3` |
| 只采集指定集合 | `collection: "all"`；使用店铺已有的集合 handle |
| 每天只获取新商品 | 外部保存 `product_key`，下次通过 `excludeProductIds` 传入 |
| 监控已有商品价格变化 | 不排除这些商品；定时重跑并在外部比较 `content_hash` 和价格 |

```json
{"storeUrls":["https://www.allbirds.com"],"maxProductsPerStore":3,"maxResults":3,"onlyAvailable":true}
```

一个 `type="product"` 行包含一个商品及其 `variants`、`price_min`、`price_max`、`currency`、`available`、`images` 和 `description_text`。变体不另计费。价格保留源币种；币种无法获取时为 `null`。商品名称和描述不自动翻译，关键词应使用店铺原文。

费用为 **每 1,000 个商品 $2**；3 个商品最多 $0.006。过滤、排除和去重先于计费及结果上限。无启动费；免费状态行、空结果和失败请求不按商品收费。金额上限使用 Apify 运行选项 `maxTotalChargeUsd`，不要将它放入输入 JSON。检查 `SUMMARY` 和 `type="status"`；公开目录关闭或访问失败会明确报告错误。历史价格和定时通知需要由外部工作流保存及执行。

这是独立的 Kestrel 工具，与 [Shopify](https://www.shopify.com) 无隶属关系。

### Shopify products: what does it return?

Each product row contains prices, currency, variants, SKUs, availability, images, vendor, tags and full product descriptions. All variants remain nested inside one product row; variants are not separately charged. Prices are decimal currency units from the public catalog, not cents. Currency is read from the storefront cart endpoint and remains null if that lookup fails. Availability means at least one variant is explicitly available; it does not provide inventory quantity. The public product catalog is paginated, up to 250 source products per page, with a maximum of 20 pages per store. Stores that disable or block the feed return an explicit error. Collection handles are supported. This actor does not discover stores, access admin data, place orders or track historical inventory. A feed may be incomplete for products with exceptionally large variant sets; the output contains all variants the public feed actually returns.

The actor separates records from diagnostics. A record has `type="product"` and is the only billable unit. A diagnostic has `type="status"`, identifies its `source`, and describes what happened during that source's retrieval. Read the `SUMMARY` record from the run's key-value store for totals. A successful platform run is not proof that every requested source had matches: inspect the diagnostic rows and the summary before declaring a research task complete.

### Shopify products for AI agents: which input answers which request?

Use this actor when the requested data matches the public source described above. Use the explicit input fields instead of constructing browser scripts. The target values identify what to search; the result limits bound the number of records delivered; filters narrow the matching records before charging. The actor does not interpret natural-language instructions outside the schema.

| User request | Input change |
|---|---|
| Products from one store | `storeUrls: ["https://www.allbirds.com"]` |
| Only available products | `onlyAvailable: true` |
| Match a title and vendor | `titleKeywords: ["runner"]`, `vendorKeywords: ["Allbirds"]` |
| Products in a collection | `collection: "shoes"` |
| New products since my last run | Save and pass `excludeProductIds` |
| Watch prices of known products | Rerun without exclusions and compare `content_hash`; no built-in price history |

The actor does not schedule itself, retain your earlier dataset as monitoring state, or send notifications. In a recurring workflow, store identifiers after each successful run and pass them in the exclusion field next time. If the user requests a complete historical archive or facts that the public source does not expose, explain the limitation before calling. Do not manufacture a target, fill a missing value from guesswork, or describe an empty result as evidence that no such record exists anywhere.

### Shopify products quick start

Paste this minimal example into the Input tab. It requests three records at most, so it is inexpensive to inspect. A smaller result is possible if the source is unavailable, has fewer records, or does not contain enough matches. The sample is a live public source, and its contents can change between runs.

```json
{
  "storeUrls": [
    "https://kith.com"
  ],
  "maxProductsPerStore": 3,
  "maxResults": 3
}
```

After the run, open Output or the default dataset. Retain rows where `type` equals `product` for downstream processing. Keep status rows in operational logs so failures remain visible. Download JSON when you need nested objects and arrays; CSV is useful for the flat columns but does not turn nested structures into a separate table automatically.

This example filters products to those with an available variant. All variants still appear in the delivered product.

```json
{
  "storeUrls": [
    "https://kith.com"
  ],
  "maxProductsPerStore": 3,
  "maxResults": 3,
  "onlyAvailable": true
}
```

### Inputs and source limits

| Input | Contract |
|---|---|
| `storeUrls` | Required: 1-10 public store homepage URLs, e.g. \["https://www.allbirds.com"]. HTTPS only; no paths or query strings. The public Shopify products.json feed must be enabled. No global store discovery, login or API key. One product with all its variants = one charged row. |
| `maxProductsPerStore` | 100 by default; 1-5000. Maximum matching products per store AFTER filters and exclusions, not variants. Source order; not sorted by newest. May return fewer if the catalog ends or maxPagesPerStore is reached. 10 products cost $0.02. |
| `maxResults` | 100 by default. Maximum delivered product rows across all inputs, after filters and deduplication. Stops early even if sources remain. Free status rows do not count. Cost is at most maxResults × $0.002; use Apify run option maxTotalChargeUsd for a dollar cap. |
| `onlyAvailable` | false (default) keeps all products. true keeps products with at least one variant explicitly available. All variants, including unavailable ones, stay in each returned product. Availability is a storefront flag, not an inventory quantity. Free filter before billing and caps. |
| `titleKeywords` | \["shoe", "runner"] keeps product titles containing either phrase, case insensitive. Empty = any title. Multiple title phrases use OR; combined with vendorKeywords and onlyAvailable using AND. Free filter before the product cap. |
| `vendorKeywords` | \["Nike"] keeps vendor names containing a phrase, case insensitive. Empty = all vendors. Multiple phrases use OR. Combined with titleKeywords and onlyAvailable using AND. Free filter before billing. |
| `collection` | Empty (default) scans the public catalog. A handle such as "shoes" scans /collections/shoes/products.json on every store. Pass the handle, not a URL. Unknown collections may be empty or fail. No extra charge. |
| `excludeProductIds` | Product IDs or full product_key values from previous output. Prefer product_key across multiple stores. Example \["www.allbirds.com:7340901859408"]. Up to 10000 strings. Exclusions are free and applied before caps. Save/pass IDs in your workflow; state and scheduling are external. |
| `maxPagesPerStore` | 5 by default; 1-20. Each public catalog page requests up to 250 products. Filtered and excluded rows still consume this scan allowance. Reaching it sets cap_reached=true and status=scan_limit; the run may have more matches outside the scanned pages. Requests themselves are not billed. |

Filters apply before delivered-record limits. This means a request for ten matches will not spend its entire output allowance on records that fail the filters. However, output limits and source coverage are different things. A source may return a bounded feed or a scanned portion of its catalog. The actor reports retrieval limits rather than silently claiming complete coverage. Raising the output cap cannot force an upstream source to expose more data than it actually returns.

Sources are processed in the order supplied. The total limit spans the whole run, and reaching it can leave later sources unprocessed. Check `sources_not_processed` in `SUMMARY` before assuming each target was visited. For an equal allocation across sources, use the per-source limit and choose a total limit large enough to accommodate the sum. The actor deduplicates repeated target strings before starting requests, and record identifiers are also deduplicated across the run.

### Output fields and stable identifiers

| Field | Meaning |
|---|---|
| `product_id` | Product id. |
| `product_key` | Product key. |
| `title` | Title. |
| `url` | Url. |
| `vendor` | Vendor. |
| `price_min` | Price min. |
| `price_max` | Price max. |
| `currency` | Currency. |
| `available` | Available. |
| `variants` | Variants. |
| `images` | Images. |
| `description_text` | Description text. |
| `store_url` | Store url. |
| `handle` | Handle. |
| `product_type` | Product type. |
| `tags` | Tags. |
| `description_html` | Description html. |
| `variant_count` | Variant count. |
| `options` | Options. |
| `published_at` | Published at. |
| `created_at` | Created at. |
| `updated_at` | Updated at. |
| `type` | Type. |
| `content_hash` | Content hash. |
| `fetched_at` | Fetched at. |
| `source` | Source. |
| `status` | Status. |
| `rows` | Rows. |
| `source_rows` | Source rows. |
| `filtered` | Filtered. |
| `duplicates` | Duplicates. |
| `errors` | Errors. |
| `error` | Error. |
| `pages` | Pages. |
| `cap_reached` | Cap reached. |
| `currency_warning` | Currency warning. |

Every returned record also includes `content_hash` and `fetched_at`. The hash is a SHA-256 digest of normalized content, excluding the fetch timestamp and, for news, the query that happened to retrieve it. It is useful for comparing repeated observations of the same identifier. It is not a guarantee that the underlying source has never changed: an edit can occur between runs, and only observed versions are available. Treat the timestamp as the time this actor observed the record, not the original publication date.

Keep source-provided identifiers as strings. Large numeric product identifiers can exceed safe integer precision in some spreadsheet or JavaScript workflows. Converting them to floating point can produce false matches or broken exclusion lists. Use the stable identifier as a database key and keep the human-readable title as a separate attribute. If you compare data from several sources, preserve the source component of the key.

Some fields can be null or empty. Null means the source did not supply a usable value or the documented lookup was unavailable. An empty list means no entries were available for that field. Neither means that a missing fact has been verified false. Preserve these distinctions in downstream reports instead of replacing null values with zeros, invented dates, or a default currency.

### Pricing and maximum cost

The price is $2 per 1,000 delivered product records. Ten records cost $0.02. There is no start fee. Proxy and compute are included in the event price for callers. Free status rows do not count toward the bill or the `maxResults` input. Filters, excluded identifiers, duplicates, empty results, failed requests and invalid input do not generate billable record events.

Billing is tied to successfully persisted records through the Apify SDK. A partial run can deliver and charge for valid records before a later source fails. Those delivered records remain useful and are not discarded because another source is unavailable. The actor does not charge a failed request as though it had produced a record. Review the dataset and status rows together when reconciling a partial result.

Set the maximum total price using Apify's `maxTotalChargeUsd` run option. This is a platform option, not an input property, and must be passed outside the JSON input body when using the API. The actor stops when the SDK reports that the event charge limit has been reached. For predictable calls, use both the record limit and the dollar cap. A smaller cap can stop the run before the requested count is reached; it cannot guarantee that the source contains enough matching data.

### Status rows and error handling

A status row includes `source`, `status`, `rows`, `source_rows`, `filtered`, `duplicates`, `errors`, `error`, `pages`, `cap_reached` and `currency_warning`. `rows` is the number delivered for that source; `source_rows` counts the source entries inspected, before filtering and deduplication. Repeated pages may make the inspected count larger than the number of distinct entries. `filtered` and `duplicates` explain why inspected entries did not become paid output.

`ok` indicates that the retrieval completed without a reported error. `empty` means the public source returned no entries. `no_matches` means entries were inspected but none survived the filters or exclusions. `error` means the source failed before delivering records; `partial_error` means a failure occurred after some records were already delivered. The `error` message explains the failure without including credentials. If source errors prevent all delivery, the actor marks the run failed. If some useful records were delivered, inspect the error count and decide whether to retry the affected sources.

`budget_reached` means the dollar cap stopped output. `cap_reached=true` means a count or scan boundary was reached; it does not promise that all matching data was exhausted. Catalog runs may also report `scan_limit` or `repeated_page`. The former indicates the maximum page count was used. The latter prevents an upstream endpoint that repeats the same page from causing a long, unproductive loop. A run summary includes `rows`, `sources`, `errors`, `filtered`, `duplicates`, `http_requests`, `budget_reached`, `result_limit_reached` and `sources_not_processed`.

### API examples: curl, Python and JavaScript

Keep your Apify token in an environment variable or the platform's credential manager. Do not paste it into a public workflow, shared input example or repository. The token authenticates your call to Apify; the actor itself does not require a separate source API key. Replace the sample input with a JSON object that follows the schema, and retain the same dollar cap when testing changes.

```bash
curl -X POST 'https://api.apify.com/v2/acts/kestrel~shopify-products/runs?maxTotalChargeUsd=0.05' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"storeUrls": ["https://kith.com"], "maxProductsPerStore": 3, "maxResults": 3}'
```

The asynchronous REST endpoint returns a run object. Poll that run until it reaches a terminal state, then read its `defaultDatasetId` and `defaultKeyValueStoreId`. Do not assume that a successful HTTP response to the start request means the extraction itself has finished. The client libraries can wait for completion, which is often simpler for a short agent task.

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('kestrel/shopify-products').call(
    run_input={'storeUrls': ['https://kith.com'], 'maxProductsPerStore': 3, 'maxResults': 3},
    max_total_charge_usd=0.05,
)
for row in client.dataset(run['defaultDatasetId']).iterate_items():
    if row.get('type') == 'product':
        print(row)
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('kestrel/shopify-products').call(
  {"storeUrls": ["https://kith.com"], "maxProductsPerStore": 3, "maxResults": 3},
  { maxTotalChargeUsd: 0.05 }
);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const records = items.filter(row => row.type === 'product');
console.log(records);
```

### Automation, MCP, n8n and Make

An AI agent using the Apify MCP server can search for the platform and data type, inspect this actor's schema, and run it with a bounded input. Field descriptions explain important distinctions at the point of use, including the per-source limit and the total cap. Ask the agent to inspect status rows before summarizing the results. This prevents a technically successful run with no matches from becoming an unsupported claim about the broader market.

In n8n or Make, connect the Apify run step to a dataset retrieval step. Filter the dataset by `type` before sending records to a spreadsheet, database or analysis model. For scheduled monitoring, create the schedule in your automation platform, store the identifiers there, and supply exclusions on subsequent calls. Keep separate handling for empty results and failures: an empty successful search may need no action, while a failed source might warrant a later retry with the same bounded input.

When a run is retried after partial delivery, pass the identifiers already saved so they are not billed again. The actor does not know whether your downstream database successfully committed those rows. Your workflow should save records and their identifiers together, or use an idempotent upsert keyed by the stable identifier. Avoid blindly retrying a large run immediately after a rate-limit response; a short delay and a smaller set of sources make it easier to determine whether the problem is temporary.

### FAQ: coverage, data quality and legal use

#### Does this actor guarantee a complete dataset?

No. It reports what the public source exposes within the documented retrieval bounds. Source ordering, publication changes, feed limits and endpoint availability affect results. Inspect caps and status messages, and keep the input alongside the dataset for reproducibility. A small sample is appropriate for checking a workflow; it is not automatically representative of the entire source or market.

#### Can it return information that is missing from the source?

No. It normalizes public records and preserves missing values. It does not infer stock quantities, reconstruct a historical archive, generate article bodies, or estimate facts the source did not provide. If an analysis needs additional evidence, treat that as a separate retrieval task rather than treating a nullable field as a model prompt to invent an answer.

#### Can I rerun the same search tomorrow?

Yes. Use an external schedule and retain your previous identifiers if you want only newly observed records. A new observation is not necessarily a new publication: the actor may not have seen an older record previously. If you want to observe changes to known records, do not exclude those identifiers; compare their content hashes and preserve both versions with their fetch timestamps.

#### Why did the run return fewer records than requested?

The source may contain fewer matches, filters or exclusions may remove entries, a source boundary may stop scanning, or the dollar cap may be smaller than the requested result cost. Status rows distinguish these situations. Increasing the count does not fix a blocked source. Check the error or cap first, then change the input that addresses that specific condition.

#### How should I handle an unavailable source?

Keep the diagnostic with the failed target and timestamp. Retry only that target after checking whether the public endpoint is accessible. A storefront may disable its public feed, and a news query may temporarily return an error. The actor does not bypass passwords, log in to private systems or promise access to every source. Successful sources can still be processed and retained independently.

#### Is the output suitable for legal or commercial reuse?

The actor accesses public information, but public access does not grant unrestricted rights to republish source content. Check the source's applicable terms and your intended use. Preserve attribution and source links, minimize copied content when publishing summaries, and handle any downstream personal information appropriately. The actor is a retrieval tool and does not determine the legal status of a particular reuse.

# Changelog

This Actor's version history is a separate document: https://apify.com/kestrel/shopify-products/changelog.md

# Actor input Schema

## `storeUrls` (type: `array`):

Required: 1-10 public store homepage URLs, e.g. \["https://www.allbirds.com"]. HTTPS only; no paths or query strings. The public Shopify products.json feed must be enabled. No global store discovery, login or API key. One product with all its variants = one charged row.

## `maxProductsPerStore` (type: `integer`):

100 by default; 1-5000. Maximum matching products per store AFTER filters and exclusions, not variants. Source order; not sorted by newest. May return fewer if the catalog ends or maxPagesPerStore is reached. 10 products cost $0.02.

## `maxResults` (type: `integer`):

100 by default. Maximum delivered product rows across all inputs, after filters and deduplication. Stops early even if sources remain. Free status rows do not count. Cost is at most maxResults × $0.002; use Apify run option maxTotalChargeUsd for a dollar cap.

## `onlyAvailable` (type: `boolean`):

false (default) keeps all products. true keeps products with at least one variant explicitly available. All variants, including unavailable ones, stay in each returned product. Availability is a storefront flag, not an inventory quantity. Free filter before billing and caps.

## `titleKeywords` (type: `array`):

\["shoe", "runner"] keeps product titles containing either phrase, case insensitive. Empty = any title. Multiple title phrases use OR; combined with vendorKeywords and onlyAvailable using AND. Free filter before the product cap.

## `vendorKeywords` (type: `array`):

\["Nike"] keeps vendor names containing a phrase, case insensitive. Empty = all vendors. Multiple phrases use OR. Combined with titleKeywords and onlyAvailable using AND. Free filter before billing.

## `collection` (type: `string`):

Empty (default) scans the public catalog. A handle such as "shoes" scans /collections/shoes/products.json on every store. Pass the handle, not a URL. Unknown collections may be empty or fail. No extra charge.

## `excludeProductIds` (type: `array`):

Pass product_id strings or exact product_key values from previous output. A key is host:id WITHOUT https://, e.g. \["www.allbirds.com:7340901859408"]. If a supplied key begins with https://, remove that prefix before calling; URL-shaped keys do not match. Prefer keys across stores. Up to 10000 strings. Free exclusions before caps. Save/pass IDs externally; scheduling and history are external.

## `maxPagesPerStore` (type: `integer`):

5 by default; 1-20. Each public catalog page requests up to 250 products. Filtered and excluded rows still consume this scan allowance. Reaching it sets cap_reached=true and status=scan_limit; the run may have more matches outside the scanned pages. Requests themselves are not billed.

## Actor input object example

```json
{
  "storeUrls": [
    "https://kith.com"
  ],
  "maxProductsPerStore": 100,
  "maxResults": 100,
  "onlyAvailable": false,
  "titleKeywords": [],
  "vendorKeywords": [],
  "collection": "",
  "excludeProductIds": [],
  "maxPagesPerStore": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "storeUrls": [
        "https://kith.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kestrel/shopify-products").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "storeUrls": ["https://kith.com"] }

# Run the Actor and wait for it to finish
run = client.actor("kestrel/shopify-products").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "storeUrls": [
    "https://kith.com"
  ]
}' |
apify call kestrel/shopify-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kestrel/shopify-products"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Y3dciWCrktgWTtZ9L/builds/RQtb1zck1ZtimfdRY/openapi.json
