# Cannabis License Verifier — 10 States, No Login, $20/1k (`chimerical_quicklime/cannabis-license-verifier`) Actor

Verify a list of cannabis licensees by license number or business name against official regulator data (CA, NY, MA, WA, CO, NV, MD, ME, CT, FL) and get normalised status, expiry, type and address back per query. No login. MCP-ready for AI agents. $20 per 1,000 verifications.

- **URL**: https://apify.com/chimerical\_quicklime/cannabis-license-verifier.md
- **Developed by:** [Khrystyna Skotte](https://apify.com/chimerical_quicklime) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 verifications

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

## Cannabis License Verifier — batch check license status in 12 US states

Give it a list of cannabis licensees (license numbers and/or business names) and get back, for each one,
whether it exists in the **official state regulator data**, its **normalised status**
(`active`, `inactive`, `expired`, `suspended`, `revoked`, `pending`), the raw regulator status, license type,
issue/expiry dates and premises address. Built for compliance, KYC/onboarding, banking, insurance and
supply-chain teams that need to re-verify a vendor or customer list — not download a whole state.

No browser, no proxies, no login: every source is a public regulator API, open-data feed or published
spreadsheet, fetched live on each run.

### Supported states

| State | Source (fetched live) | Coverage | Status detail | Freshness |
|---|---|---|---|---|
| **CA** | Dept. of Cannabis Control search API (`search.cannabis.ca.gov`) | All ~21k licenses incl. surrendered/expired/revoked | Full raw status + status date | Refreshed daily by DCC |
| **NY** | Office of Cannabis Management on data.ny.gov | ~3k current OCM licenses | Active / Inactive / In-Process / Under review | Weekly |
| **MA** | Cannabis Control Commission open-data extract | ~950 approved licenses | Active / Payment Pending + suspension/revocation dates | Weekly |
| **WA** | LCB retailer spreadsheet + producer/processor renewals on data.wa.gov | ~1.4k retailers, producers, processors | ACTIVE / CLOSED / EXPIRED / TITLE CERTIFICATE | Weekly (LCB) |
| **CO** | MED licensed-facility lists (all 9 facility types) | ~600 current facilities | Current licensees only (`Licensed`) + expiry date | Monthly |
| **NV** | Cannabis Compliance Board establishment list | ~640 active, conditional, surrendered/revoked | Active / Conditional / Surrendered / Revoked | Monthly |
| **MD** | Maryland Cannabis Administration data-dashboard workbook | ~210 growers, processors, dispensaries, ancillary | Current licensees only (`Licensed`) + start/expiry | Monthly |
| **ME** | Office of Cannabis Policy adult-use workbook + medical establishments & caregivers | ~1.9k | Active / Conditional / Pending | Monthly |
| **CT** | Dept. of Consumer Protection establishments on data.ct.gov | ~115 | Current licensees only (`Licensed`) | Weekly |
| **FL** | OMMU licensed MMTC table | ~50 MMTC license holders | Initial Licensure / Dispensing Authorization | As published |
| **MI** | Cannabis Regulatory Agency Accela portal, full "Download results" CSV export (adult-use + medical facility modules) | ~10k records incl. void/closed/suspended/revoked | Active / Active - Late Renewal / License Void / Closed / Suspended / Revoked + disciplinary-action flag | Live (exported from the CRA portal on every run) |
| **OH** | Division of Cannabis Control license lists (dispensary, cultivator, processor, testing-lab workbooks) | ~320 current dual-use licenses | Current licensees only (`Licensed`) + issue/expiry; legacy MMCP numbers also match | As published (list dated in the file, updated roughly weekly) |

States where the list contains only current licensees (CO, MD, CT, OH) return `status: "active"` with a
`statusRaw` that says so; a license that has lapsed simply won't match there (`matched: false`).

Not supported (and why): **OR** (OLCC moved its license table
into a Socrata story with no public tabular feed as of Sep 2026), **MO** (Tableau only), **AZ** (regulator PDF last
updated 2021), **IL** (PDF updated Jan 2026, layout too unstable for verification), **NM** (feed has no license
numbers or status), **MN / VT / NJ** (browser-only portals), **RI** (blocks cloud requests).

Michigan rows carry the CRA record number (`AU-R-…`, `GR-C-…`, `PC-…`), the Accela status verbatim and a
`; disciplinary action on file` suffix in `statusRaw` when the CRA has public disciplinary documents for that
license; the export has no issue date. Ohio rows use the dual-use license number (`CCD…`, `CCC…`, `CCP…`, `CCL…`); a
query with the legacy medical-program number (`MMCP…`) still matches the same record.

### Input

```json
{
  "queries": [
    { "state": "CA", "licenseNumber": "C10-0000651-LIC" },
    { "state": "ME", "businessName": "The Cannabis Cabin LLC" },
    { "state": "NY", "businessName": "7 Leaf Clover" },
    { "state": "WA", "licenseNumber": "414884" },
    { "state": "MI", "licenseNumber": "AU-R-001610" },
    { "state": "OH", "businessName": "Ancient Roots LLC" },
    { "businessName": "Curaleaf" }
  ],
  "states": [],
  "matchMode": "fuzzy",
  "maxResultsPerQuery": 3,
  "maxItems": 10
}
```

| Field | Default | Notes |
|---|---|---|
| `queries` | 3 sample lookups | Each object: `state` (2-letter), `licenseNumber` and/or `businessName`. Omit `state` to search every supported state. If both are given the license number is used. |
| `states` | `[]` (all) | Only process queries for these states. |
| `matchMode` | `fuzzy` | `fuzzy` ignores case, punctuation and entity suffixes (LLC, Inc, Corp…) and tolerates small typos; `exact` requires the normalised name / license number to match exactly. |
| `maxResultsPerQuery` | `3` | Cap on candidate rows per query (multi-location operators often hold several licenses under one name). |
| `maxItems` | `10` | Cap on total result rows. |

License numbers are matched on alphanumerics only, so `c10-0000651`, `C10 0000651` and `C10-0000651-LIC`
all resolve to the same California license (confidence 0.95 when a state suffix is missing, 1.0 on exact).

### Output

One row per match, or **one row with `matched: false`** when nothing in the state's data matches the query.
An unmatched result is a valid verification outcome (the licensee is *not* on the regulator's list) and is charged
like a match. Rows whose `note` reports an unsupported state or an unreachable source are not charged.

```json
{
  "query": { "state": "CA", "licenseNumber": "C10-0000651-LIC" },
  "state": "CA",
  "matched": true,
  "matchConfidence": 1,
  "matchedOn": "licenseNumber",
  "licenseNumber": "C10-0000651-LIC",
  "businessName": "Psgc, Inc.",
  "dbaName": "Atomic Budz",
  "licenseType": "Commercial - Retailer",
  "status": "active",
  "statusRaw": "Active",
  "issueDate": "2019-11-08",
  "expirationDate": "2026-11-07",
  "address": "36063 Cathedral Canyon Dr",
  "city": "Cathedral CTY",
  "zip": "92234",
  "county": "Riverside",
  "sourceUrl": "https://search.cannabis.ca.gov/",
  "note": null,
  "checkedAt": "2026-09-28T18:58:01.354Z"
}
```

`matchConfidence` is 0–1: `1` = exact normalised match, `0.95` = license number without state suffix,
`0.6–0.95` = fuzzy name match (Dice similarity / whole-word containment). `matchedOn` tells you whether the hit
was on `licenseNumber`, `businessName` or `dbaName`.

#### Status normalisation

| `status` | Raw regulator values mapped here |
|---|---|
| `active` | Active, ACTIVE (ISSUED), Active (renewal on file), Open, Licensed, Approved, Limited Operations, Initial Licensure, Dispensing Authorization |
| `pending` | Pending, Conditional, Pending Conditional, In-Process, Payment Pending, PCA / Amendment Under Review, Provisional |
| `inactive` | Inactive, Surrendered, Canceled, CLOSED (PERMANENT/TEMPORARY), Not Operating, Former Title Certificate, Withdrawn, Denied |
| `expired` | Expired |
| `suspended` | Suspended |
| `revoked` | Revoked |
| `unknown` | anything else — `statusRaw` always keeps the original text |

### Pricing

Pay-per-event: a small start fee plus **one event per verification row** (matched or unmatched). A 500-line vendor
list checked across the supported states costs a few dollars — versus a per-state data dump you would still have to
join yourself.

### Tips

- Sources are downloaded once per run and cached in memory, so batches of hundreds of queries are fast; California
  is queried per lookup through the DCC search API and needs no bulk download.
- Use `matchMode: "exact"` for automated re-checks of a list you already resolved once (store the returned
  `licenseNumber` and verify by number from then on).
- Query without `state` to find out where an operator is licensed — each state hit comes back as its own row.

# Actor input Schema

## `queries` (type: `array`):

One object per licensee to verify. Each needs a `state` (2-letter code) plus a `licenseNumber` and/or a `businessName`. Omit `state` to search every supported state. Supported: CA, NY, MA, WA, CO, NV, MD, ME, CT, FL, MI, OH.

## `states` (type: `array`):

Optional. Only process queries for these states (2-letter codes). Empty = all supported states.

## `matchMode` (type: `string`):

`fuzzy` tolerates punctuation, entity suffixes (LLC, Inc) and small spelling differences; `exact` requires the normalised name or license number to match exactly.

## `maxResultsPerQuery` (type: `integer`):

How many candidate records to return for one query when several licenses match (e.g. a multi-location operator).

## `maxItems` (type: `integer`):

Maximum number of result rows to write in total.

## Actor input object example

```json
{
  "queries": [
    {
      "state": "CA",
      "licenseNumber": "C10-0000651-LIC"
    },
    {
      "state": "ME",
      "businessName": "The Cannabis Cabin LLC"
    },
    {
      "state": "NY",
      "businessName": "7 Leaf Clover"
    }
  ],
  "states": [],
  "matchMode": "fuzzy",
  "maxResultsPerQuery": 3,
  "maxItems": 10
}
```

# Actor output Schema

## `records` (type: `string`):

Dataset of verification results, one row per query match (JSON).

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("chimerical_quicklime/cannabis-license-verifier").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("chimerical_quicklime/cannabis-license-verifier").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 '{}' |
apify call chimerical_quicklime/cannabis-license-verifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,chimerical_quicklime/cannabis-license-verifier"
        }
    }
}
```

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/D7Q4ifMxai5shfGFT/builds/ud7Qk7T6daIrHTAYQ/openapi.json
