minmaxkey

API reference

API reference

Base URL: http://your-server:8080. All responses are JSON. Interactive Swagger UI lives at /docs on your server.

Authentication

Two credentials, two header names:

Header Used for Set where
X-MinMax-Admin-Token management endpoints (CRUD, reset, revoke) your server only
X-API-Key activation / validation endpoints your client app

Wrong or missing credentials return 401.

Errors

Errors are JSON: {"detail": "..."} with a status code.

Code Meaning
401 bad or missing auth
404 unknown product / license / license type (or key doesn't exist)
403 license revoked, or expired
429 no seats available on this license
409 duplicate name / slug
422 validation error

/activate deliberately answers 404 for unknown keys — no key sniffing.

Endpoints

GET /healthz

Liveness probe. {"status": "ok", "service": "minmaxkey"}.

GET /

Service info: links to /docs, /admin, /healthz.

Products

POST /v1/products · admin Create a product. Body: {"name": "PixelForge Pro", "slug": "pixelforge-pro"}. Returns the product including api_key and public_key.

GET /v1/products · admin List all products.

GET /v1/products/{id} · admin One product.

POST /v1/products/{id}/webhook · admin Set (or clear, with {"url": ""}) the product's webhook endpoint. Returns {"url": ..., "secret": ...} — the secret is generated once and stays stable. Keep it server-side.

Mail

GET /v1/mail/status · admin {"configured": bool, "from": ...} — is SMTP set up (without leaking the password)?

POST /v1/mail/test · admin Body: {"to": "[email protected]"}. Sends a real test email to verify SMTP key delivery without a purchase. 422 when SMTP isn't configured, 502 if the send fails — never silent.

License types

POST /v1/products/{id}/license-types · admin Body: {"name": "pro-monthly", "kind": "subscription", "max_seats": 1, "duration_days": 30}. kind is one of lifetime | subscription | floating | trial. max_seats defaults to 0 (unlimited). duration_days applies to subscription/trial.

GET /v1/products/{id}/license-types · admin

Licenses

POST /v1/products/{id}/licenses · admin Body: {"license_type_id": 1, "customer_email": "[email protected]", "metadata": {...}}. license_type_id must belong to that product. Returns the license including the human-friendly key.

GET /v1/products/{id}/licenses · admin All licenses of a product, each with its current activations.

POST /v1/licenses/{id}/revoke · admin Revokes the license. All future activations and validations fail; existing offline tokens keep working until the client revalidates (see Offline tokens for the trade-off).

POST /v1/licenses/{id}/reset · admin Frees every seat of the license in one go — the customer reinstalled their OS and their fingerprint changed. The key stays active and can bind a new machine immediately. Returns {"status": "reset", "freed": N} and fires license.reset.

POST /v1/licenses/{id}/offline · admin Returns {"token": "..."} — a signed offline token for a license not bound to a specific device.

Activation (client-facing, X-API-Key)

POST /v1/products/{id}/activate Body: {"key": "...", "fingerprint": "<sha256 hex, ≥8 chars>", "name": "My MacBook", "platform": "windows"}. Returns license, device, seat usage, and offline_token.

POST /v1/products/{id}/deactivate Body: {"key": "...", "fingerprint": "..."}. Idempotent: {"freed": true} or {"freed": false}.

GET /v1/products/{id}/validate?key=...&fingerprint=... Server-side status check for webhooks and payment processors. A fresh offline_token is returned only when that fingerprint already has an activation (a device bound via /activate) — this is how clients auto-revalidate without minting tokens for arbitrary machines. A valid key alone still returns valid: true (status, seats, expiry), it just doesn't issue a token for an unbound fingerprint.

Client-facing responses (/activate, /validate) return a stripped license object — id, key, status, expires_at, license_type. The buyer's email and license metadata are admin-only (the product X-API-Key is embedded in client apps, so PII never rides along).

Response shape:

{
  "valid": true,
  "status": "active",
  "license": {"id": 1, "key": "9TMC-...", "status": "active", "expires_at": null, "...": "..."},
  "device": {"fingerprint": "9f86d081...", "platform": "windows", "...": "..."},
  "seats": {"used": 1, "max": 2},
  "offline_token": "eyJ..."
}

When invalid: {"valid": false, "status": "invalid" | "revoked" | "expired"}.