> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nmcrate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# NMKey HTTP API

> The raw NMKey validation endpoints — for runtimes where the JVM library isn't an option.

If your product doesn't run on the JVM (or you want full control), NMKey is just a signed HTTP endpoint. Anything that can POST JSON can validate a key. All endpoints are **public** — the license key itself is the credential, no API key is needed.

## Base URL

```text theme={null}
https://nmcrate.com/api/nmkey/v1
```

## POST /validate

Checks a key and claims (or refreshes) this server's license slot. Call on boot and periodically as a heartbeat.

```bash expandable theme={null}
curl -sX POST "https://nmcrate.com/api/nmkey/v1/validate" \
  -H "Content-Type: application/json" \
  -d '{
    "pluginId": "a7f3k9q2",
    "key": "NMK-XXXXXXXX-XXXXXXXX-XXXXXXXX",
    "fingerprint": "stable-per-server-hash",
    "nonce": "random-per-request-value",
    "hostname": "mc.example.com"
  }'
```

| Body field    | Required | Description                                                                   |
| ------------- | -------- | ----------------------------------------------------------------------------- |
| `pluginId`    | yes      | Your 8-character NMKey plugin id (Licensing tab).                             |
| `key`         | yes      | The buyer's `NMK-…` license key.                                              |
| `fingerprint` | yes      | A **stable** per-server hash — this is the unit the seat lock counts against. |
| `nonce`       | yes      | A fresh random value per request; echoed into the signature (replay defence). |
| `hostname`    | no       | Display name for the studio's key-management UI.                              |

The response is always HTTP `200` — the verdict is in the body:

```json theme={null}
{
  "status": "valid",
  "ok": true,
  "pluginId": "a7f3k9q2",
  "issuedAt": "2026-07-13T18:00:00.000Z",
  "nonce": "random-per-request-value",
  "maxServers": 1,
  "activeServers": 1,
  "expiresAt": null,
  "signature": "base64url-ed25519-signature"
}
```

| `status`         | Meaning                                                           |
| ---------------- | ----------------------------------------------------------------- |
| `valid`          | Run. This server holds a seat (`ok: true`, only for this status). |
| `max_servers`    | The key is already live on its maximum number of other servers.   |
| `expired`        | Past the key's `expiresAt`.                                       |
| `disabled`       | Revoked / disabled by the studio.                                 |
| `not_found`      | No such license for this plugin.                                  |
| `invalid_format` | Malformed `pluginId` / `key`, or missing required fields.         |

Non-`valid` responses include a human-readable `message`.

### Verifying the signature

When signing is enabled, `signature` is a base64url Ed25519 signature over this pipe-delimited canonical string, which you rebuild from values you already know plus the returned `status` / `issuedAt`:

```text theme={null}
v1|{pluginId}|{key}|{fingerprint}|{status}|{nonce}|{issuedAt}
```

Verify it (raw message, no pre-hash) against the public key from `GET /public-key`. **Embed that public key in your plugin** rather than fetching it at runtime from the same host you're distrusting — otherwise a rerouted API could serve both a fake "valid" and a matching fake key.

## POST /release

Frees this server's seat on graceful shutdown, so the buyer can move hosts instantly instead of waiting for the seat to go stale.

```bash theme={null}
curl -sX POST "https://nmcrate.com/api/nmkey/v1/release" \
  -H "Content-Type: application/json" \
  -d '{ "pluginId": "a7f3k9q2", "key": "NMK-…", "fingerprint": "stable-per-server-hash" }'
```

Returns `{ "ok": true }` when a seat was released.

## GET /public-key

Returns the Ed25519 public key as SPKI PEM (`text/plain`). `404` when response signing is not enabled on the server.

## GET /health

Reachability check plus integration hints:

```json theme={null}
{
  "ok": true,
  "signing": true,
  "heartbeatMs": 300000,
  "staleAfterMs": 900000
}
```

`heartbeatMs` is the recommended re-validation interval; a seat that hasn't validated for `staleAfterMs` is considered stale and can be claimed by another server.
