# Client API reference

The three public endpoints your app calls to activate, validate and deactivate a license. No secret needed.

These are the endpoints the SDKs call. They need no secret: the product ID is public and the license key identifies the customer. Use them directly if there is no SDK for your stack.

- Base URL: `https://api.keyrail.tech/v1` (configurable in every SDK).
- JSON request and response bodies, `Content-Type: application/json`.
- CORS is open on these routes.
- `fingerprint` is the 64-character lowercase hex [machine fingerprint](/docs/concepts/#machines-and-fingerprints).

## Activate

`POST /v1/licenses/activate`

```json
{
  "product": "prd_7k2m9q4x1c8d3f6h0j5n",
  "key": "KR-7K2M9-Q4X1C-8D3F6-H0J5N",
  "fingerprint": "7a1acf765768ebbc09bfbbac0878d5e729d655a4be4db7510d2188f630881134",
  "name": "DESKTOP-42",
  "platform": "windows",
  "appVersion": "1.4.2"
}
```

`name`, `platform` and `appVersion` are optional. Response `200`:

```json
{ "certificate": "KR1.eyJ2IjoxLCJraWQiOiJrMSIs….1qOAhxQWB0OO…", "activation": "act_9c4h7k1m5p8r2t6v0x3z" }
```

Idempotent: activating a fingerprint that is already activated refreshes it and does not use another seat.

```bash
curl https://api.keyrail.tech/v1/licenses/activate \
  -H "Content-Type: application/json" \
  -d '{"product":"prd_7k2m9q4x1c8d3f6h0j5n","key":"KR-7K2M9-Q4X1C-8D3F6-H0J5N","fingerprint":"7a1acf765768ebbc09bfbbac0878d5e729d655a4be4db7510d2188f630881134"}'
```

## Validate

`POST /v1/licenses/validate`

Same body as activate, without `name`:

```json
{
  "product": "prd_7k2m9q4x1c8d3f6h0j5n",
  "key": "KR-7K2M9-Q4X1C-8D3F6-H0J5N",
  "fingerprint": "7a1acf765768ebbc09bfbbac0878d5e729d655a4be4db7510d2188f630881134",
  "platform": "windows",
  "appVersion": "1.4.2"
}
```

Response `200`: `{ "certificate": "KR1…" }`. Fails with `404 not_activated` if this fingerprint has no activation on the license. Updates the activation’s `lastSeenAt`.

The SDKs call validate during their license check once the certificate’s `refreshAfter` has passed. They keep the cached certificate on network errors, `429` and `5xx`, and clear it on `403` and on `404 unknown_key`, `unknown_product` or `not_activated`.

## Deactivate

`POST /v1/licenses/deactivate`

```json
{
  "product": "prd_7k2m9q4x1c8d3f6h0j5n",
  "key": "KR-7K2M9-Q4X1C-8D3F6-H0J5N",
  "fingerprint": "7a1acf765768ebbc09bfbbac0878d5e729d655a4be4db7510d2188f630881134"
}
```

Response `200`: `{ "deactivated": true }`. The seat is free for another machine. The app should delete its stored certificate (the SDKs do, and also when the answer is `404`; on a network error they keep it so the call can be retried).

## Errors

Every error has the same shape:

```json
{ "error": { "code": "activation_limit", "message": "This license is already active on 2 machines." } }
```

`message` is human-readable English, fine to log but not meant to be parsed. Branch on `code`. What to tell your customer for each code: [troubleshooting](/docs/troubleshooting/#client-api-errors).

| HTTP | `code` | When |
|---|---|---|
| 400 | `invalid_request` | Malformed body, bad fingerprint, bad key format. |
| 404 | `unknown_product` | No product with this ID. |
| 404 | `unknown_key` | No license with this key for this product. |
| 404 | `not_activated` | This fingerprint has no activation on the license. |
| 403 | `license_expired` | Past `validUntil`. |
| 403 | `license_suspended` | Suspended by the vendor, or subscription paused. |
| 403 | `license_refunded` | Refunded. |
| 403 | `license_revoked` | Revoked (chargeback or manual). |
| 403 | `account_closed` | Activation only: the vendor closed its Keyrail account. Machines already activated keep validating for 30 days. |
| 409 | `activation_limit` | All activations are in use. |
| 429 | `rate_limited` | Too many calls. |
| 500 | `internal_error` | Our fault. Retry later; the stored certificate keeps working. |

## Activation limit

The limit is `plan.maxActivations × license.seats`, unless the vendor overrides `maxActivations` on the license.

## Rate limits

- 30 calls per hour per license key and IP address.
- 600 calls per hour per IP address.

The SDKs stay far below this: one call at activation, then at most about one refresh a day.
