{
  "openapi": "3.1.0",
  "info": {
    "title": "WoolKey Credential Generator",
    "version": "1.1.0",
    "description": "Generates cryptographically secure passwords and passphrases.\n\nStateless by design: nothing generated by this API is stored, logged, or retained on the server. The caller is responsible for whatever it does with the returned value.",
    "license": { "name": "MIT" }
  },
  "servers": [
    { "url": "https://api.woolkey.com", "description": "Production" },
    { "url": "/api", "description": "Relative to the deployed site root" }
  ],
  "security": [{ "ApiKeyHeader": [] }],
  "paths": {
    "/generate": {
      "get": {
        "operationId": "describeGenerator",
        "summary": "Capability descriptor",
        "description": "Machine-readable description of modes, options, limits, and defaults. No authentication required, so an agent can discover the contract before it holds a token.",
        "security": [],
        "responses": {
          "200": {
            "description": "Descriptor",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "generateCredential",
        "summary": "Generate password(s) or passphrase(s)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/GenerateRequest" },
              "examples": {
                "password": {
                  "summary": "24-character password with symbols",
                  "value": {
                    "mode": "password",
                    "options": {
                      "length": 24,
                      "includeLowercase": true,
                      "includeUppercase": true,
                      "includeNumbers": true,
                      "includeSymbols": true,
                      "avoidAmbiguous": true,
                      "excludedCharacters": ""
                    }
                  }
                },
                "passphrase": {
                  "summary": "Six-word passphrase",
                  "value": {
                    "mode": "passphrase",
                    "options": {
                      "wordCount": 6,
                      "separator": "hyphen",
                      "capitalize": false,
                      "addNumber": false
                    }
                  }
                },
                "batch": {
                  "summary": "Five passwords in one call",
                  "value": { "mode": "password", "count": 5, "options": { "length": 32 } }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated credentials",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/GenerateResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "413": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/NotConfigured" }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Liveness probe",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "const": "ok" },
                    "version": { "type": "string" },
                    "time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Shared secret configured in api/config.php. Alternatively send `Authorization: Bearer <token>`."
      }
    },
    "schemas": {
      "GenerateRequest": {
        "type": "object",
        "required": ["mode"],
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": ["password", "passphrase"],
            "description": "Which generator to run."
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "default": 1,
            "description": "How many credentials to return. All share the same options and therefore the same entropy figure."
          },
          "entropyMode": {
            "type": "string",
            "enum": ["system"],
            "default": "system",
            "description": "Only \"system\" is valid server-side. \"system+user\" exists in the browser API (window.WoolKeyAPI) where pointer and keyboard timing can be mixed in; sending it here returns 400 rather than silently downgrading."
          },
          "options": {
            "oneOf": [
              { "$ref": "#/components/schemas/PasswordOptions" },
              { "$ref": "#/components/schemas/PassphraseOptions" }
            ]
          }
        }
      },
      "PasswordOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "length": { "type": "integer", "minimum": 8, "maximum": 128, "default": 24 },
          "includeLowercase": { "type": "boolean", "default": true },
          "includeUppercase": { "type": "boolean", "default": true },
          "includeNumbers": { "type": "boolean", "default": true },
          "includeSymbols": { "type": "boolean", "default": false, "description": "Symbol set: !@#$%^&*()-_=+[]{};:,.?" },
          "avoidAmbiguous": { "type": "boolean", "default": false, "description": "Excludes 0O1Il5S8B." },
          "excludedCharacters": { "type": "string", "maxLength": 128, "default": "" }
        }
      },
      "PassphraseOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "wordCount": { "type": "integer", "minimum": 4, "maximum": 8, "default": 4 },
          "separator": { "type": "string", "enum": ["hyphen", "underscore", "dot", "space"], "default": "hyphen" },
          "capitalize": { "type": "boolean", "default": false },
          "addNumber": { "type": "boolean", "default": false, "description": "Appends a two-digit suffix. Not counted toward the reported entropy." }
        }
      },
      "GenerateResponse": {
        "type": "object",
        "required": ["mode", "count", "value", "entropy", "metadata", "results"],
        "properties": {
          "mode": { "type": "string", "enum": ["password", "passphrase"] },
          "count": { "type": "integer" },
          "value": { "type": "string", "description": "Convenience mirror of results[0].value." },
          "entropy": { "$ref": "#/components/schemas/Entropy" },
          "metadata": { "type": "object" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Result" }
          }
        }
      },
      "Result": {
        "type": "object",
        "required": ["value", "entropy", "metadata"],
        "properties": {
          "value": { "type": "string" },
          "entropy": { "$ref": "#/components/schemas/Entropy" },
          "metadata": { "type": "object" }
        }
      },
      "Entropy": {
        "type": "object",
        "required": ["bits", "label", "level"],
        "properties": {
          "bits": { "type": "number" },
          "label": { "type": "string", "enum": ["Weak", "Fair", "Strong", "Very strong", "Excellent"] },
          "level": { "type": "integer", "minimum": 0, "maximum": 4 }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "status"],
        "properties": {
          "error": { "type": "string" },
          "status": { "type": "integer" },
          "field": { "type": "string", "description": "Request field the caller should correct, when applicable." }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request. Out-of-range values are rejected, never silently clamped.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing or incorrect API key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Per-IP rate limit exceeded. See the Retry-After and X-RateLimit-* headers.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" } },
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotConfigured": {
        "description": "api/config.php is missing or has no api_token.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
