Problem
No doc for humans 😄
Note: The (partially generated) doc below might have mistakes.
Proposed solution
Overview
Keygate
is a license management API for software products. This document covers
the public SDK endpoints and portal endpoints. The API uses a
consistent JSON envelope for all responses.
Base URL: {server}/api/v1
Install
(see in readme)
First Admin run
Into the .env:
- Add an admin email into ADMIN_EMAILS
- If no SMTP is set up, validation code is in your keygate container's logs.
docker compose up -d
Visit http://localhost:9000
Create a product (desktop, saas or hybrid).
Create a plan for the product.
In another browser, register a test customer (needs working SMTP settings in your .env)
Back into the admin:
check for the customer
Issue a new license for this customer
Customer Example Workflow
SDK Integration (Mobile App)
First Customer Run:
# Get public key
curl https://your-server.com/api/v1/license/pubkey
# Save the public_key for offline verification
Activation:
curl -X POST https://your-server.com/api/v1/license/activate \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-unique-id" \
-d '{
"license_key": "KG-...",
"identifier": "device-uuid",
"label": "iPhone 15"
}'
On Each Launch:
curl -X POST https://your-server.com/api/v1/license/verify \
-H "Content-Type: application/json" \
-d '{
"license_key": "KG-...",
"identifier": "device-uuid"
}'
# Verify the returned token offline using saved public key
Usage Recording:
curl -X POST https://your-server.com/api/v1/license/usage \
-H "Content-Type: application/json" \
-H "Idempotency-Key: usage-20260820-001" \
-d '{
"license_key": "KG-...",
"feature": "api_calls",
"quantity": 1
}'
Floating License
Checkout:
curl -X POST https://your-server.com/api/v1/license/floating/checkout \
-H "Content-Type: application/json" \
-H "Idempotency-Key: checkout-001" \
-d '{
"license_key": "KG-...",
"identifier": "engineering-01",
"label": "Engineer Workstation"
}'
Heartbeat every 15 minutes:
curl -X POST https://your-server.com/api/v1/license/floating/heartbeat \
-H "Content-Type: application/json" \
-d '{
"license_key": "KG-...",
"identifier": "engineering-01"
}'
Checkin when done:
curl -X POST https://your-server.com/api/v1/license/floating/checkin \
-H "Content-Type: application/json" \
-d '{
"license_key": "KG-...",
"identifier": "engineering-01"
}'
Authentication & Security
SDK Public Endpoints
Public SDK endpoints (/license/*, /releases/:slug/feed.*) do NOT require an API key. The license_key
in the request body is the credential. Stable release feeds are fully
public — trust comes from the per-product Ed25519 signature on each
artifact, not from URL secrecy.
Admin Server-to-Server
For server-to-server admin access (CI/CD, billing backends, automation scripts) use API keys with the admin scope. Pass them as:
Authorization: Bearer kg_live_…
Admin keys are equivalent to a logged-in admin session and can call any /admin/* endpoint. Mint them in the admin dashboard under API Keys.
Portal Endpoints
Portal endpoints (/portal/*) require a session cookie issued by /auth/otp/verify, /auth/dev-login, or /invites/accept. The cookie is HttpOnly + SameSite=Lax; browsers attach it automatically.
Idempotency
Write endpoints (/license/activate, /license/usage, /license/floating/checkout) honor the Idempotency-Key header:
Same key + same body: Replays the cached response (200/4xx) within 24 hours
Same key + different body: Returns 422 IDEMPOTENCY_KEY_CONFLICT
Concurrent retries while original is still running: Returns 409 IDEMPOTENCY_IN_FLIGHT (retry with backoff)
Replayed idempotent responses carry the header Idempotent-Replayed: true.
Response Format
Success Response
{
"success": true,
"data": {
// Response data
}
}Error Response
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "...",
"details": { ... }
}
}
Endpoints
License Management
Get Verification Key
GET /license/pubkey
Returns the Ed25519 public key used to verify offline tokens.
Request Example:
GET /api/v1/license/pubkey
Host: your-server.com
Response Example:
{
"success": true,
"data": {
"algorithm": "ed25519",
"format": "hex",
"public_key": "a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef"
}
}
Activate License
POST /license/activate
Registers a device or user identifier against a license. Returns a signed verification token.
Request Headers:
Request Body:
{
"license_key": "KG-ABCD1234-EFGH5678-IJKL9012-MNOP3456",
"identifier": "device-a1b2c3d4",
"identifier_type": "device",
"label": "MacBook Pro"
}Response Example:
{
"success": true,
"data": {
"status": "activated",
"license_id": "550e8400-e29b-41d4-a716-446655440000",
"token": "eyJhbGciOiJIUzI1NiIsInR...<base64url-payload>.<base64url-signature>"
}
}Error Responses:
400: Validation failure
403: License not usable (expired/suspended/revoked/canceled)
404: License not found
409: ACTIVATION_LIMIT (slots exhausted) or IDEMPOTENCY_IN_FLIGHT
422: IDEMPOTENCY_KEY_CONFLICT
429: Brute-force lockout
Verify License
POST /license/verify
Checks that a license is valid and the identifier is activated. Returns the license status, plan features, and a signed token.
Request Body:
{
"license_key": "KG-ABCD1234-EFGH5678-IJKL9012-MNOP3456",
"identifier": "device-a1b2c3d4"
}Response Example:
{
"success": true,
"data": {
"status": "active",
"plan_id": "plan_123",
"plan_name": "Pro",
"valid_until": "2027-01-01T00:00:00Z",
"features": {
"premium_support": true,
"api_calls": "50000",
"storage_gb": "100"
},
"token": "eyJhbGciOiJIUzI1NiIsInR...",
"grace_days": 0
}
}Error Response:
{
"success": false,
"error": {
"code": "LICENSE_NOT_FOUND",
"message": "License not found or invalid",
"details": null
}
}Note: All "license-knowable" failures collapse to 404 LICENSE_NOT_FOUND to prevent probing.
Deactivate License
POST /license/deactivate
Removes an activation, freeing a slot for a new device.
Request Body:
{
"license_key": "KG-ABCD1234-EFGH5678-IJKL9012-MNOP3456",
"identifier": "device-a1b2c3d4"
}Response Example:
{
"success": true,
"data": null
}Error Response:
{
"success": false,
"error": {
"code": "LICENSE_NOT_FOUND",
"message": "License not found or identifier not activated",
"details": null
}
}
Entitlements
Check Feature Entitlements
POST /license/entitlements
Returns all features available for the license's plan, including quota usage for metered features.
Request Body:
{
"license_key": "KG-ABCD1234-EFGH5678-IJKL9012-MNOP3456",
"feature": "premium_support" // Optional
}Response Example:
{
"success": true,
"data": {
"licensed": true,
"status": "active",
"plan_id": "plan_123",
"plan_name": "Pro",
"features": {
"premium_support": {
"enabled": true,
"value_type": "bool",
"value": "true",
"used": null,
"limit": null,
"remaining": null,
"period": "lifetime",
"resets_at": null
},
"api_calls": {
"enabled": true,
"value_type": "quota",
"value": "50000",
"used": 451,
"limit": 1000,
"remaining": 549,
"period": "monthly",
"resets_at": "2026-04-01T00:00:00Z"
}
}
}
}
Usage
Record Usage
POST /license/usage
Increments a usage counter for a metered feature. Atomic — concurrent requests will never exceed the quota.
Request Headers:
Request Body:
{
"license_key": "KG-ABCD1234-EFGH5678-IJKL9012-MNOP3456",
"feature": "api_calls",
"quantity": 5,
"metadata": {
"endpoint": "/users",
"method": "GET"
}
}Response Example:
{
"success": true,
"data": {
"accepted": true,
"used": 456,
"limit": 1000,
"remaining": 544,
"period": "monthly",
"period_key": "2026-03"
}
}Error Response:
{
"success": false,
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Usage quota exceeded for api_calls",
"details": {
"used": 1000,
"limit": 1000,
"period": "monthly"
}
}
}
Get Quota Status
POST /license/usage/status
Returns current usage and remaining quota for a feature.
Request Body:
{
"license_key": "KG-ABCD1234-EFGH5678-IJKL9012-MNOP3456",
"feature": "api_calls"
}Response Example:
{
"success": true,
"data": {
"feature": "api_calls",
"used": 456,
"limit": 1000,
"remaining": 544,
"period": "monthly",
"period_key": "2026-03"
}
}
Alternatives considered
No response
Problem
No doc for humans 😄
Note: The (partially generated) doc below might have mistakes.
Proposed solution
Overview
Keygate is a license management API for software products. This document covers the public SDK endpoints and portal endpoints. The API uses a consistent JSON envelope for all responses.
Base URL:
{server}/api/v1Install
(see in readme)
First Admin run
Into the
.env:docker compose up -d
Visit http://localhost:9000
Create a product (desktop, saas or hybrid).
Create a plan for the product.
In another browser, register a test customer (needs working SMTP settings in your
.env)Back into the admin:
check for the customer
Issue a new license for this customer
Customer Example Workflow
SDK Integration (Mobile App)
First Customer Run:
Activation:
curl -X POST https://your-server.com/api/v1/license/activate \ -H "Content-Type: application/json" \ -H "Idempotency-Key: my-unique-id" \ -d '{ "license_key": "KG-...", "identifier": "device-uuid", "label": "iPhone 15" }'On Each Launch:
curl -X POST https://your-server.com/api/v1/license/verify \ -H "Content-Type: application/json" \ -d '{ "license_key": "KG-...", "identifier": "device-uuid" }' # Verify the returned token offline using saved public keyUsage Recording:
curl -X POST https://your-server.com/api/v1/license/usage \ -H "Content-Type: application/json" \ -H "Idempotency-Key: usage-20260820-001" \ -d '{ "license_key": "KG-...", "feature": "api_calls", "quantity": 1 }'Floating License
Checkout:
curl -X POST https://your-server.com/api/v1/license/floating/checkout \ -H "Content-Type: application/json" \ -H "Idempotency-Key: checkout-001" \ -d '{ "license_key": "KG-...", "identifier": "engineering-01", "label": "Engineer Workstation" }'Heartbeat every 15 minutes:
curl -X POST https://your-server.com/api/v1/license/floating/heartbeat \ -H "Content-Type: application/json" \ -d '{ "license_key": "KG-...", "identifier": "engineering-01" }'Checkin when done:
curl -X POST https://your-server.com/api/v1/license/floating/checkin \ -H "Content-Type: application/json" \ -d '{ "license_key": "KG-...", "identifier": "engineering-01" }'Authentication & Security
SDK Public Endpoints
Public SDK endpoints (
/license/*,/releases/:slug/feed.*) do NOT require an API key. Thelicense_keyin the request body is the credential. Stable release feeds are fully public — trust comes from the per-product Ed25519 signature on each artifact, not from URL secrecy.Admin Server-to-Server
For server-to-server admin access (CI/CD, billing backends, automation scripts) use API keys with the
adminscope. Pass them as:Authorization: Bearer kg_live_…Admin keys are equivalent to a logged-in admin session and can call any
/admin/*endpoint. Mint them in the admin dashboard under API Keys.Portal Endpoints
Portal endpoints (
/portal/*) require a session cookie issued by/auth/otp/verify,/auth/dev-login, or/invites/accept. The cookie is HttpOnly + SameSite=Lax; browsers attach it automatically.Idempotency
Write endpoints (
/license/activate,/license/usage,/license/floating/checkout) honor theIdempotency-Keyheader:Same key + same body: Replays the cached response (200/4xx) within 24 hours
Same key + different body: Returns 422
IDEMPOTENCY_KEY_CONFLICTConcurrent retries while original is still running: Returns 409
IDEMPOTENCY_IN_FLIGHT(retry with backoff)Replayed idempotent responses carry the header
Idempotent-Replayed: true.Response Format
Success Response
Error Response
Endpoints
License Management
Get Verification Key
GET /license/pubkeyReturns the Ed25519 public key used to verify offline tokens.
Request Example:
Response Example:
Activate License
POST /license/activateRegisters a device or user identifier against a license. Returns a signed verification token.
Request Headers:
Idempotency-Key(optional, string, max 256 chars)Request Body:
Response Example:
Error Responses:
400: Validation failure403: License not usable (expired/suspended/revoked/canceled)404: License not found409:ACTIVATION_LIMIT(slots exhausted) orIDEMPOTENCY_IN_FLIGHT422:IDEMPOTENCY_KEY_CONFLICT429: Brute-force lockoutVerify License
POST /license/verifyChecks that a license is valid and the identifier is activated. Returns the license status, plan features, and a signed token.
Request Body:
Response Example:
Error Response:
Note: All "license-knowable" failures collapse to
404 LICENSE_NOT_FOUNDto prevent probing.Deactivate License
POST /license/deactivateRemoves an activation, freeing a slot for a new device.
Request Body:
Response Example:
Error Response:
Entitlements
Check Feature Entitlements
POST /license/entitlementsReturns all features available for the license's plan, including quota usage for metered features.
Request Body:
Response Example:
Usage
Record Usage
POST /license/usageIncrements a usage counter for a metered feature. Atomic — concurrent requests will never exceed the quota.
Request Headers:
Idempotency-Key(optional)Request Body:
Response Example:
Error Response:
Get Quota Status
POST /license/usage/statusReturns current usage and remaining quota for a feature.
Request Body:
Response Example:
Alternatives considered
No response