{
    "openapi": "3.1.0",
    "info": {
        "title": "PWF Auth API",
        "version": "2026-08-20",
        "summary": "License keys, sessions, trials, OTA updates and remote content for desktop applications.",
        "description": "REST API for PWF Auth (https://pwfauth.com). Every request is JSON over HTTPS.\n\nMost client endpoints authenticate with the `X-App-Secret` header (64-char hex from your dashboard). SDK-grade endpoints additionally wrap request AND response bodies in an encrypted envelope `{\"p\": base64(IV || AES-256-CBC ciphertext), \"t\": unix_ts, \"s\": hmac_sha256_hex(p + t, mac_key)}` — keys are derived from the app secret as `enc_key = SHA256(\"enc:\" + secret)`, `mac_key = SHA256(\"mac:\" + secret)`; envelopes older than ±300 s are rejected. Endpoints whose request body is `EncryptedEnvelope` in this spec are not usable with plain JSON — use an official SDK (PWFAuth on NuGet, pwfauth on npm/PyPI) or implement the envelope.\n\nHuman-readable reference with samples: https://pwfauth.com/api.php",
        "contact": { "name": "PWF Auth", "url": "https://pwfauth.com/contact.php" }
    },
    "servers": [ { "url": "https://pwfauth.com" } ],
    "tags": [
        { "name": "Licenses", "description": "Sessions, key validation, device resets" },
        { "name": "Trials", "description": "Free trial issuance" },
        { "name": "Accounts", "description": "Username/password app users" },
        { "name": "Content", "description": "Remote texts, slides, app info, pricing, changelog" },
        { "name": "Updates", "description": "OTA update channel" },
        { "name": "Admin", "description": "Dashboard automation (Bearer JWT or Personal Access Token)" }
    ],
    "paths": {
        "/api/auth/login.php": {
            "post": {
                "tags": ["Licenses"],
                "summary": "Start a license session",
                "description": "Encrypted-envelope endpoint. Inner request: `{license_key, hwid}`. Inner response: session_id, user block (key_type, expires_at, days_remaining, status), feature flags and heartbeat_interval.",
                "operationId": "licenseLogin",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                "responses": {
                    "200": { "description": "Encrypted envelope containing the login result.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                    "400": { "$ref": "#/components/responses/Error" },
                    "401": { "$ref": "#/components/responses/Error" },
                    "429": { "$ref": "#/components/responses/RateLimited" }
                }
            }
        },
        "/api/auth/heartbeat.php": {
            "post": {
                "tags": ["Licenses"],
                "summary": "Keep a session alive (kill-switch enforcement point)",
                "description": "Encrypted-envelope endpoint. Inner request: `{session_id, license_key?}`. Call every `heartbeat_interval` seconds. A banned / paused / expired / reset key answers `success:false` with the reason and the session is dropped.",
                "operationId": "heartbeat",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                "responses": {
                    "200": { "description": "Encrypted envelope: `{success, message}` or `{success:false, error_code, message}`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                    "400": { "$ref": "#/components/responses/Error" }
                }
            }
        },
        "/api/auth/logout.php": {
            "post": {
                "tags": ["Licenses"],
                "summary": "End a session",
                "description": "Encrypted-envelope endpoint. Inner request: `{session_id}`. Frees the device seat immediately.",
                "operationId": "logout",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                "responses": { "200": { "description": "Encrypted envelope acknowledgement.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } } }
            }
        },
        "/api/auth/check-key.php": {
            "post": {
                "tags": ["Licenses"],
                "summary": "Validate a key without starting a session",
                "description": "Accepts plain JSON or the encrypted envelope (auto-detected). The only stateful-free check — safe to call from scripts and CI.",
                "operationId": "checkKey",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["license_key"],
                    "properties": { "license_key": { "type": "string", "example": "XXXXX-XXXXX-XXXXX-XXXXX" } }
                } } } },
                "responses": {
                    "200": { "description": "Validation result.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckKeyResponse" } } } },
                    "400": { "$ref": "#/components/responses/Error" },
                    "401": { "$ref": "#/components/responses/Error" },
                    "429": { "$ref": "#/components/responses/RateLimited" }
                }
            }
        },
        "/api/auth/request-hwid-reset.php": {
            "post": {
                "tags": ["Licenses"],
                "summary": "Request a device (HWID) reset",
                "operationId": "requestHwidReset",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object",
                    "properties": {
                        "license_key": { "type": "string", "description": "Either license_key or username is required." },
                        "username": { "type": "string" },
                        "reason": { "type": "string" }
                    }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "400": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/auth/trial.php": {
            "post": {
                "tags": ["Trials"],
                "summary": "Issue a trial key bound to this device",
                "description": "One trial per HWID per app; per-IP caps apply (`TRIAL_LIMIT`). Returns the trial key plus a ready session.",
                "operationId": "createTrial",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["hwid"],
                    "properties": { "hwid": { "type": "string" } }
                } } } },
                "responses": {
                    "200": { "description": "Trial created (or the existing trial for this device is returned).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrialResponse" } } } },
                    "400": { "$ref": "#/components/responses/Error" },
                    "403": { "$ref": "#/components/responses/Error" }
                }
            }
        },
        "/api/auth/account-register.php": {
            "post": {
                "tags": ["Accounts"],
                "summary": "Register an app user account",
                "operationId": "accountRegister",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["username", "password"],
                    "properties": { "username": { "type": "string" }, "password": { "type": "string" }, "email": { "type": "string" } }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "400": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/auth/account-login.php": {
            "post": {
                "tags": ["Accounts"],
                "summary": "Log an app user in",
                "operationId": "accountLogin",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["username", "password", "hwid"],
                    "properties": { "username": { "type": "string" }, "password": { "type": "string" }, "hwid": { "type": "string" } }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "401": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/auth/change-password.php": {
            "post": {
                "tags": ["Accounts"],
                "summary": "Change an app user's password",
                "operationId": "changePassword",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["username", "current_password", "new_password"],
                    "properties": { "username": { "type": "string" }, "current_password": { "type": "string" }, "new_password": { "type": "string" } }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "401": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/app/text.php": {
            "get": {
                "tags": ["Content"],
                "summary": "Fetch remote texts",
                "description": "Encrypted-envelope response. `?name` narrows to a single text.",
                "operationId": "appText",
                "security": [ { "AppSecret": [] } ],
                "parameters": [ { "name": "name", "in": "query", "required": false, "schema": { "type": "string" } } ],
                "responses": { "200": { "description": "Encrypted envelope with the texts.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } } }
            }
        },
        "/api/app/slides.php": {
            "post": {
                "tags": ["Content"],
                "summary": "Fetch slide/banner content",
                "description": "Encrypted-envelope endpoint.",
                "operationId": "appSlides",
                "security": [ { "AppSecret": [] } ],
                "responses": { "200": { "description": "Encrypted envelope with the slides.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } } }
            }
        },
        "/api/app/info.php": {
            "get": {
                "tags": ["Content"],
                "summary": "Fetch app metadata",
                "description": "Encrypted-envelope response: app name, current version, maintenance flag, social links.",
                "operationId": "appInfo",
                "security": [ { "AppSecret": [] } ],
                "responses": { "200": { "description": "Encrypted envelope with the app info.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } } }
            }
        },
        "/api/app/social-click.php": {
            "post": {
                "tags": ["Content"],
                "summary": "Count a social-link click",
                "description": "Encrypted-envelope endpoint. Inner request: `{link_id}`.",
                "operationId": "socialClick",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                "responses": { "200": { "description": "Encrypted envelope acknowledgement.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } } }
            }
        },
        "/api/app/changelog.php": {
            "get": {
                "tags": ["Content"],
                "summary": "Public changelog feed",
                "operationId": "appChangelog",
                "parameters": [
                    { "name": "app_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } },
                    { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer" } },
                    { "name": "since", "in": "query", "required": false, "schema": { "type": "string" }, "description": "ISO date or version — only newer entries." },
                    { "name": "category", "in": "query", "required": false, "schema": { "type": "string" } }
                ],
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "404": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/app/pricing.php": {
            "get": {
                "tags": ["Content"],
                "summary": "Public pricing for an app",
                "operationId": "appPricing",
                "parameters": [
                    { "name": "app_id", "in": "query", "required": true, "schema": { "type": "string", "format": "uuid" } },
                    { "name": "type", "in": "query", "required": false, "schema": { "type": "string" } }
                ],
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "404": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/app/fulfill.php": {
            "post": {
                "tags": ["Content"],
                "summary": "Fulfill an approved order (server-to-server)",
                "description": "Authenticated by the per-app fulfillment secret in the body — not the app secret.",
                "operationId": "fulfillOrder",
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["order_id", "secret"],
                    "properties": { "order_id": { "type": "string" }, "secret": { "type": "string" }, "payment_ref": { "type": "string" } }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "401": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/update/check.php": {
            "post": {
                "tags": ["Updates"],
                "summary": "Check for an application update",
                "description": "Encrypted-envelope endpoint. Inner request: `{v, channel?, os?, arch?, hwid?, license_key?}`. Inner response includes version, sha256, file_size, is_mandatory, changelog and download_url.",
                "operationId": "updateCheck",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } },
                "responses": { "200": { "description": "Encrypted envelope with the update decision.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EncryptedEnvelope" } } } } }
            }
        },
        "/api/update/download.php": {
            "get": {
                "tags": ["Updates"],
                "summary": "Download an update binary",
                "operationId": "updateDownload",
                "security": [ { "AppSecret": [] } ],
                "parameters": [ { "name": "v", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Version to download." } ],
                "responses": {
                    "200": { "description": "The binary.", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } },
                    "404": { "$ref": "#/components/responses/Error" }
                }
            }
        },
        "/api/update/validate.php": {
            "post": {
                "tags": ["Updates"],
                "summary": "Validate a key in the update flow",
                "operationId": "updateValidate",
                "security": [ { "AppSecret": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["license_key"],
                    "properties": { "license_key": { "type": "string" } }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "401": { "$ref": "#/components/responses/Error" } }
            }
        },
        "/api/admin/login.php": {
            "post": {
                "tags": ["Admin"],
                "summary": "Obtain an admin Bearer token",
                "description": "Prefer a Personal Access Token (Panel → Profile → API Tokens, `pwf_…`) for scripts — it works as the Bearer value on every /api/admin/* endpoint without storing a password.",
                "operationId": "adminLogin",
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["username", "password"],
                    "properties": { "username": { "type": "string" }, "password": { "type": "string" } }
                } } } },
                "responses": {
                    "200": { "description": "JWT for the Authorization: Bearer header.", "content": { "application/json": { "schema": {
                        "type": "object", "properties": { "success": { "type": "boolean" }, "token": { "type": "string" } }
                    } } } },
                    "401": { "$ref": "#/components/responses/Error" },
                    "429": { "$ref": "#/components/responses/RateLimited" }
                }
            }
        },
        "/api/admin/keys.php": {
            "post": {
                "tags": ["Admin"],
                "summary": "Generate license keys",
                "description": "One of many /api/admin/* endpoints (apps, sessions, updates, feature-flags, webhooks, analytics, audit, resellers…) — all take the same Bearer auth.",
                "operationId": "adminCreateKeys",
                "security": [ { "BearerAuth": [] } ],
                "requestBody": { "required": true, "content": { "application/json": { "schema": {
                    "type": "object", "required": ["app_id", "count", "key_type"],
                    "properties": {
                        "app_id": { "type": "string", "format": "uuid" },
                        "count": { "type": "integer", "example": 100 },
                        "key_type": { "type": "string", "enum": ["days", "lifetime"] },
                        "duration_value": { "type": "integer", "example": 30 }
                    }
                } } } },
                "responses": { "200": { "$ref": "#/components/responses/Success" }, "401": { "$ref": "#/components/responses/Error" } }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "AppSecret": { "type": "apiKey", "in": "header", "name": "X-App-Secret", "description": "64-char hex app secret from your dashboard. Server-side only — never ship it in front-end code." },
            "BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT or PAT (pwf_…)" }
        },
        "responses": {
            "Success": { "description": "Standard success body: `{success: true, …}`.", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } }, "additionalProperties": true } } } },
            "Error": { "description": "Standard error body.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
            "RateLimited": {
                "description": "Rate limited — honour the Retry-After header.",
                "headers": { "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds to wait before retrying." } },
                "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
            }
        },
        "schemas": {
            "EncryptedEnvelope": {
                "type": "object",
                "description": "AES-256-CBC + HMAC-SHA256 envelope. p = base64(IV || ciphertext); s = hmac_sha256_hex(p + t, mac_key); t must be within ±300 s of server time.",
                "required": ["p", "t", "s"],
                "properties": {
                    "p": { "type": "string", "description": "base64(IV[16] || AES-256-CBC ciphertext of the inner JSON)" },
                    "t": { "type": "integer", "description": "Unix timestamp" },
                    "s": { "type": "string", "description": "hex HMAC-SHA256 over p + t with the derived mac key" }
                }
            },
            "ErrorResponse": {
                "type": "object",
                "properties": {
                    "success": { "type": "boolean", "const": false },
                    "message": { "type": "string" },
                    "error_code": { "type": "string", "description": "Stable machine-readable code, e.g. MISSING_FIELDS, INVALID_KEY, HWID_MISMATCH, EXPIRED, BANNED, PAUSED, MAINTENANCE, SESSION_EXPIRED, SESSION_MISMATCH, TOO_MANY_ATTEMPTS, TRIAL_DISABLED, TRIAL_USED, TRIAL_LIMIT, CRYPTO_ERROR" },
                    "retry_after": { "type": "integer", "description": "Present on TOO_MANY_ATTEMPTS — seconds until the lockout lifts." }
                }
            },
            "KeyInfo": {
                "type": "object",
                "properties": {
                    "status": { "type": "string", "enum": ["active", "unused", "paused", "banned", "expired"] },
                    "key_type": { "type": "string", "enum": ["days", "lifetime"] },
                    "duration": { "type": "integer" },
                    "activated_at": { "type": ["string", "null"], "format": "date-time" },
                    "expires_at": { "type": ["string", "null"], "format": "date-time", "description": "null = lifetime — do not parse as a date." },
                    "days_remaining": { "type": ["integer", "null"] },
                    "max_devices": { "type": "integer" }
                }
            },
            "CheckKeyResponse": {
                "type": "object",
                "properties": {
                    "success": { "type": "boolean" },
                    "valid": { "type": "boolean" },
                    "key": { "$ref": "#/components/schemas/KeyInfo" }
                }
            },
            "TrialResponse": {
                "type": "object",
                "properties": {
                    "success": { "type": "boolean" },
                    "message": { "type": "string" },
                    "trial_key": { "type": "string", "example": "TRIAL-1A2B3-C4D5E-F6A7B" },
                    "session_id": { "type": "string" },
                    "expires_at": { "type": "string", "format": "date-time" },
                    "days_remaining": { "type": "integer" },
                    "user": { "$ref": "#/components/schemas/KeyInfo" }
                }
            }
        }
    },
    "externalDocs": { "description": "Human-readable API reference", "url": "https://pwfauth.com/api.php" }
}
