# LicenseKit — Complete Documentation > LicenseKit is a licensing API for software vendors. It handles license creation, activation, validation, consumption metering, floating/concurrent seats, offline issuance, device management, and webhook delivery. The backend is PostgreSQL-backed with Ed25519 response signing. SDKs wrap the API so an AI coding agent can wire up licensing in minutes, not days. - Canonical product name: `licensekit.dev` - Public beta launch date: `April 5, 2026` - Hosted API base URL: `https://api.licensekit.dev` - Public beta pricing: `Sandbox` free, `Founding Beta` `$49/month`, `Scale` contact - API contract locked at `v1 core` — stable for SDK generation - Management responses: `{data, meta}`. Runtime responses: `{data, signature, meta}`. - Runtime responses are Ed25519-signed. Signature verification is mandatory. - OpenAPI spec: `api/openapi.yaml` (OpenAPI 3.1.0) - TypeScript SDK: `@licensekit/sdk` on npm - Python SDK: `licensekit-sdk` live on PyPI - Go SDK: `github.com/drmain1/licensekit-go` live as a public module - Ruby SDK: `licensekit-ruby` live on RubyGems as a prerelease gem --- ## DOCUMENT SOURCE: docs/agent_quickstart.md # Agent Quickstart Updated: March 24, 2026 (America/Los_Angeles) This note is for humans and AI agents wiring LicenseKit into an application with minimal back-and-forth. Use it together with: - `api/openapi.yaml` - `docs/api_contract.md` ## Choose The Right Client - Management client: - path family: `/api/v1/...` except the public runtime and system endpoints - auth: `Authorization: Bearer ` - success envelope: `{data, meta}` - Runtime client: - path family: `/api/v1/license/...` - auth: `Authorization: License ` - success envelope: `{data, signature, meta}` - System client: - path family: `/health`, `/healthz`, `/readyz`, `/metrics`, `/api/v1/system/public-keys` - auth: none ## Scope Rules - Read the operation-level `x-required-scopes` field in `api/openapi.yaml` before calling a management endpoint. - `x-required-scopes` is the least-privilege non-admin scope requirement for that operation. - `admin` satisfies all management operations, but it should be treated as a bootstrap or operator token, not the default token for day-to-day automation. - If a workflow spans multiple domains, either use separate scoped keys or intentionally create one combined scoped key. - If the server returns `403 TOKEN_SCOPE_DENIED`, compare the presented token scopes to that operation's `x-required-scopes`. ## Common Scope Bundles - Catalog setup and maintenance: `product:write` - Catalog readback: `product:read` - Customer management and API-key management: `admin` - License creation, renewal, lifecycle actions, and feature assignment: `license:write` - License reads, custom-field reads, device reads, and license feature reads: `license:read` - Device reset and blacklist actions: `device:write` - Event feed polling: `event:read` - Webhook endpoint management: `webhook:write` ## First-Pass Integration Flow 1. Use a bootstrap or existing `admin` token to create least-privilege management keys with `POST /api/v1/api-keys`. 2. Create a product and policy. 3. Create a customer if needed. Customer endpoints are `admin` in the locked `v1 core`. 4. Create a license, optionally linking `order_id` and `subscription_id`. 5. In the protected application, call runtime endpoints with the license key, not the management token. 6. Verify runtime response signatures against `GET /api/v1/system/public-keys`. 7. Supply `Idempotency-Key` on mutation endpoints that require it. ## Rules For Agent Authors - Prefer `operationId` plus `x-required-scopes` over route-name guessing. - Treat `docs/api_contract.md` as the compatibility note for renewal semantics, commerce linkage, version eligibility, and signature verification. - Expect additive fields over time, not envelope-shape changes. - Treat runtime signature verification as part of success handling, not as optional diagnostics. - Do not assume orders or subscriptions run a billing engine. They are external record objects plus license-origin linkage in `v1 core`. --- ## DOCUMENT SOURCE: docs/api_contract.md # API Contract Notes Updated: March 24, 2026 (America/Los_Angeles) This note freezes the compatibility-sensitive behavior that SDKs and external integrations should rely on for the locked `v1 core` backend. ## Envelope Shape - Management success responses use `{data, meta}`. - Signed runtime success responses use `{data, signature, meta}`. - `204 No Content` endpoints intentionally return no body. - Error responses use `{error, meta}` with stable machine codes such as `INVALID_QUERY`, `VALIDATION_ERROR`, `NOT_FOUND`, and `IDEMPOTENCY_CONFLICT`. ## Management Auth Scope Disclosure - Management endpoints in `api/openapi.yaml` advertise least-privilege requirements via the operation-level `x-required-scopes` vendor extension. - `x-required-scopes` describes the minimum non-admin scope set needed for that operation. - A management token with `admin` satisfies all management operations even when a narrower `x-required-scopes` value is listed. - SDKs, docs, and AI agents should use `x-required-scopes` when choosing which scoped API key to create for an automation task. - A `403 TOKEN_SCOPE_DENIED` response means the presented token did not satisfy that operation's `x-required-scopes` requirement. ## License Origin Linkage - `POST /api/v1/licenses` persists validated `order_id` and `subscription_id` on the license record. - If `order_id` is supplied and that order is already linked to a subscription, the license create path infers `subscription_id` when it is omitted. - Supplied order and subscription records must belong to the same product as the license. - If both are supplied, they must match each other. - `GET /api/v1/licenses` supports `order_id`, `subscription_id`, `expires_before`, and `expires_after` filters in addition to the earlier product, policy, customer, and status filters. ## Renewal Semantics - Only `subscription`, `time_limited`, and `trial` licenses are renewable. - Renewals are idempotent per license and `Idempotency-Key`. - A renewal request must provide exactly one of: - `expires_at` - `extend_by_days` - Renewal basis is: - current stored `expires_at` when the license has not yet expired - current server time when the license is already expired - `expires_at` must move the license expiry forward relative to that basis. - `extend_by_days` adds whole 24-hour day increments to that basis. - Renewals set `current_period_start` to the chosen renewal basis. - Renewals keep a suspended license suspended. Other renewable non-revoked states become `active`. - Renewals do not rewrite the stored origin linkage on the license. - If a renewal request includes `order_id` and/or `subscription_id`, those values are validated and written into audit metadata only. ## Order And Subscription Period Semantics - Subscriptions are first-class external billing records. - Subscription `current_period_start` and `current_period_end` are required, and `current_period_end` must be after `current_period_start`. - `canceled_at` is valid only when subscription `status` is `canceled`. - Orders are first-class external billing records. - Order `period_start` and `period_end` are optional, but they must be provided together when used. - Order `period_end` must be after `period_start`. - The service stores these periods as provided. It does not apply calendar normalization, proration, or billing-provider-specific rules on top of them. - The service treats order/subscription statuses as operator-visible record state, not as an automatic billing engine. ## Version Eligibility - Runtime version checks are driven by `app_version` on runtime requests. - If `app_version` is omitted, no version-eligibility object is returned and no version gate is evaluated. - If a product has no version catalog yet, version gating is not enforced and runtime responses return `version_eligibility.enforced = false`. - Once a product has any version catalog entries, unknown `app_version` values are rejected. - If a known version is outside the entitlement window, the runtime request is rejected. - Eligibility cutoff is: - `maintenance_expires_at` for perpetual licenses - `expires_at` for non-perpetual licenses ## Signing And Public-Key Verification - Runtime activation, validation, check, consume, deactivate, and floating responses are signed. - `signature` covers the JSON bytes of the `data` payload. - Public verification keys are exposed at `GET /api/v1/system/public-keys`. - The active signing key is used for new responses. - Retired public keys remain published so previously signed payloads can still be verified after key rotation. - Offline issuance returns an encrypted envelope. - The offline envelope decrypts to a signed inner payload, which should then be verified against the published public keys. ## Compatibility Intent - The locked `v1 core` goal is to keep request and response shapes stable enough for SDK generation. - Future backend work may add new fields additively, but consumers should not rely on undocumented side effects beyond the behaviors above. --- ## DOCUMENT SOURCE: docs/sdk_spec.md # SDK Specification Updated: March 24, 2026 (America/Los_Angeles) This document is the implementation brief for first-party licensekit.dev SDKs. It is intentionally language-agnostic so the same spec can be reused for TypeScript first, then for additional languages without redefining the product contract each time. The canonical product name is `licensekit.dev`. ## Primary Goal The SDKs should let a human or AI coding agent integrate licensekit.dev quickly without reverse-engineering the API surface from server code. The ideal flow is: 1. agent reads the spec and OpenAPI 2. agent installs the SDK 3. agent creates the right least-privilege token 4. agent wires runtime validation and signature verification correctly 5. the human reviews the patch instead of designing the licensing integration from scratch ## Design Priorities 1. AI-agent readable before human-optimized prose 2. exact contract fidelity with the backend 3. least-privilege auth that is machine-discoverable 4. predictable method naming and package structure across languages 5. typed errors with stable machine codes 6. first-class runtime signature verification 7. minimal hidden behavior ## Standard SDK Scope Every first-party SDK covers four surfaces: ### 1. Management Surface - operator and automation access to management endpoints under `/api/v1/...` - bearer-token auth - typed request and response models - access to operation-level `x-required-scopes` ### 2. Runtime Surface - application-side calls to `/api/v1/license/...` - `Authorization: License ` auth - signed-response handling - runtime signature verification helpers ### 3. System Surface - unauthenticated system endpoints: `/healthz`, `/readyz`, `/metrics`, `/api/v1/system/public-keys` ### 4. Crypto Helper Surface - verify signed runtime response payloads against public keys - fetch and parse public keys from `/api/v1/system/public-keys` - key lookup by `kid` - optional: decrypt offline-license envelopes ## Standard Client Shape Every SDK exposes three primary clients and one crypto helper: - `ManagementClient` — bearer-token auth, management endpoints - `RuntimeClient` — license-key auth, runtime endpoints - `SystemClient` — unauthenticated, health and public keys - `Crypto` or `Verification` — signature verification helpers ## Auth Rules ### Management Auth - `Authorization: Bearer ` - Valid scopes: `admin`, `product:read`, `product:write`, `license:read`, `license:write`, `device:write`, `event:read`, `webhook:write` - Per-operation least-privilege requirements derived from `x-required-scopes` in OpenAPI ### Runtime Auth - `Authorization: License ` - Never reuse management auth path for runtime calls ## Method Naming Method naming maps cleanly to OpenAPI `operationId`: - `listProducts`, `createLicense`, `renewLicense`, `checkoutFloatingLicense` ## Return Shapes ### Management Result ``` ManagementResult { data: T, meta: ResponseMeta } ``` ### Runtime Result ``` RuntimeResult { data: T, signature: Signature, meta: ResponseMeta } ``` ## Error Model ``` ApiError { status, code, message, detail?, requestId?, timestamp?, body? } ``` - Preserves server machine codes exactly - `TOKEN_SCOPE_DENIED` exposed distinctly (actionable for agents) - Idempotency conflicts exposed distinctly ## Signature Verification Every SDK must implement: 1. Fetch public keys from `/api/v1/system/public-keys` 2. Select key matching `signature.kid` 3. Verify signature over JSON bytes of `data` payload ``` verifyRuntimeResult(result, publicKeys) -> VerificationResult verifyRuntimePayload(data, signature, publicKeys) -> bool fetchPublicKeys() -> [PublicKey...] ``` ## AI-Agent-Focused Requirements Every SDK must answer these questions without reading server code: 1. Which client should I use for this task? 2. Which token type does this operation require? 3. Which least-privilege scope do I need? 4. Does this response need signature verification? 5. Which operations require `Idempotency-Key`? 6. Which operation corresponds to this task in the API? ## Required Examples Per Language 1. Create a scoped management key 2. Create a product and policy 3. Create a customer and license 4. Call runtime `check` or `validate` 5. Verify the returned signature with public keys 6. Renew a license with `Idempotency-Key` 7. Manage a device reset or blacklist action ## TypeScript SDK - Package: `@licensekit/sdk` on npm - Formats: ESM and CommonJS with TypeScript definitions - Types auto-generated from `api/openapi.yaml` via `openapi-typescript` - Scope metadata auto-generated from `x-required-scopes` - Platform `fetch` API with transport injection - `baseUrl` explicit and configurable for local, self-hosted, and hosted deployments ## Python SDK - Package: `licensekit-sdk` on PyPI - Install: `pip install --pre licensekit-sdk` - Package page: `https://pypi.org/project/licensekit-sdk/` - Current status: live prerelease package on PyPI - Primary import shape: `from licensekit import ManagementClient, RuntimeClient, SystemClient` - Base URL, auth model, and signature verification follow the same contract as the TypeScript SDK ## Go SDK - Module: `github.com/drmain1/licensekit-go` - Install: `go get github.com/drmain1/licensekit-go` - Source repo: `https://github.com/drmain1/licensekit-go` - Current status: live public Go module tagged at `v0.1.1` - Primary constructor shape: `licensekit.NewManagementClient(...)` - Scope metadata and Ed25519 verification helpers mirror the TypeScript and Python surfaces ## Ruby SDK - Gem name: `licensekit-ruby` - Package page: `https://rubygems.org/gems/licensekit-ruby` - Source repo: `https://github.com/drmain1/licensekit-ruby` - Current status: live prerelease gem on RubyGems at `0.1.0.alpha.1` - Install: `gem install licensekit-ruby -v 0.1.0.alpha.1` - Primary constructor shape: `LicenseKit::ManagementClient.new(...)` - Scope metadata and Ed25519 verification helpers mirror the TypeScript, Python, and Go surfaces ## Public Beta Pricing - `Sandbox` — Free - 1 product - up to 25 active licenses - hosted API and public SDK access - `Founding Beta` — `$49/month` - up to 2,500 active licenses - activation, validation, offline issuance, floating seats, webhooks, and version checks - 10,000 metered events per month included - `+$10` per additional 10,000 metered events - manual onboarding during public beta - `Scale` — Contact - custom limits, support, migration help, and higher-volume metered entitlement terms --- ## DOCUMENT SOURCE: API Endpoint Reference # Complete API Endpoint Reference 50 endpoints across 3 surfaces. ## Management Endpoints (Bearer token auth) ### Products - `GET /api/v1/products` — list products (product:read) - `POST /api/v1/products` — create product (product:write) - `GET /api/v1/products/{id}` — get product (product:read) - `PATCH /api/v1/products/{id}` — update product (product:write) - `DELETE /api/v1/products/{id}` — delete product (product:write) ### Versions - `GET /api/v1/products/{id}/versions` — list versions (product:read) - `POST /api/v1/products/{id}/versions` — create version (product:write) ### Policies - `GET /api/v1/products/{id}/policies` — list policies (product:read) - `POST /api/v1/products/{id}/policies` — create policy (product:write) - `GET /api/v1/policies/{id}` — get policy (product:read) - `PATCH /api/v1/policies/{id}` — update policy (product:write) - `DELETE /api/v1/policies/{id}` — delete policy (product:write) ### Features - `POST /api/v1/products/{id}/features` — create feature (product:write) - `GET /api/v1/features/{id}` — get feature (product:read) ### Customers - `GET /api/v1/customers` — list customers (admin) - `POST /api/v1/customers` — create customer (admin) - `GET /api/v1/customers/{id}` — get customer (admin) - `PATCH /api/v1/customers/{id}` — update customer (admin) - `DELETE /api/v1/customers/{id}` — delete customer (admin) ### Licenses - `GET /api/v1/licenses` — list licenses (license:read) - `POST /api/v1/licenses` — create license (license:write) - `GET /api/v1/licenses/{id}` — get license (license:read) - `POST /api/v1/licenses/{id}/renew` — renew license (license:write) - `POST /api/v1/licenses/{id}/suspend` — suspend license (license:write) - `POST /api/v1/licenses/{id}/reinstate` — reinstate license (license:write) - `POST /api/v1/licenses/{id}/revoke` — revoke license (license:write) - `POST /api/v1/licenses/{id}/transfer` — transfer license (license:write) - `POST /api/v1/licenses/{id}/usage/reset` — reset consumption (license:write) ### License Features - `GET /api/v1/licenses/{id}/features` — list assigned features (license:read) - `POST /api/v1/licenses/{id}/features` — assign feature (license:write) - `DELETE /api/v1/licenses/{id}/features` — remove feature (license:write) ### Devices - `GET /api/v1/licenses/{id}/devices` — list devices (license:read) - `GET /api/v1/licenses/{id}/devices/{device_id}` — get device (license:read) - `POST /api/v1/licenses/{id}/devices/{device_id}/reset` — reset device (device:write) - `POST /api/v1/licenses/{id}/devices/{device_id}/blacklist` — blacklist device (device:write) ### Custom Fields - `GET /api/v1/products/{id}/custom-fields` — list field definitions (product:read) - `POST /api/v1/products/{id}/custom-fields` — create field definition (product:write) - `GET /api/v1/products/{id}/custom-fields/{field_id}` — get field definition (product:read) - `PATCH /api/v1/products/{id}/custom-fields/{field_id}` — update field definition (product:write) - `DELETE /api/v1/products/{id}/custom-fields/{field_id}` — delete field definition (product:write) - `GET /api/v1/licenses/{id}/custom-fields/{field_id}` — get license field value (license:read) - `PUT /api/v1/licenses/{id}/custom-fields/{field_id}` — set license field value (license:write) - `GET /api/v1/customers/{id}/custom-fields/{field_id}` — get customer field value (admin) - `PUT /api/v1/customers/{id}/custom-fields/{field_id}` — set customer field value (admin) ### Commerce - `GET /api/v1/products/{id}/orders` — list orders (product:read) - `POST /api/v1/products/{id}/orders` — create order (product:write) - `GET /api/v1/orders/{id}` — get order (product:read) - `PATCH /api/v1/orders/{id}` — update order (product:write) - `GET /api/v1/products/{id}/subscriptions` — list subscriptions (product:read) - `POST /api/v1/products/{id}/subscriptions` — create subscription (product:write) - `GET /api/v1/subscriptions/{id}` — get subscription (product:read) - `PATCH /api/v1/subscriptions/{id}` — update subscription (product:write) ### API Keys - `GET /api/v1/api-keys` — list API keys (admin) - `POST /api/v1/api-keys` — create scoped API key (admin) ### Events & Webhooks - `GET /api/v1/events` — list events (event:read) - `GET /api/v1/webhooks` — list webhooks (webhook:write) - `POST /api/v1/webhooks` — create webhook (webhook:write) - `GET /api/v1/webhooks/{id}` — get webhook (webhook:write) - `PATCH /api/v1/webhooks/{id}` — update webhook (webhook:write) - `DELETE /api/v1/webhooks/{id}` — delete webhook (webhook:write) ## Runtime Endpoints (License key auth) All use `Authorization: License `. All responses include `signature` for Ed25519 verification. - `POST /api/v1/license/activate` — activate a license on a device - `POST /api/v1/license/validate` — validate license status and entitlements - `POST /api/v1/license/check` — lightweight license check - `POST /api/v1/license/consume` — record metered consumption - `POST /api/v1/license/deactivate` — deactivate a device - `POST /api/v1/license/offline` — issue offline license (encrypted envelope) - `POST /api/v1/license/floating/checkout` — check out a floating seat - `POST /api/v1/license/floating/checkin` — release a floating seat - `POST /api/v1/license/floating/heartbeat` — keep a floating seat alive ## System Endpoints (No auth) - `GET /health` — hosted-safe liveness probe for `api.licensekit.dev` - `GET /healthz` — liveness probe - `GET /readyz` — readiness probe (DB connected) - `GET /metrics` — Prometheus metrics - `GET /api/v1/system/public-keys` — Ed25519 public keys for signature verification --- ## DOCUMENT SOURCE: Worked Integration Example # Complete Integration Example This example walks through the full licensing setup flow: from creating a product to validating a license in your application. ## Step 1: Create a scoped management key Use your admin bootstrap token to create a least-privilege key for catalog and license management. ```bash curl -X POST https://api.licensekit.dev/api/v1/api-keys \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: setup-key-001" \ -d '{ "name": "catalog-setup", "scopes": ["product:write", "license:write"] }' ``` Response: ```json { "data": { "id": "ak_...", "name": "catalog-setup", "scopes": ["product:write", "license:write"], "key": "lsk_live_..." }, "meta": { "request_id": "..." } } ``` Save the `key` value. It will not be returned again. ## Step 2: Create a product ```bash curl -X POST https://api.licensekit.dev/api/v1/products \ -H "Authorization: Bearer $SCOPED_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "My App Pro", "code": "my-app-pro" }' ``` ## Step 3: Create a policy Policies define the license behavior — type, device limits, expiry rules. ```bash curl -X POST https://api.licensekit.dev/api/v1/products/$PRODUCT_ID/policies \ -H "Authorization: Bearer $SCOPED_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Pro License", "type": "perpetual", "max_devices": 3 }' ``` ## Step 4: Issue a license ```bash curl -X POST https://api.licensekit.dev/api/v1/licenses \ -H "Authorization: Bearer $SCOPED_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: license-001" \ -d '{ "product_id": "'$PRODUCT_ID'", "policy_id": "'$POLICY_ID'" }' ``` Response includes the license key (e.g., `LK-XXXX-XXXX-XXXX`). Give this to your customer. ## Step 5: Activate in your application ```typescript import { RuntimeClient } from "@licensekit/sdk"; const client = new RuntimeClient({ baseUrl: "https://api.licensekit.dev", licenseKey: process.env.LICENSE_KEY, }); // Activate on this device const activation = await client.activateLicense({ body: { fingerprint: getMachineFingerprint(), label: "Workstation A", } }); console.log("Activated:", activation.data.device_id); ``` ## Step 6: Validate on startup ```typescript const { data, signature } = await client.validateLicense({ body: { fingerprint: getMachineFingerprint() }, }); if (data.valid) { console.log("License valid"); console.log("Features:", data.features); console.log("Expires:", data.expires_at ?? "never (perpetual)"); } else { console.error("License invalid:", data.detail); process.exit(1); } ``` ## Step 7: Verify signature (mandatory) ```typescript import { PublicKeyStore, SystemClient, verifyRuntimeResult, } from "@licensekit/sdk"; const system = new SystemClient({ baseUrl: "https://api.licensekit.dev", }); const publicKeys = await system.listPublicKeys(); const verified = await verifyRuntimeResult( result, new PublicKeyStore(publicKeys.data) ); if (!verified.ok) { console.error("Signature verification failed — response may be tampered"); process.exit(1); } ``` ## Step 8: Record consumption (for metered licenses) ```typescript await client.consumeLicense({ body: { units: "1" }, }); ``` ## Step 9: Floating license checkout (for concurrent seat licenses) ```typescript const seat = await client.checkoutFloatingLicense({ body: { fingerprint: getMachineFingerprint() }, }); // Keep the seat alive with periodic heartbeats setInterval(async () => { await client.heartbeatFloatingLicense({ body: { lease_id: seat.data.lease_id }, }); }, 60_000); // Release when done process.on("exit", async () => { await client.checkinFloatingLicense({ body: { lease_id: seat.data.lease_id }, }); }); ``` ## Step 10: Renew a license (server-side) ```bash curl -X POST https://api.licensekit.dev/api/v1/licenses/$LICENSE_ID/renew \ -H "Authorization: Bearer $SCOPED_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: renewal-2026-03" \ -d '{"extend_by_days": 365}' ``` --- ## DOCUMENT SOURCE: Deployment # Deployment LicenseKit runs as a single container image with two modes: ## Google Cloud Run (Recommended) Use 2 Cloud Run workloads from the same container image: 1. **Job** — runs `locksmith migrate` at deploy time 2. **Service** — runs `locksmith serve` for API traffic ### Required Environment Variables - `LOCKSMITH_DB_URL` — PostgreSQL connection string - `LOCKSMITH_SIGNING_KEYSET` — JSON signing keyset - `LOCKSMITH_BOOTSTRAP` — optional JSON bootstrap config ### Docker ```bash docker build -t licensekit . docker run -p 8080:8080 \ -e LOCKSMITH_DB_URL="postgres://..." \ -e LOCKSMITH_SIGNING_KEYSET='...' \ licensekit serve ``` ## Local Development ```bash make build ./locksmith init ./locksmith migrate ./locksmith serve ``` The server starts on `:8080` by default. Use `GET /health` for hosted liveness checks at `api.licensekit.dev`, and keep `GET /healthz` for local and self-hosted compatibility.