# How to check a package's licence before installing it, step by step with curl

Last updated: 2026-10-13

To read the licence of a package an agent is about to install, send up to 10 names to `POST /v1/check/packages` (US$0.005 per batch). AttestPage looks each name up in its registry (npm, PyPI or crates.io) and returns the `license` field the registry holds, with `exists`, the latest version, the links and any OSV.dev advisories, under one signed receipt.

This guide covers the `license` field and what to decide from it. For the whole call and the other fields, see [How to check packages before installing them](/guides/check-packages-before-installing). Commands use `curl` and `jq` against `https://vehcdj664efetfrsolne5umanq.srv.us`, then the same call as an MCP tool.

## What the licence field is

- **The registry's own text.** `license` is copied from the registry as the publisher wrote it. AttestPage does not read the package's files or its LICENSE file, and does not judge the text.
- **The latest version's licence.** For npm and PyPI it is the latest version's licence field; for crates.io it is the default version's licence. A `version` you send changes the advisories, not the licence.
- **Often an SPDX expression.** `MIT`, `Apache-2.0` and `MIT OR Apache-2.0` are SPDX forms. Older packages may hold free text such as `SEE LICENSE IN LICENSE.txt`, or a loose name like `BSD`. For npm, a licence given as an object is read as its `type`.
- **`null` when there is nothing to read.** A name the registry does not list, a failed lookup, and a package whose licence field is empty or missing all give `null`.

## Price

US$0.005 per batch of 1 to 10 names, paid in USDC with x402 v2 on Base mainnet (real USDC). No account, key or sign-up. A request that fails validation gets 400 before payment and is never charged, and a batch where every registry lookup fails gets 502 and is not charged. All prices: [pricing.json](https://vehcdj664efetfrsolne5umanq.srv.us/pricing.json).

## 1. See an answer for free

The free sample checks four demo names. Three are listed, each with a different licence form, and one does not exist:

```sh
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/packages | jq '.results[] | {name, exists, license}'
```

```json
{
  "name": "left-pad",
  "exists": true,
  "license": "WTFPL"
}
{
  "name": "requests",
  "exists": true,
  "license": "Apache-2.0"
}
{
  "name": "serde",
  "exists": true,
  "license": "MIT OR Apache-2.0"
}
{
  "name": "left-pad-demo-missing",
  "exists": false,
  "license": null
}
```

## 2. Read the price from the 402 offer

Put the names you plan to install in a file. A request with a valid body gets the payment offer without being charged. The `PAYMENT-REQUIRED` header is base64 JSON:

```sh
cat > packages.json <<'EOF'
{ "packages": [{ "ecosystem": "npm", "name": "left-pad" }, { "ecosystem": "pypi", "name": "requests" }] }
EOF
curl -si -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/check/packages -H 'content-type: application/json' -d @packages.json \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' | base64 -d \
  | jq '.accepts[0] | {scheme, network, amount, payTo}'
```

`amount` is in USDC base units (6 decimals), so `5000` is US$0.005.

## 3. Check the names

Each name needs an `ecosystem` (`npm`, `pypi` or `crates`) and a `name`. Put the batch in the same `packages.json` as step 2:

```sh
cat > packages.json <<'EOF'
{ "packages": [{ "ecosystem": "npm", "name": "left-pad" }, { "ecosystem": "pypi", "name": "requests" }] }
EOF
```

Pay with any x402 v2 client: it reads the offer from step 2, signs it and sends the same request again with a `PAYMENT-SIGNATURE` header. [One x402 payment step by step](/docs/examples/fetch-x402) shows this with plain `fetch`, and the [MCP server](/docs/mcp) does it for you. Save the answer as `answer.json` for the steps below.

## 4. Decide from the licence

List the licence next to the other fields that decide an install:

```sh
jq '.results[] | {name, exists, license, latest_version, homepage}' answer.json
```

Then sort each name into one of three groups. The allow list is yours.

| `license` | What to do |
|---|---|
| A form on your allow list, for example `MIT`, `Apache-2.0` or `BSD-3-Clause` | Install. Open `homepage` or `repository` once to confirm it is the package you meant. |
| An expression such as `MIT OR Apache-2.0` | `OR` offers a choice: you may use the package under either. `AND` means every licence applies. Check each part against your list. |
| Anything else: `null`, free text, `UNLICENSED`, `SEE LICENSE IN …`, or a name not on your list | Do not install on the agent's own say-so. Read the licence in the package's repository, or ask the person who owns the decision. |

A `null` licence is not a permissive one: with no licence stated you have no stated permission to use the code. When `exists` is `false` there is nothing to install, and the model may have invented the name.

To print every listed name whose licence is not on your allow list, with `ALLOW` a JSON array of the licences you accept:

```sh
ALLOW='["MIT","Apache-2.0","BSD-3-Clause","ISC"]'
jq --argjson allow "$ALLOW" '.results[] | select(.exists != false and ((.license // "") as $l | $allow | index($l) | not)) | {name, license}' answer.json
```

This is an exact match: `MIT OR Apache-2.0` is not in the list above, so it is printed for you to read. The check reads what the registry says. It does not detect a registry licence that differs from the one in the package's files.

## 5. Keep the receipt

The `receipt` field is a compact JWS. It signs, for each name, whether it exists and its latest version, with the hash of the registry's answer, so you can keep it with the decision. Check it with the free verify route:

```sh
jq '{receipt}' answer.json \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'content-type: application/json' -d @- | jq .valid
```

## With MCP

The same check is the MCP tool `attestpage_check_packages`, with the body from step 3 as its arguments. To use it from an MCP client with nothing to install, add the remote endpoint:

```json
{
  "mcpServers": {
    "attestpage": { "type": "http", "url": "https://vehcdj664efetfrsolne5umanq.srv.us/mcp" }
  }
}
```

One call to the remote endpoint with `curl`. `_meta["attestpage/trial"]` set to `false` asks for the payment offer rather than a trial call, so nothing is charged:

```sh
curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/mcp -H 'content-type: application/json' -d @- <<'EOF' \
  | jq '.result.structuredContent.accepts[0] | {scheme, network, amount}'
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "attestpage_check_packages",
    "arguments": { "packages": [{ "ecosystem": "npm", "name": "left-pad" }, { "ecosystem": "pypi", "name": "requests" }] },
    "_meta": { "attestpage/trial": false } } }
EOF
```

```json
{
  "scheme": "exact",
  "network": "eip155:8453",
  "amount": "5000"
}
```

The result has `isError: true` and the x402 payment requirements in `structuredContent`, with the same `amount` as step 2. Sign one of `accepts`, put the payload in `_meta["x402/payment"]` and call again: `structuredContent` is then the same JSON as `answer.json`, and `_meta["x402/payment-response"]` holds the settlement.

## Questions

### How much does a check cost?

US$0.005 per batch of up to 10 names. Checking 23 names takes 3 batches, so 3 calls.

### Is this a licence compliance check?

No. It returns the licence text the registry holds for the latest version. It does not read the package's files, its dependencies' licences or your own policy. Use it to catch a missing or unexpected licence before an install, and make the final call yourself.

### Why is `license` null for a package that exists?

The registry holds no licence text for the latest version, or only whitespace. Treat it as unknown and read the package's repository.

### Does sending a `version` give that version's licence?

No. A `version` selects the version whose advisories are listed. `license` is still the latest version's (npm and PyPI) or the default version's (crates.io).
