openapi: "3.0.3" info: title: Licenta API version: "1.0.0" description: | Multi-tenant license management API. State-changing endpoints accept an optional `Idempotency-Key` header. Errors use a uniform envelope: `{ success: false, error: { code, message, fields? }, meta: { request_id } }`. servers: - url: /api/v1 security: - bearerAuth: [] tags: - name: Licensing - name: Management paths: /licenses/activate: post: tags: [Licensing] summary: Activate a device against a license operationId: activateLicense parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: type: object required: [license_key, device_fingerprint] properties: license_key: { type: string } device_fingerprint: { type: string } device_label: { type: string } platform: { type: string } responses: "200": { $ref: "#/components/responses/ActivationSuccess" } "403": { $ref: "#/components/responses/LicenseForbidden" } "404": { $ref: "#/components/responses/LicenseNotFound" } "422": { $ref: "#/components/responses/ValidationError" } "429": { $ref: "#/components/responses/RateLimited" } /licenses/validate: post: tags: [Licensing] summary: Validate a license online operationId: validateLicense parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: type: object required: [license_key] properties: license_key: { type: string } activation_id: { type: string } responses: "200": description: Validation result (valid:false is a normal outcome) content: application/json: schema: type: object properties: success: { type: boolean } data: type: object properties: valid: { type: boolean } reason: { type: string, nullable: true } entitlement: { $ref: "#/components/schemas/Entitlement" } "404": { $ref: "#/components/responses/LicenseNotFound" } /licenses/deactivate: post: tags: [Licensing] summary: Deactivate a device by activation id operationId: deactivateLicenseByKey parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: type: object required: [license_key, activation_id] properties: license_key: { type: string } activation_id: { type: string } responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/LicenseNotFound" } /licenses/checkout: post: tags: [Licensing] summary: Check out a floating/concurrent seat operationId: checkoutLicense parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: type: object required: [license_key] properties: license_key: { type: string } responses: "200": description: Seat checked out content: application/json: schema: type: object properties: success: { type: boolean } data: type: object properties: session_id: { type: string } activated_at: { type: string, format: date-time } "403": { $ref: "#/components/responses/LicenseForbidden" } "404": { $ref: "#/components/responses/LicenseNotFound" } /licenses/{id}/checkin: post: tags: [Licensing] summary: Release a floating/concurrent seat operationId: checkinLicense parameters: - $ref: "#/components/parameters/LicenseId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: type: object required: [session_id] properties: session_id: { type: string } responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/NotFound" } /licenses/{id}/activations: get: tags: [Licensing] summary: List activations for a license operationId: listActivations parameters: - $ref: "#/components/parameters/LicenseId" responses: "200": description: List of activations content: application/json: schema: type: array items: { $ref: "#/components/schemas/Activation" } "404": { $ref: "#/components/responses/NotFound" } /licenses/{id}/activations/{activation}: delete: tags: [Licensing] summary: Deactivate a specific device activation operationId: deleteActivation parameters: - $ref: "#/components/parameters/LicenseId" - name: activation in: path required: true schema: { type: string } responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/NotFound" } /licenses: get: tags: [Management] summary: List licenses (paginated) operationId: listLicenses responses: "200": description: Paginated licenses content: application/json: schema: type: object properties: data: { type: array, items: { $ref: "#/components/schemas/License" } } post: tags: [Management] summary: Create a license operationId: createLicense requestBody: required: true content: application/json: schema: type: object required: [product_id, plan_id, customer_id] properties: product_id: { type: string } plan_id: { type: string } customer_id: { type: string } max_activations: { type: integer, nullable: true } expires_at: { type: string, format: date-time, nullable: true } responses: "201": description: Created license (raw key returned once) content: application/json: schema: type: object properties: success: { type: boolean } data: { $ref: "#/components/schemas/License" } "422": { $ref: "#/components/responses/ValidationError" } /licenses/{id}: get: tags: [Management] summary: Get a license operationId: getLicense parameters: - $ref: "#/components/parameters/LicenseId" responses: "200": description: License content: application/json: schema: { $ref: "#/components/schemas/License" } "404": { $ref: "#/components/responses/NotFound" } put: tags: [Management] summary: Update a license operationId: updateLicense parameters: - $ref: "#/components/parameters/LicenseId" requestBody: required: true content: application/json: schema: type: object properties: max_activations: { type: integer, nullable: true } expires_at: { type: string, format: date-time, nullable: true } responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/NotFound" } /licenses/{id}/revoke: post: tags: [Management] summary: Revoke a license (terminal) operationId: revokeLicense parameters: - $ref: "#/components/parameters/LicenseId" responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/NotFound" } /licenses/{id}/renew: post: tags: [Management] summary: Renew a license operationId: renewLicense parameters: - $ref: "#/components/parameters/LicenseId" requestBody: content: application/json: schema: type: object properties: expires_at: { type: string, format: date-time } responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/NotFound" } /licenses/{id}/suspend: post: tags: [Management] summary: Suspend a license (reversible) operationId: suspendLicense parameters: - $ref: "#/components/parameters/LicenseId" responses: "200": { $ref: "#/components/responses/Success" } "404": { $ref: "#/components/responses/NotFound" } /products: get: tags: [Management] summary: List products operationId: listProducts responses: "200": description: Products content: application/json: schema: type: array items: { $ref: "#/components/schemas/Product" } post: tags: [Management] summary: Create a product operationId: createProduct requestBody: required: true content: application/json: schema: type: object required: [name, slug] properties: name: { type: string } slug: { type: string } responses: "201": description: Created product content: application/json: schema: { $ref: "#/components/schemas/Product" } /customers: get: tags: [Management] summary: List customers operationId: listCustomers responses: "200": description: Customers content: application/json: schema: type: array items: { $ref: "#/components/schemas/Customer" } post: tags: [Management] summary: Create a customer operationId: createCustomer requestBody: required: true content: application/json: schema: type: object required: [name, email] properties: name: { type: string } email: { type: string, format: email } responses: "201": description: Created customer content: application/json: schema: { $ref: "#/components/schemas/Customer" } /features: get: tags: [Management] summary: List features operationId: listFeatures responses: "200": description: Features content: application/json: schema: type: array items: { $ref: "#/components/schemas/Feature" } post: tags: [Management] summary: Create a feature operationId: createFeature requestBody: required: true content: application/json: schema: type: object required: [key, name] properties: key: { type: string } name: { type: string } responses: "201": description: Created feature content: application/json: schema: { $ref: "#/components/schemas/Feature" } /plan-features: post: tags: [Management] summary: Attach a feature value to a plan operationId: attachFeatureToPlan requestBody: required: true content: application/json: schema: type: object required: [plan_id, feature_id] properties: plan_id: { type: string } feature_id: { type: string } value: { nullable: true } responses: "201": { $ref: "#/components/responses/Success" } /license-features: post: tags: [Management] summary: Attach a feature value to a license operationId: attachFeatureToLicense requestBody: required: true content: application/json: schema: type: object required: [license_id, feature_id] properties: license_id: { type: string } feature_id: { type: string } value: { nullable: true } responses: "201": { $ref: "#/components/responses/Success" } /events: get: tags: [Management] summary: List license events (cursor-paginated) operationId: listEvents parameters: - name: after in: query schema: { type: string } responses: "200": description: Events content: application/json: schema: type: object properties: success: { type: boolean } data: type: array items: { $ref: "#/components/schemas/LicenseEvent" } meta: type: object properties: request_id: { type: string } components: securitySchemes: bearerAuth: type: http scheme: bearer description: "API key (lk_live_... / lk_test_...)" parameters: LicenseId: name: id in: path required: true schema: { type: string } description: License ULID IdempotencyKey: name: Idempotency-Key in: header required: false schema: { type: string } description: Client-generated idempotency key (24h replay window) schemas: Entitlement: type: object properties: token: { type: string, description: Signed EdDSA entitlement token } expires_at: { type: string, format: date-time } Activation: type: object properties: id: { type: string } license_id: { type: string } device_fingerprint: { type: string } device_label: { type: string, nullable: true } platform: { type: string, nullable: true } activated_at: { type: string, format: date-time, nullable: true } last_seen_at: { type: string, format: date-time, nullable: true } deactivated_at: { type: string, format: date-time, nullable: true } License: type: object properties: id: { type: string } status: { type: string, enum: [active, suspended, revoked, expired] } key_display: { type: string } max_activations: { type: integer, nullable: true } expires_at: { type: string, format: date-time, nullable: true } issued_at: { type: string, format: date-time, nullable: true } Product: type: object properties: id: { type: string } name: { type: string } slug: { type: string } Customer: type: object properties: id: { type: string } name: { type: string } email: { type: string } Feature: type: object properties: id: { type: string } key: { type: string } name: { type: string } LicenseEvent: type: object properties: id: { type: string } organization_id: { type: string } license_id: { type: string, nullable: true } type: { type: string, example: "license.activated" } payload: { type: object, nullable: true } created_at: { type: string, format: date-time } ErrorEnvelope: type: object properties: success: { type: boolean, example: false } error: type: object properties: code: { type: string } message: { type: string } fields: { type: object, nullable: true } meta: type: object properties: request_id: { type: string } responses: Success: description: Success content: application/json: schema: type: object properties: success: { type: boolean, example: true } ActivationSuccess: description: Activation with signed entitlement content: application/json: schema: type: object properties: success: { type: boolean, example: true } data: type: object properties: license: { $ref: "#/components/schemas/License" } activation: { $ref: "#/components/schemas/Activation" } entitlement: { $ref: "#/components/schemas/Entitlement" } LicenseNotFound: description: License not found content: application/json: schema: type: object properties: success: { type: boolean, example: false } error: type: object properties: code: { type: string, example: "LICENSE_NOT_FOUND" } LicenseForbidden: description: License state failure (not active / expired / limit reached) content: application/json: schema: type: object properties: success: { type: boolean, example: false } error: type: object properties: code: type: string enum: [LICENSE_NOT_ACTIVE, LICENSE_EXPIRED, ACTIVATION_LIMIT_REACHED, ACTIVATION_FAILED] message: { type: string } meta: type: object properties: request_id: { type: string } NotFound: description: Resource not found content: application/json: schema: type: object properties: success: { type: boolean, example: false } error: type: object properties: code: { type: string, example: "NOT_FOUND" } ValidationError: description: Validation error content: application/json: schema: { $ref: "#/components/schemas/ErrorEnvelope" } RateLimited: description: Rate limited headers: Retry-After: schema: { type: integer } content: application/json: schema: { $ref: "#/components/schemas/ErrorEnvelope" }