{
  "openapi": "3.0.1",
  "info": {
    "title": "Cannabis Dispensary Monitor: 47 US States & All of Canada",
    "description": "Track cannabis dispensary openings, closures, and license changes across 47 US states and 13 Canadian provinces. Official registries plus platforms (Weedmaps, Leafly, Dutchie), deduplicated, with change detection and per-state license coverage: know which states have complete official data each run.",
    "version": "0.1",
    "x-build-id": "AlxrLV1UtWALmcSXE"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/registryfeeds~cannabis-dispensary-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-registryfeeds-cannabis-dispensary-scraper",
        "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/registryfeeds~cannabis-dispensary-scraper/runs": {
      "post": {
        "operationId": "runs-sync-registryfeeds-cannabis-dispensary-scraper",
        "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/registryfeeds~cannabis-dispensary-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-registryfeeds-cannabis-dispensary-scraper",
        "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",
        "properties": {
          "testMode": {
            "title": "Test Mode",
            "type": "boolean",
            "description": "When enabled, collects only 3 results per source per state and does NOT update the run snapshot or change detection baseline. Use this to verify the Actor works before running a full job. Costs nearly nothing.",
            "default": false
          },
          "estimateOnly": {
            "title": "Estimate only (no scraping)",
            "type": "boolean",
            "description": "Work out what this configuration would cost and roughly how many results it would return, then stop without scraping anything. Nothing is fetched and no results are produced, so the run costs a fraction of a cent. Use it to size a job before paying for it. The estimate reports two numbers: the maximum you could be charged, which is exact because it follows from Max Results Per Source, and a rough expectation based on measured runs. Results appear in the log and under the RUN_ESTIMATE key in the key-value store.",
            "default": false
          },
          "sources": {
            "title": "Platform Sources",
            "type": "array",
            "description": "Which platform sources to scrape. Weedmaps and Leafly are enabled by default and cover all US states with no extra setup. iHeartJane is also enabled by default but blocks non-residential traffic — it silently returns 0 results unless you configure a residential Proxy Configuration below. Dutchie requires the same residential proxy. Potguide and AllBud are included for completeness but currently return 0 results regardless of setup (potguide.com now redirects to an unrelated retailer; allbud.com requires JS rendering with no accessible API) — left selectable in case either comes back online, but not recommended.",
            "items": {
              "type": "string",
              "enum": [
                "weedmaps",
                "leafly",
                "iheartjane",
                "dutchie",
                "potguide",
                "allbud"
              ]
            },
            "default": [
              "weedmaps",
              "leafly",
              "iheartjane"
            ]
          },
          "states": {
            "title": "States / Provinces",
            "type": "array",
            "description": "US state/territory or Canadian province/territory abbreviations to scrape. Example: [\"PA\", \"NY\", \"ON\"]. US official license databases available for: PA, MD, DE, NY, NJ, CT, MA, CO, WA, CA, MI, IL, OR, NV, MO, OH, AZ, FL, MN, OK, NM, MT, VA, TX, AK, HI, LA, AR, MS, ND, ME, VT, RI, DC, NH, WV, SD, UT, IA, GA, AL, KY, NC, NE, PR, GU, VI (47 total). NC, NE, NJ, GU, and VI are legal cannabis jurisdictions whose regulators don't currently publish a usable public dispensary directory (NJ's CRC retired theirs in a site restructure; GU has licensed cultivators only; VI has 10 conditional dispensary licenses but none operating) — those five return 0 state-source results until one publishes again (platform sources still cover them). IL and PR are parsed from a government PDF export (their APIs were retired) rather than a live API; MA is parsed from a public consumer directory page rather than an API (its old Socrata API was also retired) — expect slightly lower field completeness than the Socrata-backed states, though MA now includes real coordinates the old API never had. CANADA — ALL 13 jurisdictions (10 provinces + 3 territories), official regulator/retailer sources: ON (Ontario AGCO, ~1,884 stores — richest Canadian source: full address + coordinates + website), BC (British Columbia LCRB licence export + BC Cannabis Stores, ~586), AB (Alberta AGLC retailer search, ~751), MB (Manitoba LGCA store list, ~247), QC (Québec SQDC, ~113), SK (Saskatchewan SLGA permit roster, ~204), NS (Nova Scotia NSLC, ~84 — via NSLC's store API, includes coordinates; NSLC blocks datacenter IPs, so NS needs an Apify residential proxy when run in the cloud — enable proxyConfiguration; without one NS returns 0 and is flagged 'not_yet_available', not an error), NB (New Brunswick Cannabis NB, ~33 — note: the government store network is scheduled to close 2026-08-03, after which this returns the remaining/private retailers), NL (Newfoundland & Labrador retailer locator, ~64), PE (Prince Edward Island PEI Cannabis, 5), YT (Yukon granted licence-holders, ~10), NT (Northwest Territories, 6), NU (Nunavut mail-order retailers, 2). SK is sourced from the latest archived permit roster (SLGA blocks live automated access), so it may lag by a few weeks. Canadian codes don't collide with any US code and can be mixed freely with US states in the same run. Note: the 6 platform sources (Weedmaps/Leafly/etc.) currently only cover US states — Canadian coverage comes from the official government/retailer sources above.",
            "default": [
              "PA",
              "MD",
              "DE",
              "NY",
              "NJ",
              "CT",
              "MA",
              "CO",
              "WA"
            ],
            "items": {
              "type": "string"
            }
          },
          "includeStateSources": {
            "title": "Include Official State Databases",
            "type": "boolean",
            "description": "When enabled, also pulls from official state licensing databases. Adds license numbers, license status, issue/expiry dates, and verified addresses the platforms may not have.",
            "default": true
          },
          "maxResultsPerSource": {
            "title": "Max Results Per Source Per State",
            "minimum": 0,
            "type": "integer",
            "description": "Maximum number of dispensaries to collect per source per state. Set to 0 for unlimited. Ignored when Test Mode is enabled. Default kept low (100) so a first run without a proxy stays well inside Apify's automated 5-minute reliability check — raise it for a full production run.",
            "default": 100
          },
          "sourceTimeoutSecs": {
            "title": "Per-Source Timeout (seconds)",
            "minimum": 30,
            "maximum": 1800,
            "type": "integer",
            "description": "Maximum seconds a single source is allowed to run before being cut off. Prevents one broken source from blocking the entire run and blowing up your compute budget. Default: 300 (5 minutes).",
            "default": 300
          },
          "includeMenuData": {
            "title": "Include Menu Stats",
            "type": "boolean",
            "description": "When enabled, scrapes basic menu statistics per dispensary: product counts by category and price ranges. Significantly increases run time and cost.",
            "default": false
          },
          "includeEnforcementData": {
            "title": "Include Enforcement & Compliance Actions",
            "type": "boolean",
            "description": "When enabled and Colorado is in your states list, scrapes Colorado MED's public enforcement/disciplinary action records (citations, fines, suspensions, license denials) into a separate 'enforcement-actions' dataset. Not merged into dispensary records — CO's list only has business names, no license numbers, so it can't be reliably matched by name alone. Currently Colorado only; more states planned.",
            "default": false
          },
          "proxyConfiguration": {
            "title": "Proxy Configuration",
            "type": "object",
            "description": "Optional proxy settings. Recommended for large runs to avoid rate limiting. Required for Dutchie (currently blocks direct HTTP).",
            "default": {
              "useApifyProxy": false
            }
          },
          "geocodeMissingCoordinates": {
            "title": "Geocode Missing Coordinates",
            "type": "boolean",
            "description": "When enabled, uses the free OpenStreetMap Nominatim API to fill in lat/lng for dispensaries where state databases did not publish location data. Rate limited to 1 request/second. Adds significant run time for large datasets. Results are cached between runs.",
            "default": false
          },
          "maxGeocodePerRun": {
            "title": "Max Geocode Lookups Per Run",
            "minimum": 0,
            "type": "integer",
            "description": "Ceiling on how many missing-coordinate records to look up via Nominatim in a single run (0 = unlimited). At ~1.1 seconds per lookup, this bounds run time and cost; anything not resolved this run is attempted on the next run. Only applies when Geocode Missing Coordinates is enabled.",
            "default": 2000
          },
          "maxMenuItemsPerDispensary": {
            "title": "Max Menu Items Per Dispensary",
            "minimum": 1,
            "maximum": 500,
            "type": "integer",
            "description": "Maximum number of menu products to fetch per dispensary when 'Include Menu Stats' is enabled. Hard cap at 500. Higher values increase run time and cost significantly.",
            "default": 200
          },
          "webhookUrl": {
            "title": "Webhook URL",
            "type": "string",
            "description": "Optional HTTPS endpoint to receive change alerts after each run. When set, the Actor POSTs structured JSON events for new dispensary openings, closures, license suspensions, and expiring licenses. Useful for real-time monitoring without polling."
          },
          "webhookStateFilter": {
            "title": "Webhook State Filter",
            "type": "array",
            "description": "If set, only send webhook events for dispensaries in these states. Leave empty to receive events for all scraped states.",
            "items": {
              "type": "string"
            }
          },
          "licenseExpiryAlertDays": {
            "title": "License Expiry Alert Window (days)",
            "type": "array",
            "description": "Alert thresholds (in days) for license expiry detection. Dispensaries whose license expires within any of these windows are included in the EXPIRING_LICENSES KV store key and webhook events.",
            "default": [
              30,
              60,
              90
            ]
          },
          "suppressInitialStateChanges": {
            "title": "Suppress Initial State Change Events",
            "type": "boolean",
            "description": "When enabled (default), prevents webhook floods when adding a new state to an existing run. Adding CA for the first time would otherwise fire 1,200+ new_dispensary events — with this on, those events are suppressed on the first run and detected normally in subsequent runs.",
            "default": true
          },
          "onlyOutputChanges": {
            "title": "Only Output Changed Dispensaries",
            "type": "boolean",
            "description": "When enabled, the dataset only contains dispensaries that are new or changed since the last run — not the full multi-thousand-record dataset every time. Built for recurring/scheduled monitoring: if you're running this weekly to track openings, closures, and license changes rather than re-extracting the full market each time, this can cut per-run dataset costs dramatically. MARKET_INTELLIGENCE, TREND_HISTORY, and the standby API still reflect the full current dataset regardless — only what's written to the default dataset changes. The very first run (no previous snapshot) always outputs everything, since there's nothing yet to diff against. Ignored in testMode.",
            "default": false
          },
          "enrichSocialMedia": {
            "title": "Enrich Social Media Handles",
            "type": "boolean",
            "description": "When enabled, fetches each dispensary's website homepage and extracts publicly linked social media profiles (Instagram, Facebook, Twitter/X, TikTok, YouTube, LinkedIn). Results populate the socialMedia field. Adds significant run time — capped at 1,000 dispensaries per run. Disabled in testMode.",
            "default": false
          },
          "enrichEmail": {
            "title": "Enrich Contact Email",
            "type": "boolean",
            "description": "When enabled, fetches each dispensary's website homepage and extracts a publicly listed contact email (mailto: links first, then visible page text as a fallback). Results populate the email field for dispensaries whose source didn't already publish one. Adds significant run time — capped at 1,000 dispensaries per run. Disabled in testMode.",
            "default": false
          },
          "cities": {
            "title": "Cities",
            "type": "array",
            "description": "Only output dispensaries in these cities. Case-insensitive, matched against the normalized city on each record. Combine with States to disambiguate repeated city names (there is a Springfield in most states). Leave empty for every city.",
            "items": {
              "type": "string"
            }
          },
          "centerLat": {
            "title": "Center latitude",
            "type": "string",
            "description": "Latitude of the point to measure from. Used only when Radius (miles) is also set. Example: 39.7392 for Denver."
          },
          "centerLon": {
            "title": "Center longitude",
            "type": "string",
            "description": "Longitude of the point to measure from. Used only when Radius (miles) is also set. Example: -104.9903 for Denver."
          },
          "radiusMiles": {
            "title": "Radius (miles)",
            "minimum": 1,
            "type": "integer",
            "description": "Only output dispensaries within this many miles of the center point. Requires both Center latitude and Center longitude. Great-circle distance. Records with no coordinates cannot be measured and are excluded; the run log reports how many, so a coverage gap is never mistaken for a distance result. Turn on Geocode missing coordinates to resolve more of them."
          },
          "services": {
            "title": "Service types",
            "type": "array",
            "description": "Only output dispensaries offering ALL of the selected services. Leave empty for no service filtering.",
            "items": {
              "type": "string",
              "enum": [
                "delivery",
                "pickup",
                "storefront"
              ],
              "enumTitles": [
                "Delivery",
                "Pickup",
                "Storefront"
              ]
            }
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}