openapi: 3.0.3
info:
  title: WebFry API
  version: 0.9.5
  description: |
    Current OpenAPI description for the WebFry `/api` scope (Starter/Pro stage).

    Authentication modes:
    - `X-API-Key` header for most API endpoints.
    - session cookie (`auth_session`) for `/api/regenerate` (dashboard action).
servers:
  - url: https://webfry.dev/api
    description: Production
  - url: http://127.0.0.1:9000/api
    description: Local development

tags:
  - name: auth
  - name: password
  - name: hash
  - name: tools

paths:
  /get_api_key:
    post:
      tags: [auth]
      summary: Get API key from email/password
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyRequest'
      responses:
        '200':
          description: API key issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyResponse'
        '400': { description: Invalid request payload }
        '401': { description: Invalid credentials }
        '500': { description: Internal server error }

  /new_api_key:
    post:
      tags: [auth]
      summary: Rotate API key using current API key
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: API key rotated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyResponse'
        '401': { description: Missing or invalid API key }
        '500': { description: Internal server error }

  /regenerate:
    post:
      tags: [auth]
      summary: Rotate API key using dashboard session
      security:
        - SessionCookie: []
      responses:
        '200':
          description: API key rotated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyResponse'
        '401': { description: Login required }
        '500': { description: Internal server error }

  /user_info:
    post:
      tags: [auth]
      summary: Return account and current plan usage details
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: User profile and usage snapshot
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserInfoResponse'
        '401': { description: Missing or invalid API key }
        '500': { description: Internal server error }

  /password_check:
    post:
      tags: [password]
      summary: Evaluate password strength
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordCheckBody'
      responses:
        '200':
          description: Strength result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StrengthResult'
        '400': { description: Invalid payload }
        '401': { description: Unauthorized }
        '429': { description: Starter plan limit reached }

  /hash_lookup:
    post:
      tags: [hash]
      summary: Reverse hash lookup (authenticated)
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HashLookupBody'
      responses:
        '200':
          description: Lookup results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HashLookupOutput'
        '400': { description: Invalid hash input }
        '401': { description: Unauthorized }
        '502': { description: Hash backend failed }

  /hash_lookup_site:
    post:
      tags: [hash]
      summary: Reverse hash lookup (public site helper)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HashLookupBody'
      responses:
        '200':
          description: Lookup results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HashLookupOutput'
        '400': { description: Invalid hash input }
        '502': { description: Hash backend failed }

  /hash_generator:
    post:
      tags: [hash]
      summary: Generate hash from plaintext
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HashGeneratorRequest'
      responses:
        '200':
          description: Generated hash
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HashGeneratorResponse'
        '401': { description: Unauthorized }
        '429': { description: Starter plan limit reached }

  /ip_info:
    post:
      tags: [tools]
      summary: Geo/IP metadata lookup
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IPRetrieverRequest'
      responses:
        '200':
          description: IP metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeoInfo2'
        '401': { description: Unauthorized }
        '500': { description: Lookup failure }

  /base64:
    post:
      tags: [tools]
      summary: Base64 encode/decode
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Base64Request'
      responses:
        '200':
          description: Base64 result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Base64Response'
        '400': { description: Invalid input }
        '401': { description: Unauthorized }

  /entropy:
    post:
      tags: [password]
      summary: Calculate entropy and brute-force estimate
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntropyRequest'
      responses:
        '200':
          description: Entropy result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EntropyResponse'
        '401': { description: Unauthorized }

  /hash_identifier:
    post:
      tags: [hash]
      summary: Identify probable hash format
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HashIdentifierRequest'
      responses:
        '200':
          description: Identification result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HashIdentifierResponse'
        '401': { description: Unauthorized }

  /generate_random_key:
    post:
      tags: [tools]
      summary: Generate random cryptographic key material
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Generated key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateRandomKeyResponse'
        '401': { description: Unauthorized }

  /jwt_decoder:
    post:
      tags: [tools]
      summary: Decode JWT parts (no signature verification)
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JwtRequest'
      responses:
        '200':
          description: Decoded output or inline error message
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JwtResponse'
        '400': { description: Invalid JWT input }
        '401': { description: Unauthorized }

  /secure_encrypt:
    post:
      tags: [tools]
      summary: Encrypt text with password-derived key
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CryptoRequest'
      responses:
        '200':
          description: Encryption result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CryptoResponse'
        '400': { description: Encryption failed or invalid input }
        '401': { description: Unauthorized }

  /secure_decrypt:
    post:
      tags: [tools]
      summary: Decrypt text with password-derived key
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CryptoRequest'
      responses:
        '200':
          description: Decryption result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CryptoResponse'
        '400': { description: Decryption failed or invalid input }
        '401': { description: Unauthorized }

  /json_format:
    post:
      tags: [tools]
      summary: Pretty-print JSON
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JsonRequest'
      responses:
        '200':
          description: Formatted JSON text
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JsonResponse'
        '400': { description: Invalid JSON input }
        '401': { description: Unauthorized }

  /json_minify:
    post:
      tags: [tools]
      summary: Minify JSON
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JsonRequest'
      responses:
        '200':
          description: Minified JSON text
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JsonResponse'
        '400': { description: Invalid JSON input }
        '401': { description: Unauthorized }

  /common_pwd:
    post:
      tags: [password]
      summary: Check if password appears in common-password dataset
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordRequest'
      responses:
        '200':
          description: Common-password result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PasswordResponse'
        '401': { description: Unauthorized }

  /suggestion:
    post:
      tags: [tools]
      summary: Submit feature suggestion
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SuggestionInput'
      responses:
        '200':
          description: Suggestion accepted
          content:
            text/plain:
              schema:
                type: string
                example: Suggestion received
        '400': { description: Invalid message length }
        '401': { description: Unauthorized }
        '500': { description: Storage failure }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    SessionCookie:
      type: apiKey
      in: cookie
      name: auth_session

  schemas:
    ApiKeyRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string

    ApiKeyResponse:
      type: object
      required: [api_key]
      properties:
        api_key:
          type: string

    UserInfoResponse:
      type: object
      properties:
        email:
          type: string
        paid_until:
          type: string
          nullable: true
        plan:
          type: string
          example: starter
        api_usage:
          type: integer
        max_usage_for_plan:
          type: integer
          nullable: true
        created_at:
          type: string

    PasswordCheckBody:
      type: object
      required: [password]
      properties:
        password:
          type: string

    StrengthResult:
      type: object
      properties:
        score:
          type: integer
          minimum: 0
          maximum: 4
        label:
          type: string
        feedback:
          type: array
          items:
            type: string
        length:
          type: integer
          nullable: true
        entropy:
          type: number
          nullable: true
        charset_size:
          type: integer
          nullable: true
        estimated_crack_time:
          type: string
          nullable: true

    HashLookupBody:
      type: object
      required: [hashes]
      properties:
        hashes:
          type: string

    HashResult:
      type: object
      properties:
        hash:
          type: string
        plaintext:
          type: string
          nullable: true
        type:
          type: string
          nullable: true
        found:
          type: boolean

    HashSummary:
      type: object
      properties:
        total_searched:
          type: integer
        total_found:
          type: integer
        success_rate:
          type: number

    HashLookupOutput:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/HashResult'
        summary:
          $ref: '#/components/schemas/HashSummary'

    HashGeneratorRequest:
      type: object
      required: [algorithm, plaintext]
      properties:
        algorithm:
          type: string
          example: SHA256
        plaintext:
          type: string

    HashGeneratorResponse:
      type: object
      properties:
        algorithm:
          type: string
        hash:
          type: string

    IPRetrieverRequest:
      type: object
      required: [ip_string]
      properties:
        ip_string:
          type: string
          example: 8.8.8.8

    GeoInfo2:
      type: object
      properties:
        ip: { type: string, nullable: true }
        city: { type: string, nullable: true }
        region: { type: string, nullable: true }
        country: { type: string, nullable: true }
        country_iso: { type: string, nullable: true }
        country_flag: { type: string, nullable: true }
        postal: { type: string, nullable: true }
        latitude: { type: number, nullable: true }
        longitude: { type: number, nullable: true }
        timezone: { type: string, nullable: true }
        eu: { type: boolean, nullable: true }
        calling_code: { type: string, nullable: true }
        currency: { type: string, nullable: true }
        languages:
          type: array
          nullable: true
          items:
            type: string
        asn: { type: integer, nullable: true }
        isp: { type: string, nullable: true }
        org: { type: string, nullable: true }

    Base64Request:
      type: object
      required: [text, option]
      properties:
        text:
          type: string
        option:
          type: string
          enum: [encode, decode]

    Base64Response:
      type: object
      properties:
        answer:
          type: string
        error:
          type: string
          nullable: true

    EntropyRequest:
      type: object
      required: [password]
      properties:
        password:
          type: string

    EntropyResponse:
      type: object
      properties:
        entropy:
          type: number
        estimate_brute_force:
          type: string

    HashIdentifierRequest:
      type: object
      required: [hash]
      properties:
        hash:
          type: string

    HashIdentifierResponse:
      type: object
      properties:
        estimate:
          type: string

    GenerateRandomKeyResponse:
      type: object
      properties:
        random_key:
          type: string

    JwtRequest:
      type: object
      required: [token]
      properties:
        token:
          type: string

    JwtResponse:
      type: object
      properties:
        answer:
          type: string
        error:
          type: string

    CryptoRequest:
      type: object
      required: [text, password]
      properties:
        text:
          type: string
        password:
          type: string

    CryptoResponse:
      type: object
      properties:
        result:
          type: string
        error:
          type: string

    JsonRequest:
      type: object
      required: [text]
      properties:
        text:
          type: string

    JsonResponse:
      type: object
      properties:
        result:
          type: string
        error:
          type: string

    PasswordRequest:
      type: object
      required: [password]
      properties:
        password:
          type: string

    PasswordResponse:
      type: object
      properties:
        is_common:
          type: boolean

    SuggestionInput:
      type: object
      required: [message]
      properties:
        email:
          type: string
          nullable: true
        message:
          type: string
