{
  "openapi": "3.1.0",
  "info": {
    "title": "Keylight Management API",
    "version": "1.0.0",
    "summary": "Token-authenticated JSON API for managing a Keylight tenant from the terminal, CI, or an AI agent.",
    "description": "The Keylight Management API is the control plane behind the `keylight` CLI: one API, many thin clients. Every endpoint is tenant-scoped by the bearer token — there is no tenant id in the path.\n\n## Authentication\n\nSend a personal access token as `Authorization: Bearer klm_...`. Tokens are minted in the dashboard (Settings -> API tokens), shown once, and carry a fixed set of scopes. A missing or bad token is `401`; a token without the required scope is `403`. This is a plain bearer PAT, not OAuth2 — the scopes below are attached to the token at creation, not negotiated per request.\n\n## Scopes\n\n`products:read`, `products:write`, `licenses:read`, `licenses:write`, `integrations:read`, `integrations:write`, `customers:read`, `customers:write`, `usage:read`, and `danger`. The `danger` scope is required *in addition* to the resource scope for every confirm-gated operation. Each operation lists the exact scopes it needs in `x-required-scopes`.\n\n## Confirmation gate (dangerous operations)\n\nDestructive, hijack-shaped, or secret-revealing operations are not removed — they are confirm-gated in the style of npm's web login. A gated call with no confirmation header does not execute: it returns `202` with a pending confirmation (`id`, `approveUrl`, `expiresInSeconds`). A human opens `approveUrl` in a dashboard session and approves or denies. The agent polls `GET /v1/confirmations/{id}` until the status is `approved`, `denied`, or `expired`, then re-issues the identical request with `X-Keylight-Confirmation: {id}`. The server verifies the request's parameter hash matches the approved one and consumes the confirmation atomically, so a gated operation executes exactly once even under racing retries. A consumed, denied, expired, or mismatched confirmation is `403`.\n\n## Idempotency\n\n`POST /v1/licenses` and `POST /v1/licenses/import` accept an `Idempotency-Key` request header. The first success is stored for 24h keyed on a hash of the header; a replay with the same key returns the stored response body verbatim with `Idempotent-Replay: true` and does not re-execute.\n\n## Pagination\n\nList endpoints are cursor-paginated: `?cursor=` + `?limit=` (max 100, default 25). Responses are `{ \"items\": [...], \"nextCursor\": string | null }`. Ordering is stable (createdAt desc with an id tiebreak). Follow `nextCursor` until it is `null`.\n\n## Errors\n\nEvery error is `{ \"error\": \"message\" }`. `400` malformed input, `401` bad/revoked token, `402` plan-gated, `403` missing scope or failed confirmation, `404` absent OR cross-tenant (indistinguishable — no existence leak), `405` wrong method, `409` state conflict, `422` semantic validation, `429` rate-limited (with `Retry-After`).\n\n## Secrets\n\nRaw license keys are returned only once, in the mint/remint responses. The SDK key, webhook signing secret, and Gumroad ping URL are each revealed exactly once on their gated write/read paths. List and detail responses never contain a raw key or secret — only masked display forms.\n\n## Polling\n\nThe poll-shaped endpoints (`/v1/confirmations/{id}`, `/v1/test-purchase/{id}`) and `/v1/usage` are built to be polled and are cheap to call repeatedly. Honour their `Retry-After`.",
    "contact": {
      "name": "Keylight",
      "url": "https://keylight.dev"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://keylight.dev"
    }
  },
  "servers": [
    {
      "url": "https://api.keylight.dev",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Management API reference",
    "url": "https://docs.keylight.dev/reference/api/management/"
  },
  "security": [
    {
      "bearerToken": []
    }
  ],
  "tags": [
    {
      "name": "Meta",
      "description": "Spec, token introspection, and confirmation polling."
    },
    {
      "name": "Auth",
      "description": "CLI login (device-authorization flow) and logout."
    },
    {
      "name": "Products",
      "description": "Apps: CRUD, settings, test mode, free tier."
    },
    {
      "name": "Key types",
      "description": "License key types on an app: CRUD and price verification."
    },
    {
      "name": "Licenses",
      "description": "Mint, list, inspect, revoke, remint, deactivate devices, import, export."
    },
    {
      "name": "SDK key",
      "description": "Read and rotate the live SDK key (confirm-gated)."
    },
    {
      "name": "Integrations",
      "description": "Payment providers: connect, disconnect, rotate, Stripe Connect, per-app config."
    },
    {
      "name": "Webhook",
      "description": "License-delivery webhook settings (confirm-gated write)."
    },
    {
      "name": "Customers",
      "description": "Customers and their entitlement overrides."
    },
    {
      "name": "Usage",
      "description": "Activation and validation statistics."
    },
    {
      "name": "Test purchase",
      "description": "End-to-end purchase simulation."
    }
  ],
  "paths": {
    "/v1/openapi.json": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "This OpenAPI document",
        "description": "The machine-readable OpenAPI 3.1 description of the Management API. Public — no authentication.",
        "operationId": "getOpenApi",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tokens/verify": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "Who am I",
        "description": "Introspect the calling token: the tenant it belongs to, its id, and its scopes. Any valid token works — no scope required.",
        "operationId": "verifyToken",
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Token identity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenIdentity"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/confirmations/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ConfirmationId"
        }
      ],
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "Poll a pending confirmation",
        "description": "Poll the status of a confirmation returned by a gated `202`. Cheap to poll. Honour `Retry-After`. Terminal states are `approved`, `denied`, and `expired`; agents must handle all three.",
        "operationId": "getConfirmation",
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Current confirmation status.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfirmationStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "List apps",
        "operationId": "listProducts",
        "x-required-scopes": [
          "products:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of apps.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Create app",
        "operationId": "createProduct",
        "x-required-scopes": [
          "products:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "App created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        }
      ],
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "App detail + settings",
        "operationId": "getProduct",
        "x-required-scopes": [
          "products:read"
        ],
        "responses": {
          "200": {
            "description": "App detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "tags": [
          "Products"
        ],
        "summary": "Update app settings",
        "operationId": "updateProduct",
        "x-required-scopes": [
          "products:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProductBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "App updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}/test-mode": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        }
      ],
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Enable test mode",
        "description": "Store the app's Stripe test-mode keys, enabling test-mode checkout.",
        "operationId": "enableTestMode",
        "x-required-scopes": [
          "products:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetTestModeBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Test mode enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Products"
        ],
        "summary": "Clear test mode",
        "operationId": "clearTestMode",
        "x-required-scopes": [
          "products:write"
        ],
        "responses": {
          "200": {
            "description": "Test mode cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}/free-tier": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        }
      ],
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Toggle free tier",
        "operationId": "setFreeTier",
        "x-required-scopes": [
          "products:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Free tier toggled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}/key-types": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        }
      ],
      "get": {
        "tags": [
          "Key types"
        ],
        "summary": "List key types",
        "operationId": "listKeyTypes",
        "x-required-scopes": [
          "products:read"
        ],
        "responses": {
          "200": {
            "description": "The app's key types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "keyTypes"
                  ],
                  "properties": {
                    "keyTypes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KeyType"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Key types"
        ],
        "summary": "Create key type",
        "operationId": "createKeyType",
        "x-required-scopes": [
          "products:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKeyTypeBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key type created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "created"
                  ],
                  "properties": {
                    "created": {
                      "$ref": "#/components/schemas/KeyType"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}/key-types/verify-prices": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        }
      ],
      "post": {
        "tags": [
          "Key types"
        ],
        "summary": "Verify provider price IDs",
        "description": "Re-check the Stripe price IDs mapped to this app's key types. `skipped` is true when there is nothing to verify (no Stripe connection or no mappings).",
        "operationId": "verifyKeyTypePrices",
        "x-required-scopes": [
          "products:write"
        ],
        "responses": {
          "200": {
            "description": "Verification result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "keyTypes",
                    "skipped"
                  ],
                  "properties": {
                    "keyTypes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KeyType"
                      }
                    },
                    "skipped": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}/key-types/{keyTypeId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        },
        {
          "$ref": "#/components/parameters/KeyTypeId"
        }
      ],
      "patch": {
        "tags": [
          "Key types"
        ],
        "summary": "Edit key type",
        "description": "Partial update — omitted fields keep their current value. `keyTypeId` is immutable.",
        "operationId": "updateKeyType",
        "x-required-scopes": [
          "products:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateKeyTypeBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key type updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "updated"
                  ],
                  "properties": {
                    "updated": {
                      "$ref": "#/components/schemas/KeyType"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Key types"
        ],
        "summary": "Delete key type",
        "description": "Confirm-gated. Requires the `danger` scope in addition to `products:write`. The server refuses (`409`) while live licenses reference the key type, and refuses to delete the app's only key type. See the confirmation gate in the API description.",
        "operationId": "deleteKeyType",
        "x-required-scopes": [
          "products:write",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Key type deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses": {
      "get": {
        "tags": [
          "Licenses"
        ],
        "summary": "List / search licenses",
        "description": "Cursor-paginated. Keys are masked (`displayKey` only). Filter with `?product=` and `?status=`.",
        "operationId": "listLicenses",
        "x-required-scopes": [
          "licenses:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "product",
            "in": "query",
            "description": "Filter to one app id.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by license status (e.g. `active`, `revoked`).",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of licenses (keys masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicensePage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Licenses"
        ],
        "summary": "Mint a license",
        "description": "Mint a license manually. The raw `licenseKey` is returned exactly once — store it. Accepts an `Idempotency-Key` header; a replay returns the original result with `Idempotent-Replay: true` and does not double-mint.",
        "operationId": "mintLicense",
        "x-required-scopes": [
          "licenses:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MintLicenseBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "License minted. Contains the raw key (once).",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MintLicenseResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses/import": {
      "post": {
        "tags": [
          "Licenses"
        ],
        "summary": "Bulk import licenses",
        "description": "Import a chunk of legacy licenses (one chunk per call). The server re-validates every row. Accepts an `Idempotency-Key` header; a replay returns the original counts and does not re-import.",
        "operationId": "importLicenses",
        "x-required-scopes": [
          "licenses:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportLicensesBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import result counts.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportLicensesResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses/export": {
      "get": {
        "tags": [
          "Licenses"
        ],
        "summary": "Export licenses as CSV",
        "description": "Confirm-gated. Requires the `danger` scope in addition to `licenses:read`. Also plan-gated to Enterprise (`402` otherwise) — the plan check runs before a confirmation is parked. On approval returns a CSV file (`text/csv`).",
        "operationId": "exportLicenses",
        "x-required-scopes": [
          "licenses:read",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV export.",
            "headers": {
              "Content-Disposition": {
                "description": "Attachment filename.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses/{licenseId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LicenseId"
        }
      ],
      "get": {
        "tags": [
          "Licenses"
        ],
        "summary": "License detail",
        "description": "Full detail: devices, status, remint lineage, and recent activity. The key is masked.",
        "operationId": "getLicense",
        "x-required-scopes": [
          "licenses:read"
        ],
        "responses": {
          "200": {
            "description": "License detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LicenseDetailResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses/{licenseId}/revoke": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LicenseId"
        }
      ],
      "post": {
        "tags": [
          "Licenses"
        ],
        "summary": "Revoke a license",
        "description": "Confirm-gated. Requires the `danger` scope in addition to `licenses:write`, and is subject to a stricter rate bucket (30/hour). Already-revoked is `409`.",
        "operationId": "revokeLicense",
        "x-required-scopes": [
          "licenses:write",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "License revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "revoked",
                    "license"
                  ],
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    },
                    "license": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/LicenseDetail"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses/{licenseId}/remint": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LicenseId"
        }
      ],
      "post": {
        "tags": [
          "Licenses"
        ],
        "summary": "Remint a license",
        "description": "Re-issue a replacement key for a license. The new raw key is returned once. Body is optional; a bare POST reissues without emailing the customer.",
        "operationId": "remintLicense",
        "x-required-scopes": [
          "licenses:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sendEmail": {
                    "type": "boolean",
                    "default": false,
                    "description": "Email the linked customer their replacement key."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "License reminted. Contains the new raw key (once).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemintLicenseResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/licenses/{licenseId}/deactivate-device": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LicenseId"
        }
      ],
      "post": {
        "tags": [
          "Licenses"
        ],
        "summary": "Deactivate a device",
        "description": "Free a seat by deactivating one activated device instance.",
        "operationId": "deactivateDevice",
        "x-required-scopes": [
          "licenses:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "instanceId"
                ],
                "properties": {
                  "instanceId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The device instance id from the license's devices list."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Device deactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deactivated",
                    "license"
                  ],
                  "properties": {
                    "deactivated": {
                      "type": "boolean",
                      "const": true
                    },
                    "license": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/LicenseDetail"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sdk-key": {
      "get": {
        "tags": [
          "SDK key"
        ],
        "summary": "Reveal the SDK key",
        "description": "Confirm-gated read of the live SDK key (`sdk_live_...`). Requires the `danger` scope in addition to `integrations:read`. Legacy pre-encryption keys cannot be revealed (`409`) — rotate instead. The key is returned once on the approving response.",
        "operationId": "getSdkKey",
        "x-required-scopes": [
          "integrations:read",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "The live SDK key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "sdkKey"
                  ],
                  "properties": {
                    "sdkKey": {
                      "type": "string",
                      "example": "sdk_live_..."
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sdk-key/rotate": {
      "post": {
        "tags": [
          "SDK key"
        ],
        "summary": "Rotate the SDK key",
        "description": "Confirm-gated. Requires the `danger` scope in addition to `integrations:write`. Rotation invalidates the key baked into every shipped app build immediately — the approval screen states this. The new key is returned once.",
        "operationId": "rotateSdkKey",
        "x-required-scopes": [
          "integrations:write",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "SDK key rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "rotated",
                    "sdkKey"
                  ],
                  "properties": {
                    "rotated": {
                      "type": "boolean",
                      "const": true
                    },
                    "sdkKey": {
                      "type": "string",
                      "example": "sdk_live_..."
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/integrations": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "List integrations",
        "description": "Payment providers and their status per app, plus Stripe Connect status.",
        "operationId": "listIntegrations",
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "Integrations overview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationsOverview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/integrations/stripe/connect": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Stripe Connect authorize URL",
        "description": "Returns the Stripe OAuth authorize URL to open in a browser (Connect onboarding cannot be terminal-only). Body optional; empty means live mode. Nothing is persisted here — state changes on the OAuth callback.",
        "operationId": "stripeConnectUrl",
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ],
                    "default": "live"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The authorize URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url",
                    "mode"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/integrations/stripe/status": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "Stripe Connect status",
        "description": "Poll the Stripe connection status (live and sandbox slots + per-app enablement).",
        "operationId": "stripeStatus",
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "Stripe Connect status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StripeConnectStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/integrations/{provider}/connect": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Provider"
        }
      ],
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Connect a merchant-of-record provider",
        "description": "Connect a payment provider. Env-capable providers (paddle, lemonsqueezy, polar) configure one environment slot per call and require `environment`; omitting it returns 422. `apiKey` is required for Paddle (its credentials are verified against the Paddle API) and optional for Lemon Squeezy and Polar, where a blank value preserves the stored key. Single-slot providers (shopify, creem) take `secret`; Gumroad ignores it and returns a generated ping URL exactly once.",
        "operationId": "connectIntegration",
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectIntegrationBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Provider connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectIntegrationResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "422": {
            "description": "environment is missing for an env-capable provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/integrations/{provider}/rotate-secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Provider"
        }
      ],
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Rotate a provider secret",
        "description": "Confirm-gated. Requires the `danger` scope in addition to `integrations:write`. Only Gumroad supports rotation (others: disconnect and reconnect — `400`). The current ping URL stops working immediately; the new ping URL is returned once.",
        "operationId": "rotateIntegrationSecret",
        "x-required-scopes": [
          "integrations:write",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Secret rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "rotated",
                    "provider",
                    "pingUrl"
                  ],
                  "properties": {
                    "rotated": {
                      "type": "boolean",
                      "const": true
                    },
                    "provider": {
                      "type": "string"
                    },
                    "pingUrl": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/integrations/{provider}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Provider"
        }
      ],
      "delete": {
        "tags": [
          "Integrations"
        ],
        "summary": "Disconnect a provider",
        "description": "Disconnect a merchant-of-record provider. For Paddle, `?environment=sandbox|live` targets one slot (defaults to live); an invalid value is `422`. A not-connected provider is `404`.",
        "operationId": "disconnectIntegration",
        "x-required-scopes": [
          "integrations:write"
        ],
        "parameters": [
          {
            "name": "environment",
            "in": "query",
            "description": "Env-capable providers (paddle, lemonsqueezy, polar): which environment slot to remove. Removing one slot keeps the other connected.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "sandbox",
                "live"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Provider disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "disconnected",
                    "provider"
                  ],
                  "properties": {
                    "disconnected": {
                      "type": "boolean",
                      "const": true
                    },
                    "provider": {
                      "type": "string"
                    },
                    "remainingEnvironments": {
                      "type": "boolean",
                      "description": "Paddle only — true when the other environment slot is still connected."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products/{productId}/integrations/{provider}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProductId"
        },
        {
          "$ref": "#/components/parameters/Provider"
        }
      ],
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Configure a provider on an app",
        "description": "Enable/disable a provider for one app, or set its price mappings. Provide exactly one of: `enabled` (toggle), `prices` (keyTypeId -> price id), or `environments` (Paddle per-environment maps). `enabled` cannot be combined with the mapping fields — the mappings set the enabled state themselves.",
        "operationId": "configureProductIntegration",
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductIntegrationBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "App integration configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductIntegrationResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhook": {
      "get": {
        "tags": [
          "Webhook"
        ],
        "summary": "Read webhook settings",
        "description": "The current license-delivery webhook settings. Never returns the signing secret — only whether one is on file.",
        "operationId": "getWebhook",
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "Webhook settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Webhook"
        ],
        "summary": "Set webhook settings",
        "description": "Confirm-gated (delivery-hijack risk — license keys get POSTed to this URL). Requires the `danger` scope in addition to `integrations:write`. `url` is always required (send `null` to clear). The signing secret is returned once on the approving response.",
        "operationId": "updateWebhook",
        "x-required-scopes": [
          "integrations:write",
          "danger"
        ],
        "x-confirm-gated": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/ConfirmationHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook settings saved. Contains the signing secret (once).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookUpdateResult"
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/PendingConfirmation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/customers": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "operationId": "listCustomers",
        "x-required-scopes": [
          "customers:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of customers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Create or refresh a customer",
        "description": "The email is the identity: re-posting an existing email updates the name instead of erroring. `201` on first create, `200` on an upsert of an existing customer.",
        "operationId": "createCustomer",
        "x-required-scopes": [
          "customers:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "minLength": 1
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing customer refreshed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateCustomerResult"
                }
              }
            }
          },
          "201": {
            "description": "Customer created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateCustomerResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/customers/{customerId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Customer detail",
        "description": "Linked licenses (keys masked), payments, revenue total, and license activity.",
        "operationId": "getCustomer",
        "x-required-scopes": [
          "customers:read"
        ],
        "responses": {
          "200": {
            "description": "Customer detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetailResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/customers/{customerId}/link-license": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Link a license to a customer",
        "description": "Attach an existing license to this customer. The customer must already exist (create it first via POST /v1/customers) — `404` otherwise.",
        "operationId": "linkLicense",
        "x-required-scopes": [
          "customers:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "licenseId"
                ],
                "properties": {
                  "licenseId": {
                    "type": "string",
                    "pattern": "^[a-f0-9]+$",
                    "description": "The license id (hex key hash) from list/detail/mint."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "License linked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "linked",
                    "license"
                  ],
                  "properties": {
                    "linked": {
                      "type": "boolean",
                      "const": true
                    },
                    "license": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/LicenseDetail"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/customers/{customerId}/entitlements": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "patch": {
        "tags": [
          "Customers"
        ],
        "summary": "Override entitlements",
        "description": "Set or clear the entitlement override on one of this customer's licenses. Entitlements live on the license, so the target license is named in the body; a license not linked to this customer is `404`. Send `entitlements: null` to clear the override (the key-type default applies again).",
        "operationId": "setCustomerEntitlements",
        "x-required-scopes": [
          "customers:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "licenseId",
                  "entitlements"
                ],
                "properties": {
                  "licenseId": {
                    "type": "string",
                    "pattern": "^[a-f0-9]+$",
                    "description": "The license id (hex key hash)."
                  },
                  "entitlements": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "The override, or null to clear it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entitlements updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EntitlementsResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Usage statistics",
        "description": "Activation and validation stats for the tenant. Cheap to poll. `?days` trims the daily series (default 30, max 60).",
        "operationId": "getUsage",
        "x-required-scopes": [
          "usage:read"
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "description": "Number of days in the daily series (1-60).",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage series + summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/test-purchase": {
      "post": {
        "tags": [
          "Test purchase"
        ],
        "summary": "Simulate a purchase",
        "description": "Run an end-to-end purchase simulation. Mints a real (test-marked) license through the production issuance pipeline and emails it to the tenant's own inbox. Returns the masked key plus a run id pollable for 24h.",
        "operationId": "createTestPurchase",
        "x-required-scopes": [
          "licenses:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "productId"
                ],
                "properties": {
                  "productId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "keyTypeId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Simulation run result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestPurchaseRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/test-purchase/{runId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/RunId"
        }
      ],
      "get": {
        "tags": [
          "Test purchase"
        ],
        "summary": "Simulation result",
        "description": "Poll a test-purchase run by id. Cheap to poll. A foreign or unknown run id is `404`.",
        "operationId": "getTestPurchaseRun",
        "x-required-scopes": [
          "licenses:read"
        ],
        "responses": {
          "200": {
            "description": "Simulation run result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestPurchaseRun"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/login/device": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Start a device-authorization login (public)",
        "description": "RFC 8628. Returns a device_code (secret, poll with it) and a short user_code the human enters at verification_uri. No auth.",
        "operationId": "startDeviceLogin",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Requested scopes; defaults to all except danger."
                  },
                  "name": {
                    "type": "string",
                    "description": "Token name, e.g. 'CLI on host'."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Started",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "device_code",
                    "user_code",
                    "verification_uri",
                    "interval",
                    "expires_in"
                  ],
                  "properties": {
                    "device_code": {
                      "type": "string"
                    },
                    "user_code": {
                      "type": "string"
                    },
                    "verification_uri": {
                      "type": "string"
                    },
                    "verification_uri_complete": {
                      "type": "string"
                    },
                    "interval": {
                      "type": "integer"
                    },
                    "expires_in": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/login/device/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Poll a device-authorization login (public)",
        "description": "Poll with device_code at the returned interval. 200 with status pending|denied|approved; the token is returned once when approved.",
        "operationId": "pollDeviceLogin",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_code"
                ],
                "properties": {
                  "device_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "denied",
                        "approved"
                      ]
                    },
                    "token": {
                      "type": "string"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/logout": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Revoke the calling token",
        "description": "Self-revoke: revokes the bearer token used for this request. Backs keylight logout. Idempotent.",
        "operationId": "logout",
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "revoked"
                  ],
                  "properties": {
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "klm_<random>",
        "description": "A Keylight personal access token, minted in the dashboard (Settings -> API tokens). Send as `Authorization: Bearer klm_...`. Each token carries a fixed scope set; per-operation scope requirements are listed in `x-required-scopes`. Confirm-gated operations additionally require the `danger` scope."
      }
    },
    "parameters": {
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Opaque pagination cursor from a previous page's `nextCursor`. Omit for the first page.",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size (1-100).",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "ConfirmationId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The confirmation id (32 hex chars) from a gated `202`.",
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{32}$"
        }
      },
      "ConfirmationHeader": {
        "name": "X-Keylight-Confirmation",
        "in": "header",
        "required": false,
        "description": "The approved confirmation id to consume. Omit on the first call (the server returns a `202` pending confirmation); supply it to re-issue an approved gated call. The request parameters must be byte-identical to the ones that were approved.",
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{32}$"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A client-chosen key that makes this call safe to retry. The first success is cached for 24h; a replay with the same key returns the stored body with `Idempotent-Replay: true` and does not re-execute.",
        "schema": {
          "type": "string"
        }
      },
      "ProductId": {
        "name": "productId",
        "in": "path",
        "required": true,
        "description": "The app (product) id.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]{0,31}$"
        }
      },
      "KeyTypeId": {
        "name": "keyTypeId",
        "in": "path",
        "required": true,
        "description": "The key type id within the app.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9_-]+$"
        }
      },
      "LicenseId": {
        "name": "licenseId",
        "in": "path",
        "required": true,
        "description": "The license id — the sha256 key hash (hex) from list/detail/mint.",
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]+$"
        }
      },
      "CustomerId": {
        "name": "customerId",
        "in": "path",
        "required": true,
        "description": "The customer id — the sha256 email hash (64 hex chars).",
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$"
        }
      },
      "Provider": {
        "name": "provider",
        "in": "path",
        "required": true,
        "description": "A merchant-of-record provider slug (e.g. `paddle`, `gumroad`, `lemonsqueezy`, `polar`, `creem`, `shopify`). Not `stripe` — Stripe has dedicated endpoints.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z]+$"
        }
      },
      "RunId": {
        "name": "runId",
        "in": "path",
        "required": true,
        "description": "The test-purchase run id (32 hex chars).",
        "schema": {
          "type": "string",
          "pattern": "^[a-f0-9]{32}$"
        }
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Minimum seconds to wait before retrying/polling.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplay": {
        "description": "Present and `true` when this response was replayed from the idempotency cache rather than re-executed.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request (bad JSON or invalid parameters).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, expired, or revoked token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The operation requires a higher Keylight plan.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The token lacks a required scope, or a confirmation was denied, expired, already consumed, or its parameters did not match.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist, or belongs to another tenant (indistinguishable — no existence leak).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "The HTTP method is not supported on this path.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the resource's current state (e.g. already revoked, live licenses reference the key type, the only key type).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "The request was well-formed but semantically invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited. Retry after the `Retry-After` header.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PendingConfirmation": {
        "description": "The operation is confirm-gated and no valid confirmation was supplied. A pending confirmation was created — open `approveUrl` in a dashboard session, then poll `GET /v1/confirmations/{id}` and re-issue with the `X-Keylight-Confirmation` header once approved.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PendingConfirmation"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "string",
            "description": "A human-readable error message."
          }
        }
      },
      "TokenIdentity": {
        "type": "object",
        "required": [
          "tenantId",
          "tokenId",
          "scopes"
        ],
        "properties": {
          "tenantId": {
            "type": "string"
          },
          "tokenId": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          }
        }
      },
      "Scope": {
        "type": "string",
        "enum": [
          "products:read",
          "products:write",
          "licenses:read",
          "licenses:write",
          "integrations:read",
          "integrations:write",
          "customers:read",
          "customers:write",
          "usage:read",
          "danger"
        ]
      },
      "ConfirmationStatus": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-f0-9]{32}$"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "expired",
              "consumed"
            ],
            "description": "Terminal states are `approved`, `denied`, and `expired`."
          }
        }
      },
      "PendingConfirmation": {
        "type": "object",
        "required": [
          "confirmation"
        ],
        "properties": {
          "confirmation": {
            "type": "object",
            "required": [
              "id",
              "status",
              "approveUrl",
              "expiresInSeconds"
            ],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-f0-9]{32}$"
              },
              "status": {
                "type": "string",
                "const": "pending"
              },
              "approveUrl": {
                "type": "string",
                "format": "uri",
                "description": "Open in a dashboard session to approve or deny."
              },
              "expiresInSeconds": {
                "type": "integer",
                "description": "Seconds until the pending confirmation expires."
              }
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "description": "An app. Encrypted Stripe override secrets are stripped from API responses. Additional settings fields may appear.",
        "required": [
          "productId",
          "displayName",
          "keyPrefix"
        ],
        "properties": {
          "productId": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "keyPrefix": {
            "type": "string",
            "description": "Key prefix (e.g. `CLMD`)."
          },
          "purchaseURL": {
            "type": "string"
          },
          "supportEmail": {
            "type": "string"
          },
          "freeTierEnabled": {
            "type": "boolean"
          },
          "testModeEnabled": {
            "type": "boolean"
          },
          "enabledIntegrations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stripeConnectEnabled": {
            "type": "boolean"
          }
        },
        "additionalProperties": true
      },
      "CreateProductBody": {
        "type": "object",
        "required": [
          "displayName",
          "keyPrefix",
          "supportEmail"
        ],
        "properties": {
          "displayName": {
            "type": "string",
            "minLength": 1
          },
          "keyPrefix": {
            "type": "string",
            "minLength": 1
          },
          "supportEmail": {
            "type": "string",
            "minLength": 1
          },
          "purchaseURL": {
            "type": "string"
          },
          "sdk": {
            "type": "string",
            "description": "Optional SDK/platform hint."
          }
        }
      },
      "UpdateProductBody": {
        "type": "object",
        "description": "Partial update — omitted fields are unchanged.",
        "properties": {
          "supportEmail": {
            "type": "string",
            "minLength": 1
          },
          "purchaseURL": {
            "type": "string"
          }
        }
      },
      "SetTestModeBody": {
        "type": "object",
        "required": [
          "stripeTestSecretKey",
          "stripeTestWebhookSecret"
        ],
        "properties": {
          "stripeTestSecretKey": {
            "type": "string",
            "minLength": 1
          },
          "stripeTestWebhookSecret": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "KeyType": {
        "type": "object",
        "description": "A license key type on an app.",
        "required": [
          "keyTypeId"
        ],
        "properties": {
          "keyTypeId": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "activationLimit": {
            "type": "integer"
          },
          "durationDays": {
            "type": [
              "integer",
              "null"
            ]
          },
          "entitlements": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stripePriceId": {
            "type": [
              "string",
              "null"
            ]
          },
          "stripePriceVerified": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "CreateKeyTypeBody": {
        "type": "object",
        "properties": {
          "keyTypeId": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "activationLimit": {
            "type": "number"
          },
          "durationDays": {
            "type": [
              "number",
              "null"
            ]
          },
          "entitlements": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UpdateKeyTypeBody": {
        "type": "object",
        "description": "Partial update — omitted fields are unchanged. `keyTypeId` is immutable.",
        "properties": {
          "displayName": {
            "type": "string"
          },
          "activationLimit": {
            "type": "number"
          },
          "durationDays": {
            "type": [
              "number",
              "null"
            ]
          },
          "entitlements": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "License": {
        "type": "object",
        "description": "A license in list form. The raw key is never present — only `displayKey` (masked).",
        "required": [
          "id",
          "productId",
          "keyTypeId",
          "status",
          "displayKey",
          "activationLimit",
          "activationCount",
          "expiresAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The license id (sha256 key hash, hex)."
          },
          "productId": {
            "type": "string"
          },
          "keyTypeId": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "displayKey": {
            "type": "string",
            "description": "Masked display form of the key."
          },
          "activationLimit": {
            "type": "integer"
          },
          "activationCount": {
            "type": "integer"
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix ms."
          },
          "createdAt": {
            "type": "integer",
            "description": "Unix ms."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Unix ms."
          }
        }
      },
      "LicenseDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/License"
          },
          {
            "type": "object",
            "properties": {
              "billingModel": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "subscriptionStatus": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "remintedFromId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "remintedToId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "migratedAt": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "migrationSource": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "isTest": {
                "type": "boolean"
              }
            }
          }
        ]
      },
      "Device": {
        "type": "object",
        "required": [
          "instanceId",
          "name",
          "status",
          "firstSeenAt",
          "lastSeenAt"
        ],
        "properties": {
          "instanceId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "deactivated"
            ]
          },
          "firstSeenAt": {
            "type": "integer",
            "description": "Unix ms."
          },
          "lastSeenAt": {
            "type": "integer",
            "description": "Unix ms."
          }
        }
      },
      "Activity": {
        "type": "object",
        "required": [
          "action",
          "actorType",
          "actorId",
          "timestamp"
        ],
        "properties": {
          "action": {
            "type": "string"
          },
          "actorType": {
            "type": "string"
          },
          "actorId": {
            "type": "string"
          },
          "timestamp": {
            "type": "integer",
            "description": "Unix ms."
          }
        }
      },
      "MintLicenseBody": {
        "type": "object",
        "required": [
          "productId"
        ],
        "properties": {
          "productId": {
            "type": "string",
            "minLength": 1
          },
          "keyTypeId": {
            "type": "string"
          },
          "customerEmail": {
            "type": "string"
          },
          "customerName": {
            "type": "string"
          },
          "sendEmail": {
            "type": "boolean",
            "default": false,
            "description": "Deliver the key to the linked customer by email (default off for API mints)."
          }
        }
      },
      "MintLicenseResult": {
        "type": "object",
        "required": [
          "license",
          "licenseKey",
          "emailedCustomer"
        ],
        "properties": {
          "license": {
            "$ref": "#/components/schemas/LicenseDetail"
          },
          "licenseKey": {
            "type": "string",
            "description": "The plaintext key — returned once, never retrievable again."
          },
          "emailedCustomer": {
            "type": "boolean"
          }
        }
      },
      "RemintLicenseResult": {
        "type": "object",
        "required": [
          "license",
          "licenseKey",
          "remintedFromId"
        ],
        "properties": {
          "license": {
            "$ref": "#/components/schemas/LicenseDetail"
          },
          "licenseKey": {
            "type": "string",
            "description": "The plaintext replacement key — returned once."
          },
          "remintedFromId": {
            "type": "string",
            "description": "The id of the license this replaced."
          }
        }
      },
      "ImportLicensesBody": {
        "type": "object",
        "required": [
          "productId",
          "rows"
        ],
        "properties": {
          "productId": {
            "type": "string",
            "minLength": 1
          },
          "migrationSource": {
            "type": "string"
          },
          "sendEmail": {
            "type": "boolean",
            "default": false
          },
          "rows": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "email",
                "key_type"
              ],
              "properties": {
                "email": {
                  "type": "string",
                  "minLength": 1
                },
                "key_type": {
                  "type": "string",
                  "minLength": 1
                },
                "license_key": {
                  "type": "string",
                  "description": "Existing key to preserve; empty to mint a fresh one."
                },
                "name": {
                  "type": "string"
                },
                "expires_at": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ImportLicensesResult": {
        "type": "object",
        "required": [
          "imported",
          "failed",
          "skipped"
        ],
        "properties": {
          "imported": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          }
        }
      },
      "LicenseDetailResponse": {
        "type": "object",
        "required": [
          "license",
          "devices",
          "predecessor",
          "successor",
          "activity"
        ],
        "properties": {
          "license": {
            "$ref": "#/components/schemas/LicenseDetail"
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Device"
            }
          },
          "predecessor": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/LicenseDetail"
              },
              {
                "type": "null"
              }
            ]
          },
          "successor": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/LicenseDetail"
              },
              {
                "type": "null"
              }
            ]
          },
          "activity": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Activity"
            }
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The customer id (sha256 email hash)."
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "createdAt": {
            "type": "integer",
            "description": "Unix ms."
          }
        }
      },
      "CreateCustomerResult": {
        "type": "object",
        "required": [
          "customer",
          "created"
        ],
        "properties": {
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "created": {
            "type": "boolean",
            "description": "True on a genuine create, false when an existing customer was refreshed."
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string"
          },
          "amountCents": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "productName": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "Unix ms."
          }
        }
      },
      "CustomerDetailResponse": {
        "type": "object",
        "required": [
          "customer",
          "licenses",
          "totalRevenueCents",
          "payments",
          "activity"
        ],
        "properties": {
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "licenses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LicenseDetail"
            }
          },
          "totalRevenueCents": {
            "type": "integer"
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "activity": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Activity"
            }
          }
        }
      },
      "EntitlementsResult": {
        "type": "object",
        "required": [
          "entitlements",
          "overridden",
          "keyTypeDefault",
          "license"
        ],
        "properties": {
          "entitlements": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The effective entitlements after the change."
          },
          "overridden": {
            "type": "boolean",
            "description": "Whether an override is now in place."
          },
          "keyTypeDefault": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The key-type default entitlements."
          },
          "license": {
            "$ref": "#/components/schemas/LicenseDetail"
          }
        }
      },
      "ConnectIntegrationBody": {
        "type": "object",
        "description": "Single-slot providers submit `secret`. Env-capable providers (paddle, lemonsqueezy, polar) submit `environment` plus `webhookSecret`, and `apiKey` where the provider needs one. Gumroad ignores `secret`.",
        "properties": {
          "secret": {
            "type": "string",
            "description": "Single-slot platforms: the pasted webhook secret."
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Env-capable providers (paddle, lemonsqueezy, polar): required — which environment slot to configure."
          },
          "webhookSecret": {
            "type": "string",
            "description": "Env-capable providers: the environment's webhook signing secret."
          },
          "apiKey": {
            "type": "string",
            "description": "Env-capable providers: required for Paddle, optional for Lemon Squeezy and Polar (a blank value preserves the stored key)."
          }
        }
      },
      "ConnectIntegrationResult": {
        "type": "object",
        "required": [
          "connected",
          "provider"
        ],
        "properties": {
          "connected": {
            "type": "boolean",
            "const": true
          },
          "provider": {
            "type": "string"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Env-capable providers — the slot that was configured."
          },
          "alreadyConnected": {
            "type": "boolean",
            "description": "True when the provider was already connected (no-op)."
          },
          "pingUrl": {
            "type": "string",
            "format": "uri",
            "description": "Gumroad only — the generated ping URL, returned once."
          }
        }
      },
      "ProductIntegrationBody": {
        "type": "object",
        "description": "Provide exactly one of `enabled`, `prices`, or `environments`. `enabled` cannot be combined with the mapping fields.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Enable/disable the provider for this app (no mapping changes)."
          },
          "prices": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "keyTypeId -> price id."
          },
          "environments": {
            "type": "object",
            "description": "Paddle per-environment price maps.",
            "properties": {
              "sandbox": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "live": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ProductIntegrationResult": {
        "type": "object",
        "required": [
          "productId",
          "provider",
          "enabled"
        ],
        "properties": {
          "productId": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "mappedPrices": {
            "type": "integer",
            "description": "Present on a mapping update — how many prices are now mapped."
          }
        }
      },
      "EnvironmentStatus": {
        "type": "object",
        "description": "Per-environment credential and mapping state for an env-capable provider (paddle, lemonsqueezy, polar).",
        "required": [
          "configured",
          "mappedPrices",
          "credentialsVerifiedAt",
          "firstSuccessfulEventAt",
          "lastErrorCode"
        ],
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "mappedPrices": {
            "type": "integer"
          },
          "credentialsVerifiedAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "firstSuccessfulEventAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "lastErrorCode": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProviderStatus": {
        "type": "object",
        "required": [
          "provider",
          "label",
          "connected",
          "mappedPrices",
          "products"
        ],
        "properties": {
          "provider": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "connected": {
            "type": "boolean"
          },
          "mappedPrices": {
            "type": "integer"
          },
          "products": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "App ids with this provider enabled."
          },
          "environments": {
            "type": "object",
            "description": "Env-capable providers only (paddle, lemonsqueezy, polar) — per-environment credential/mapping state.",
            "properties": {
              "sandbox": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/EnvironmentStatus"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "live": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/EnvironmentStatus"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        }
      },
      "StripeSlot": {
        "type": "object",
        "required": [
          "connected",
          "accountId"
        ],
        "properties": {
          "connected": {
            "type": "boolean"
          },
          "accountId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "StripeConnectStatus": {
        "type": "object",
        "required": [
          "connected",
          "live",
          "sandbox",
          "products"
        ],
        "properties": {
          "connected": {
            "type": "boolean"
          },
          "live": {
            "$ref": "#/components/schemas/StripeSlot"
          },
          "sandbox": {
            "$ref": "#/components/schemas/StripeSlot"
          },
          "products": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "productId",
                "stripeConnectEnabled",
                "accountId",
                "testAccountId"
              ],
              "properties": {
                "productId": {
                  "type": "string"
                },
                "stripeConnectEnabled": {
                  "type": "boolean"
                },
                "accountId": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "testAccountId": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "IntegrationsOverview": {
        "type": "object",
        "required": [
          "providers",
          "stripe"
        ],
        "properties": {
          "providers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProviderStatus"
            }
          },
          "stripe": {
            "$ref": "#/components/schemas/StripeConnectStatus"
          }
        }
      },
      "WebhookSettings": {
        "type": "object",
        "required": [
          "url",
          "hasSigningSecret",
          "disableCustomerEmail",
          "events"
        ],
        "properties": {
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "hasSigningSecret": {
            "type": "boolean",
            "description": "Whether a signing secret is on file — never the secret itself."
          },
          "disableCustomerEmail": {
            "type": "boolean"
          },
          "events": {
            "type": "object",
            "required": [
              "activated",
              "deactivated"
            ],
            "properties": {
              "activated": {
                "type": "boolean"
              },
              "deactivated": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "WebhookUpdateBody": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The license-delivery webhook URL (https, public host). null clears it."
          },
          "disableCustomerEmail": {
            "type": "boolean",
            "description": "Omitted keeps the current value."
          },
          "events": {
            "type": "object",
            "properties": {
              "activated": {
                "type": "boolean"
              },
              "deactivated": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "WebhookUpdateResult": {
        "type": "object",
        "required": [
          "url",
          "disableCustomerEmail",
          "events",
          "signingSecret"
        ],
        "properties": {
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "disableCustomerEmail": {
            "type": "boolean"
          },
          "events": {
            "type": "object",
            "required": [
              "activated",
              "deactivated"
            ],
            "properties": {
              "activated": {
                "type": "boolean"
              },
              "deactivated": {
                "type": "boolean"
              }
            }
          },
          "signingSecret": {
            "type": [
              "string",
              "null"
            ],
            "description": "The signing secret — returned once, only on the approving response."
          }
        }
      },
      "UsageDay": {
        "type": "object",
        "required": [
          "day",
          "activations",
          "validations",
          "activeDevices",
          "mau",
          "byCountry",
          "byVersion",
          "byPlatform"
        ],
        "properties": {
          "day": {
            "type": "string",
            "description": "The day (YYYY-MM-DD)."
          },
          "activations": {
            "type": "integer"
          },
          "validations": {
            "type": "integer"
          },
          "activeDevices": {
            "type": "integer"
          },
          "mau": {
            "type": "integer"
          },
          "byCountry": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "byVersion": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "byPlatform": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        }
      },
      "UsageResponse": {
        "type": "object",
        "required": [
          "days",
          "summary"
        ],
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageDay"
            },
            "description": "Newest first."
          },
          "summary": {
            "type": "object",
            "required": [
              "activeDevices",
              "mau",
              "activations7d",
              "validations7d"
            ],
            "properties": {
              "activeDevices": {
                "type": "integer"
              },
              "mau": {
                "type": "integer"
              },
              "activations7d": {
                "type": "integer"
              },
              "validations7d": {
                "type": "integer"
              }
            }
          }
        }
      },
      "TestPurchaseRun": {
        "type": "object",
        "required": [
          "id",
          "status",
          "createdAt",
          "productId",
          "keyTypeId",
          "licenseKeyMasked",
          "keyHash",
          "email",
          "isTest"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The run id (32 hex chars)."
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "Unix ms."
          },
          "productId": {
            "type": "string"
          },
          "keyTypeId": {
            "type": [
              "string",
              "null"
            ]
          },
          "licenseKeyMasked": {
            "type": "string",
            "description": "Masked license key — the raw key is emailed to the tenant's own inbox."
          },
          "keyHash": {
            "type": "string",
            "description": "The license id (sha256 key hash)."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "The inbox the test key was emailed to."
          },
          "isTest": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "ProductPage": {
        "type": "object",
        "description": "A page of apps.",
        "required": [
          "items",
          "nextCursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as the `cursor` query parameter to fetch the next page. Null on the last page."
          }
        }
      },
      "LicensePage": {
        "type": "object",
        "description": "A page of licenses.",
        "required": [
          "items",
          "nextCursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/License"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as the `cursor` query parameter to fetch the next page. Null on the last page."
          }
        }
      },
      "CustomerPage": {
        "type": "object",
        "description": "A page of customers.",
        "required": [
          "items",
          "nextCursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as the `cursor` query parameter to fetch the next page. Null on the last page."
          }
        }
      }
    }
  }
}