# npm & PyPI Package Checker for AI Agents — ExactGround (`dropin-apis/exactground-api`) Actor

npm and PyPI package checker: confirms packages exist and that functions, classes and methods exist in the exact version you use, before an AI agent writes or installs code. MCP endpoint + REST. Pay per check.

- **URL**: https://apify.com/dropin-apis/exactground-api.md
- **Developed by:** [drop-in apis](https://apify.com/dropin-apis) (community)
- **Categories:** AI, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 symbol checkeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## npm & PyPI Package Checker for AI Agents — ExactGround

**ExactGround is an npm and PyPI package checker: before an AI coding agent writes or installs code, it confirms that the package exists and that the function, class or method it is about to call exists in the exact version in your lockfile.** It reads the published package itself (TypeScript `.d.ts` declarations, DefinitelyTyped, Python wheel source and `.pyi` stubs) without running any of its code, and answers over MCP or REST. You pay per check.

**At a glance:** pay per check, no subscription: **$0.002 per symbol**, **$0.0005 per package**, **$0.01 per diff** (a typical agent session of 5 package checks, 20 symbol checks and 2 diffs costs about $0.06; failed requests are not charged). Try it with no code: click **Start** with the prefilled input (batch mode), or connect your agent to the MCP endpoint below.

Last updated: 2026-10-03 · Free local install guard and source: https://github.com/alidaram99/exactground

**Why this exists:**

- A USENIX Security 2025 study (Spracklen et al.) had 16 models write **576,000 code samples**. Of the **2.23 million package recommendations** in them, **440,445 (19.7%) named packages that do not exist**, 205,474 unique names. In a separate test, the authors re-ran 500 prompts that had produced a hallucination 10 times each, and 43% of those hallucinated names came back in all 10 runs ([paper](https://www.usenix.org/conference/usenixsecurity25/presentation/spracklen)). Predictable names are what make "slopsquatting" (registering the invented name with malware) practical.
- The quieter failure is calling an API that is real, but not in *your* version. Two examples:

  - `useActionState` is in React 19 but not React 18.
  - `numpy.asfarray` was removed in NumPy 2.0.

  The code looks right, passes review, and breaks at runtime.

### What it checks

| Tool (MCP) / endpoint (REST) | Question it answers | Event and price |
|---|---|---|
| `check_symbols` · `POST /v1/symbols` | Does `symbol` exist in `package@version`? Returns `exists`, kind, signature, `@deprecated` notes and did-you-mean suggestions | `symbol-check` **$0.002** per symbol |
| `check_packages` · `POST /v1/packages` | Does the package exist? Was the pinned version published? Is it a look-alike of a popular package, brand new, rarely downloaded, deprecated, or an npm security placeholder? Verdict `ok` / `warn` / `block` | `package-check` **$0.0005** per package |
| `check_diff` · `POST /v1/diff` | For a `git diff` or `apply_patch`: every **added** JS/TS and Python import, whether its package exists, and whether each imported name and `alias.member` use exists in the version you pass | `diff-check` **$0.01** per diff |

`exists: null` means "cannot be known statically". Examples: a Python module that defines `__getattr__`, a compiled extension, or an npm package with no types at all. ExactGround never reports a guess as a fact.

### Connect an agent (MCP)

The server speaks MCP over Streamable HTTP at the Actor's Standby URL plus `/mcp`. Authenticate with your [Apify API token](https://console.apify.com/settings/integrations) as a Bearer token.

**Claude Code**

```sh
claude mcp add --transport http exactground https://dropin-apis--exactground-api.apify.actor/mcp \
  --header "Authorization: Bearer $APIFY_TOKEN"
```

**Cursor / VS Code / any MCP client (`mcp.json`)**

```json
{
  "mcpServers": {
    "exactground": {
      "url": "https://dropin-apis--exactground-api.apify.actor/mcp",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

**Codex** (`~/.codex/config.toml`)

```toml
[mcp_servers.exactground]
url = "https://dropin-apis--exactground-api.apify.actor/mcp"
bearer_token_env_var = "APIFY_TOKEN"
```

The server's instructions tell the agent when to call each tool:

- `check_packages` before installing;
- `check_symbols` with the lockfile version before using an API it is unsure of;
- `check_diff` before claiming the change is done.

Agents connected to the [Apify MCP server](https://mcp.apify.com) can also find this Actor with `search-actors` and run it in batch mode with `call-actor`.

### REST examples (real output)

```bash
curl -s https://dropin-apis--exactground-api.apify.actor/v1/symbols \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"ecosystem":"pypi","package":"numpy","version":"2.1.0","symbols":["numpy.linalg.norm","numpy.asfarray"]}'
```

```json
{
  "ecosystem": "pypi", "package": "numpy", "version": "2.1.0", "packageExists": true,
  "artifact": "numpy-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
  "results": [
    { "symbol": "numpy.linalg.norm", "exists": true, "kind": "function",
      "signature": "(x: ArrayLike, ord: None | float | L[\"fro\", \"nuc\"] = ..., axis: SupportsInt | SupportsIndex | tuple[int, ...] = ..., keepdims: bool = ...,) -> Any",
      "declaredIn": "numpy/linalg/_linalg.pyi" },
    { "symbol": "numpy.asfarray", "exists": false, "missingAt": "numpy.asfarray",
      "suggestions": ["array", "asarray", "NDArray", "ndarray", "recarray"] }
  ]
}
```

`react@18.2.0` with `["useState", "useActionState"]` returns `useState` with its signature (types read from `@types/react@18.3.31 (DefinitelyTyped)`) and `useActionState: exists: false`.

```bash
curl -s https://dropin-apis--exactground-api.apify.actor/v1/packages \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"packages":["react@18.2.0","pypi:requests","pypi:reqeusts"]}'
```

```json
{ "results": [
    { "ecosystem": "npm", "name": "react", "spec": "18.2.0", "verdict": "ok", "registry": "exists", "popularRank": 441, "reasons": [] },
    { "ecosystem": "pypi", "name": "requests", "verdict": "ok", "registry": "exists", "popularRank": 7, "reasons": [] },
    { "ecosystem": "pypi", "name": "reqeusts", "verdict": "block", "registry": "missing",
      "reasons": ["\"reqeusts\" does not exist on PyPI", "did you mean \"requests\"? (it is 1 edit away)"] } ],
  "blocked": 1, "warned": 0 }
```

In one `check_diff` test, a single diff added imports from React, axios, zod, NumPy, requests and an invented package. ExactGround reported:

- the invented package;
- `useActionState` against React 18.2.0;
- `axios.fetchJson`, `z.objekt` and `requests.fetch`;
- `numpy.asfarray` against NumPy 2.1.0.

That took 4.4 seconds with cold caches.

### How it works, and its limits

- **npm:**
  - The package tarball for the resolved version (exact, range or dist-tag) is read in memory.
  - Its declaration entry is found through `exports`/`types`/`typesVersions`, and the TypeScript compiler resolves the export path.
  - Packages without types fall back to the matching-major `@types/*` package; the response says so in `typesSource`.
- **PyPI:**
  - The pure-Python wheel (or the platform wheel, or the sdist) is read in memory and parsed statically.
  - The parser understands re-exports, `from x import *` with `__all__`, class members and base classes, and `.pyi` stubs (preferred when present).
  - Dynamic attributes and compiled modules return `null`, not `false`.
- **Not executed, ever:** no install scripts, no imports. Archives are capped at 60 MB.
- Private registries are not reachable. The free local guard lets you allow-list private names and scopes.

### Pricing

Pay per event, no subscription:

| Event | Price |
|---|---|
| `symbol-check` | **$0.002** per symbol ($2 per 1,000) |
| `package-check` | **$0.0005** per package ($0.50 per 1,000) |
| `diff-check` | **$0.01** per diff |

- A typical agent session (5 package checks, 20 symbol checks, 2 diffs) costs about $0.06.
- Requests that fail (invalid input, registry unreachable) are not charged.
- If you set a maximum total charge, the API stops with HTTP 402 when it is reached.
- Standby requests have no per-run start fee beyond Apify's standard Actor start event.

### Batch mode (no code)

Run the Actor normally with `packages`, `symbolChecks` and/or `diff` in the input to get one dataset row per check. This is also what Apify's `call-actor` uses.

### FAQ

#### How do I stop Claude Code or Cursor from installing hallucinated packages?

Install the free ExactGround hook (https://github.com/alidaram99/exactground). It blocks `npm/pnpm/yarn/bun/npx` and `pip/uv/poetry/pdm/pipx` installs of names that do not exist, before they run. This Actor adds the version-exact API checks on top.

#### What is slopsquatting?

Attackers publish malware under package names that AI models tend to invent. When an agent runs `pip install <invented-name>`, it installs the attacker's code.

#### How is this different from Context7?

Context7 puts documentation text into the prompt. ExactGround answers a yes/no question from the published artifact ("does `z.objekt` exist in zod 4.6.5?") and returns a verdict (`ok`, `warn` or `block`); the free local ExactGround hook is what refuses an install. They work well together.

#### How is this different from Socket?

Socket scores the security of packages that exist and blocks known malware at install. ExactGround focuses on names and APIs that do **not** exist in the version you use. Its local guard runs inside the agent's hooks.

#### Which languages are supported?

npm (JavaScript and TypeScript) and PyPI (Python).

#### Is my code sent anywhere?

Only what you send in a request: package names and symbol paths, or the diff for `check_diff`. Nothing is stored after the response.

#### Is this affiliated with npm, PyPI, Anthropic, OpenAI or Cursor?

No. It is an independent tool.

# Actor input Schema

## `packages` (type: `array`):

Package names with optional versions. npm by default; prefix PyPI names with pypi:, e.g. react@18.2.0, pypi:requests==2.32.3, pypi:reqeusts. One 'package-check' event per package.

## `symbolChecks` (type: `array`):

List of {"ecosystem": "npm"|"pypi", "package", "version", "symbols": \[...]}. One 'symbol-check' event per symbol.

## `diff` (type: `string`):

Optional unified diff (git diff) or apply_patch text. Added JS/TS/Python imports are checked. One 'diff-check' event.

## `versions` (type: `object`):

Optional package -> version map (from your lockfile) used by the diff check, e.g. {"react": "18.2.0"}.

## `ecosystem` (type: `string`):

Ecosystem used for package names without an npm: or pypi: prefix.

## Actor input object example

```json
{
  "packages": [
    "react@18.2.0",
    "pypi:requests",
    "pypi:reqeusts"
  ],
  "symbolChecks": [
    {
      "ecosystem": "pypi",
      "package": "numpy",
      "version": "2.1.0",
      "symbols": [
        "numpy.linalg.norm",
        "numpy.asfarray"
      ]
    }
  ],
  "ecosystem": "npm"
}
```

# Actor output Schema

## `results` (type: `string`):

All items in the run's default dataset.

# 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 = {
    "packages": [
        "react@18.2.0",
        "pypi:requests",
        "pypi:reqeusts"
    ],
    "symbolChecks": [
        {
            "ecosystem": "pypi",
            "package": "numpy",
            "version": "2.1.0",
            "symbols": [
                "numpy.linalg.norm",
                "numpy.asfarray"
            ]
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dropin-apis/exactground-api").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 = {
    "packages": [
        "react@18.2.0",
        "pypi:requests",
        "pypi:reqeusts",
    ],
    "symbolChecks": [{
            "ecosystem": "pypi",
            "package": "numpy",
            "version": "2.1.0",
            "symbols": [
                "numpy.linalg.norm",
                "numpy.asfarray",
            ],
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("dropin-apis/exactground-api").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 '{
  "packages": [
    "react@18.2.0",
    "pypi:requests",
    "pypi:reqeusts"
  ],
  "symbolChecks": [
    {
      "ecosystem": "pypi",
      "package": "numpy",
      "version": "2.1.0",
      "symbols": [
        "numpy.linalg.norm",
        "numpy.asfarray"
      ]
    }
  ]
}' |
apify call dropin-apis/exactground-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dropin-apis/exactground-api"
        }
    }
}
```

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/AUionSCmILcGS7B7A/builds/WbQVzcwtZNDSVAKQ1/openapi.json
