Integration API documentation
Integration API Documentation
Table of Contents
- Overview
- Interactive API Reference (Swagger)
- Creating API Credentials
- Authentication
- Available Scopes & Permissions
- API Endpoints
- Usage Examples
Expected Outputs- Security Best Practices
- Troubleshooting
API Reference
Overview
PatchMonPatchMon's Integration API Credentials provideprovides programmatic access to your PatchMon instance, enabling automation, integration with third-party tools, and custom workflows. API credentials use HTTP Basic Authentication with scoped permissions to control access to specific resources and actions.
Key Features
- Scoped Permissions: Fine-grained control over what each credential can access
- IP Restrictions: Optional IP allowlisting for enhanced security
- Expiration Dates: Set automatic expiration for temporary access
- Basic Authentication: Industry-standard authentication method (RFC 7617)
- Rate Limiting: Built-in protection against abuse
- Audit Trail: Track credential usage with last-used timestamps
Use Cases
- Automation: Integrate PatchMon data into CI/CD pipelines
- Inventory Management: Use with Ansible, Terraform, or other IaC tools
- Monitoring: Feed PatchMon data into monitoring dashboards
- Custom Scripts: Build custom tools that interact with PatchMon
- Third-Party Integrations: Connect PatchMon to other systems
Interactive API Reference (Swagger)
PatchMon includes a built-in interactive API reference powered by Swagger UI. You can explore all available endpoints, view request/response schemas, and test API calls directly from your browser.
To access the Swagger UI:
https://<your-patchmon-url>/api/v1/api-docs
Note: The Swagger UI requires you to be logged in to PatchMon (JWT authentication). Log in to your PatchMon dashboard first, then navigate to the URL above in the same browser session.
The Swagger reference covers all internal and scoped API endpoints. This documentation page focuses specifically on the scoped Integration API that uses Basic Authentication with API credentials.
Creating API Credentials
Step-by-Step Guide
1. Navigate to Integrations PageSettings
- Log in to your PatchMon instance as an administrator
- Go to Settings → Integrations
ClickYouonwill see the Auto-Enrollment & API tab
2. Click "New Credential"Token"
Click the "New Credential"Token" buttonbutton. inA modal will appear where you can select the top-rightcredential corner of the API tab.type.
3. Select "API" as the Usage Type
In the creation modal, select "API" as the usage type. This configures the credential for programmatic access via Basic Authentication.
4. Configure the Credential
Fill in the following fields in the creation form:fields:
Required FieldsFields:
| Field | Description | Example |
|---|---|---|
| Token Name | A descriptive name |
Ansible , Monitoring Dashboard |
| Scopes | The permissions this credential should have |
host: |
Optional FieldsFields:
| Field | Description | Example |
|---|---|---|
| Allowed IP Addresses | Comma-separated list of |
192.168.1. |
| Expiration Date | Automatic expiration date for the |
2026-12-31T23:59:59 |
| Default |
Optionally assign group |
Production |
5. Save Your Credentials
⚠️ CRITICAL: Save these credentials nowimmediately -— the secret cannot be retrieved later!
After creation, you'lla see asuccess modal displaying:displays:
Token Name: Your chosen name- Token Key: The API key (
username)used as the username in Basic Auth), prefixed withpatchmon_ae_ - Token Secret: The API secret (used as the password)
-— shown onlyshownonce - Granted Scopes: The permissions assigned
- Usage Examples: Pre-filled cURL commands ready to copy
Important Actions:
- Copy both the Token Key and Token Secret
Storeand store them securely(passwordbeforemanager, secrets vault, etc.)Testclosing thecredential immediatelyClose the modal only after saving both values
Configuration Options
Token Name
Required: YesType: StringDescription: Human-readable identifier for the credentialBest Practice: Use descriptive names that indicate the credential's purposeExamples:"Production Ansible Inventory""Monitoring Dashboard - Grafana""CI/CD Pipeline - Jenkins"
Scopes
Required: YesType: Object with arraysDescription: Defines what actions the credential can perform on which resourcesStructure:{ "resource": ["action1", "action2"] }Validation: At least one action must be selected for at least one resource
Allowed IP Ranges
Required: NoType: Array of strings (comma-separated in UI)Description: Restricts credential usage to specific IP addressesFormat: Single IPs or CIDR notationExamples:192.168.1.100- Single IP10.0.0.0/24- CIDR range192.168.1.100, 10.0.0.50- Multiple IPs
Default: Empty (no restrictions)
Expiration Date
Required: NoType: ISO 8601 datetimeDescription: Automatic expiration date for the credentialFormat:YYYY-MM-DDTHH:mm:ssExample:2026-12-31T23:59:59Default: null (no expiration)
Authentication
Basic Authentication
PatchMon API credentials use HTTP Basic Authentication as defined in RFC 7617.
Format
Authorization: Basic <base64(token_key:token_secret)>
How It Works
- Combine your token key and secret with a colon:
token_key:token_secret - Encode the combined string in Base64
- Prepend
"Basicto the encoded string" - Send it in the
Authorizationheader
Most HTTP clients handle this automatically with— thefor example, cURL's -u flag (cURL) or Python's authHTTPBasicAuth parameter (Python requests).
Authentication Flow
┌─────────────┐ ┌─────────────┐
│ Client │ │ PatchMon │
│ Application │ │ Server │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. Send Requestrequest with Basic Auth │
│ Authorization: Basic <base64> │
│───────────────────────────────────────────────>│
│ │
│ 2. Validate Credentialscredentials │
│ -a. Decode Base64 │
│ -b. Find token inby DBkey │
│ -c. Check is_active │
│ d. Check expiration │
│ e. Verify integration type │
│ f. Verify secret (bcrypt) │
│ -g. Check if active │
│ - Check expiration │
│ - Verify IP (if set) │
│ - Validate scopesrestrictions │
│ │
│ 3. Validate scopes │
│ a. Check resource access │
│ b. Check action permission │
│ │
│ 4. Return Responseresponse │
│<───────────────────────────────────────────────│
│ 200 OK + Data (if authorized)authorised) │
│ 401 UnauthorizedUnauthorised (if auth fails) │
│ 403 Forbidden (if scopescope/IP missing)check fails) │
│ │
│ 4.5. Update last_used_at │
│ timestamp in DB │
│ │
Validation Steps
(In Order)
The server performs the following validationthese checks (insequentially. order):If any step fails, the request is rejected immediately:
Response Codes
| ||
| ||
| ||
|
Available Scopes & Permissions
API credentials use a resource-resource–action scope model. Scopes are defined as:model:
{
"resource": ["action1", "action2", ...]
}
Current Resources
Host Resource
Resource Namename:: host
Available Actions:
| Action | Description | ||
|---|---|---|---|
get |
Read host data | ||
put |
Replace host data | ||
patch |
|||
update | General update operations | ||
delete |
Delete hosts |
Example Scopescope Configurationconfigurations::
// Read-only access
{ "host": ["get"] }
// Read-onlyRead accessand toupdate
hosts
}
{ "host": ["get", "patch"] }
// ReadFull andaccess
update hosts
}
{ "host": ["get", "put", "patch", "update", "delete"] // Full access
}
ScopeImportant InheritanceNotes
- Scopes are explicit
-— no inheritance or wildcards. Each action must be explicitly granted. getpermissiondoes not automatically includepatchpermission.or
otherMinimumanyScopesaction.
- At least one action must be granted for at least one resource. Credentials with no scopes will be rejected during creation.
For example,
API Endpoints
All endpoints are prefixed with /api/v1/api and require Basic Authentication with a credential that has the appropriate scope.
Endpoints Summary
| Endpoint | Method | Scope | Description |
|---|---|---|---|
/api/v1/api/hosts |
GET | host:get |
List all hosts with IP, groups, and optional stats |
/api/v1/api/hosts/:id/stats |
GET | host:get |
Get host package/repo statistics |
/api/v1/api/hosts/:id/info |
GET | host:get |
Get detailed host information |
/api/v1/api/hosts/:id/network |
GET | host:get |
Get host network configuration |
/api/v1/api/hosts/:id/system |
GET | host:get |
Get host system details |
/api/v1/api/hosts/:id/packages |
GET | host:get |
Get host packages (with optional update filter) |
/api/v1/api/hosts/:id/package_reports |
GET | host:get |
Get package update history |
/api/v1/api/hosts/:id/agent_queue |
GET | host:get |
Get agent queue status and jobs |
/api/v1/api/hosts/:id/notes |
GET | host:get |
Get host notes |
/api/v1/api/hosts/:id/integrations |
GET | host:get |
Get host integration status |
List Hosts
GetRetrieve a list of all hosts with their IP addresses and host group memberships. Optionally include package update statistics inline with each host.
Endpoint
Endpoint:
GET /api/v1/api/hosts
Required ScopeScope:
{host:get"host": ["get"] }
Query ParametersParameters:
| Parameter | Type | Required | Description | |
|---|---|---|---|---|
hostgroup |
string | No | Filter by host group |
|
include |
string |
No | Comma-separated list of additional data to include. Supported values: . |
Request
Filtering Headersby Host Groups:
Authorization:# BasicFilter <base64(token_key:token_secret)>by Content-Type:group application/jsonname
GET /api/v1/api/hosts?hostgroup=Production
# Filter by multiple groups (hosts in ANY of the listed groups)
GET /api/v1/api/hosts?hostgroup=Production,Development
# Filter by group UUID
GET /api/v1/api/hosts?hostgroup=550e8400-e29b-41d4-a716-446655440000
# Mix names and UUIDs
GET /api/v1/api/hosts?hostgroup=Production,550e8400-e29b-41d4-a716-446655440000
Response
Including Stats:
Use ?include=stats to add package update counts and additional host metadata to each host in a single request. This is more efficient than making separate /stats calls for every host.
# List all hosts with stats
GET /api/v1/api/hosts?include=stats
# Combine with host group filter
GET /api/v1/api/hosts?hostgroup=Production&include=stats
SuccessNote: If your host group names contain spaces, URL-encode them with%20(e.g.Web%20Servers). Most HTTP clients handle this automatically.
Response (200 OK) — Without stats::
{
"hosts": [
{
"id": "uuid-1234"550e8400-e29b-41d4-a716-446655440000",
"friendly_name": "web-server-01",
"hostname": "web01.example.com",
"ip": "192.168.1.100",
"host_groups": [
{
"id": "group-uuid-1"660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
}
]
}
],
"total": 1,
"filtered_by_groups": ["Production"]
}
Response (200 OK) — With ?include=stats:
{
"hosts": [
{
"id": "group-uuid-2",
"name": "Web Servers"
}
]
},
{
"id": "uuid-5678"550e8400-e29b-41d4-a716-446655440000",
"friendly_name": "db-web-server-01",
"hostname": "db01.web01.example.com",
"ip": "192.168.1.101"100",
"host_groups": [
{
"id": "group-uuid-1"660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
}
],
{
"id"os_type": "group-uuid-3"Ubuntu",
"name"os_version": "Database24.04 Servers"LTS",
}"last_update": ]"2026-02-12T10:30:00.000Z",
"status": "active",
"needs_reboot": false,
"updates_count": 15,
"security_updates_count": 3,
"total_packages": 342
}
],
"total": 2,1,
"filtered_by_groups": ["Production"]
// Only present if filtering
}
The filtered_by_groups field is only present when a hostgroup filter is applied.
The filtered_by_groups field is only present when a hostgroup filter is applied.
Response FieldsFields:
| Field | Type | Description |
|---|---|---|
hosts |
array | Array of host objects |
hosts[].id |
string (UUID) | Unique host identifier |
hosts[].friendly_name |
string | Human-readable host name |
hosts[].hostname |
string | System hostname |
hosts[].ip |
string | Primary IP address |
hosts[].host_groups |
array | Groups this host belongs to |
hosts[]. |
string |
include=stats) |
hosts[]. |
string | include=stats) |
hosts[].last_update |
string (ISO 8601) | Timestamp of last agent update (only with include=stats) |
hosts[].status |
string | Host status, e.g. active, pending (only with include=stats) |
hosts[].needs_reboot |
boolean | Whether a reboot is pending (only with include=stats) |
hosts[].updates_count |
integer | Number of packages needing updates (only with include=stats) |
hosts[].security_updates_count |
integer | Number of security updates available (only with include=stats) |
hosts[].total_packages |
integer | Total installed packages (only with include=stats) |
total |
integer | Total number of hosts returned |
filtered_by_groups |
array | Groups used for filtering ( |
Filtering
byGet Host Groups
Statistics
Retrieve package and repository statistics for a specific host.
Filter by Group NameEndpoint::
GET /api/v1/api/hosts?hostgroup=Productionhosts/:id/stats
FilterRequired by Multiple GroupsScope: host:get
Response (OR200 logicOK):
{
hosts"host_id": in"550e8400-e29b-41d4-a716-446655440000",
ANY"total_installed_packages": of342,
"outdated_packages": 15,
"security_updates": 3,
"total_repos": 8
}
Response Fields:
| Field | Type | Description |
|---|---|---|
host_id |
string (UUID) | The host identifier |
total_installed_packages |
integer | Total packages installed on this host |
outdated_packages |
integer | Packages that need updates |
security_updates |
integer | Packages with security updates available |
total_repos |
integer | Total repositories associated with the |
Get Host Information
Retrieve detailed information about a specific host including OS details and host groups.
Endpoint:
GET /api/v1/api/hosts?hostgroup=Production,Developmenthosts/:id/info
FilterRequired byScope: Grouphost:get
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"machine_id": "abc123def456",
"friendly_name": "web-server-01",
"hostname": "web01.example.com",
"ip": "192.168.1.100",
"os_type": "Ubuntu",
"os_version": "24.04 LTS",
"agent_version": "1.4.0",
"host_groups": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
}
]
}
Get Host Network Information
Retrieve network configuration details for a specific host.
Endpoint:
GET /api/v1/api/hosts?hostgroup=550e8400-e29b-41d4-a716-446655440000hosts/:id/network
MixRequired NamesScope: host:get
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"ip": "192.168.1.100",
"gateway_ip": "192.168.1.1",
"dns_servers": ["8.8.8.8", "8.8.4.4"],
"network_interfaces": [
{
"name": "eth0",
"ip": "192.168.1.100",
"mac": "00:11:22:33:44:55"
}
]
}
Get Host System Information
Retrieve system-level information for a specific host including hardware, kernel, and UUIDsreboot status.
Endpoint::
GET /api/v1/api/hosts?hostgroup=Production,550e8400-e29b-41d4-a716-446655440000hosts/:id/system
⚠️Required Important:Scope: URLhost:get
Response for(200 GroupOK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"architecture": "x86_64",
"kernel_version": "6.8.0-45-generic",
"installed_kernel_version": "6.8.0-50-generic",
"selinux_status": "disabled",
"system_uptime": "15 days, 3:22:10",
"cpu_model": "Intel Xeon E5-2680 v4",
"cpu_cores": 4,
"ram_installed": "8192 MB",
"swap_size": "2048 MB",
"load_average": {
"1min": 0.5,
"5min": 0.3,
"15min": 0.2
},
"disk_details": [
{
"filesystem": "/dev/sda1",
"size": "50G",
"used": "22G",
"available": "28G",
"use_percent": "44%",
"mounted_on": "/"
}
],
"needs_reboot": true,
"reboot_reason": "Kernel update pending"
}
Get Host Packages
Retrieve the list of packages installed on a specific host. Use the optional updates_only parameter to return only packages with Spacesavailable updates.
Endpoint:
GET /api/v1/api/hosts/:id/packages
IfRequired your host group names contain spaces, you must URL-encode them usingScope: %20host:get:
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
updates_only |
string | No | — | Set to true to return only packages that need updates |
Examples:
# GroupGet name:all packages for a host
curl -u "Webpatchmon_ae_abc123:your_secret_here" Servers"\
https://patchmon.example.com/api/v1/api/hosts/HOST_UUID/packages
# Get only packages with available updates
curl -u "patchmon_ae_abc123:your_secret_here" \
"https://patchmon.example.com/api/v1/api/hosts/HOST_UUID/packages?updates_only=true"
Response (200 OK):
{
"host": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"hostname": "web01.example.com",
"friendly_name": "web-server-01"
},
"packages": [
{
"id": "package-host-uuid",
"name": "nginx",
"description": "High performance web server",
"category": "web",
"current_version": "1.18.0-0ubuntu1.5",
"available_version": "1.24.0-2ubuntu1",
"needs_update": true,
"is_security_update": false,
"last_checked": "2026-02-12T10:30:00.000Z"
},
{
"id": "package-host-uuid-2",
"name": "openssl",
"description": "Secure Sockets Layer toolkit",
"category": "security",
"current_version": "3.0.2-0ubuntu1.14",
"available_version": "3.0.2-0ubuntu1.18",
"needs_update": true,
"is_security_update": true,
"last_checked": "2026-02-12T10:30:00.000Z"
}
],
"total": 2
}
Response Fields:
| Field | Type | Description |
|---|---|---|
host |
object | Basic host identification |
host.id |
string (UUID) | Host identifier |
host.hostname |
string | System hostname |
host.friendly_name |
string | Human-readable host name |
packages |
array | Array of package objects |
packages[].id |
string (UUID) | Host-package record identifier |
packages[].name |
string | Package name |
packages[].description |
string | Package description |
packages[].category |
string | Package category |
packages[].current_version |
string | Currently installed version |
packages[].available_version |
string | null | Available update version (null if up to date) |
packages[].needs_update |
boolean | Whether an update is available |
packages[].is_security_update |
boolean | Whether the available update is security-related |
packages[].last_checked |
string (ISO 8601) | When this package was last checked |
total |
integer | Total number of packages returned |
Tip: Packages are returned sorted by security updates first, then by update availability. This puts the most critical packages at the top.
Get Host Package Reports
Retrieve package update history reports for a specific host.
Endpoint:
GET /api/v1/api/hosts?hostgroup=Web%20Servershosts/:id/package_reports
#
Required name:Scope: host:get
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
integer | No | 10 | Maximum number of reports to return |
Response (200 OK):
{
"Productionhost_id": Database""550e8400-e29b-41d4-a716-446655440000",
"reports": [
{
"id": "report-uuid",
"status": "success",
"date": "2026-02-12T10:30:00.000Z",
"total_packages": 342,
"outdated_packages": 15,
"security_updates": 3,
"payload_kb": 12.5,
"execution_time_seconds": 4.2,
"error_message": null
}
],
"total": 1
}
Get Host Agent Queue
Retrieve agent queue status and job history for a specific host.
Endpoint:
GET /api/v1/api/hosts?hostgroup=Production%20Databasehosts/:id/agent_queue
#
Required groupsScope: host:get
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
integer | No | 10 | Maximum number of jobs to return |
Response (200 OK):
{
"host_id": "550e8400-e29b-41d4-a716-446655440000",
"queue_status": {
"waiting": 0,
"active": 1,
"delayed": 0,
"failed": 0
},
"job_history": [
{
"id": "job-history-uuid",
"job_id": "bull-job-id",
"job_name": "package_update",
"status": "completed",
"attempt": 1,
"created_at": "2026-02-12T10:00:00.000Z",
"completed_at": "2026-02-12T10:05:00.000Z",
"error_message": null,
"output": null
}
],
"total_jobs": 1
}
Get Host Notes
Retrieve notes associated with spacesa specific host.
Endpoint:
GET /api/v1/api/hosts?hostgroup=Web%20Servers,Database%20Servershosts/:id/notes
MostRequired HTTP clients handle this automatically, but if constructing URLs manually, remember to replace spaces withScope: %20host:get
Response (200 OK):
{
"host_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Production web server. Enrolled via Proxmox auto-enrollment on 2026-01-15."
}
Get Host Integrations
Retrieve integration status and details for a specific host (e.g. Docker).
Endpoint:
GET /api/v1/api/hosts/:id/integrations
Required Scope: host:get
Response (200 OK) — Docker enabled:
{
"host_id": "550e8400-e29b-41d4-a716-446655440000",
"integrations": {
"docker": {
"enabled": true,
"containers_count": 12,
"volumes_count": 5,
"networks_count": 3,
"description": "Monitor Docker containers, images, volumes, and networks. Collects real-time container status events."
}
}
}
Response (200 OK) — Docker not enabled:
{
"host_id": "550e8400-e29b-41d4-a716-446655440000",
"integrations": {
"docker": {
"enabled": false,
"description": "Monitor Docker containers, images, volumes, and networks. Collects real-time container status events."
}
}
}
Common Error Responses (All Endpoints)
401404 UnauthorizedNot Found -— InvalidHost ordoes expirednot credentials:exist (for single-host endpoints):
{
"error": "Invalid API key"
}
{
"error": "API key is disabled"
}
{
"error": "API key has expired"
}
403 Forbidden - Invalid IP address:
{
"error": "IP addressHost not allowed"
}
403 Forbidden - Missing scope:
{
"error": "Access denied",
"message": "This API key does not have permission to get host"found"
}
500 Internal Server Error -— ServerUnexpected server error:
{
"error": "Failed to fetch hosts"
}
See the Troubleshooting section for authentication and permission errors.
Usage Examples
cURL Examples
Basic Request - List All Hosts
curl -u "patchmon_api_abc123:def456xyz789"patchmon_ae_abc123:your_secret_here" \
https://patchmon.example.com/api/v1/api/hosts
List Hosts with Stats
curl -u "patchmon_ae_abc123:your_secret_here" \
"https://patchmon.example.com/api/v1/api/hosts?include=stats"
Filter by SingleHost Group
curl -u "patchmon_api_abc123:def456xyz789"patchmon_ae_abc123:your_secret_here" \
"https://patchmon.example.com/api/v1/api/hosts?hostgroup=Production"
Filter by Host Group with Stats
curl -u "patchmon_ae_abc123:your_secret_here" \
"https://patchmon.example.com/api/v1/api/hosts?hostgroup=Production&include=stats"
Filter by Multiple Groups
curl -u "patchmon_api_abc123:def456xyz789"patchmon_ae_abc123:your_secret_here" \
"https://patchmon.example.com/api/v1/api/hosts?hostgroup=Production,Development"
FilterGet byHost Group with Spaces in NameStatistics
# Group name: "Web Servers" - use %20 for spaces
curl -u "patchmon_api_abc123:def456xyz789"patchmon_ae_abc123:your_secret_here" \
https://patchmon.example.com/api/v1/api/hosts/HOST_UUID/stats
Get Host System Information
curl -u "patchmon_ae_abc123:your_secret_here" \
https://patchmon.example.com/api/v1/api/hosts/HOST_UUID/system
Get All Packages for a Host
curl -u "patchmon_ae_abc123:your_secret_here" \
https://patchmon.example.com/api/v1/api/hosts/HOST_UUID/packages
Get Only Packages with Available Updates
curl -u "patchmon_ae_abc123:your_secret_here" \
"https://patchmon.example.com/api/v1/api/hosts?hostgroup=Web%20Servers"hosts/HOST_UUID/packages?updates_only=true"
Pretty Print JSON Output
curl -u "patchmon_api_abc123:def456xyz789"patchmon_ae_abc123:your_secret_here" \
https://patchmon.example.com/api/v1/api/hosts | jq .
Save Response to File
curl -u "patchmon_api_abc123:def456xyz789" \
https://patchmon.example.com/api/v1/api/hosts \
-o hosts.json
Verbose Output (See Headers)
curl -v -u "patchmon_api_abc123:def456xyz789" \
https://patchmon.example.com/api/v1/api/hosts
Python Examples
Using requests Library
import requests
from requests.auth import HTTPBasicAuth
# API credentials
API_KEY = "patchmon_api_abc123"patchmon_ae_abc123"
API_SECRET = "def456xyz789"your_secret_here"
BASE_URL = "https://patchmon.example.com"
# Create session with authentication
session = requests.Session()
session.auth = HTTPBasicAuth(API_KEY, API_SECRET)
# GetList all hosts
response = session.get(f"{BASE_URL}/api/v1/api/hosts")
if response.status_code == 200:
data = response.json()
print(f"Total hosts: {data['total']}")
for host in data['hosts']:
print(f"Host:groups {host['friendly_name']}= ({host['ip']})")
print(f" Groups: {', '.join([g['name'] for g in host['host_groups']])
print(f" {host['friendly_name']} ({host['ip']}) — Groups: {groups}")
else:
print(f"Error: {response.status_code} -— {response.json()}")
Filter by Host Group
# GetFilter hosts in Productionby group response = session.get(
f"{BASE_URL}/api/v1/api/hosts",
params={"hostgroup": "Production"}
)
if response.status_code == 200:
data = response.json()
print(f"Production hosts: {data['total']}")
# Get hosts in group with spacesname (requests handles URL encoding automatically)
response = session.get(
f"{BASE_URL}/api/v1/api/hosts",
params={"hostgroup": "WebProduction"}
Servers")
List Hosts with Inline Stats
# Get hosts with stats in a single request (more efficient than per-host /stats calls)
response = session.get(
f"{BASE_URL}/api/v1/api/hosts",
params={"include": "stats"}
)
if response.status_code == 200:
data = response.json()
for host in data['hosts']:
print(f"{host['friendly_name']}: {host['updates_count']} updates, "
f"{host['security_updates_count']} security, "
f"{host['total_packages']} total packages")
Get Host Packages (Updates Only)
# Get only packages that need updates for a specific host
response = session.get(
f"{BASE_URL}/api/v1/api/hosts/{host_id}/packages",
params={"updates_only": "true"}
)
if response.status_code == 200:
data = response.json()
print(f"Host: {data['host']['friendly_name']}")
print(f"Packages needing updates: {data['total']}")
for pkg in data['packages']:
security = " [SECURITY]" if pkg['is_security_update'] else ""
print(f" {pkg['name']}: {pkg['current_version']} → {pkg['available_version']}{security}")
Get Host Details and Stats
# First, get list of hosts
hosts_response = session.get(f"{BASE_URL}/api/v1/api/hosts")
hosts = hosts_response.json()['hosts']
# AutomaticallyThen encodedget tostats Web%20Serversfor the first host
if hosts:
host_id = hosts[0]['id']
stats = session.get(f"{BASE_URL}/api/v1/api/hosts/{host_id}/stats").json()
print(f"Installed: {stats['total_installed_packages']}")
print(f"Outdated: {stats['outdated_packages']}")
print(f"Security: {stats['security_updates']}")
info = session.get(f"{BASE_URL}/api/v1/api/hosts/{host_id}/info").json()
print(f"OS: {info['os_type']} {info['os_version']}")
print(f"Agent: {info['agent_version']}")
Error Handling
def get_hosts(hostgroup=None):
"""Get hosts with error handling"handling."""
try:
params = {"hostgroup": hostgroup} if hostgroup else {}
response = session.get(
f"{BASE_URL}/api/v1/api/hosts",
params=params,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print("Authentication failed -— check credentials")
elif e.response.status_code == 403:
print("Access denied -— insufficient permissions")
else:
print(f"HTTP error: {e}")
return None
except requests.exceptions.Timeout:
print("Request timed out")
return None
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
# Usage
hosts = get_hosts(hostgroup="Production")
if hosts:
for host in hosts['hosts']:
print(f"{host['friendly_name']}: {host['ip']}")
Generate Ansible Inventory
import json
import requests
from requests.auth import HTTPBasicAuth
API_KEY = "patchmon_api_abc123"patchmon_ae_abc123"
API_SECRET = "def456xyz789"your_secret_here"
BASE_URL = "https://patchmon.example.com"
def generate_ansible_inventory():
"""Generate Ansible inventory from PatchMon hosts"hosts.""
# Authenticate"
auth = HTTPBasicAuth(API_KEY, API_SECRET)
response = requests.get(f"{BASE_URL}/api/v1/api/hosts", auth=auth)auth, timeout=30)
if response.status_code != 200:
print(f"Error fetching hosts: {response.status_code}")
return
data = response.json()
# Build Ansible inventory structure
inventory = {
"_meta": {
"hostvars": {}
},
"all": {
"hosts": [], "children": []
}
}
# Process hosts
for host in data['hosts']:
hostname = host['friendly_name']
inventory["all"]["hosts"].append(hostname)
# Add host variables
inventory["_meta"]["hostvars"][hostname] = {
"ansible_host": host['ip'],
"patchmon_id": host['id'],
"patchmon_hostname": host['hostname']
}
# Add to groups
for group in host['host_groups']:
group_name = group['name'].lower().replace(' ', '_')
if group_name not in inventory:
inventory[group_name] = {
"hosts": [], "vars": {}
}
inventory["all"]["children"].append(group_name)
inventory[group_name]["hosts"].append(hostname)
# Output inventory
print(json.dumps(inventory, indent=2))
if __name__ == "__main__":
generate_ansible_inventory()
JavaScript/Node.js Examples
Using Native axiosfetch (Node.js 18+)
const axios = require('axios');
// API credentials
const API_KEY = 'patchmon_api_abc123'patchmon_ae_abc123';
const API_SECRET = 'def456xyz789'your_secret_here';
const BASE_URL = 'https://patchmon.example.com';
// Create axios instance with authentication
const client = axios.create({
baseURL: BASE_URL,
auth: {
username: API_KEY,
password: API_SECRET
}
});
// Get all hosts
async function getAllHosts() {
try {
const response = await client.get('/api/v1/api/hosts');
const { hosts, total } = response.data;
console.log(`Total hosts: ${total}`);
hosts.forEach(host => {
console.log(`Host: ${host.friendly_name} (${host.ip})`);
const groups = host.host_groups.map(g => g.name).join(', ');
console.log(` Groups: ${groups}`);
});
return hosts;
} catch (error) {
if (error.response) {
// Server responded with error
console.error(`Error: ${error.response.status} - ${error.response.data.error}`);
} else if (error.request) {
// Request made but no response
console.error('No response from server');
} else {
// Error setting up request
console.error(`Error: ${error.message}`);
}
throw error;
}
}
// Get hosts by group
async function getHostsByGroup(groupName) {
try {
const response = await client.get('/api/v1/api/hosts', {
params: { hostgroup: groupName } // axios handles URL encoding automatically
});
return response.data.hosts;
} catch (error) {
console.error(`Failed to get hosts for group ${groupName}:`, error.message);
throw error;
}
}
// Usage
(async () => {
try {
// Get all hosts
await getAllHosts();
// Get production hosts
const productionHosts = await getHostsByGroup('Production');
console.log(`Production hosts: ${productionHosts.length}`);
} catch (error) {
console.error('Failed to fetch hosts');
process.exit(1);
}
})();
Using Native fetch (Node.js 18+)
// API credentials
const API_KEY = 'patchmon_api_abc123';
const API_SECRET = 'def456xyz789';
const BASE_URL = 'https://patchmon.example.com';
// Create Basic Auth header
const authHeader = 'Basic ' + Buffer.from(`${API_KEY}:${API_SECRET}`).toString('base64');
async function getHosts(hostgroup = null) {
const url = new URL('/api/v1/api/hosts', BASE_URL);
if (hostgroup) {
url.searchParams.append('hostgroup', hostgroup);
// URL class handles encoding automatically
}
try {
const response = await fetch(url, {
headers: {
'Authorization': authHeader,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`HTTP ${response.status}: ${error.error}`);
}
return await response.json();
}
catch (error) {
console.error('Failed to fetch hosts:', error.message);
throw error;
}
}
// UsageList all hosts
getHosts('Production')
.then(data => {
console.log(`Total: ${data.total}`);
data.hosts.forEach(host => {
console.log(`${host.friendly_name}: ${host.ip}`);
});
})
.catch(error => {
console.error('Error:', error.message);
});
// Group with spaces - URL encoding handled automatically
getHosts('Web Servers')
.then(data => {
console.log(`Web Servers total: ${data.total}`);
});
Ansible Integration
Dynamic Inventory Script
Save this as patchmon_inventory.py and make it executable:executable (chmod +x):
#!/usr/bin/env python3
"""
PatchMon Dynamic Inventory Script for AnsibleAnsible.
Usage: ansible-playbook -i patchmon_inventory.py playbook.yml
"""
import json
import os
import sys
import requests
from requests.auth import HTTPBasicAuth
# Configuration from environment variables
API_KEY = os.environ.get('PATCHMON_API_KEY')
API_SECRET = os.environ.get('PATCHMON_API_SECRET')
BASE_URL = os.environ.get('PATCHMON_URL', 'https://patchmon.example.com')
if not API_KEY or not API_SECRET:
print("Error: PATCHMON_API_KEY and PATCHMON_API_SECRET must be set", file=sys.stderr)
sys.exit(1)
def get_inventory():
"""Fetch inventory from PatchMon API"""
auth = HTTPBasicAuth(API_KEY, API_SECRET)
try:
response = requests.get(
f"{BASE_URL}/api/v1/api/hosts", auth=auth, timeout=30
)30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching inventory: {e}", file=sys.stderr)
sys.exit(1)
def build_ansible_inventory(patchmon_data):
"""Convert PatchMon data to Ansible inventory format"""
inventory = {
"_meta": {
"hostvars": {}
},
"all": {
"hosts": []
}
}
groups = {}
for host in patchmon_data['hosts']:
hostname = host['friendly_name']
# Add to all hosts
inventory["all"]["hosts"].append(hostname)
# Set host variables
inventory["_meta"]["hostvars"][hostname] = {
"ansible_host": host['ip'],
"patchmon_id": host['id'],
"patchmon_hostname": host['hostname']
}
# Process groups
for group in host['host_groups']:
group_name = group['name'].lower().replace(' ', '_').replace('-', '_')
if group_name not in groups:
groups[group_name] = {
"hosts": [],
"vars": {
"patchmon_group_id": group['id']
}
}
groups[group_name]["hosts"].append(hostname)
# Add groups to inventory
inventory.update(groups)
return inventory
def main():
"""Main entry point"""
# Ansible passes --list or --host <hostname>
if len(sys.argv) == 2 and sys.argv[1] == '--list':
# Return full inventory
patchmon_data = get_inventory()
inventory = build_ansible_inventory(patchmon_data)
print(json.dumps(inventory, indent=2))
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
# Return empty dict (we use _meta for hostvars)
print(json.dumps({}))
else:
print("Usage: patchmon_inventory.py --list", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
Usage with Ansible
Usage:
# Export credentials
export PATCHMON_API_KEY="patchmon_api_abc123"patchmon_ae_abc123"
export PATCHMON_API_SECRET="def456xyz789"your_secret_here"
export PATCHMON_URL="https://patchmon.example.com"
# Make script executable
chmod +x patchmon_inventory.py
# Test inventory
./patchmon_inventory.py --list
# Use with ansible
ansible-playbook -i patchmon_inventory.py playbook.yml
# Use with ansible ad-hoc commands
ansible -i patchmon_inventory.py production -m ping
ansible -i patchmon_inventory.py all -m setup
Example Playbook
---
- name: Update all PatchMon hosts
hosts: all
gather_facts: yes
become: yes
tasks:
- name: Display host info
debug:
msg: "Host {{ inventory_hostname }} ({{ ansible_host }}) - PatchMon ID: {{ patchmon_id }}"
- name: Update package cache
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: Upgrade all packages
apt:
upgrade: dist
when: ansible_os_family == "Debian"
- name: Production-specific tasks
hosts: production
gather_facts: no
become: yes
tasks:
- name: Restart services after update
systemd:
name: "{{ item }}"
state: restarted
loop:
- nginx
- php-fpm
Expected Outputs
Successful Request - All Hosts
Request:
curl -u "patchmon_api_abc123:def456xyz789" \
https://patchmon.example.com/api/v1/api/hosts
Response (HTTP 200):
{
"hosts": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendly_name": "web-server-01",
"hostname": "web01.example.com",
"ip": "192.168.1.100",
"host_groups": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
},
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "Web Servers"
}
]
},
{
"id": "550e8400-e29b-41d4-a716-446655440003",
"friendly_name": "db-server-01",
"hostname": "db01.example.com",
"ip": "192.168.1.101",
"host_groups": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
},
{
"id": "880e8400-e29b-41d4-a716-446655440004",
"name": "Database Servers"
}
]
},
{
"id": "550e8400-e29b-41d4-a716-446655440005",
"friendly_name": "web-server-dev-01",
"hostname": "webdev01.example.com",
"ip": "192.168.2.100",
"host_groups": [
{
"id": "990e8400-e29b-41d4-a716-446655440006",
"name": "Development"
},
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "Web Servers"
}
]
}
],
"total": 3
}
Successful Request - Filtered by Group
Request:
curl -u "patchmon_api_abc123:def456xyz789" \
"https://patchmon.example.com/api/v1/api/hosts?hostgroup=Production"
Response (HTTP 200):
{
"hosts": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendly_name": "web-server-01",
"hostname": "web01.example.com",
"ip": "192.168.1.100",
"host_groups": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
},
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "Web Servers"
}
]
},
{
"id": "550e8400-e29b-41d4-a716-446655440003",
"friendly_name": "db-server-01",
"hostname": "db01.example.com",
"ip": "192.168.1.101",
"host_groups": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Production"
},
{
"id": "880e8400-e29b-41d4-a716-446655440004",
"name": "Database Servers"
}
]
}
],
"total": 2,
"filtered_by_groups": ["Production"]
}
Empty Result - No Hosts Found
Request:
curl -u "patchmon_api_abc123:def456xyz789" \
"https://patchmon.example.com/api/v1/api/hosts?hostgroup=NonExistentGroup"
Response (HTTP 200):
{
"hosts": [],
"total": 0,
"filtered_by_groups": ["NonExistentGroup"]
}
Error - Invalid Credentials
Request:
curl -u "invalid_key:invalid_secret" \
https://patchmon.example.com/api/v1/api/hosts
Response (HTTP 401):
{
"error": "Invalid API key"
}
Error - Expired Credential
Response (HTTP 401):
{
"error": "API key has expired"
}
Error - Insufficient Permissions
Request: Credential with no host:get scope
Response (HTTP 403):
{
"error": "Access denied",
"message": "This API key does not have permission to get host"
}
Error - IP Not Allowed
Request: From IP not in allowed_ip_ranges
Response (HTTP 403):
{
"error": "IP address not allowed"
}
Error - Inactive Credential
Response (HTTP 401):
{
"error": "API key is disabled"
}ping
Security Best Practices
Credential Management
1. Store Credentials Securely
✅ DO:Do:
UseStore credentials in a password manager(1Password, LastPass, Bitwarden)Store inor secretsmanagement systemsvault (e.g. HashiCorp Vault, AWS Secrets Manager)- Use environment variables for automation scripts
EncryptSet expiration dates (recommended: 90 days)- Grant only the minimum permissions needed (principle of least privilege)
- Rotate credentials
atregularlyrestand delete old ones after migration
❌ DON'T:Don't:
- Hard-code credentials in source code
- Commit credentials to version control
- Share credentials via email or chat
- Store credentials in
plainplain-text files
2. Use Environment Variables
# ~/.bashrc or ~/.zshrc
export PATCHMON_API_KEY="patchmon_api_abc123"
export PATCHMON_API_SECRET="def456xyz789"
export PATCHMON_URL="https://patchmon.example.com"
# Python
import os
API_KEY = os.environ.get('PATCHMON_API_KEY')
API_SECRET = os.environ.get('PATCHMON_API_SECRET')
// Node.js
const API_KEY = process.env.PATCHMON_API_KEY;
const API_SECRET = process.env.PATCHMON_API_SECRET;
3. Rotate Credentials Regularly
Set expiration dates (recommended: 90 days)Create new credentials before old ones expireUpdate all systems using the credentialDelete old credentials after migration
4. Use Least Privilege
Grant only the minimum permissions needed:
// Read-only inventory access
{
"host": ["get"]
}
// Update hosts but no delete
{
"host": ["get", "patch"]
}
5. Implement IP Restrictions
Restrict credentials to known IP addresses:addresses whenever possible:
Allowed IPs: 192.168.1.100, 10.0.0.500/24
For dynamic IPs, consider:
- using
- a VPN with a static exit
IPsIP, Cloudaprovidercloud NATgatewaygateway,IPsor Proxyaserversproxywith static IPs
Network Security
1.
- Always
Useuse HTTPS
✅in DO:
curlenvironments
-u "key:secret" https://patchmon.example.com/api/v1/api/hosts
❌ DON'T:
curl -u "key:secret" http://patchmon.example.com/api/v1/api/hosts
2.
#— Default (verifies SSL)
curl -u "key:secret" https://patchmon.example.com/api/v1/api/hosts
# Onlyonly disable verification (-k) for testing/developmentdevelopment/testing# Python - verify SSL
requests.get(url, auth=auth, verify=True) # Default
# Never in production!
requests.get(url, auth=auth, verify=False) # Insecure
3.
Restrictto restrict PatchMon API access at the network level:
# Allow only specific IPs
iptables -A INPUT -p tcp --dport 443 -s 192.168.1.100 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j DROP
Monitoring & Auditing
1. Monitor Credential Usage
- Check "Last Used" timestamps regularly in the Integrations settings page
- Investigate credentials that
haven'thave not been used in 30+ days Look for unusual access patterns
2. Set Up Alerts
Monitor for:
Failed authentication attemptsAccess from unexpected IPsDisabled credentials still being usedExpired credentials being used
3. Regular Audits
- Review all active credentials monthly
- Remove credentials for decommissioned systems
Verify credential owners are still validCheck scope assignments are still appropriate
Incident Response
If Credentials Are Compromised:
Compromised
- Immediately disable the credential
in PatchMon UI Review "Last Used" timestampto understand exposureCheck server logsfor unauthorized accessCreate new credentialswith different scope if neededDelete compromised credentialsafter verificationNotify security teamif data was accessedUpdate incident response documentation
Troubleshooting
Authentication Issues
Problem: "Invalid API key"
Possible Causes:
Token key is incorrectToken has been deletedToken key format is invalid
Solution:
Verify the token key is correct (check for copy/paste errors)Confirm the credential exists in PatchMon UICheck for extra whitespace or special charactersTry creating a new credential and testing
Problem: "Invalid API secret"
Possible Causes:
Token secret is incorrectSecret was copied incorrectly
Solution:
Secrets cannot be retrieved - create a new credentialEnsure no trailing spaces or newlines in secretVerify the entire secret was copied
Problem: "API key is disabled"
Possible Causes:
Credential was manually disabledSecurity incident response
Solution:
Check credential statusin PatchMon UI (Settings → Integrations →API)toggle off)ClickReview the "Enable"Last Used" timestamp toreactivateunderstand the window of exposureOrCheckcreateserveralogsnewforcredentialanyifunauthorisedthis one should remain disabled
Problem: "API key has expired"
Possible Causes:
Expiration date has passed
Solution:
Create a new credential to replace the expired oneaccessUpdate your systems with theCreate new credentialsDeletewiththeaold credential
Permission Issues
Problem: "Access denied"
Possible Causes:
Credential doesn't have requireddifferent scopeWrong integration type
Solution:
Check the error message for specific permissionif neededGoDeletetotheSettingscompromised→credentialIntegrationsafter→ APIverificationClickNotify"Edit"youronsecuritytheteamcredentialif Addsensitivethedatarequiredmayscopehave(e.g.,beenhost: get)Save and retry the requestaccessed
Problem:
"This API key does not have permission to get host"
Solution:
Edit the credential and add the get action to the host resource scope.
Troubleshooting
Network Issues
Problem: "IP address not allowed"
Possible Causes:
Request is coming from an IP not inallowed_ip_rangesProxy or load balancer changing source IP
Solution:
Identify your actual source IP:curl https://ifconfig.meAdd this IP to the credential's allowed IP rangesIf behind a proxy/NAT, add the proxy's IPConsider removing IP restrictions for development
Problem: Connection timeout
Possible Causes:
Network connectivity issuesFirewall blocking accessPatchMon server is down
Solution:
Test basic connectivity:ping patchmon.example.com curl -I https://patchmon.example.com/healthCheck firewall rulesVerify PatchMon server statusTry from a different network
Problem: SSL certificate errors
Possible Causes:
Self-signed certificateExpired certificateCertificate name mismatch
Solution:
For production: Fix the SSL certificateFor development only:curl -k ... # Skip verification (insecure)Python:requests.get(url, auth=auth, verify=False) # Development only
Response Issues
Problem: Empty hosts array
Possible Causes:
No hosts in the systemFiltering by non-existent groupNo hosts in the specified group
Solution:
Verify hosts exist: Check PatchMon UI → HostsCheck filter spelling:# Wrong ?hostgroup=Productin # Correct ?hostgroup=ProductionList all hosts without filters to verify API access
Problem: Unexpected response format
Possible Causes:
Wrong API endpointAPI version mismatchServer error returning HTML
Solution:
Verify endpoint URL is correctCheck API version in URL (/api/v1/...)Add verbose output to see full response:curl -v ...Check Content-Type header isapplication/json
Common ErrorsError Reference
| Error Message | HTTP Code | Cause | Solution |
|---|---|---|---|
Missing or invalid authorization |
401 | No Authorization Basic |
Use -u key:secret with Authorization: Basic <base64> header |
Invalid credentials |
401 | Check format is key:secret |
|
Invalid API |
401 | Token key not found in the database | Verify the credential exists |
API key is |
401 | Credential |
|
API key |
401 | ||
Invalid API |
401 | The credential's integration_type is not "api" |
Ensure you created the credential with the "API" usage type |
Invalid API secret |
401 | Secret doesn't match the stored bcrypt hash | |
IP address not |
403 | allowed_ip_ranges |
Add curl https://ifconfig.me to |
Access — does not have permission to {action} {resource} |
403 | ||
Access denied — does not have access to {resource} |
403 | The resource is not included in the credential's scopes at all | Edit the credential's scopes to include the resource |
Host not found |
404 | The host UUID does not exist | Verify the UUID from the list hosts endpoint |
Failed to fetch |
500 | Check PatchMon server |
|
Authentication failed |
500 | Unexpected error during authentication processing | Check PatchMon server logs; may indicate a database issue |
Debug ModeTips
EnablecURL verbose output for troubleshooting:mode:
cURL
# Verbose mode - shows full request/response
curl -v -u "key:secret"patchmon_ae_abc123:your_secret_here" https://patchmon.example.com/api/v1/api/hosts
# Trace mode - even more detailed
curl --trace-ascii - -u "key:secret"\
https://patchmon.example.com/api/v1/api/hosts
Python debug logging:
import logging
import requests
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
Common Issues
Empty hosts array
- Verify hosts exist in PatchMon UI → Hosts page
- Check the
hostgroupfilter spelling matches exactly (case-sensitive) - Try listing all hosts without filters first to confirm API access works
Connection timeouts
# MakeTest requestbasic connectivity
ping patchmon.example.com
curl -I will show full details
response = requests.get(url, auth=auth)https://patchmon.example.com/health
JavaScriptSSL certificate errors
For development/testing with self-signed certificates:
constcurl axios-k =-u require('axios');"patchmon_ae_abc123:your_secret_here" \
https:// Enable debug mode
axios.defaults.debug = true;
// Or use interceptors
axios.interceptors.request.use(request => {
console.log('Request:', JSON.stringify(request, null, 2));
return request;
});
axios.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response.data, null, 2));
return response;
});patchmon.example.com/api/v1/api/hosts
For production, install a valid SSL certificate (e.g. Let's Encrypt).
Getting Help
If you'reissues still experiencing issues:persist:
Check Documentation: Review this guide thoroughlyServer Logs:Check PatchMon server logs for detailed errordetailsinformationCommunityUseSupport:theAskbuilt-inPatchMonSwaggercommunityUIforumsto test endpoints interactivelyGitHub Issues:Search or create an issue at github.com/9technologygroup/patchmon.netPatchMon/PatchMonSupport:JoinContactthe PatchMonsupportcommunitywith:onError message (exact text)Steps to reproducecURL command that fails (sanitize credentials!)Expected vs actual behavior
Discord
API Reference
Base URL
https://your-patchmon-instance.com/api/v1
API Version
Current version: v1
The API version can be configured via the API_VERSION environment variable (default: v1).
Authentication
All API endpoints require Basic Authentication:
Authorization: Basic <base64(token_key:token_secret)>
Content Type
All requests and responses use JSON:
Content-Type: application/json
Rate Limiting
API requests are subject to rate limiting:
Limit: Varies by instance configurationHeaders: Rate limit information is included in response headersExceeded: Returns429 Too Many Requests
Endpoints Summary
| |
Changelog
Version 1.0 (Current)
Initial release of API credentials systemBasic Authentication with scoped permissionsHost listing endpoint with group filteringIP restrictions and expiration datesIntegration with auto-enrollment token system
Appendix
Scope Reference Table
HTTP Status Codes
Glossary
API Credential: Authentication token for programmatic accessBasic Authentication: HTTP authentication scheme using username:passwordScope: Permission defining what actions a credential can performToken Key: The "username" part of the API credentialToken Secret: The "password" part of the API credential (hashed)Host: A server or system monitored by PatchMonHost Group: Collection of hosts for organizational purposesIntegration Type: Category of API credential (api, gethomepage, proxmox-lxc)
Last Updated: November 10, 2025
API Version: v1
Document Version: 1.0
For the latest documentation, visit: https://docs.patchmon.net