Skip to main content

Auto-enrolment api docs

Auto-Enrollment API Documentation

Overview

This document provides comprehensive API documentation for PatchMon's Proxmox integration, focusing on how to leverage the auto-enrollment APIsystem, endpointscovering fortoken management, host enrollment, and agent installation endpoints. These APIs enable automated device managementonboarding using tools like Ansible.

Ansible,

TheTerraform, PatchMonor Proxmoxcustom integration provides a secure, token-based API for automatically enrolling LXC containers from Proxmox hosts into PatchMon for centralized patch management.scripts.

Table of Contents

API Architecture

Base URL Structure

https://your-patchmon-server.com/api/v1/

Note: The API version is configurable via the API_VERSION environment variable (defaults to v1).

Endpoint Categories

  1. Endpoints (- Endpoints (- Endpoints (-
    CategoryPath PrefixAuthenticationPurpose
    Admin /auto-enrollment/tokens/*) JWT (Bearer token)Token management
  2. (CRUD)
  3. Enrollment /auto-enrollment/*) Token key + secret (headers)Host enrollment
  4. & script download
  5. Host /hosts/*) API HostID management+ andkey agent(headers)Agent installation & data reporting

    Two-Tier Security Model

    Tier 1: Auto-Enrollment Token

    • Purpose:Purpose: Create new host entries via enrollment
    • Scope:Scope: Limited to enrollment operations only
    • Authentication:Authentication: Token keyX-Auto-Enrollment-Key + secretX-Auto-Enrollment-Secret headers
    • Rate Limited:Limited: Yes (configurable hosts per day)day per token)
    • Storage: Secret is hashed (bcrypt) in the database

    Tier 2: Host API Credentials

    • Purpose:Purpose: Agent communication and (data reportingreporting, updates, commands)
    • Scope:Scope: Per-host unique credentials
    • Authentication:Authentication: API X-API-ID + keyX-API-KEY headers
    • Rate Limited:Limited: No (per-host)
    • Storage: API key is hashed (bcrypt) in the database

    Why two tiers?

    • Compromised enrollment token ≠ compromised hosts
    • Compromised host credential ≠ compromised enrollment
    • Revoking an enrollment token stops new enrollments without affecting existing hosts

    Authentication

    Admin Endpoints Authentication(JWT)

    AdminAll admin endpoints require a valid JWT authentication:Bearer token from an authenticated user with "Manage Settings" permission:

    curl -H "Authorization: Bearer <jwt_token>" \
         -H "Content-Type: application/json" \
         https://your-patchmon-server.com/api/v1/auto-enrollment/tokens
    

    Enrollment Endpoints Authentication(Token Key + Secret)

    Enrollment endpoints useauthenticate token-basedvia authentication:custom headers:

    curl -H "X-Auto-Enrollment-Key: patchmon_ae_abc123..." \
         -H "X-Auto-Enrollment-Secret: def456ghi789..." \
         -H "Content-Type: application/json" \
         https://your-patchmon-server.com/api/v1/auto-enrollment/enroll
    

    Host Endpoints Authentication(API ID + Key)

    Host endpoints useauthenticate via API credentials:credential headers:

    curl -H "X-API-ID: patchmon_abc123" \
         -H "X-API-KEY: def456ghi789" \
         https://your-patchmon-server.com/api/v1/hosts/install
    

    Admin Endpoints

    All admin endpoints require JWT authentication and "Manage Settings" permission.

    Create Auto-Enrollment Token

    Endpoint: POST /api/v1/auto-enrollment/tokens

    Headers:Request Body:

    
    
    
    
    
    
    
    
    
    
    
    
    Bearer<jwt_token>Content-Type:application/json
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    FieldTypeRequiredDefaultDescription
    Authorization:token_name string Yes Descriptive name (max 255 chars)
    max_hosts_per_dayintegerNo100Rate limit (1–1000)
    default_host_group_idstringNonullUUID of host group to auto-assign
    allowed_ip_rangesstring[]No[]IP whitelist (exact IPs or CIDR notation)
    expires_atstringNonullISO 8601 expiration date
    metadataobjectNo{}Custom metadata (e.g. integration_type, environment)
    scopesobjectNonullPermission scopes (only for API integration type tokens)

    RequestExample Body:Request:

    {
      "token_name": "Proxmox Production",
      "max_hosts_per_day": 100,
      "default_host_group_id": "uuid-of-host-group",
      "allowed_ip_ranges": ["192.168.1.10", "10.0.0.0/24"],
      "expires_at": "2026-12-31T23:59:59Z",
      "metadata": {
        "integration_type": "proxmox-lxc",
        "environment": "production",
        "description": "Token for production Proxmox cluster"
      }
    }
    

    Response: 201 Created

    {
      "message": "Auto-enrollment token created successfully",
      "token": {
        "id": "uuid",
        "token_name": "Proxmox Production",
        "token_key": "patchmon_ae_abc123...",
        "token_secret": "def456ghi789...",
        "max_hosts_per_day": 100,
        "default_host_group": {
          "id": "uuid",
          "name": "Proxmox LXC",
          "color": "#3B82F6"
        },
        "created_by": {
          "id": "uuid",
          "username": "admin",
          "first_name": "John",
          "last_name": "Doe"
        },
        "expires_at": "2026-12-31T23:59:59Z",
        "scopes": null
      },
      "warning": "⚠️ Save the token_secret now - it cannot be retrieved later!"
    }
    

    Important: The token_secret is only returned in this response. It is hashed before storage and cannot be retrieved again.

    List Auto-Enrollment Tokens

    Endpoint: GET /api/v1/auto-enrollment/tokens

    Response: 200 OK

    [
      {
        "id": "uuid",
        "token_name": "Proxmox Production",
        "token_key": "patchmon_ae_abc123...",
        "is_active": true,
        "allowed_ip_ranges": ["192.168.1.10"],
        "max_hosts_per_day": 100,
        "hosts_created_today": 15,
        "last_used_at": "2025-10-11T14:30:00Z",
        "expires_at": "2026-12-31T23:59:59Z",
        "created_at": "2025-10-01T10:00:00Z",
        "default_host_group_id": "uuid",
        "metadata": { "integration_type": "proxmox-lxc" },
        "scopes": null,
        "host_groups": {
          "id": "uuid",
          "name": "Proxmox LXC",
          "color": "#3B82F6"
        },
        "users": {
          "id": "uuid",
          "username": "admin",
          "first_name": "John",
          "last_name": "Doe"
        }
      }
    ]
    

    Tokens are returned in descending order by creation date. The token_secret is never included in list responses.

    Get Token Details

    Endpoint: GET /api/v1/auto-enrollment/tokens/{tokenId}

    Response: 200 OK (same— Same structure as a single token in list)the list response (without token_secret).

    Error: 404 Not Found if tokenId does not exist.

    Update Token

    Endpoint: PATCH /api/v1/auto-enrollment/tokens/{tokenId}

    All fields are optional — only include fields you want to change.

    Request Body:

    FieldTypeDescription
    token_namestringUpdated name (1–255 chars)
    is_activebooleanEnable or disable the token
    max_hosts_per_dayintegerUpdated rate limit (1–1000)
    allowed_ip_rangesstring[]Updated IP whitelist
    default_host_group_idstringUpdated host group (set to empty string to clear)
    expires_atstringUpdated expiration date (ISO 8601)
    scopesobjectUpdated scopes (API integration type tokens only)

    Example Request:

    {
      "is_active": false,
      "max_hosts_per_day": 200,
      "allowed_ip_ranges": ["192.168.1.0/24"],
      "expires_at": "2027-01-01T00:00:00Z"
    }
    

    Response: 200 OK

    {
      "message": "Token updated successfully",
      "token": {
        "id": "uuid",
        "token_name": "Proxmox Production",
        "token_key": "patchmon_ae_abc123...",
        "is_active": false,
        "max_hosts_per_day": 200,
        "allowed_ip_ranges": ["192.168.1.0/24"],
        "host_groups": { "id": "uuid", "name": "Proxmox LXC", "color": "#3B82F6" },
        "users": { "id": "uuid", "username": "admin", "first_name": "John", "last_name": "Doe" }
      }
    }
    

    Errors:

    • 404 Not Found — Token does not exist
    • 400 Bad Request — Host group not found, or scopes update attempted on a non-API token

    Delete Token

    Endpoint: DELETE /api/v1/auto-enrollment/tokens/{tokenId}

    Response: 200 OK

    {
      "message": "Auto-enrollment token deleted successfully",
      "deleted_token": {
        "id": "uuid",
        "token_name": "Proxmox Production"
      }
    }
    

    Error: 404 Not Found if tokenId does not exist.

    Enrollment Endpoints

    Download Proxmox Enrollment Script

    Endpoint: GET /api/v1/auto-enrollment/proxmox-lxcscript

    This endpoint validates the token credentials, then serves a bash script with the PatchMon server URL, token credentials, and configuration injected automatically.

    Query Parameters:

    • (required):
    • (required):
    • (optional):
      ParameterRequiredDescription
      typeYesScript type: proxmox-lxc or direct-host
      token_key Yes Auto-enrollment token key
      token_secret Yes Auto-enrollment token secret
      force NoSet to true to enable force install mode (for broken packages)

      Example:

      curl "https://patchmon.example.com/api/v1/auto-enrollment/script?type=proxmox-lxc?lxc&token_key=KEY&token_secret=SECRET&force=true"SECRET"
      

      Response: 200 OK (— Plain text bash script with credentials injected)injected.

      Errors:

      • 400 Bad Request — Missing or invalid type parameter
      • 401 Unauthorized — Missing credentials, invalid/inactive token, invalid secret, or expired token
      • 404 Not Found — Script file not found on server

      Enroll Single Host

      Endpoint: POST /api/v1/auto-enrollment/enroll

      Headers:

      X-Auto-Enrollment-Key: patchmon_ae_abc123...
      X-Auto-Enrollment-Secret: def456ghi789...
      Content-Type: application/json
      

      Request Body:

      FieldTypeRequiredDescription
      friendly_namestringYesDisplay name for the host (max 255 chars)
      machine_idstringNoUnique machine identifier (max 255 chars)
      metadataobjectNoAdditional metadata (vmid, proxmox_node, ip_address, os_info, etc.)

      Example Request:

      {
        "friendly_name": "webserver",
        "machine_id": "proxmox-lxc-100-abc123",
        "metadata": {
          "vmid": "100",
          "proxmox_node": "proxmox01",
          "ip_address": "10.0.0.10",
          "os_info": "Ubuntu 22.04 LTS",
          "container_type": "lxc"
        }
      }
      

      Response: 201 Created

      {
        "message": "Host enrolled successfully",
        "host": {
          "id": "uuid",
          "friendly_name": "webserver",
          "api_id": "patchmon_abc123"patchmon_abc123def456",
          "api_key": "def456ghi789"raw-api-key-value",
          "host_group": {
            "id": "uuid",
            "name": "Proxmox LXC",
            "color": "#3B82F6"
          },
          "status": "pending"
        }
      }
      

      Note: The api_key is only returned in this response (plain text). It is hashed before storage. The host_group is null if no default host group is configured on the token.

      Error Responses:

      Bad Request -
      "errors":[{
            "msg": "Friendly name is required",
            "param": "friendly_name",
            "location": "body"
          }
        ]
      }
      
      

      Unauthorized - Invalid credentials:

      }
      
      
      
      
      
      
      
      "error":
      
      
      
      
      
      
      
      
      
      }(matchedbymachine ID)"
      }
      
      

      Too Many Requests -
      "message":"Maximum100
      StatusErrorCause
      400 Validation errors:

      errors
      Missing or invalid {friendly_name
      401 { "error": "Auto-enrollment credentials required"required Missing

      403 ForbiddenX-Auto-Enrollment-Key -or IPX-Auto-Enrollment-Secret headers

      401Invalid or inactive tokenToken key not authorized:

      found
      or token is disabled
      {401 Invalid "token secretSecret does not match
      401Token expiredToken has passed its expiration date
      403IP address not authorized for this token"token Client IP

      409 Conflict - Host already exists:

      {
        "error": "Host already exists",
        "host_id": "uuid",
        "api_id": "patchmon_abc123",
        "machine_id": "proxmox-lxc-100-abc123",
        "friendly_name": "webserver",
        "message": "This machine is already enrollednot in PatchMonallowed_ip_ranges
      429 Rate limit exceeded:

      exceeded
      Token's { "error": "Ratemax_hosts_per_day limit exceeded",reached
      hosts
      per

      Duplicate dayhandling: allowedThe API does not perform server-side duplicate host checks. Duplicate prevention is handled client-side by the enrollment script, which checks for thisan token"existing }agent configuration (/etc/patchmon/config.yml

    ) inside each container before calling the API.

    Bulk Enroll Hosts

    Endpoint: POST /api/v1/auto-enrollment/enroll/bulk

    Headers:

    X-Auto-Enrollment-Key: patchmon_ae_abc123...
    X-Auto-Enrollment-Secret: def456ghi789...
    Content-Type: application/json
    

    Request Body:

    {
      "hosts": [
        {
          "friendly_name": "webserver",
          "machine_id": "proxmox-lxc-100-abc123",
          "metadata": {
            "vmid": "100",
            "proxmox_node": "proxmox01"
          }
        },
        {
          "friendly_name": "database",
          "machine_id": "proxmox-lxc-101-def456",
          "metadata": {
            "vmid": "101",
            "proxmox_node": "proxmox01"
          }
        }
      ]
    }
    

    Limits:

    • Minimum: 1 host per request
    • Maximum: 50 hosts per request
    • Each host must have a friendly_name (required); machine_id is optional

    Response: 201 Created

    {
      "message": "Bulk enrollment completed: 2 succeeded, 0 failed, 0 skipped",
      "results": {
        "success": [
          {
            "id": "uuid",
            "friendly_name": "webserver",
            "api_id": "patchmon_abc123",
            "api_key": "def456"
          },
          {
            "id": "uuid",
            "friendly_name": "database",
            "api_id": "patchmon_ghi789",
            "api_key": "jkl012"
          }
        ],
        "failed": [],
        "skipped": []
      }
    }
    

    Rate Limit Error (429):

    {
      "error": "Rate limit exceeded",
      "message": "Only 5 hosts remaining in daily quota"
    }
    

    The bulk endpoint checks the remaining daily quota before processing. If the number of hosts in the request exceeds the remaining quota, the entire request is rejected.

    Host Management Endpoints

    These endpoints are used by the PatchMon agent (not the enrollment script). They authenticate using the per-host X-API-ID and X-API-KEY credentials returned during enrollment.

    Download Agent Installation Script

    Endpoint: GET /api/v1/hosts/install

    Serves a shell script that bootstraps the PatchMon agent on a host. The script uses a secure bootstrap token mechanism — actual API credentials are not embedded directly in the script.

    Headers:

    X-API-ID: patchmon_abc123
    X-API-KEY: def456ghi789
    

    Query Parameters:

    • (optional):
      ParameterRequiredDescription
      force NoSet to true to enable force install mode
      archNoArchitecture override (e.g. amd64, arm64); auto-detected if omitted

      Response: 200 OK (bash— Plain text shell script with credentialsbootstrap injected)token injected.

      Download Agent Binary/Script

      Endpoint: GET /api/v1/hosts/agent/download

      Downloads the PatchMon agent binary (Go binary for modern agents) or migration script (for legacy bash agents).

      Headers:

      X-API-ID: patchmon_abc123
      X-API-KEY: def456ghi789
      

      Query Parameters:

      ParameterRequiredDescription
      archNoArchitecture (e.g. amd64, arm64)
      forceNoSet to binary to force binary download

      Response: 200 OK (agent scriptBinary withfile credentialsor injected)shell script.

      Host Data Update

      Endpoint: POST /api/v1/hosts/update

      Used by the agent to report package data, system information, and hardware details.

      Headers:

      X-API-ID: patchmon_abc123
      X-API-KEY: def456ghi789
      Content-Type: application/json
      

      Request Body:Body Fields:

      FieldTypeRequiredDescription
      packagesarrayYesArray of package objects (max 10,000)
      packages[].namestringYesPackage name
      packages[].currentVersionstringYesCurrently installed version
      packages[].availableVersionstringNoAvailable update version
      packages[].needsUpdatebooleanYesWhether an update is available
      packages[].isSecurityUpdatebooleanNoWhether the update is security-related
      agentVersionstringNoReporting agent version
      osTypestringNoOperating system type
      osVersionstringNoOperating system version
      hostnamestringNoSystem hostname
      ipstringNoSystem IP address
      architecturestringNoCPU architecture
      cpuModelstringNoCPU model name
      cpuCoresintegerNoNumber of CPU cores
      ramInstalledfloatNoInstalled RAM in GB
      swapSizefloatNoSwap size in GB
      diskDetailsarrayNoArray of disk objects
      gatewayIpstringNoDefault gateway IP
      dnsServersarrayNoArray of DNS server IPs
      networkInterfacesarrayNoArray of network interface objects
      kernelVersionstringNoRunning kernel version
      installedKernelVersionstringNoInstalled (on-disk) kernel version
      selinuxStatusstringNoSELinux status (enabled, disabled, or permissive)
      systemUptimestringNoSystem uptime
      loadAveragearrayNoLoad average values
      machineIdstringNoMachine ID
      needsRebootbooleanNoWhether a reboot is required
      rebootReasonstringNoReason a reboot is required
      repositoriesarrayNoConfigured package repositories
      executionTimestringNoTime taken to gather data

      Example Request:

      {
        "packages": [
          {
            "name": "nginx",
            "currentVersion": "1.18.0",
            "availableVersion": "1.20.0",
            "needsUpdate": true,
            "isSecurityUpdate": false
          }
        ],
        "agentVersion": "1.2.3",
        "cpuModel": "Intel Xeon E5-2680 v4",
        "cpuCores": 8,
        "ramInstalled": 16.0,
        "swapSize": 2.0,
        "diskDetails": [
          {
            "device": "/dev/sda1",
            "mountPoint": "/",
            "size": "50GB",
            "used": "25GB",
            "available": "25GB"
          }
        ],
        "gatewayIp": "192.168.1.1",
        "dnsServers": ["8.8.8.8", "8.8.4.4"],
        "networkInterfaces": [
          {
            "name": "eth0",
            "ip": "192.168.1.10",
            "mac": "00:11:22:33:44:55"
          }
        ],
        "kernelVersion": "5.4.0-74-generic",
        "selinuxStatus": "disabled"
      }
      

      Response: 200 OK

      {
        "message": "Host updated successfully",
        "packagesProcessed": 1,
        "updatesAvailable": 1,
        "securityUpdates": 0
      }
      

      Ansible Integration Examples

      Basic Ansible Playbook for Proxmox Enrollment

      ---
      - name: Enroll Proxmox LXC containers in PatchMon
        hosts: proxmox_hosts
        become: yes
        vars:
          patchmon_url: "https://patchmon.example.com"
          token_key: "{{ vault_patchmon_token_key }}"
          token_secret: "{{ vault_patchmon_token_secret }}"
          host_prefix: "prod-"
      
        tasks:
          - name: Install dependencies
            apt:
              name:
                - curl
                - jq
              state: present
      
          - name: Download enrollment script
            get_url:
              url: "{{ patchmon_url }}/api/v1/auto-enrollment/script?type=proxmox-lxc?lxc&token_key={{ token_key }}&token_secret={{ token_secret }}"
              dest: /root/proxmox_auto_enroll.sh
              mode: '0700'
      
          - name: Run enrollment
            command: /root/proxmox_auto_enroll.sh
            environment:
              HOST_PREFIX: "{{ host_prefix }}"
              DEBUG: "true"
            register: enrollment_output
      
          - name: Show enrollment results
            debug:
              var: enrollment_output.stdout_lines
      

      Advanced Ansible Playbook with Token Management

      ---
      - name: Manage PatchMon Proxmox Integration
        hosts: localhost
        vars:
          patchmon_url: "https://patchmon.example.com"
          admin_token: "{{ vault_patchmon_admin_token }}"
      
        tasks:
          - name: Create Proxmox enrollment token
            uri:
              url: "{{ patchmon_url }}/api/v1/auto-enrollment/tokens"
              method: POST
              headers:
                Authorization: "Bearer {{ admin_token }}"
                Content-Type: "application/json"
              body_format: json
              body:
                token_name: "{{ inventory_hostname }}-proxmox"
                max_hosts_per_day: 200
                default_host_group_id: "{{ proxmox_host_group_id }}"
                allowed_ip_ranges: ["{{ proxmox_host_ip }}"]
                expires_at: "2026-12-31T23:59:59Z"
                metadata:
                  integration_type: "proxmox-lxc"
                  environment: "{{ environment }}"
              created_by_ansible: true
              status_code: 201
            register: token_response
      
          - name: Store token credentials
            set_fact:
              enrollment_token_key: "{{ token_response.json.token.token_key }}"
              enrollment_token_secret: "{{ token_response.json.token.token_secret }}"
      
          - name: Deploy enrollment script to Proxmox hosts
            include_tasks: deploy_enrollment.yml
            vars:
              enrollment_token_key: "{{ enrollment_token_key }}"
              enrollment_token_secret: "{{ enrollment_token_secret }}"
      

      Ansible TaskPlaybook for Bulk Enrollment via API

      ---
      - name: Bulk enroll Proxmox containers
        hosts: proxmox_hosts
        become: yes
        vars:
          patchmon_url: "https://patchmon.example.com"
          token_key: "{{ vault_patchmon_token_key }}"
          token_secret: "{{ vault_patchmon_token_secret }}"
      
        tasks:
          - name: Get LXC container list
            shell: |
              pct list | tail -n +2 | while read -r line; do
                vmid=$(echo "$line" | awk '{print $1}')
                name=$(echo "$line" | awk '{print $3}')
                status=$(echo "$line" | awk '{print $2}')
      
                if [ "$status" = "running" ]; then
                  machine_id=$(pct exec "$vmid" -- bash -c "cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null || echo 'proxmox-lxc-$vmid-'$(cat /proc/sys/kernel/random/uuid)" 2>/dev/null || echo "proxmox-lxc-$vmid-unknown")
                  echo "{\"friendly_name\":\"$name\",\"machine_id\":\"$machine_id\",\"metadata\":{\"vmid\":\"$vmid\",\"proxmox_node\":\"$(hostname)\"}}"
                fi
              done | jq -s '.'
            register: containers_json
      
          - name: Bulk enroll containers
            uri:
              url: "{{ patchmon_url }}/api/v1/auto-enrollment/enroll/bulk"
              method: POST
              headers:
                X-Auto-Enrollment-Key: "{{ token_key }}"
                X-Auto-Enrollment-Secret: "{{ token_secret }}"
                Content-Type: "application/json"
              body_format: json
              body:
                hosts: "{{ containers_json.stdout | from_json }}"
              status_code: 201
            register: enrollment_result
      
          - name: Display enrollment results
            debug:
              msg: "{{ enrollment_result.json.message }}"
      

      Ansible Role for PatchMon Integration

      # roles/patchmon_proxmox/tasks/main.yml
      ---
      - name: Install PatchMon dependencies
        package:
          name:
            - curl
            - jq
          state: present
      
      - name: Create PatchMon directory
        file:
          path: /opt/patchmon
          state: directory
          mode: '0755'
      
      - name: Download enrollment script
        get_url:
          url: "{{ patchmon_url }}/api/v1/auto-enrollment/script?type=proxmox-lxc?lxc&token_key={{ token_key }}&token_secret={{ token_secret }}&force={{ force_install | default('false') }}"
          dest: /opt/patchmon/proxmox_auto_enroll.sh
          mode: '0700'
      
      - name: Run enrollment script
        command: /opt/patchmon/proxmox_auto_enroll.sh
        environment:
          PATCHMON_URL: "{{ patchmon_url }}"
          AUTO_ENROLLMENT_KEY: "{{ token_key }}"
          AUTO_ENROLLMENT_SECRET: "{{ token_secret }}"
          HOST_PREFIX: "{{ host_prefix | default('') }}"
          DRY_RUN: "{{ dry_run | default('false') }}"
          DEBUG: "{{ debug | default('false') }}"
          FORCE_INSTALL: "{{ force_install | default('false') }}"
        register: enrollment_output
      
      - name: Display enrollment results
        debug:
          var: enrollment_output.stdout_lines
        when: enrollment_output.stdout_lines is defined
      
      - name: Fail if enrollment had errors
        fail:
          msg: "Enrollment failed with errors"
        when: enrollment_output.rc != 0
      

      Ansible Vault Integrationfor Credentials

      # group_vars/all/vault.yml (encrypted with ansible-vault)
      ---
      vault_patchmon_admin_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
      vault_patchmon_token_key: "patchmon_ae_abc123..."
      vault_patchmon_token_secret: "def456ghi789..."
      

      Ansible Playbook with Error Handling and Retries

      ---
      - name: Robust Proxmox enrollment with error handling
        hosts: proxmox_hosts
        become: yes
        vars:
          patchmon_url: "https://patchmon.example.com"
          token_key: "{{ vault_patchmon_token_key }}"
          token_secret: "{{ vault_patchmon_token_secret }}"
          max_retries: 3
          retry_delay: 30
      
        tasks:
          - name: Test PatchMon connectivity
            uri:
              url: "{{ patchmon_url }}/api/v1/auto-enrollment/tokens"
              method: GET
              headers:
                Authorization: "Bearer {{ vault_patchmon_admin_token }}"
              status_code: 200
            retries: "{{ max_retries }}"
            delay: "{{ retry_delay }}"
      
          - name: Download enrollment script
            get_url:
              url: "{{ patchmon_url }}/api/v1/auto-enrollment/script?type=proxmox-lxc?lxc&token_key={{ token_key }}&token_secret={{ token_secret }}"
              dest: /root/proxmox_auto_enroll.sh
              mode: '0700'
            retries: "{{ max_retries }}"
            delay: "{{ retry_delay }}"
      
          - name: Run enrollment with retry logic
            shell: |
              for i in {1..{{ max_retries }}}; do
                echo "Attempt $i of {{ max_retries }}"
                if /root/proxmox_auto_enroll.sh; then
                  echo "Enrollment successful"
                  exit 0
                else
                  echo "Enrollment failed, retrying in {{ retry_delay }} seconds..."
                  sleep {{ retry_delay }}
                fi
              done
              echo "All enrollment attempts failed"
              exit 1
            register: enrollment_result
      
          - name: Handle enrollment failure
            fail:
              msg: "Proxmox enrollment failed after {{ max_retries }} attempts"
            when: enrollment_result.rc != 0
      
          - name: Parse enrollment results
            set_fact:
              enrolled_count: "{{ enrollment_result.stdout | regex_search('Successfully Enrolled:\\s+(\\d+)', '\\1') | default('0') }}"
              failed_count: "{{ enrollment_result.stdout | regex_search('Failed:\\s+(\\d+)', '\\1') | default('0') }}"
      
          - name: Report enrollment statistics
            debug:
              msg: |
                Enrollment completed:
                - Successfully enrolled: {{ enrolled_count }} containers
                - Failed: {{ failed_count }} containers
      

      Error Handling

      Common HTTP Status Codes

      Code Meaning DescriptionWhen It Occurs
      200 OK RequestSuccessful successfulread/update operations
      201 Created ResourceToken or host created successfully
      400 Bad Request InvalidValidation requesterrors, datainvalid host group, invalid script type
      401 Unauthorized InvalidMissing, authenticationinvalid, or expired credentials
      403 Forbidden IP address not authorizedin token's whitelist
      404 Not Found ResourceToken or resource not found
      409ConflictHost already exists
      429 Too Many Requests RateToken's daily host creation limit exceeded
      500 Internal Server Error ServerUnexpected server error

      Error Response FormatFormats

      Simple error:

      {
        "error": "Error message",message "message":describing "Detailedwhat description",went "code": "ERROR_CODE",
        "details": {
          "field": "additional context"
        }wrong"
      }
      

      Validation

      Error Errors

      with
      {
        "errors": [
          {
            "msg": "Token name is required",
            "param": "token_name",
            "location": "body"
          }
        ]
      }
      

      Rate Limiting

      Token Rate Limits

      • Defaultdetail:: 100 hosts per day per token
      • Maximum: 1000 hosts per day per token
      • Reset: Daily at midnight UTC
      • Scope: Per-token (not per-host)

      Rate Limit Headers

      X-RateLimit-Limit: 100
      X-RateLimit-Remaining: 85
      X-RateLimit-Reset: 1640995200
      

      Rate Limit Exceeded Response

      {
        "error": "Rate limit exceeded",
        "message": "Maximum 100 hosts per day allowed for this token",
        "retry_after": 3600
      }
      

      Validation errors (400):

      {
        "errors": [
          {
            "msg": "Token name is required (max 255 characters)",
            "param": "token_name",
            "location": "body"
          }
        ]
      }
      

      Rate Limiting

      Token-Based Rate Limits

      Each auto-enrollment token has a configurable max_hosts_per_day limit:

      • Default: 100 hosts per day per token
      • Range: 1–1000 hosts per day
      • Reset: Daily (when the first request of a new day is received)
      • Scope: Per-token, not per-IP

      When the limit is exceeded, the API returns 429 Too Many Requests:

      {
        "error": "Rate limit exceeded",
        "message": "Maximum 100 hosts per day allowed for this token"
      }
      

      For bulk enrollment, the remaining daily quota is checked against the request size. If the request contains more hosts than the remaining quota allows, the entire request is rejected:

      {
        "error": "Rate limit exceeded",
        "message": "Only 5 hosts remaining in daily quota"
      }
      

      Global Rate Limiting

      The auto-enrollment endpoints are also subject to the server's global authentication rate limiter, which applies to all authentication-related endpoints.

      Security Considerations

      Token Security

        • Secret Storagehashing: Token secrets are hashed usingwith bcrypt (cost factor 10) before storage
        • One-Timetime Displaydisplay: Secrets are only shownreturned during token creation
        • Rotation: RegularRecommended tokenevery rotation recommended (90 days)days
        • Scope limitation: Tokens can only create hosts,hosts — they cannot accessread, modify, or delete existing host data

    IP Restrictions

    Tokens support IP whitelisting with both exact IPs and CIDR notation:

    {
      "allowed_ip_ranges": ["192.168.1.10", "10.0.0.0/24"]
    }
    

    IPv4-mapped IPv6 addresses (e.g. ::ffff:192.168.1.10) are automatically handled.

    Host API Key Security

    • Host API keys (api_key) are hashed with bcrypt before storage
    • The installation script uses a bootstrap token mechanism — the actual API credentials are not embedded in the script
    • Bootstrap tokens are single-use and expire after 5 minutes

    Network Security

    • UseAlways use HTTPS in production
    • The ignore_ssl_self_signed server setting automatically configures curl flags in served scripts
    • Implement proper firewall rules
    • Considerto VPNrestrict forPatchMon remoteserver Proxmoxaccess hosts
    • to
    • Monitorknown for unusual enrollment patternsIPs

    Audit LoggingTrail

    All enrollment activitiesactivity areis logged with:logged:

    • Token name andincluded IDin host notes (e.g. "Auto-enrolled via Production Proxmox on 2025-10-11T14:30:00Z")
    • EnrolledToken hostcreation detailstracks created_by_user_id
    • Timestamplast_used_at andtimestamp sourceupdated IP
    • on
    • Success/failureeach statusenrollment

    API Reference

    Complete Endpoint List

    Summary

    Admin Endpoints (JWT Authentication)

    • -
    • -
    • - details
    • -
    • -

      MethodPathDescription
      POST /api/v1/auto-enrollment/tokens Create token
      GET /api/v1/auto-enrollment/tokens List all tokens
      GET /api/v1/auto-enrollment/tokens/{tokenId} Get single token
      PATCH /api/v1/auto-enrollment/tokens/{tokenId} Update token
      DELETE /api/v1/auto-enrollment/tokens/{tokenId} Delete token

      Enrollment Endpoints (Token Authentication)

      • -
      • -
      • -
        MethodPathDescription
        GET /api/v1/auto-enrollment/proxmox-lxcscript?type=... Download enrollment script
        POST /api/v1/auto-enrollment/enroll Enroll single host
        POST /api/v1/auto-enrollment/enroll/bulk Bulk enroll hosts (max

        50)

        Host Endpoints (API Credentials)

        • -
        • -
        • - Update
          MethodPathDescription
          GET /api/v1/hosts/install Download installation script
          GET /api/v1/hosts/agent/download Download agent binary/script
          POST /api/v1/hosts/update Report host data

          Request/ResponseQuick Reference: curl Examples

          Create Tokena Request

          token:

          curl -X POST \
            -H "Authorization: Bearer <jwt_token>" \
            -H "Content-Type: application/json" \
            -d '{
              "token_name": "Production Proxmox",
              "max_hosts_per_day": 100,
              "default_host_group_id": "uuid",
              "allowed_ip_ranges": ["192.168.1.10"],
              "expires_at": "2026-12-31T23:59:59Z"
            }' \
            https://patchmon.example.com/api/v1/auto-enrollment/tokens
          

          Download and run enrollment script:

          curl -s "https://patchmon.example.com/api/v1/auto-enrollment/script?type=proxmox-lxc&token_key=KEY&token_secret=SECRET" | bash
          

          Enroll Hosta Request

          host directly:

          curl -X POST \
            -H "X-Auto-Enrollment-Key: patchmon_ae_abc123..." \
            -H "X-Auto-Enrollment-Secret: def456ghi789..." \
            -H "Content-Type: application/json" \
            -d '{
              "friendly_name": "webserver",
              "machine_id": "proxmox-lxc-100-abc123",
              "metadata": {
                "vmid": "100",
                "proxmox_node": "proxmox01"
              }
            }' \
            https://patchmon.example.com/api/v1/auto-enrollment/enroll
          

          Download Installationagent Script

          installation script:

          curl -H "X-API-ID: patchmon_abc123" \
               -H "X-API-KEY: def456ghi789" \
               https://patchmon.example.com/api/v1/hosts/install | bash
          

          Integration Patterns

          Pattern 1: DirectScript-Based Script Download

          (Simplest)

          # Download and execute in one command — credentials are injected into the script
          curl -s "https://patchmon.example.com/api/v1/auto-enrollment/script?type=proxmox-lxc?lxc&token_key=KEY&token_secret=SECRET" | bash
          

          Pattern 2: API-First Approach

          (Most Control)

          # 1. Create token via admin API
          # 2. Use token to enrollEnroll hosts via enrollment API (single or bulk)
          # 3. Download agent scripts forusing eachper-host hostAPI credentials
          # 4. Install agents usingwith host-specific credentials
          

          Pattern 3: Hybrid Approach

          (Recommended for Automation)

          # 1. Create token via admin API (or UI)
          # 2. Download enrollment script with token embedded
          # 3. RunDistribute and run script on Proxmox hosts
          # 4. Script handles both enrollment and agent installation
          

          This API documentation provides everything needed to integrate PatchMon's Proxmox functionality with automation tools like Ansible. The endpoints are designed to be secure, scalable, and easy to integrate into existing infrastructure automation workflows.