{
  "openapi": "3.0.1",
  "info": {
    "title": "Shopify Store Monitor: Price Drops, Restocks & New Products",
    "description": "Watch any Shopify store on a schedule and get back only what changed: price drops, sales started and ended, restocks, sell-outs, new and removed products, variants added and removed, with real per-variant stock counts. Give it a domain or a collection URL. No API key, no app install, no password.",
    "version": "0.1",
    "x-build-id": "irXcOe1gszdhq1g4d"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/insight.solutions~shopify-store-monitor/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-insight.solutions-shopify-store-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/insight.solutions~shopify-store-monitor/runs": {
      "post": {
        "operationId": "runs-sync-insight.solutions-shopify-store-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/insight.solutions~shopify-store-monitor/run-sync": {
      "post": {
        "operationId": "run-sync-insight.solutions-shopify-store-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "required": [
          "stores"
        ],
        "properties": {
          "stores": {
            "title": "Shopify stores to monitor",
            "type": "array",
            "description": "One entry per store. A bare domain (`allbirds.com`), a full URL (`https://www.allbirds.com`) and a `myshopify.com` domain all work, and so does a link to any page of the shop — a product page, a link with tracking parameters glued on. A collection URL such as `https://www.allbirds.com/collections/mens` narrows **that entry** to that collection. Redirects are followed, and `example.com` and `www.example.com` are the same shop: they share one watch history and are billed once.",
            "items": {
              "type": "string"
            }
          },
          "watch": {
            "title": "Changes to report",
            "type": "array",
            "description": "Which changes are worth a row. This is applied **before** anything is billed, so a type you did not tick is never charged for. `inventory-change` needs \"Track per-variant stock\" on. `content-change` is off by default because themed stores rewrite their own descriptions and image sets constantly.",
            "items": {
              "type": "string",
              "enum": [
                "new-product",
                "removed-product",
                "price-drop",
                "price-increase",
                "sale-started",
                "sale-ended",
                "restock",
                "out-of-stock",
                "variant-added",
                "variant-removed",
                "inventory-change",
                "content-change"
              ],
              "enumTitles": [
                "New product",
                "Removed product",
                "Price drop",
                "Price increase",
                "Sale started (compare-at above the price)",
                "Sale ended",
                "Restock (back in stock)",
                "Out of stock",
                "Variant added",
                "Variant removed",
                "Stock count changed (needs trackInventory)",
                "Title, tags, description or images changed"
              ]
            },
            "default": [
              "new-product",
              "removed-product",
              "price-drop",
              "price-increase",
              "sale-started",
              "sale-ended",
              "restock",
              "out-of-stock",
              "variant-added",
              "variant-removed"
            ]
          },
          "minPriceChangePct": {
            "title": "Minimum price move (%)",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "Report a price drop or increase only when the move is at least this big, in either direction. `5` is a good setting for a daily schedule: it filters out the cent-level rounding that currency conversion and tax settings produce on some stores. It does not affect sale-started and sale-ended, which are about the compare-at price rather than the price.",
            "default": 0
          },
          "firstRunBehavior": {
            "title": "First run behaviour",
            "enum": [
              "baseline-only",
              "emit-all"
            ],
            "type": "string",
            "description": "What to do the first time a store is seen, when there is nothing to compare against. `baseline-only` records the catalogue and writes just the per-store summary, so you are not billed for a backfill you did not ask for. `emit-all` reports every product in the catalogue as new — which is billed per product, so pair it with a small \"Products per store\" the first time.",
            "default": "baseline-only"
          },
          "trackInventory": {
            "title": "Track per-variant stock counts",
            "type": "boolean",
            "description": "Also read each watched product's own `/products/<handle>.js` endpoint, which is the only public one that publishes a stock count. This is one extra request per product, so it is bounded by \"Stock lookups per run\" and spent on new products, products that moved, and products we already hold a count for. **Not every store publishes counts**: a store that does not track inventory, or whose theme withholds the field, returns `inventory: null` with `inventoryTracked` saying which of the two it was.",
            "default": false
          },
          "collections": {
            "title": "Collections to watch in every store",
            "type": "array",
            "description": "Collection handles applied to **every** store in the list, so one store with three handles is three watched targets, each with its own history and its own summary row. A handle (`mens`), a path (`/collections/mens`) or a whole collection URL all work. A store you named with a collection URL keeps its own handle. Leave empty to watch each store's whole catalogue.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "productHandles": {
            "title": "Only these product handles",
            "type": "array",
            "description": "An allowlist. When it is not empty, only these handles are watched: everything else in the catalogue is ignored, never compared and never billed. The handle is the last part of a product URL — `wool-runner-mizzle` in `/products/wool-runner-mizzle`. Changing this list between runs looks like products appearing and disappearing, because it changes what is being watched.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxInventoryLookups": {
            "title": "Stock lookups per run",
            "minimum": 0,
            "maximum": 1000,
            "type": "integer",
            "description": "Hard ceiling on how many products per run get the extra stock request, when \"Track per-variant stock counts\" is on. Ignored otherwise. This is the only part of a run whose cost scales with anything other than the number of stores, which is why it is capped rather than unbounded.",
            "default": 100
          },
          "maxProductsPerStore": {
            "title": "Products per store",
            "minimum": 1,
            "maximum": 10000,
            "type": "integer",
            "description": "Ceiling on how many products are watched per store. Keep it comfortably above the size of the catalogues you watch: if a store fills the ceiling, the products beyond it were not read, and unread is not removed — so removal detection is suspended for that store and its summary row is flagged `truncated`. It is also the page size, so asking for 25 downloads about 160 KB instead of 1.6 MB. See *Limits* in the README for snapshot sizing.",
            "default": 2000
          },
          "maxChangesPerStore": {
            "title": "Maximum changes per store per run",
            "minimum": 1,
            "maximum": 50000,
            "type": "integer",
            "description": "The guard against a store-wide sale. Twenty per cent off a 5,000-product catalogue at ten variants each is 50,000 billable rows, and nobody means to buy that. Changes past this ceiling are not recorded in the snapshot either, so the next run reports them — each change is delivered, and billed, exactly once.",
            "default": 2000
          },
          "outageGuardThreshold": {
            "title": "Catalogue-collapse guard threshold",
            "minimum": 0,
            "maximum": 10000,
            "type": "integer",
            "description": "If a store suddenly returns almost nothing and its previous snapshot held more than this many products, the run treats it as a storefront fault rather than a mass unpublish: no change events, the old snapshot is kept, nothing is charged, and a diagnostic row explains what happened. If the next run sees the same thing, the changes are reported then. Set to 0 to disable the guard — not recommended.",
            "default": 10
          },
          "stateStoreName": {
            "title": "State store name",
            "type": "string",
            "description": "Name of the key-value store that holds the previous run's catalogue snapshot. The Actor creates it on its first run and reads it back on every run after that — that memory is what makes this a monitor rather than a scraper. Keep one store per watchlist: two schedules sharing a store would compare against the same snapshot, so the second stops with a clear message instead of double-reporting. Do not point this at a store you made by hand; the Actor cannot read those.",
            "default": "shopify-store-monitor-state"
          },
          "requestTimeoutSecs": {
            "title": "Request timeout (seconds)",
            "minimum": 5,
            "maximum": 120,
            "type": "integer",
            "description": "Per-HTTP-request timeout. Raise it only if you are watching very large catalogues through a slow proxy.",
            "default": 20
          },
          "maxConcurrency": {
            "title": "Stores in parallel",
            "minimum": 1,
            "maximum": 8,
            "type": "integer",
            "description": "How many stores are read at once. Each parallel worker keeps its own proxy session, so a store that gets blocked burns only its own exit IP. Pages *within* one store are sequential and spaced 250–600 ms apart, so this is the only lever on speed for a long watchlist.",
            "default": 3
          },
          "maxRunSecs": {
            "title": "Run time budget (seconds)",
            "minimum": 30,
            "maximum": 3600,
            "type": "integer",
            "description": "Wall-clock budget for the whole run, so one unresponsive storefront cannot hold a scheduled run open until the platform timeout. When it is reached the Actor stops fetching, keeps and bills for everything already delivered, and writes a free diagnostic row for each store it never reached — those stores keep their snapshots and are not charged for.",
            "default": 240
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Shopify serves `/products.json` to anyone, so this Actor defaults to Apify's datacenter proxy — the cheap one — and its cost is already inside the event prices. Some large brands sit behind a bot filter that refuses datacenter address ranges; those come back as free `blocked` rows. Switching to the RESIDENTIAL group helps with some of them and not with others, and it is much slower on a large catalogue.",
            "default": {
              "useApifyProxy": true
            }
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}