Management API
The Management API is the control plane behind the keylight CLI and MCP server — one API, many thin clients. It lets you (and agents acting for you) create apps, configure key types, mint licenses, connect payment providers, and verify your setup without opening the dashboard.
This is a different surface from the SDK API: the SDK endpoints (activate, validate, …) are called by your app with an SDK key; the Management API is called by you or your tooling with a personal access token.
Base URL
Section titled “Base URL”https://api.keylight.dev/v1Every endpoint is tenant-scoped by the token — there is no tenant id in the path. Requests and responses are JSON.
Authentication
Section titled “Authentication”Send a personal access token as a bearer header:
Authorization: Bearer klm_...Tokens are minted in the dashboard under Settings → API tokens, shown once, and carry a fixed set of scopes chosen at creation. A missing or bad token is 401; a token that lacks a required scope is 403.
For agents and CI, set KEYLIGHT_API_TOKEN in the environment — no browser needed.
Logging in from the CLI
Section titled “Logging in from the CLI”The keylight CLI authorizes with the device-authorization flow, so it works
the same everywhere — desktop, a server over SSH, or CI.
- Run
keylight login. It callsPOST /v1/login/deviceand prints a short code plus a URL. - Open the URL in any browser, sign in, and confirm the access the CLI asked
for (you can narrow it; destructive
dangeraccess is off unless you turn it on). - The CLI polls
POST /v1/login/device/tokenand stores the token once you approve.
For non-interactive use (agents, CI), set KEYLIGHT_API_TOKEN instead — no
browser step. keylight logout (or POST /v1/logout) revokes the token.
Scopes
Section titled “Scopes”| Scope | Grants |
|---|---|
products:read / products:write | Read / manage apps and key types |
licenses:read / licenses:write | Read / mint, remint, deactivate, import licenses |
integrations:read / integrations:write | Read / manage payment providers, webhook, SDK key |
customers:read / customers:write | Read / manage customers and entitlements |
usage:read | Read usage statistics |
danger | Required in addition to the resource scope for every confirm-gated operation |
Never ship an unscoped token. Give an agent only the scopes its task needs.
The confirmation gate
Section titled “The confirmation gate”Destructive, hijack-shaped, or secret-revealing operations are not removed from the API — they are confirm-gated, in the style of npm’s browser login. The gated operations are: delete a key type, revoke a license, export licenses, read or rotate the SDK key, set the webhook URL, and rotate a provider secret.
A gated call with no confirmation does not execute. Instead:
-
You call the endpoint normally. The server parks a pending confirmation and returns
202:{"confirmation": {"id": "9f3c…","status": "pending","approveUrl": "https://app.keylight.dev/dashboard/<tenant>/confirmations/9f3c…","expiresInSeconds": 600}} -
A human opens
approveUrlin a dashboard session, reads a plain-language description of exactly what will happen, and approves or denies. -
You poll
GET /v1/confirmations/{id}untilstatusisapproved,denied, orexpired. Honour theRetry-Afterheader. -
On
approved, re-issue the identical request with the confirmation id in a header:X-Keylight-Confirmation: 9f3c…The server verifies the request parameters are byte-identical to the ones that were approved, consumes the confirmation atomically, and executes exactly once — even under racing retries. A denied, expired, already-consumed, or parameter-mismatched confirmation is
403.
Pagination
Section titled “Pagination”List endpoints are cursor-paginated with ?cursor= and ?limit= (max 100, default 25):
{ "items": [ /* … */ ], "nextCursor": "eyJ…" }Follow nextCursor until it is null. Ordering is stable (newest first, with an id tiebreak).
Idempotency
Section titled “Idempotency”POST /v1/licenses (mint) and POST /v1/licenses/import accept an Idempotency-Key request header. The first success is cached for 24 hours keyed on that value; a replay with the same key returns the stored response with Idempotent-Replay: true and does not re-execute. Agents that retry on timeout will never double-mint.
Errors
Section titled “Errors”Every error is a single-field envelope:
{ "error": "message" }| Status | Meaning |
|---|---|
400 | Malformed request (bad JSON or invalid parameters) |
401 | Missing, expired, or revoked token |
402 | The operation requires a higher Keylight plan |
403 | Missing scope, or a denied / expired / mismatched confirmation |
404 | Absent or cross-tenant resource (indistinguishable — no existence leak) |
405 | Wrong method for the path |
409 | State conflict (e.g. already revoked, key type still in use) |
422 | Well-formed but semantically invalid |
429 | Rate limited — retry after Retry-After |
Secrets
Section titled “Secrets”Raw license keys appear only once, in the mint and remint responses. The SDK key, the webhook signing secret, and the Gumroad ping URL are each revealed exactly once on their gated path. List and detail responses only ever carry masked display forms.
Endpoints at a glance
Section titled “Endpoints at a glance”| Group | Endpoints |
|---|---|
| Products | GET/POST /products, GET/PATCH /products/{id}, POST/DELETE /products/{id}/test-mode, POST /products/{id}/free-tier |
| Key types | GET/POST /products/{id}/key-types, PATCH/DELETE /products/{id}/key-types/{ktId} (delete gated), POST …/verify-prices |
| Licenses | GET/POST /licenses, GET /licenses/{id}, POST /licenses/{id}/revoke (gated), POST /licenses/{id}/remint, POST /licenses/{id}/deactivate-device, POST /licenses/import, GET /licenses/export (gated) |
| SDK key | GET /sdk-key (gated), POST /sdk-key/rotate (gated) |
| Integrations | GET /integrations, POST /integrations/{provider}/connect, DELETE /integrations/{provider}, POST /integrations/{provider}/rotate-secret (gated), POST /integrations/stripe/connect, GET /integrations/stripe/status, POST /products/{id}/integrations/{provider} |
| Webhook | GET /webhook, PUT /webhook (gated) |
| Customers | GET/POST /customers, GET /customers/{id}, POST /customers/{id}/link-license, PATCH /customers/{id}/entitlements |
| Usage | GET /usage |
| Test purchase | POST /test-purchase, GET /test-purchase/{id} |
| Meta | GET /tokens/verify, GET /confirmations/{id}, GET /openapi.json |
| Auth | POST /login/device, POST /login/device/token, POST /logout |
See the OpenAPI document for full request and response schemas.