Cannabis Dispensary Monitor — 44 US States + Change Detection
Pricing
from $5.00 / 1,000 results
Cannabis Dispensary Monitor — 44 US States + Change Detection
Track every licensed dispensary across all 44 US legal states. Combines Weedmaps, Leafly, iHeartJane, Dutchie + official state license DBs. Detects new openings, closures & license changes weekly. Geocoded coordinates, structural fingerprinting, menu pricing, expiry alerts & webhooks.
Pricing
from $5.00 / 1,000 results
Rating
0.0
(0)
Developer
Z W
Actor stats
0
Bookmarked
13
Total users
4
Monthly active users
20 days ago
Last modified
Categories
Share
The only cannabis Actor that tracks dispensary openings and closures across weekly runs. Combines 6 platform sources (Weedmaps, Leafly, iHeartJane, Dutchie, Potguide, AllBud) with official state license databases from 44 states and territories — deduplicates everything into one clean dataset, then diffs each run against the last to surface what changed: new stores, closures, license suspensions, and address updates. Change events include previousValues and currentValues so you can see exactly what changed without diffing records yourself.
Also includes deep menu data (optional) — strain type (indica/sativa/hybrid), THC/CBD percentages, effects, terpenes, per-weight price tiers (gram → ounce), brand, dosage, and staff picks — across 4 menu sources. Market intelligence and license expiry alerts included on every run at no extra cost.
What does Cannabis Dispensary Monitor do?
This Actor scrapes publicly available dispensary listings from major cannabis platforms plus official government licensing databases. It normalizes all records into a consistent schema, deduplicates across sources, and flags changes between runs.
Demo
Output table view (Apify Console → Output tab):
The dataset renders as a table with labeled columns: Name, City, State, License #, Status, Issued, Expires, Rating, Sources, ID Confidence, and more. Each row is one deduplicated dispensary. Run the Actor, click the Output tab, and you'll see this immediately.
RUN_HEALTH report (Key-Value store → RUN_HEALTH key):
{"overallStatus": "healthy","totalDispensaries": 20418,"deduplicationRate": "14%","sources": {"weedmaps": { "status": "ok", "itemsScraped": 8241, "errors": 0 },"leafly": { "status": "ok", "itemsScraped": 6183, "errors": 0 },"iheartjane": { "status": "ok", "itemsScraped": 5902, "errors": 0 },"state-ca": { "status": "ok", "itemsScraped": 1247, "errors": 0 },"state-co": { "status": "ok", "itemsScraped": 572, "errors": 0 },"state-ny": { "status": "ok", "itemsScraped": 314, "errors": 0 }},"changesDetected": { "new": 12, "closed": 3, "updated": 27 },"fingerprintDrifts": { "critical": 0, "sources": [] }}
Coverage:
- 44 states and territories with official state license databases: 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
- 6 platform sources — Weedmaps, Leafly, iHeartJane, Dutchie, Potguide, AllBud — covering all US states with overlapping coverage for higher deduplication quality
- Menu pricing (optional) — product counts, price ranges, per-category median/avg/min/max per dispensary
- Market intelligence — per-state analytics, chain concentration, top MSOs, delivery rates — automatically computed after every run
- Social media handles (optional) — Instagram, Facebook, Twitter/X, TikTok extracted from dispensary websites
State coverage at a glance
| State | Program | Est. licensed dispensaries | Official DB |
|---|---|---|---|
| California (CA) | Recreational + Medical | 1,200+ | CA DCC (Socrata) |
| Florida (FL) | Medical only | 550+ | FDOH OMMU |
| Oklahoma (OK) | Medical + Recreational | 2,000+ | OMMA (Socrata) |
| Colorado (CO) | Recreational + Medical | 550+ | CO MED CSV |
| Washington (WA) | Recreational + Medical | 700+ | WA LCB (Socrata) |
| Michigan (MI) | Recreational + Medical | 600+ | MRA (Socrata) |
| Oregon (OR) | Recreational + Medical | 700+ | OLCC (Socrata) |
| Illinois (IL) | Recreational + Medical | 180+ | IDFPR (Socrata) |
| New York (NY) | Recreational + Medical | 250+ | OCM API |
| New Jersey (NJ) | Recreational + Medical | 130+ | CRC (Socrata) |
| Nevada (NV) | Recreational + Medical | 200+ | CCB (Socrata) |
| Massachusetts (MA) | Recreational + Medical | 350+ | CCC (Socrata) |
| Pennsylvania (PA) | Medical only | 180+ | DOH HTML |
| Maryland (MD) | Recreational + Medical | 100+ | MCA HTML |
| Arizona (AZ) | Recreational + Medical | 200+ | ADHS (Socrata) |
| Missouri (MO) | Recreational + Medical | 350+ | DHSS (Socrata) |
| Ohio (OH) | Recreational + Medical | 350+ | DOC (Socrata) |
| Texas (TX) | Medical only | ~50 | DSHS HTML |
| Nebraska (NE) | Medical | Licensing Q1 2026 | NCRC (monitoring) |
| + 25 more states | Various | Varies | State-specific |
Platform sources (Weedmaps, Leafly, iHeartJane, Potguide, AllBud) supplement official data and provide coverage for all US states.
Quick smoke test: Set testMode: true — runs 3 results per source in under 30 seconds and costs less than $0.01.
Why use Cannabis Dispensary Monitor?
For developers and data teams
- Cross-run change detection — every run diffs against the previous snapshot and outputs
new_dispensary,dispensary_closed,license_suspended, anddispensary_updatedevents. POST them to your webhook endpoint in real time - Official license data — government Socrata APIs (CA DCC, CO MED, WA LCB, NY OCM, MA CCC, OR OLCC, NV CCB, AZ ADHS, and 34 more) provide verified license numbers, issue/expiry dates, and addresses the platforms often get wrong
- Deep menu data (optional) — enable
includeMenuData: trueto get strain type (indica/sativa/hybrid/CBD), THC/CBD%, effects (up to 10), terpenes (up to 8), brand, dosage, per-weight price tiers (gram → ounce), staff picks, and in-stock status. Sourced from Weedmaps, Leafly, iHeartJane, and Dutchie across 4 overlapping APIs. Pushed to a separatemenu_itemsdataset - Multi-source deduplication — same location found on Weedmaps, Leafly, Dutchie, Potguide, and the state DB → one clean merged record, not five duplicates. Three-tier matching: license number (most authoritative) → phone+city+state → normalized name+city+state. State source wins on address/license; platform source wins on ratings/hours. Every record carries
dedupMethod: "license" | "phone" | "name"so you know exactly how confident the merge is - 6 platform sources — Dutchie powers ~40% of US dispensaries (unique coverage). Potguide and AllBud surface thousands of independents that never list on Weedmaps or Leafly
- License expiry alerts — flags dispensaries whose license expires within 30/60/90 days via the
EXPIRING_LICENSESKV key and webhook events - Run health monitoring + structural fingerprinting —
RUN_HEALTHshows per-source item counts, errors, andsuspicious_zeroflags. On top of that, every source's field structure is fingerprinted after each healthy run. If Weedmaps silently drops thenamefield or Leafly'saddress.citynull rate jumps 40+ percentage points,overallStatusimmediately flips to"degraded"with the specific source and field named — you find out the same run the API broke, not after bad data reaches your pipeline - Coordinate geocoding (optional) — state databases publish addresses but not lat/lng. Enable
geocodeMissingCoordinates: trueto fill gaps via OpenStreetMap Nominatim, cached across runs so each address is only ever geocoded once. Coordinates includegeocodeSource(nominatim_fullornominatim_citystate) so you know which are precise vs approximate - Any format — JSON, CSV, Excel via Apify's native export
Use cases by buyer type
Cannabis operators — track competitor openings within 5 miles of your stores. Get a webhook when a new license is issued in your market. Export to CSV for your sales team.
B2B sales teams — build contact lists of licensed dispensaries with phone numbers, websites, addresses, and social media handles. Filter by state, category (medical vs recreational), and chain affiliation. 18,000–25,000 unique dispensaries with contact info across a full run.
Investors and M&A — track chain expansion by monitoring chain field changes over time. Detect when a regional operator crosses a threshold. License status + expiry dates surface distressed assets before they make the news.
Compliance and legal — automatic license expiry notifications at 30/60/90 days. License status change webhooks (active → suspended) deliver via POST the same day the government database updates. Covers 42 state databases.
Commercial real estate — dispensary density maps using lat/lng coordinates. Find saturated markets and underserved zip codes. Cross-reference against zoning data. Export to GeoJSON.
Market research — per-dispensary product counts and pricing stats across flower, edibles, vapes, concentrates when includeMenuData: true. Median prices and sale counts enable competitive price benchmarking.
App developers — build a dispensary finder with fresh weekly data instead of maintaining your own scrapers. The deduplicated dataset + coordinates is production-ready.
Use Case Examples
Dispensary operator: monitor competitor openings in your market
A Colorado dispensary chain wants to know within 24 hours when a new license is issued within 50 miles of their stores. They run this Actor on a weekly schedule with states: ["CO"], webhookUrl pointing to their Slack integration, and webhookStateFilter: ["CO"]. Each Monday morning they receive a POST payload listing any new_dispensary events from the prior week — name, address, coordinates, license number, and license issue date. When a competitor opens near one of their locations, their ops team sees it before customers do.
B2B sales team: build a dispensary contact list for the Southeast
A cannabis software company (POS systems, compliance tools) wants to reach every active licensed dispensary in GA, AL, KY, NC, TN, FL, TX. They run the Actor with states: ["GA","AL","KY","NC","FL","TX"], maxResultsPerSource: 0, includeStateSources: true, enrichSocialMedia: true. The output CSV gives them 3,500–5,000 records with business name, phone, website, social media handles, license number, license status, and category (medical vs recreational). They filter out chain !== null to target independents, and filter licenseStatus: "active" to skip expired licenses. Total cost: under $0.50.
Cannabis investor: track MSO footprint expansion
A private equity analyst tracks multi-state operator (MSO) expansion across quarterly runs. They run the Actor on all 44 states with maxResultsPerSource: 0 twice per quarter. Each run outputs a CHANGES KV key with new_dispensary events. By filtering changes where dispensary.chain === "Trulieve" (or any target MSO), they see exactly which markets each chain entered or exited. They also pull MARKET_INTELLIGENCE.topChains for national chain concentration numbers — data that traditional cannabis data vendors (Headset, BDSA) charge $5,000+/year to access.
App developer: dispensary finder with fresh weekly data
A developer building a dispensary-finder mobile app needs a dataset of all US dispensaries with coordinates, hours, services (pickup/delivery/storefront), and ratings — updated weekly without maintaining their own scrapers. They configure a weekly Apify schedule with states set to their target markets, geocodeMissingCoordinates: true (fills null coordinates via OpenStreetMap), and sync the dataset to their database via the Apify API after each run. The deduplicated dataset (~20,000 records for a full run) arrives as production-ready JSON with consistent schema across all sources.
Compliance team: license expiry monitoring
A cannabis law firm tracks renewal deadlines for 200+ dispensary clients across PA, MD, NJ, NY, CT, MA. They run the Actor weekly with licenseExpiryAlertDays: [30, 60, 90] and webhookUrl pointing to their case management system. The license_expiring events fire exactly 30, 60, and 90 days before each license expiration date — automatically, with no manual tracking spreadsheet. The EXPIRING_LICENSES KV key also gives them a full snapshot of every client with an upcoming renewal.
How to use Cannabis Dispensary Monitor
- Open the Input tab in Apify Console
- Select which Platform Sources to scrape (Weedmaps, Leafly, iHeartJane, Potguide, AllBud enabled by default; Dutchie requires a residential proxy)
- Enter the US States you want, e.g.
["CA", "CO", "WA"] - Keep Include Official State Databases enabled for license numbers and expiry dates
- Optionally add a
webhookUrlto receive change events after each run - Click Start — a typical 4-state run takes 5–15 minutes
- Download from the Output tab in JSON, CSV, or Excel
Run via API:
curl -X POST "https://api.apify.com/v2/acts/YOUR_ACTOR_ID/runs" \-H "Authorization: Bearer YOUR_API_TOKEN" \-H "Content-Type: application/json" \-d '{"states": ["CA", "CO"], "sources": ["weedmaps", "iheartjane"], "maxResultsPerSource": 0}'
Schedule weekly with change detection:
- Run once to build the baseline snapshot
- Add a weekly schedule (Monday morning recommended)
- Each subsequent run posts only what changed to your
webhookUrl
Input
| Field | Type | Default | Description |
|---|---|---|---|
testMode | boolean | false | Collect only 3 results/source, skip snapshot update. Free smoke test |
sources | array | ["weedmaps","leafly","iheartjane","potguide","allbud"] | Platform sources to scrape. Options: weedmaps, leafly, iheartjane, potguide, allbud, dutchie (residential proxy required for Dutchie) |
states | array | ["PA","MD","DE","NY","NJ","CT","MA","CO","WA"] | US state abbreviations. Supported state DBs: 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 |
includeStateSources | boolean | true | Include official state license databases (adds license numbers, expiry dates) |
maxResultsPerSource | integer | 500 | Max dispensaries per source per state. 0 = unlimited |
sourceTimeoutSecs | integer | 300 | Max seconds per source before cutoff (prevents one hung source from killing the run) |
includeMenuData | boolean | false | Scrape deep menu data per dispensary — strain type (indica/sativa/hybrid), THC/CBD%, effects, terpenes, brand, dosage, per-weight price tiers, staff picks. Pushed to separate menu_items dataset |
webhookUrl | string | — | HTTPS endpoint to receive change/expiry events after each run |
webhookStateFilter | array | — | Only send webhook events for these states (empty = all states) |
licenseExpiryAlertDays | array | [30,60,90] | Days-before-expiry windows for license expiry alerts |
suppressInitialStateChanges | boolean | true | Suppress new_dispensary events when adding a new state for the first time |
geocodeMissingCoordinates | boolean | false | Fill null coordinates via free OpenStreetMap Nominatim API |
enrichSocialMedia | boolean | false | Scrape each dispensary's website to extract Instagram, Facebook, Twitter/X, TikTok, YouTube handles. Capped at 1,000 dispensaries. Adds run time. |
proxyConfiguration | object | — | Apify proxy settings (recommended for large runs or rate-limited sources) |
Quick test:
{"testMode": true,"states": ["CA", "CO"],"sources": ["weedmaps"]}
Full run with webhook alerts:
{"states": ["PA", "NY", "CO", "CA"],"sources": ["weedmaps", "leafly", "iheartjane", "potguide", "allbud"],"includeStateSources": true,"maxResultsPerSource": 0,"webhookUrl": "https://your-endpoint.com/cannabis-alerts","licenseExpiryAlertDays": [30, 60, 90]}
Output
Main Dataset — Dispensary Records
Download in JSON, CSV, HTML, or Excel from the Output tab or via the Apify API.
Example records:
[{"id": "wm_48291","sources": ["weedmaps", "state-pa"],"name": "Maitri Medicinals","slug": "maitri-medicinals","url": "https://weedmaps.com/dispensaries/maitri-medicinals","address": {"street": "6056 Broad St","city": "Pittsburgh","state": "PA","zip": "15206","country": "US"},"coordinates": { "lat": 40.4551, "lng": -79.9218 },"dedupMethod": "license","phone": "+14123625220","email": null,"website": "https://maitrimedicinals.com","hours": {"monday": "10am-7pm", "tuesday": "10am-7pm", "wednesday": "10am-7pm","thursday": "10am-7pm", "friday": "10am-7pm","saturday": "10am-7pm", "sunday": "11am-5pm"},"rating": 4.7,"reviewCount": 312,"services": { "delivery": false, "pickup": true, "storefront": true },"licenseNumber": "MM-PA-0012432","licenseStatus": "active","licenseIssuedDate": "2021-03-15","licenseExpiresDate": "2026-03-14","categories": ["medical"],"amenities": ["atm", "parking", "ada_accessible"],"acceptsCreditCard": true,"chain": null,"menu": {"totalProducts": 87,"onSaleCount": 5,"byCategory": { "flower": 22, "edibles": 18, "vapes": 15, "concentrates": 12 },"priceRange": { "min": 5, "max": 95 },"priceByCategory": {"flower": { "count": 22, "min": 10, "max": 55, "avg": 28.50, "median": 27.00, "onSale": 3 },"edibles": { "count": 18, "min": 5, "max": 35, "avg": 18.20, "median": 16.50 },"vapes": { "count": 15, "min": 25, "max": 75, "avg": 48.00, "median": 45.00 }}},"lastScraped": "2025-01-15T14:22:05.000Z"},{"id": "wm_55102","sources": ["weedmaps", "leafly"],"name": "Housing Works Cannabis Co.","address": { "street": "750 Broadway", "city": "New York", "state": "NY", "zip": "10003", "country": "US" },"coordinates": { "lat": 40.7285, "lng": -73.9920 },"phone": "+12125054100","website": "https://hwcannabis.co","rating": 4.4,"reviewCount": 890,"services": { "delivery": false, "pickup": true, "storefront": true },"licenseNumber": "OCM-RD-2023-00047","licenseStatus": "active","categories": ["recreational"],"amenities": null,"acceptsCreditCard": null,"chain": null,"menu": null,"dedupMethod": "license","googleMapsUrl": "https://www.google.com/maps/search/Housing+Works+Cannabis+Co.+750+Broadway+New+York+NY+10003","lastScraped": "2025-01-15T14:22:05.000Z"}]
Data Field Reference
| Field | Type | Description |
|---|---|---|
id | string | Stable unique ID — source-prefixed (wm_, lf_, jhj_, state_pa_, etc.) |
sources | string[] | All sources that contributed data to this record |
name | string | Dispensary display name |
slug | string | URL slug (from platform sources) |
url | string | Platform listing URL |
address | object | street, city, state, zip, country |
coordinates | object | lat, lng decimal degrees. When geocoded via OpenStreetMap, also includes geocodeSource: "nominatim_full" (street-level precision) or "nominatim_citystate" (city-level approximate) |
phone | string | E.164 format (+1XXXXXXXXXX) |
email | string | Public email if listed |
website | string | Dispensary website URL |
hours | object | Keyed by day name (monday–sunday) |
rating | number | Platform rating (0–5 scale) |
reviewCount | number | Total review count |
services | object | delivery, pickup, storefront booleans |
licenseNumber | string | State-issued license number (from official DB when available) |
licenseStatus | string | active, suspended, expired, revoked, pending, etc. |
licenseIssuedDate | string | ISO date string (YYYY-MM-DD) |
licenseExpiresDate | string | ISO date string — used for expiry alerts |
categories | string[] | medical, recreational, or both |
amenities | string[] | Normalized amenity list (atm, parking, ada_accessible, etc.) — from Weedmaps and Leafly when available |
acceptsCreditCard | boolean | true if any source confirms credit card acceptance (iHeartJane, Weedmaps, Leafly), null if unknown |
chain | string | MSO chain name if detected (Curaleaf, Trulieve, GTI, etc.), else null |
menu | object | Product counts + per-category pricing when includeMenuData: true. Includes totalProducts, onSaleCount, byCategory, priceRange, and priceByCategory (min/max/avg/median/onSale per category) |
socialMedia | object | Instagram, Facebook, Twitter/X, TikTok, YouTube handles when enrichSocialMedia: true |
googleMapsUrl | string | Direct Google Maps search link for the dispensary (computed from name + address) |
dedupMethod | string | How this record was identified/merged: "license" (government license number — highest confidence), "phone" (phone+city+state match), "name" (normalized name+city+state — best effort) |
lastScraped | string | ISO timestamp of this scrape |
Menu Items Dataset (when includeMenuData: true)
A separate menu_items dataset is pushed alongside the main dispensaries dataset. Each record represents a single product from a dispensary's menu:
{"dispensaryId": "wm_48291","dispensaryName": "Maitri Medicinals","source": "weedmaps","productId": "prod_9928471","name": "Blue Dream (1g)","brand": "Hollyweed","category": "flower","subcategory": "pre-roll","strainType": "hybrid","weightGrams": 1.0,"dosageMg": null,"priceUSD": 12.00,"salePriceUSD": null,"onSale": false,"priceTiers": { "gram": 12.00, "eighth": 38.00, "quarter": 70.00, "ounce": 240.00 },"thcPct": 22.4,"cbdPct": 0.1,"effects": ["euphoric", "creative", "energetic", "uplifted"],"terpenes": ["myrcene", "caryophyllene", "limonene"],"staffPick": false,"inStock": true,"imageUrl": "https://images.weedmaps.com/products/...","description": "A classic sativa-dominant hybrid with sweet berry aromas...","scrapedAt": "2025-01-15T14:22:05.000Z"}
Menu item fields:
| Field | Description |
|---|---|
strainType | indica, sativa, hybrid, cbd, or null — sourced from all 4 platforms |
dosageMg | Milligrams per serving — populated for edibles, capsules, tinctures |
thcPct | THC percentage (e.g. 22.4 for 22.4%) — available from Weedmaps, Leafly, iHeartJane, Dutchie |
cbdPct | CBD percentage — same sources |
effects | Array of up to 10 effect strings (e.g. ["euphoric","creative","relaxed"]) — from Leafly strain data, iHeartJane, Dutchie |
terpenes | Array of up to 8 terpene names (e.g. ["myrcene","limonene"]) — from Leafly, iHeartJane, Dutchie |
staffPick | Boolean — staff-curated picks from Dutchie and iHeartJane |
priceTiers | Per-weight prices: halfGram, gram, twoGram, eighth, quarter, halfOz, ounce — from Weedmaps and iHeartJane |
brand | Brand/manufacturer name — enables brand market share analysis across dispensaries |
This dataset is what makes the menu data genuinely valuable for market research — strain mix by dispensary, terpene preferences by region, brand distribution, effect profile trending across markets.
Webhook Events
Set webhookUrl to receive a POST request after each run. The payload is a JSON array of change events:
[{"event": "new_dispensary","dispensary": { "name": "Acme Cannabis", "address": { "city": "Denver", "state": "CO" }, "licenseNumber": "MED-CO-001234", ... },"detectedAt": "2025-01-22T10:00:00.000Z"},{"event": "license_suspended","dispensary": { "name": "Green Leaf Denver", ... },"changedFields": ["licenseStatus"],"previousValues": { "licenseStatus": "active" },"currentValues": { "licenseStatus": "suspended" },"detectedAt": "2025-01-22T10:00:00.000Z"},{"event": "dispensary_updated","dispensary": { "name": "The Green Solution", ... },"changedFields": ["phone", "hours.monday"],"previousValues": { "phone": "+13035550001", "hours.monday": "9am-9pm" },"currentValues": { "phone": "+13035559999", "hours.monday": "10am-8pm" },"detectedAt": "2025-01-22T10:00:00.000Z"},{"event": "license_expiring","dispensary": { "name": "High Five Dispensary", "licenseExpiresDate": "2025-02-15", ... },"daysUntilExpiry": 24,"detectedAt": "2025-01-22T10:00:00.000Z"}]
Event types: new_dispensary, dispensary_closed, dispensary_updated, license_suspended, license_expired, license_expiring.
Every updated variant (dispensary_updated, license_suspended, license_expired) includes previousValues and currentValues objects with the exact before/after for each changed field. No need to store full snapshots yourself — the diff is pre-computed.
Note: rating-only changes are tracked in changedFields but do not trigger webhook delivery (too noisy at scale). All other field changes trigger delivery.
CHANGES Key (Key-Value Store)
Every run saves a structured change report to CHANGES in the default Key-Value store:
{"summary": { "new": 3, "closed": 1, "updated": 8 },"suppressedNew": 12,"changes": [{ "changeType": "new_dispensary", "dispensary": { "name": "New Store", "address": {...} }, "detectedAt": "..." },{ "changeType": "updated", "dispensary": { "name": "Green Leaf", ... }, "changedFields": ["hours.monday"], "previousValues": { "hours.monday": "9am-9pm" }, "currentValues": { "hours.monday": "10am-8pm" }, "detectedAt": "..." }]}
RUN_HEALTH Key (Key-Value Store)
{"overallStatus": "degraded","totalDispensaries": 712,"deduplicationRate": "18%","sources": {"weedmaps": {"status": "ok","itemsScraped": 380,"errors": 0,"fingerprintDrift": "critical"},"leafly": { "status": "ok", "itemsScraped": 290, "errors": 0, "fingerprintDrift": null },"state-ny": { "status": "ok", "itemsScraped": 88, "errors": 0, "fingerprintDrift": null },"state-pa": { "status": "degraded", "itemsScraped": 42, "errors": 1, "fingerprintDrift": null }},"fingerprintDrifts": {"critical": 1,"sources": { "weedmaps": "critical" }},"changesDetected": { "new": 3, "closed": 1, "updated": 8 }}
overallStatus values: healthy · degraded (partial data, source errors, or critical fingerprint drift) · failed (all sources returned 0 items).
Per-source status values: ok · degraded (partial data) · failed (0 items) · suspicious_zero (zero when previous run had data — silent break) · timeout (killed by per-source timeout guard).
fingerprintDrift per source: null (no drift) · "warning" (non-critical field changes) · "critical" (required field disappeared or key null rate spiked — investigate immediately).
MARKET_INTELLIGENCE Key (Key-Value Store)
Computed automatically after every run. Zero extra configuration needed.
{"generatedAt": "2025-01-22T10:00:00.000Z","national": {"totalDispensaries": 18247,"medical": 4891,"recreational": 9823,"both": 3533,"withDelivery": 3841,"withPickup": 14322,"chainAffiliated": 5734,"independent": 12513,"chainConcentration": 0.3143,"avgRating": 4.21,"stateCount": 44},"byState": {"FL": {"total": 2487,"medical": 2487,"recreational": 0,"both": 0,"chainAffiliated": 1823,"chainConcentration": 0.7329,"avgRating": 4.3,"topChains": [{ "chain": "Trulieve", "count": 134 },{ "chain": "Curaleaf", "count": 57 },{ "chain": "Fluent Cannabis Care", "count": 32 }],"withDelivery": 2100,"sourceCoverage": ["weedmaps", "leafly", "state-fl"]},"CO": {"total": 591,"medical": 221,"recreational": 370,"both": 154,"chainAffiliated": 187,"chainConcentration": 0.3164,"avgRating": 4.1,"topChains": [{ "chain": "Schwazze (Star Buds)", "count": 34 },{ "chain": "LivWell", "count": 26 },{ "chain": "Native Roots", "count": 18 }],"sourceCoverage": ["weedmaps", "leafly", "iheartjane", "state-co"]}},"topChains": [{ "chain": "Trulieve", "count": 187, "stateCount": 12, "states": ["FL","PA","MA","CT","MD","GA","AL","WV","OH","TX","CO","AZ"] },{ "chain": "Curaleaf", "count": 156, "stateCount": 23, "states": ["FL","NY","PA","NJ","CT","MA","MD","OH","MI","IL","OR","AZ","CO","MO","OK","NV","UT","WV","MN","ND","SD","MS","ME"] },{ "chain": "Verano", "count": 143, "stateCount": 14, "states": ["IL","OH","PA","NJ","MD","FL","MA","WV","NM","NV","AZ","AR","MO","MI"] }],"marketConcentration": {"top5SharePct": 14.2,"top10SharePct": 21.8,"top25SharePct": 31.4,"largestChain": "Trulieve","largestChainCount": 187,"largestChainPct": 1.03},"recentActivity": {"newDispensaries": 12,"closedDispensaries": 3,"updatedDispensaries": 47}}
This level of market intelligence — per-state chain concentration, MSO footprint, delivery penetration rates — costs thousands of dollars per report from traditional cannabis data vendors (Headset, BDSA, New Cannabis Ventures). Here it's a free KV output on every run.
EXPIRING_LICENSES Key (Key-Value Store)
{"alertedAt": "2025-01-22T10:00:00.000Z","windows": {"30": [{ "name": "High Five Dispensary", "licenseExpiresDate": "2025-02-15", "daysUntilExpiry": 24, "state": "CO" }],"60": [...],"90": [...]}}
Standby REST API
When run in Apify Standby mode, this Actor exposes a live REST API. The in-memory dataset is populated from the last run snapshot at startup and refreshed after each scrape completes — so your app always queries fresh data without triggering a new run.
Base URL: https://<containerId>.runs.apify.net
Endpoints
GET /dispensaries — Query the full dataset with filters
GET /dispensaries?state=CO&category=recreational&chain=Schwazze&limit=50&offset=0&q=denver
| Parameter | Description |
|---|---|
state | 2-letter state abbreviation (e.g. CA, CO) |
category | medical or recreational |
chain | MSO chain name (e.g. Trulieve, Curaleaf) |
source | Filter by source (weedmaps, leafly, iheartjane, state-co, etc.) |
q | Full-text search on name and city |
limit | Results per page, max 500 (default: 50) |
offset | Pagination offset (default: 0) |
{"total": 127,"limit": 50,"offset": 0,"loadedAt": "2026-04-21T08:00:00.000Z","data": [ { "id": "wm_48291", "name": "L'eagle Services", ... } ]}
GET /dispensaries/:id — Single dispensary by ID
GET /dispensaries/wm_48291
GET /health — Last run health report (same as RUN_HEALTH KV key)
GET /changes?since=2026-04-01T00:00:00Z&state=CO — Change events with optional filters
| Parameter | Description |
|---|---|
since | ISO datetime — only return changes detected after this time |
state | Filter to a single state |
GET / — Readiness probe + live status
Cannabis Dispensary Monitor — 22847 dispensaries loaded, updated 2026-04-21T08:00:00.000Z
App developer quickstart
# Get all Colorado recreational dispensariescurl "https://<containerId>.runs.apify.net/dispensaries?state=CO&category=recreational&limit=100"# Get change events since last weekcurl "https://<containerId>.runs.apify.net/changes?since=2026-04-14T00:00:00Z"# Look up a specific dispensarycurl "https://<containerId>.runs.apify.net/dispensaries/wm_48291"
The Standby API is ideal for dispensary-finder apps that need sub-second query response without re-scraping on every user request. Schedule a weekly batch scrape → serve from the REST API for the rest of the week.
Pricing — How much does it cost?
| Run scope | Approx results | Estimated cost |
|---|---|---|
| 1 state, 1 platform source | 50–300 | < $0.05 |
| Northeast corridor (PA MD DE NY NJ CT MA) | 1,500–2,500 | $0.15–$0.45 |
| West Coast (CA OR WA) | 2,600–3,500 | $0.25–$0.60 |
| Full default (9 states, 5 platform sources) | 4,000–7,000 | $0.40–$0.90 |
| Full default + menu data | 4,000–7,000 dispensaries + menu items | $1.00–$2.00 |
| All 44 states, all 6 sources | 20,000–26,000 | $2.00–$4.00 |
Costs depend on Apify compute units. All platform sources use HTTP crawling (no browser, fast and cheap). Official state sources run 4 in parallel.
Tips
- Free smoke test:
testMode: truereturns 3 results per source/state, skips the snapshot update, costs < $0.01. Run this first to verify the Actor works before a paid run - Weekly scheduling: Use Apify's built-in scheduler (Monday morning recommended). The first run builds the baseline — every subsequent run reports only what changed
- Unlimited results: Set
maxResultsPerSource: 0to remove the per-source cap. Needed for CA (~1,200), WA (~700), OR (~700), CO (~550), MI (~500) - Targeted alerts: Combine
webhookUrlwithwebhookStateFilter: ["CO", "CA"]to only receive webhook events for the states you care about — reduces noise for single-market operators - Adding a new state: Set
suppressInitialStateChanges: falseif you wantnew_dispensaryevents on the first run for a freshly added state. Default (true) suppresses the flood - Debugging: Check
RUN_HEALTHin the Key-Value store — per-source item counts andsuspicious_zerocatch silent scraper breaks before they affect your pipeline - Proxy: Enable Apify proxy for large runs (500+ dispensaries) to reduce rate-limiting from Weedmaps and Leafly. Use
RESIDENTIALgroup inproxyConfigurationto unlock Dutchie coverage (~40% of US dispensaries) - Social media:
enrichSocialMedia: truehits each dispensary's website and extracts Instagram, Facebook, Twitter/X, TikTok handles — great for marketing contact lists. Capped at 1,000 dispensaries per run
FAQ, Disclaimers, and Support
Is scraping dispensary data legal? This Actor scrapes publicly available information from dispensary listing platforms and official state government databases. It does not bypass authentication, access private data, or collect personally identifiable information beyond what is publicly posted. Users are responsible for complying with applicable platform Terms of Service and laws in their jurisdiction.
What if a source breaks?
The Actor degrades gracefully — one failed source doesn't stop the run. All remaining sources continue and the failure is logged in RUN_HEALTH. Three layers of protection catch silent degradation before it reaches your data:
suspicious_zero— source ran but returned 0 items when the previous run had data (total extraction failure, e.g. a site redesign)- Structural fingerprinting — compares each source's field structure against its stored baseline. If a key field like
namedisappears oraddress.citynull rate spikes 40+ points,RUN_HEALTHimmediately flagsoverallStatus: "degraded"withfingerprintDrift: "critical"on the affected source — catches partial silent degradation thatsuspicious_zeromisses - Regression guard — refuses to overwrite the previous run's snapshot if the new run returns 80%+ fewer records, preserving your change detection baseline
What states are supported? 44 states/territories have dedicated official license database scrapers. All 6 platform sources cover every US state. States with small programs (Iowa: 5 dispensaries, New Hampshire: ~6, North Dakota: ~10) are fully supported — every active licensed dispensary is captured.
What is chain detection?
The chain field is populated by pattern-matching on dispensary names to identify multi-state operators (MSOs): Curaleaf, Trulieve, Green Thumb (GTI), Verano, Acreage, Cannabist (Columbia Care), PharmaCann, Cresco Labs, MedMen, Dutchie, Planet 13, Cookies, Jars Cannabis, and 160+ others. Useful for filtering out chain locations when analyzing independent operators, or for tracking MSO expansion footprint.
Known limitations:
- Dutchie requires a residential proxy to bypass Cloudflare Bot Management — not in the default sources list, but fully supported. Configure
proxyConfigurationwithuseApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"]and add"dutchie"tosources - Colorado MED state source is intermittently 403 from some IP ranges — Weedmaps covers CO well as a fallback
- Coordinates are null for some state-source-only records (government databases publish addresses, not lat/lng). Enable
geocodeMissingCoordinates: trueto fill via OpenStreetMap Nominatim — uses a two-pass strategy (full address first, city+state fallback), caches results across runs so each address is geocoded only once, and marks each result withgeocodeSourceso you know which coordinates are precise vs approximate - Menu data requires a separate pass per dispensary and can significantly increase run time for large states
Support: Open an issue on the Issues tab in Apify Console. For custom data pipelines, enterprise licensing, or state coverage requests, contact the author directly.