openapi: 3.0.3
info:
  title: KrakenHashes User API
  description: |
    REST API for programmatic access to KrakenHashes functionality.

    ## Authentication
    All endpoints require two headers:
    - `X-User-Email`: Your user email address
    - `X-API-Key`: Your 64-character API key

    ## Getting Started
    1. Log into the KrakenHashes web interface
    2. Navigate to Profile Settings
    3. Generate an API key (save it immediately - it won't be shown again!)
    4. Use the email and API key in your API requests

    ## Base URL
    - Production: `https://your-domain.com/api/v1`
    - Development: `http://localhost:31337/api/v1`

    ## Dynamic Validation
    Some validation limits are configurable via system settings:
    - `max_job_priority`: Maximum job priority value (default: 1000)
    - `require_client_for_hashlist`: Whether client_id is required for hashlist uploads (default: false)

  version: 1.0.0
  contact:
    name: KrakenHashes Support
    url: https://github.com/ZerkerEOD/krakenhashes

servers:
  - url: https://your-domain.com/api/v1
    description: Production server
  - url: http://localhost:31337/api/v1
    description: Development server

security:
  - UserAPIKey: []

tags:
  - name: Health
    description: API health check
  - name: Clients
    description: Manage clients (organizations/engagements)
  - name: Hashlists
    description: Upload and manage password hashlists
  - name: Jobs
    description: Create and manage cracking jobs
  - name: Agents
    description: Manage compute agents and registration vouchers
  - name: Metadata
    description: Helper endpoints for hash types, workflows, and presets

components:
  securitySchemes:
    UserAPIKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Your 64-character API key

  parameters:
    PageParam:
      name: page
      in: query
      description: Page number (1-indexed)
      schema:
        type: integer
        minimum: 1
        default: 1

    PageSizeParam:
      name: page_size
      in: query
      description: Items per page (max 100)
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

  schemas:
    Error:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Human-readable error message
        code:
          type: string
          description: Machine-readable error code
      example:
        error: "Client not found"
        code: "CLIENT_NOT_FOUND"

    Client:
      type: object
      required:
        - id
        - name
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique client identifier
        name:
          type: string
          description: Client name
        description:
          type: string
          nullable: true
          description: Client description
        domain:
          type: string
          nullable: true
          description: Client domain or contact information
        data_retention_months:
          type: integer
          nullable: true
          description: Data retention period in months (0 = keep forever, null = use system default)
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    Hashlist:
      type: object
      required:
        - id
        - name
        - hash_type
        - created_at
      properties:
        id:
          type: integer
          description: Unique hashlist identifier
        name:
          type: string
          description: Hashlist name
        client_id:
          type: string
          format: uuid
          nullable: true
          description: Associated client ID (may be null if client not required)
        hash_type:
          type: string
          description: Hash type name
        hash_type_id:
          type: integer
          description: Hashcat hash type ID
        hash_count:
          type: integer
          description: Total number of hashes
        cracked_count:
          type: integer
          description: Number of cracked hashes
        progress:
          type: number
          format: float
          description: Crack progress percentage
        status:
          type: string
          description: Hashlist processing status
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    Job:
      type: object
      required:
        - id
        - name
        - status
        - priority
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique job identifier
        name:
          type: string
          description: Job name
        status:
          type: string
          enum: [pending, running, paused, completed, failed, canceled]
          description: Job status
        priority:
          type: integer
          minimum: 1
          description: Job priority (1=lowest). Maximum determined by max_job_priority system setting (default 1000)
        max_agents:
          type: integer
          minimum: 0
          description: Maximum number of agents to assign (0=unlimited)
        increment_mode:
          type: string
          enum: [off, enabled, enabled_with_brain]
          description: Increment mode for variable-length attacks
        dispatched_percent:
          type: number
          format: float
          description: Percentage of work dispatched
        searched_percent:
          type: number
          format: float
          description: Percentage of keyspace searched
        cracked_count:
          type: integer
          description: Number of hashes cracked
        hashlist_id:
          type: integer
          description: Associated hashlist ID
        hashlist_name:
          type: string
          description: Associated hashlist name
        client_id:
          type: string
          format: uuid
          nullable: true
          description: Associated client ID
        client_name:
          type: string
          nullable: true
          description: Associated client name
        preset_job_id:
          type: string
          format: uuid
          nullable: true
          description: Preset job configuration ID
        preset_job_name:
          type: string
          nullable: true
          description: Preset job name
        workflow_id:
          type: string
          format: uuid
          nullable: true
          description: Workflow ID
        workflow_name:
          type: string
          nullable: true
          description: Workflow name
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    JobLayer:
      type: object
      properties:
        id:
          type: integer
          description: Layer ID
        job_id:
          type: string
          format: uuid
          description: Parent job ID
        min_length:
          type: integer
          description: Minimum password length for this layer
        max_length:
          type: integer
          description: Maximum password length for this layer
        status:
          type: string
          description: Layer status
        dispatched_percent:
          type: number
          format: float
          description: Percentage of layer work dispatched
        searched_percent:
          type: number
          format: float
          description: Percentage of layer keyspace searched

    Agent:
      type: object
      required:
        - id
        - name
        - status
        - version
        - created_at
      properties:
        id:
          type: integer
          description: Unique agent identifier
        name:
          type: string
          description: Agent name
        status:
          type: string
          enum: [pending, active, inactive, error, disabled]
          description: Agent status
        is_enabled:
          type: boolean
          description: Whether agent is enabled
        version:
          type: string
          description: Agent version
        hardware:
          type: object
          description: Hardware information
          properties:
            gpus:
              type: array
              items:
                type: object
                properties:
                  vendor:
                    type: string
                  model:
                    type: string
                  memory:
                    type: integer
        last_heartbeat:
          type: string
          format: date-time
          description: Last heartbeat timestamp
        created_at:
          type: string
          format: date-time

    Voucher:
      type: object
      required:
        - code
        - is_active
        - is_continuous
        - created_at
      properties:
        code:
          type: string
          description: Voucher code for agent registration
        is_active:
          type: boolean
          description: Whether voucher is active
        is_continuous:
          type: boolean
          description: Whether voucher can be used multiple times
        created_at:
          type: string
          format: date-time

    HashType:
      type: object
      required:
        - id
        - name
        - is_enabled
      properties:
        id:
          type: integer
          description: Hashcat mode number
        name:
          type: string
          description: Hash type name (e.g., "MD5", "NTLM")
        description:
          type: string
          nullable: true
          description: Description of the hash type
        example:
          type: string
          nullable: true
          description: Example hash format
        is_enabled:
          type: boolean
          description: Whether hash type is supported
        slow:
          type: boolean
          description: Whether this is a slow hash algorithm

    Workflow:
      type: object
      required:
        - id
        - name
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: Workflow ID
        name:
          type: string
          description: Workflow name
        steps:
          type: array
          description: Ordered workflow steps
          items:
            type: object
            properties:
              id:
                type: integer
              preset_job_id:
                type: string
                format: uuid
              step_order:
                type: integer
              preset_job_name:
                type: string
        created_at:
          type: string
          format: date-time

    PresetJob:
      type: object
      required:
        - id
        - name
        - attack_mode
        - hash_type
        - priority
      properties:
        id:
          type: string
          format: uuid
          description: Preset job ID
        name:
          type: string
          description: Preset job name
        wordlist_ids:
          type: array
          items:
            type: string
          description: Associated wordlist IDs
        rule_ids:
          type: array
          items:
            type: string
          description: Associated rule IDs
        attack_mode:
          type: string
          enum: [dictionary, combinator, mask, hybrid_wordlist_mask, hybrid_mask_wordlist]
          description: Attack mode
        hash_type:
          type: integer
          description: Hashcat hash type ID
        priority:
          type: integer
          description: Job priority (higher = more priority)

paths:
  /health:
    get:
      summary: Health check
      description: Verify the User API is operational
      tags: [Health]
      security: []
      responses:
        '200':
          description: API is operational
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"
                  message:
                    type: string
                    example: "User API v1 is operational"

  /clients:
    get:
      summary: List clients
      description: Retrieve a paginated list of all clients (clients are global resources)
      tags: [Clients]
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
      responses:
        '200':
          description: List of clients
          content:
            application/json:
              schema:
                type: object
                properties:
                  clients:
                    type: array
                    items:
                      $ref: '#/components/schemas/Client'
                  page:
                    type: integer
                  page_size:
                    type: integer
                  total:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      summary: Create client
      description: Create a new client (organization/engagement)
      tags: [Clients]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: Client name (must be unique)
                description:
                  type: string
                  nullable: true
                  description: Client description
                domain:
                  type: string
                  nullable: true
                  description: Client domain or contact information
                data_retention_months:
                  type: integer
                  nullable: true
                  description: Data retention in months (0 = keep forever). If omitted, uses system default.
            example:
              name: "Acme Corp"
              description: "Annual penetration test"
              domain: "acme.com"
              data_retention_months: 12
      responses:
        '201':
          description: Client created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Client'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Client name already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /clients/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Client ID

    get:
      summary: Get client
      description: Retrieve a specific client by ID
      tags: [Clients]
      responses:
        '200':
          description: Client details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Client'
        '404':
          description: Client not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      summary: Update client
      description: Update client details
      tags: [Clients]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                  nullable: true
                domain:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Client updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Client'
        '404':
          description: Client not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Client name already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      summary: Delete client
      description: Delete a client (only if no hashlists exist)
      tags: [Clients]
      responses:
        '204':
          description: Client deleted successfully
        '409':
          description: Client has associated hashlists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Client not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /hashlists:
    get:
      summary: List hashlists
      description: Retrieve a paginated list of hashlists owned by the authenticated user
      tags: [Hashlists]
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
        - name: client_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by client ID
        - name: search
          in: query
          schema:
            type: string
          description: Search by hashlist name
      responses:
        '200':
          description: List of hashlists
          content:
            application/json:
              schema:
                type: object
                properties:
                  hashlists:
                    type: array
                    items:
                      $ref: '#/components/schemas/Hashlist'
                  page:
                    type: integer
                  page_size:
                    type: integer
                  total:
                    type: integer

    post:
      summary: Create hashlist
      description: |
        Upload a new hashlist file.

        **Note:** The `client_id` field is conditionally required based on the
        `require_client_for_hashlist` system setting. If the setting is `true`,
        you must provide a client_id.
      tags: [Hashlists]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - name
                - hash_type_id
              properties:
                file:
                  type: string
                  format: binary
                  description: Hashlist file (one hash per line)
                name:
                  type: string
                  description: Hashlist name
                description:
                  type: string
                  description: Hashlist description
                client_id:
                  type: string
                  format: uuid
                  description: Associated client ID (required if require_client_for_hashlist setting is true)
                hash_type_id:
                  type: integer
                  description: Hashcat hash type ID (e.g., 1000 for NTLM)
      responses:
        '201':
          description: Hashlist created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Hashlist'
        '400':
          description: Invalid request or CLIENT_REQUIRED if client_id missing when required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Client or hash type not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /hashlists/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: Hashlist ID

    get:
      summary: Get hashlist
      description: Retrieve hashlist details with statistics
      tags: [Hashlists]
      responses:
        '200':
          description: Hashlist details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Hashlist'
        '403':
          description: Access denied (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Hashlist not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      summary: Delete hashlist
      description: Delete a hashlist and all associated data
      tags: [Hashlists]
      responses:
        '204':
          description: Hashlist deleted successfully
        '403':
          description: Access denied (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Hashlist not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Hashlist has active jobs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /jobs:
    get:
      summary: List jobs
      description: Retrieve a paginated list of jobs owned by the authenticated user
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, running, paused, completed, failed, canceled]
          description: Filter by job status
      responses:
        '200':
          description: List of jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobs:
                    type: array
                    items:
                      $ref: '#/components/schemas/Job'
                  page:
                    type: integer
                  page_size:
                    type: integer
                  total:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      summary: Create job
      description: |
        Create a new cracking job using a preset configuration or workflow.

        You must specify either `preset_job_id` OR `workflow_id`, but not both.

        **Note:** The maximum priority value is determined by the `max_job_priority`
        system setting (default: 1000).
      tags: [Jobs]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - hashlist_id
              properties:
                name:
                  type: string
                  description: Job name
                hashlist_id:
                  type: integer
                  description: ID of the hashlist to crack
                preset_job_id:
                  type: string
                  format: uuid
                  description: Preset job ID (mutually exclusive with workflow_id)
                workflow_id:
                  type: string
                  format: uuid
                  description: Workflow ID (mutually exclusive with preset_job_id)
                priority:
                  type: integer
                  minimum: 1
                  default: 5
                  description: Job priority (1=lowest). Max determined by system setting (default 1000)
                max_agents:
                  type: integer
                  minimum: 0
                  default: 0
                  description: Maximum agents to assign (0=unlimited)
                increment_mode:
                  type: string
                  enum: [off, enabled, enabled_with_brain]
                  default: "off"
                  description: Enable increment mode for variable-length password attacks
            example:
              name: "Potfile Run for Client ABC"
              hashlist_id: 123
              preset_job_id: "fe03b156-df7a-41a8-a591-abce3c0f0217"
              priority: 8
              max_agents: 5
      responses:
        '201':
          description: Job created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '400':
          description: Invalid request or validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Hashlist, preset, or workflow not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /jobs/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Job ID

    get:
      summary: Get job details
      description: Retrieve detailed information about a specific job
      tags: [Jobs]
      responses:
        '200':
          description: Job details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '403':
          description: Access denied (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      summary: Update job
      description: |
        Update job priority or max_agents allocation.

        **Note:** The maximum priority value is determined by the `max_job_priority`
        system setting (default: 1000).
      tags: [Jobs]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                priority:
                  type: integer
                  minimum: 1
                  description: New job priority. Max determined by system setting (default 1000)
                max_agents:
                  type: integer
                  minimum: 0
                  description: New max agents limit
            example:
              priority: 100
              max_agents: 10
      responses:
        '200':
          description: Job updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '400':
          description: Invalid request or validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Access denied (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /jobs/{id}/layers:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Job ID

    get:
      summary: List job layers
      description: |
        Retrieve increment layers for a job. Only applicable for jobs with
        increment_mode enabled. Each layer represents a password length range.
      tags: [Jobs]
      responses:
        '200':
          description: List of job layers
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/JobLayer'
        '403':
          description: Access denied (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /jobs/{id}/layers/{layer_id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Job ID
      - name: layer_id
        in: path
        required: true
        schema:
          type: integer
        description: Layer ID

    get:
      summary: Get layer tasks
      description: Retrieve tasks for a specific increment layer
      tags: [Jobs]
      responses:
        '200':
          description: List of tasks for the layer
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    status:
                      type: string
                    progress:
                      type: number
                    agent_id:
                      type: integer
                      nullable: true
        '403':
          description: Access denied (not owner)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Job or layer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agents/vouchers:
    post:
      summary: Generate voucher
      description: Generate a registration voucher for a new agent
      tags: [Agents]
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                is_continuous:
                  type: boolean
                  description: Whether voucher can be used multiple times
                  default: false
            example:
              is_continuous: false
      responses:
        '201':
          description: Voucher generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Voucher'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /agents:
    get:
      summary: List agents
      description: Retrieve a paginated list of all agents
      tags: [Agents]
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PageSizeParam'
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, active, inactive, error, disabled]
          description: Filter by agent status
      responses:
        '200':
          description: List of agents
          content:
            application/json:
              schema:
                type: object
                properties:
                  agents:
                    type: array
                    items:
                      $ref: '#/components/schemas/Agent'
                  page:
                    type: integer
                  page_size:
                    type: integer
                  total:
                    type: integer

  /agents/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        description: Agent ID

    get:
      summary: Get agent
      description: Retrieve agent details
      tags: [Agents]
      responses:
        '200':
          description: Agent details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      summary: Update agent
      description: Update agent settings
      tags: [Agents]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Agent name
                extra_parameters:
                  type: string
                  description: Extra hashcat parameters
                is_enabled:
                  type: boolean
                  description: Enable/disable agent
      responses:
        '200':
          description: Agent updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      summary: Delete agent
      description: Disable an agent (soft delete)
      tags: [Agents]
      responses:
        '204':
          description: Agent disabled successfully
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /hash-types:
    get:
      summary: List hash types
      description: Retrieve all available hash types
      tags: [Metadata]
      parameters:
        - name: enabled_only
          in: query
          schema:
            type: boolean
            default: false
          description: Only return enabled hash types
      responses:
        '200':
          description: List of hash types
          content:
            application/json:
              schema:
                type: object
                properties:
                  hash_types:
                    type: array
                    items:
                      $ref: '#/components/schemas/HashType'
                  total:
                    type: integer

  /workflows:
    get:
      summary: List workflows
      description: Retrieve all available job workflows
      tags: [Metadata]
      responses:
        '200':
          description: List of workflows
          content:
            application/json:
              schema:
                type: object
                properties:
                  workflows:
                    type: array
                    items:
                      $ref: '#/components/schemas/Workflow'
                  total:
                    type: integer

  /preset-jobs:
    get:
      summary: List preset jobs
      description: Retrieve all available preset jobs
      tags: [Metadata]
      responses:
        '200':
          description: List of preset jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  preset_jobs:
                    type: array
                    items:
                      $ref: '#/components/schemas/PresetJob'
                  total:
                    type: integer
