# NSQ Watch - CDSCO NSQ Drug Alerts & Batch Matcher (`leons0016/cdsco-nsq-drug-alerts`) Actor

CDSCO NSQ, state NSQ and spurious drug lists as clean data. Match your pharmacy stock against flagged batches every day.

- **URL**: https://apify.com/leons0016/cdsco-nsq-drug-alerts.md
- **Developed by:** [Leon s](https://apify.com/leons0016) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $40.00 / 1,000 stock match founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## NSQ Watch - CDSCO NSQ drug alerts and stock batch matcher

> **NSQ Watch is currently paused by its publisher while permission for the source data is being clarified.** Every run ends immediately with one result row whose status is `paused`. No data is fetched from CDSCO or any other website, your input is not read, nothing is charged, and the run succeeds. The description below explains what the Actor does when it is active. Our privacy policy and EULA remain in force.

**NSQ Watch turns the "Not of Standard Quality" (NSQ), state NSQ and spurious drug lists of India's drug regulator CDSCO into clean rows, and checks your pharmacy or distributor stock list against the flagged batches every day.** Set it up once as a scheduled Task: when CDSCO publishes new rows, you get a quarantine list of the stock lines whose batch appears in them.

It is built for pharmacy chains and e-pharmacies, hospital pharmacy and procurement teams, distributors and stockists, pharma quality and regulatory teams, health-tech apps, and researchers or journalists who want the lists as data.

> NSQ Watch reports what the official CDSCO publication lists. It is a screening signal, not medical or legal advice. A flagged batch means a tested sample failed; always verify with the official publication and your supplier before you act.
>
> **Independent tool, not affiliated with or endorsed by CDSCO.** The CDSCO Copyright Policy (https://cdsco.gov.in/opencms/opencms//en/Copyright-Policy/) states: "The contents of this website may not be reproduced partially or fully, without due permission from Central Drugs Standard Control Organization, Govt. of India. If referred to as a part of another publication, the source must be appropriately acknowledged. The contents of this website can not be used in any misleading or objectionable context." The publisher has not asked CDSCO for permission. NSQ Watch fetches the public data inside your own Apify account and links every row to its official source; if you republish the rows or build a product on them, you are responsible for asking CDSCO for permission, acknowledging CDSCO as the source and not using the data in a misleading context.

### Why use it

- **Machine-readable NSQ lists.** One row per flagged batch: product (with brand when printed), batch number, manufacturing and expiry month, manufacturer and address, the test result (reason), the testing laboratory or state, alert month, and a link to the official source.
- **Stock matching with typo tolerance.** Your stock lines are compared with the flagged batches by batch number, product name and manufacturer. Batch numbers are compared with spaces and punctuation ignored, and common letter/digit confusions (O and 0, I and 1, S and 5, B and 8, Z and 2) are tolerated. Every match shows its score and the reasons.
- **Quarantine CSV.** Matches are written with your own columns (sku, location, quantity) first, so the file can go straight into your system.
- **Only new things are billed and delivered.** A row or match you already received is never delivered or charged again. A run without new alerts costs only the small daily check.
- **No server, no proxy bill for you.** Plain HTTP and PDF reading; your stock list stays in your own Apify account.

### Sources that are monitored

| Source | What it provides |
|---|---|
| CDSCO NSQ portal, https://cdscoonline.gov.in/CDSCO/viewPublicNSQDrug (the public data the page displays) | NSQ drugs from CDSCO laboratories and state laboratories, and spurious drugs, by reporting month |
| CDSCO Alerts page, https://cdsco.gov.in/opencms/opencms/en/Alerts/ | PDF lists. Used for months the portal does not cover, and when the portal cannot be reached |

Nothing else is monitored: no state drugs-control websites, no medical device alert parsing, no other countries. NSQ Watch is not affiliated with or endorsed by CDSCO. Requests follow robots.txt and are limited to one per second per host; no login, no CAPTCHA and no form is ever used.

### How to use it

1. Click **Try for free** and keep the prefilled input. The first run is short and shows the format of the results (a sample stock line will normally not match anything).
2. Put your stock in the input: a few lines in **Stock items (quick entry)**, or a link in **Stock list CSV (URL)** to a CSV exported from your billing software (Marg, GoFrugal or similar). The CSV needs a header row; common header names such as Item Name, Product Name, Batch No, Mfg, Company, Expiry and Qty are recognised, others can be mapped in **CSV column names**.
3. Choose **Check alerts from the last** (default 6 months) and press **Save as a new task**.
4. Open the task, add a **Schedule**: for example `0 11 * * *` with timezone `Asia/Kolkata` (every day 11:00 IST), and enable it. Schedules start disabled, and an Actor must have been run once before it can be scheduled.
5. In the task's **Integrations** tab add Slack, email (Gmail) or a webhook on **Run succeeded** (see Notifications), and optionally one on **Run failed**.

### Input

| Field | What it does |
|---|---|
| `mode` | `match` (default) checks your stock; `rows` delivers every new alert row as data |
| `inventory` | Quick entry, up to 500 lines: `PRODUCT \| BATCH: x \| Mfr: y` (batch and Mfr optional; `Exp:`, `SKU:`, `Loc:`, `Qty:` also accepted) |
| `inventoryCsvUrl` | https link to a CSV, up to 50,000 rows and 20 MB. Google Drive share links and Dropbox links are converted to direct downloads |
| `columnMap` | Your CSV header for each field: product, batch, manufacturer, expiry, sku, location, quantity |
| `lookbackMonths` | 0 to 24, default 6. Used on the first run and whenever stock lines change |
| `matchLevel` | `batch` (default) or `product` (also reports same product and maker with another batch, as warnings) |
| `minScore` | 60 to 100, default 80 |
| `alertTypes` | `NSQ_CENTRAL`, `NSQ_STATE`, `SPURIOUS`, `RECALL_ALERT` (all by default) |
| `deliverAlertRows` | Also deliver every new alert row (charged per row). Always on in rows mode |
| `maxResultsPerRun` | 1 to 100,000, default 500 |
| `webhookUrl`, `slackWebhookUrl` | Optional https endpoints notified at the end of the run |
| `useSharedCache`, `includeLegacyPdfs`, `stateKey` | Advanced. `useSharedCache` is off by default and the Actor reads the official CDSCO sources directly |

Sample input:

```json
{
  "mode": "match",
  "inventory": ["Paracetamol Tablets IP 500 mg | BATCH: XYZ123 | Mfr: Example Pharma"],
  "lookbackMonths": 6,
  "matchLevel": "batch",
  "minScore": 80
}
```

Your stock list is read while the run is in progress, kept in memory, never written to the log, and written only to your own results (the matches, `REPORT`, `QUARANTINE.csv` and `OUTPUT`). Use a link that cannot be guessed, or a public record of an Apify key-value store.

### Output

The default dataset contains these record types (the `recordType` field):

- `inventory-match`: one per match, with your stock line, the flagged alert row, `matchLevel` (`batch` or `product`), `score` and `reasons`.
- `alert-row`: one per new alert row (rows mode, or **Also deliver new alert rows**).
- `alert-document`: one per new CDSCO list found in the run and how it was read (not charged).
- `run-summary`: one per run, always the last item (not charged): status, counts, charged events, sources with error codes, warnings.

Dataset views: **Stock matches**, **Alert rows**, **Alert documents**, **Run summary**. A view shows the columns of its own record type and empty cells for the others; filter on `recordType` through the API when you need only one type.

Key-value store records: `OUTPUT` (JSON summary with the first 200 matches), `REPORT` (a standalone HTML page with a red banner when a batch-level match exists, the matches, product-level warnings and the new documents), `QUARANTINE.csv`.

Illustrative sample of a match (invented names):

```json
{
  "recordType": "inventory-match",
  "matchLevel": "batch",
  "score": 100,
  "reasons": ["batch-exact", "product-strong", "mfr-strong"],
  "stock": {"product": "Veloxapril Tablets IP 10 mg", "batch": "ZN26A101", "manufacturer": "Zentora Lifesciences Pvt. Ltd.", "location": "Main store", "quantity": "240"},
  "alert": {
    "alertType": "NSQ_CENTRAL", "alertMonth": "2026-09",
    "product": "Veloxapril Tablets IP 10 mg (Veloxa-10 Tablets)", "batch": "ZN26A101",
    "mfgDate": "2026-01", "expDate": "2027-12", "manufacturer": "Zentora Lifesciences Pvt. Ltd.",
    "reason": "The sample does not conform to IP in respect of Assay", "reportedBy": "CDL, Kolkata",
    "pdfUrl": "https://cdscoonline.gov.in/CDSCO/viewPublicNSQDrug"
  },
  "detectedAt": "2026-10-24T05:40:12Z"
}
```

#### How matching works

A batch-level match needs a batch hit and a total score of at least `minScore`.

| Signal | Points |
|---|---|
| Batch identical (spaces, case and punctuation ignored) | 60 |
| Batch identical after folding O/0, I/L/1, S/5, B/8, Z/2 | 55 |
| Batch one edit apart (both at least 6 characters) | 40 |
| Product name strong (similarity 90 or more) / partial (75 or more) | 25 / 15 |
| Strength differs (for example 10 mg and 20 mg) | -15 |
| Manufacturer strong (90 or more) / partial (75 or more) / different (below 50) | +15 / +8 / -10 |
| Expiry month equal | +5 |

Examples: identical batch with a strong product name scores 85, with the same manufacturer 100. An identical batch on a different product scores 60 or less (50 when the makers also differ) and is not reported at the default minimum score, because batch numbers repeat across manufacturers. A one-edit batch is dropped when both sides have an expiry month and the months differ (a neighbouring batch, not the flagged one). A minimum score of 86 or more accepts only identical or letter/digit-confusion batch matches.

Product names are compared as sets of words without dosage-form words and strengths, so word order, `&` against `and`, and the brand in brackets do not matter. A stock name that is only one ingredient of a printed combination product is capped at "partial" so that single-ingredient stock is not matched to a different combination product from the same batch number alone.

With `matchLevel = product`, items without a batch, and rows whose batch does not match, can still be reported as warnings when the product and the manufacturer agree (score 35 to 79). They are always labelled `product`.

### Pricing

NSQ Watch is pay-per-event: you pay only for what a run does, with no subscription. The table shows the prices for the Apify Starter plan; Scale/Business plans get 10% / 20% off.

| Event | When it is charged | Price (Starter) |
|---|---|---|
| Actor start | Once per run, by Apify | Apify's standard start event |
| `watch-check` (Daily alert check) | Once per run, after your input is accepted | $0.03 |
| `inventory-item-checked` (Stock item checked) | Per stock line checked, only in runs that have new alert rows or changed stock lines | $0.0005 |
| `inventory-match` (Stock match found) | Per new match delivered | $0.05 |
| `alert-row` (Alert row delivered) | Per new alert row delivered | $0.005 |

Monthly cost = runs x check price + (runs with new alert rows) x stock lines x item price + matches x match price + delivered rows x row price. Runs per month: daily 30, weekdays 22, weekly 4.33.

| Example | Stock lines | Runs with new rows | Matches | Rows delivered | Monthly (Starter) |
|---|---|---|---|---|---|
| One pharmacy, daily | 1,500 | 4 | 1 | 0 | 30 x $0.03 + 6,000 x $0.0005 + 1 x $0.05 = $3.95 |
| Chain, daily | 20,000 | 4 | 6 | 0 | $0.90 + 80,000 x $0.0005 + 6 x $0.05 = $41.20 |
| Data user, rows only | 0 | - | 0 | 400 | $0.90 + 400 x $0.005 = $2.90 |

The first run additionally checks every stock line once against the lookback window (1,500 lines cost $0.75). How many runs have new rows depends on how CDSCO publishes: the old Alerts page released each monthly list on a single day, while the NSQ portal shows a "Pending States" button for the current month, so a month can receive rows over several days. For a large stock list a weekly schedule limits the number of checks.

**Set "Maximum cost per run" to a value that covers the checks.** If your limit cannot pay for all stock-line checks of a run, NSQ Watch checks nothing and says so in the summary (`stockItemChecksNeeded`) instead of checking half. If it runs out while delivering matches or rows, it stops exactly there, saves its progress and delivers the rest in the next run; nothing is delivered or charged twice.

**Free plan (trial):** stock up to 25 lines, at most 5 matches and 10 alert rows per run, and a 1-month lookback. The run still succeeds, and the summary names the cap that applied (`freePlanCaps`).

### Notifications

Add integrations in the task's **Integrations** tab on the event **Run succeeded**.

- **Slack** message template:
  ```
  NSQ Watch run {{resource.status}}. Results: https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}
  Run: https://console.apify.com/actors/runs/{{resource.id}}
  ```
- **Email (Gmail integration):** attach the `REPORT` or `QUARANTINE.csv` record of the run's key-value store.
- **Webhook:** or set `webhookUrl` in the input; the `OUTPUT` JSON is posted when the run ends. `slackWebhookUrl` posts a short message with up to 10 batch-level matches.
- Add the same on **Run failed** so that you notice a failed run.

### Limitations and disclaimers

- Lists are sample-specific: a flagged batch means a tested sample failed. The Actor reports what CDSCO publishes and does not interpret it. It is not medical or legal advice. Verify with the official publication and your supplier before action.
- Stock lines are compared by text. Different brand names for the same product, or a stock name that shares no word with the printed name, can be missed; wrong matches are possible when names are very short. Check the reasons and score of every match.
- Rows read from a PDF without table lines, or by OCR, are marked `lowConfidence`. OCR for scanned pages is an optional fallback; a scanned page that cannot be read is reported as a warning, never skipped silently. Layout changes at the source appear as warnings in the run summary.
- Dates are month precision, as printed. State names are inferred from the laboratory and are empty for CDSCO central laboratories.
- The Actor depends on the availability and format of the CDSCO websites. If a source is down, the run still succeeds, the summary lists the source with an error code, and the missing part follows in a later run.
- Data source: CDSCO (Central Drugs Standard Control Organization). Every row links to the official source; please acknowledge the source when you republish the data. **Copyright:** the CDSCO Copyright Policy says its website contents "may not be reproduced partially or fully, without due permission" (quoted in full at the top). This Actor is not affiliated with or endorsed by CDSCO, does not republish CDSCO data itself (the shared cache is switched off), and does not use CDSCO material in a misleading context. Permission for any republication is your responsibility.
- **Disclaimer:** NSQ Watch is an automated screening aid. It can miss batches or match wrongly. It is not medical, pharmaceutical or legal advice and does not replace checking the official CDSCO publication and your supplier.

### FAQ

**Which lists are covered?** CDSCO NSQ lists (central and state laboratories) and spurious drug lists from the CDSCO NSQ portal, and the PDF lists on the CDSCO Alerts page for months the portal does not cover.

**Do I need a stock list?** No. Set the mode to `rows` and every new alert row is delivered as data.

**Does it work with brand names?** It compares your product name with the printed name and with a brand or generic name in brackets, ignoring word order and dosage-form words. A name with no word in common with the printed name is not matched.

**Why do I see "product" matches?** They appear with `matchLevel = product` and mean the same product and maker with another batch. They are warnings, never counted as batch-level matches.

**Is a rerun charged again?** No. A run with the same input and no new alerts charges only the daily check. Rows and matches already delivered are remembered in a store named `leons-nsqwatch-state` in your account; delete it to start from scratch.

**What if I change my stock list?** Only new or changed lines are checked against the lookback window, and only those are charged for it. Editing quantities does not make a line new.

**Is my stock list stored by the publisher?** No. It stays in your account and the run. The privacy policy is at https://raw.githubusercontent.com/leons0016/ADApps/main/privacy/P4-NSQWatch.txt

**Why was nothing checked and the status is SPENDING_LIMIT?** Your maximum cost per run was too low for the stock-line checks; raise it (see Pricing).

**Can I use it through the API?** Yes: run the Actor `leons0016/cdsco-nsq-drug-alerts` with the same JSON input and read the dataset.

### Changelog and support

See the Changelog in the Information tab. Questions and bug reports: use the Issues tab of this Actor, or write to atulhritik@hotmail.com. Please do not include confidential stock data in an issue.

Third-party licences and attributions: see `docs/THIRD_PARTY_NOTICES.txt` in the Actor source.

# Changelog

This Actor's version history is a separate document: https://apify.com/leons0016/cdsco-nsq-drug-alerts/changelog.md

# Actor input Schema

## `mode` (type: `string`):

Match checks your stock list against flagged batches. Rows delivers every new alert row as data, for example rows.

## `inventory` (type: `array`):

One item per line: PRODUCT | BATCH: x | Mfr: y. Batch and Mfr are optional, for example Paracetamol Tablets IP 500 mg | BATCH: XYZ123 | Mfr: Example Pharma. For many items use the CSV link below.

## `inventoryCsvUrl` (type: `string`):

Link to a CSV file with a header row, for example https://example.org/stock.csv. Max 50,000 rows and 20 MB. Use a link nobody can guess, or a public record of an Apify key-value store.

## `columnMap` (type: `object`):

Map our field names to your CSV headers, for example {"product": "Item Name", "batch": "Batch No"}. Only product is required; common header names are recognised without this.

## `lookbackMonths` (type: `integer`):

On the first run, and whenever stock items change, your items are checked against alerts of this many past months, for example 6.

## `matchLevel` (type: `string`):

Batch: the batch number must match. Product: also reports same product and maker with another batch, as a warning to check.

## `minScore` (type: `integer`):

Batch-level matches below this score are not reported, for example 80. Lower finds more, with more false alarms. 86 or more accepts only exact or letter/digit-confusion batch matches.

## `alertTypes` (type: `array`):

Which lists to use, for example NSQ_STATE and SPURIOUS only.

## `deliverAlertRows` (type: `boolean`):

Adds every new alert row to the results besides your matches. Charged per row. Always on in rows mode.

## `maxResultsPerRun` (type: `integer`):

Largest number of matches and alert rows delivered in one run, for example 500. Protects your budget; the rest follows in the next run.

## `webhookUrl` (type: `string`):

Optional. The run summary and matches are sent here as JSON when the run ends, for example https://example.org/hook.

## `slackWebhookUrl` (type: `string`):

Optional. A Slack incoming webhook; a short message with up to 10 matches is posted when the run ends.

## `useSharedCache` (type: `boolean`):

Off by default. Reads alert rows from a shared parsed-alert cache when one is configured and fresh; otherwise the official sources are read directly.

## `includeLegacyPdfs` (type: `boolean`):

Reads the PDF lists of the CDSCO Alerts page for months the CDSCO NSQ portal does not cover, and when the portal is down.

## `stateKey` (type: `string`):

Optional name that ties runs together. Runs with the same key share what was already delivered, for example my-pharmacy. Saved Tasks use their own id.

## `indexerMode` (type: `boolean`):

Publisher only.

## Actor input object example

```json
{
  "mode": "match",
  "inventory": [
    "Paracetamol Tablets IP 500 mg | BATCH: XYZ123 | Mfr: Example Pharma"
  ],
  "columnMap": {
    "product": "product",
    "batch": "batch",
    "manufacturer": "manufacturer",
    "expiry": "expiry",
    "sku": "sku",
    "location": "location",
    "quantity": "quantity"
  },
  "lookbackMonths": 1,
  "matchLevel": "batch",
  "minScore": 80,
  "alertTypes": [
    "NSQ_CENTRAL",
    "NSQ_STATE",
    "SPURIOUS",
    "RECALL_ALERT"
  ],
  "deliverAlertRows": false,
  "maxResultsPerRun": 500,
  "useSharedCache": false,
  "includeLegacyPdfs": true,
  "indexerMode": false
}
```

# Actor output Schema

## `matches` (type: `string`):

Your stock lines that match flagged batches (default view of the dataset).

## `alertRows` (type: `string`):

New alert rows, when delivered.

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

What was checked, delivered and charged in this run.

## `report` (type: `string`):

Standalone HTML report.

## `quarantine` (type: `string`):

Matches with your own columns first, for import into your system.

# 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 = {
    "inventory": [
        "Paracetamol Tablets IP 500 mg | BATCH: XYZ123 | Mfr: Example Pharma"
    ],
    "lookbackMonths": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("leons0016/cdsco-nsq-drug-alerts").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 = {
    "inventory": ["Paracetamol Tablets IP 500 mg | BATCH: XYZ123 | Mfr: Example Pharma"],
    "lookbackMonths": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("leons0016/cdsco-nsq-drug-alerts").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 '{
  "inventory": [
    "Paracetamol Tablets IP 500 mg | BATCH: XYZ123 | Mfr: Example Pharma"
  ],
  "lookbackMonths": 1
}' |
apify call leons0016/cdsco-nsq-drug-alerts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,leons0016/cdsco-nsq-drug-alerts"
        }
    }
}
```

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/shnghQ0bTx7SqDkAB/builds/W6RTCmEK6w28CYxE8/openapi.json
