API Reference
Base URL: https://forge.glassmkr.com/api/v1
All requests and responses use JSON. Authenticated endpoints require a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_TOKEN
Error responses follow a consistent format:
{
"error": "short_code",
"message": "Human-readable description of what went wrong."
} Table of contents
- Authentication - register, login, logout, me, verify
- Servers - register, list, get, delete
- Ingest - push metrics
- Health - health status, history, alerts
- Channels - CRUD + test
- Alerts - acknowledge, mutes
Authentication
Register
/auth/register PublicCreate a new Forge account.
Request body:
{
"email": "[email protected]",
"password": "min-12-characters",
"name": "Jane Doe"
} Response (201):
{
"user": {
"id": "usr_a1b2c3d4",
"email": "[email protected]",
"name": "Jane Doe",
"verified": false,
"created_at": "2026-04-05T10:00:00Z"
},
"token": "eyJhbGciOiJIUzI1NiIs..."
} A verification email is sent automatically. The account is fully functional before verification, but some features (team invites) require a verified email.
Login
/auth/login PublicAuthenticate and receive a session token.
Request body:
{
"email": "[email protected]",
"password": "min-12-characters"
} Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": "2026-04-12T10:00:00Z"
} Tokens are valid for 7 days. Use the token in the Authorization header for all authenticated requests.
Error (401):
{
"error": "invalid_credentials",
"message": "Email or password is incorrect."
}Logout
/auth/logout AuthenticatedInvalidate the current session token.
Response (204): No content.
Get current user
/auth/me AuthenticatedReturns the authenticated user's profile.
Response (200):
{
"id": "usr_a1b2c3d4",
"email": "[email protected]",
"name": "Jane Doe",
"verified": true,
"role": "owner",
"created_at": "2026-04-05T10:00:00Z",
"servers_count": 6,
"plan": "pro"
}Verify email
/auth/verify PublicConfirm an email address using the token from the verification email.
Request body:
{
"token": "verify_abc123def456"
} Response (200):
{
"message": "Email verified successfully."
} Error (400):
{
"error": "invalid_token",
"message": "Verification token is invalid or has expired."
}Servers
Register server
/servers AuthenticatedRegister a new server with Forge. This is done via the dashboard ("+ Add Server") or programmatically with this endpoint.
Request body:
{
"name": "web-prod-01",
"hostname": "web-prod-01.example.com",
"os": "Ubuntu 24.04 LTS",
"arch": "x86_64",
"cpu_cores": 16,
"ram_total_mb": 65536,
"tags": ["production", "web"],
"location": "eu-west"
} Response (201):
{
"id": "srv_a1b2c3d4",
"name": "web-prod-01",
"hostname": "web-prod-01.example.com",
"os": "Ubuntu 24.04 LTS",
"arch": "x86_64",
"cpu_cores": 16,
"ram_total_mb": 65536,
"tags": ["production", "web"],
"location": "eu-west",
"status": "pending",
"created_at": "2026-04-05T10:00:00Z"
} The server status is pending until the first metrics push is received, then it changes to healthy.
List servers
/servers AuthenticatedList all servers in the account.
Query parameters:
| Param | Type | Description |
|---|---|---|
tag | string | Filter by tag. Repeat for multiple tags. |
status | string | Filter by status: healthy, warning, critical, offline. |
search | string | Search by name or hostname. |
page | int | Page number (default: 1). |
per_page | int | Results per page (default: 25, max: 100). |
Response (200):
{
"servers": [
{
"id": "srv_a1b2c3d4",
"name": "web-prod-01",
"status": "healthy",
"tags": ["production", "web"],
"location": "eu-west",
"cpu_percent": 23.5,
"ram_percent": 67.2,
"disk_percent": 45.0,
"last_seen": "2026-04-05T10:05:00Z"
}
],
"total": 6,
"page": 1,
"per_page": 25
}Get server
/servers/:server_id AuthenticatedGet full details for a single server.
Response (200):
{
"id": "srv_a1b2c3d4",
"name": "web-prod-01",
"hostname": "web-prod-01.example.com",
"os": "Ubuntu 24.04 LTS",
"arch": "x86_64",
"cpu_cores": 16,
"ram_total_mb": 65536,
"tags": ["production", "web"],
"location": "eu-west",
"status": "healthy",
"active_alerts": [],
"last_seen": "2026-04-05T10:05:00Z",
"created_at": "2026-04-05T10:00:00Z",
"crucible_version": "1.4.2"
}Delete server
/servers/:server_id Authenticated (Admin)Remove a server and all its stored metrics. This action is irreversible.
Response (204): No content.
Error (404):
{
"error": "not_found",
"message": "Server not found."
}Ingest
Push metrics
/ingest Authenticated (Server Token)Submit a metrics payload from a Crucible agent. This endpoint is called automatically by the agent on every collection interval.
Request body:
{
"server_id": "srv_a1b2c3d4",
"timestamp": "2026-04-05T10:05:00Z",
"crucible_version": "1.4.2",
"metrics": {
"cpu": {
"user": 15.2,
"system": 5.3,
"iowait": 1.1,
"idle": 78.4,
"steal": 0.0
},
"cpu_cores": [
{"core": 0, "user": 20.1, "system": 4.2, "iowait": 0.5, "idle": 75.2},
{"core": 1, "user": 10.3, "system": 6.4, "iowait": 1.7, "idle": 81.6}
],
"memory": {
"total_mb": 65536,
"available_mb": 21504,
"used_mb": 44032,
"buffers_mb": 512,
"cache_mb": 8192,
"swap_total_mb": 8192,
"swap_used_mb": 0
},
"disks": [
{
"mount": "/",
"device": "/dev/nvme0n1p2",
"fs_type": "ext4",
"options": "rw,relatime",
"total_gb": 500,
"used_gb": 225,
"inodes_total": 32768000,
"inodes_used": 1245000,
"read_iops": 120,
"write_iops": 45,
"read_mbps": 15.2,
"write_mbps": 3.8,
"latency_ms": 0.4
}
],
"network": [
{
"interface": "eth0",
"speed_mbps": 10000,
"rx_bytes": 125000000,
"tx_bytes": 42000000,
"rx_packets": 95000,
"tx_packets": 31000,
"rx_errors": 0,
"tx_errors": 0,
"rx_drops": 0,
"tx_drops": 0
}
],
"smart": [
{
"device": "/dev/nvme0n1",
"model": "Samsung 990 Pro 2TB",
"health": "PASSED",
"temperature_c": 38,
"percentage_used": 12,
"power_on_hours": 8760
}
],
"thermal": {
"cpu_package_c": 52,
"cores": [51, 53, 50, 52]
},
"raid": [],
"ipmi": {
"psu_1": "ok",
"psu_2": "ok",
"fan_1_rpm": 4200,
"inlet_temp_c": 24
},
"ecc": {
"correctable": 0,
"uncorrectable": 0
}
}
} Response (202):
{
"accepted": true,
"alerts_evaluated": 30,
"alerts_fired": 0
} The response includes how many alert rules were evaluated (up to 30) and how many fired. If alerts fire, notifications are dispatched asynchronously.
Notes on the payload:
cpu_coresis only present when per-core monitoring is enabled (Crucible 0.3.0+).inodes_totalandinodes_usedare used by theinode_highalert rule.optionscontains mount options; used by thefilesystem_readonlyrule to detect read-only remounts.
Health
Get server health
/servers/:server_id/health AuthenticatedGet the current health status and latest metric values for a server.
Response (200):
{
"server_id": "srv_a1b2c3d4",
"status": "healthy",
"last_seen": "2026-04-05T10:05:00Z",
"current": {
"cpu_percent": 21.6,
"ram_percent": 67.2,
"swap_used_mb": 0,
"disk_max_percent": 45.0,
"network_rx_mbps": 120.5,
"network_tx_mbps": 40.2,
"cpu_temp_c": 52,
"active_alerts": 0
}
}Get health history
/servers/:server_id/health/history AuthenticatedGet time-series metric data for a server.
Query parameters:
| Param | Type | Description |
|---|---|---|
metric | string | Metric name: cpu, memory, disk, network, temperature. |
from | ISO 8601 | Start time (default: 1 hour ago). |
to | ISO 8601 | End time (default: now). |
resolution | string | Data point interval: 1m, 5m, 1h, 1d (auto-selected if omitted). |
Response (200):
{
"server_id": "srv_a1b2c3d4",
"metric": "cpu",
"from": "2026-04-05T09:00:00Z",
"to": "2026-04-05T10:00:00Z",
"resolution": "1m",
"data": [
{
"timestamp": "2026-04-05T09:00:00Z",
"user": 14.2,
"system": 5.1,
"iowait": 0.8,
"idle": 79.9,
"steal": 0.0
}
]
}Get server alerts
/servers/:server_id/health/alerts AuthenticatedList active and historical alerts for a server.
Query parameters:
| Param | Type | Description |
|---|---|---|
status | string | active, resolved, or all (default: all). |
severity | string | Filter: critical, warning, info. |
from | ISO 8601 | Start time for history. |
to | ISO 8601 | End time for history. |
page | int | Page number (default: 1). |
Response (200):
{
"alerts": [
{
"id": "alt_x1y2z3",
"rule": "ram_high",
"severity": "warning",
"status": "active",
"value": 92.3,
"threshold": 90,
"message": "RAM usage is 92.3% (threshold: 90%)",
"triggered_at": "2026-04-05T09:47:00Z",
"resolved_at": null,
"acknowledged": false,
"acknowledged_by": null
}
],
"total": 1,
"page": 1
}Channels
Create channel
/channels Authenticated (Admin)Create a new notification channel.
Request body (Telegram example):
{
"name": "ops-telegram",
"type": "telegram",
"config": {
"bot_token": "7123456789:AAH1bGciOiJSUzI1NiIs",
"chat_id": "-1001234567890"
}
} Request body (Email example):
{
"name": "ops-email",
"type": "email",
"config": {
"recipients": ["[email protected]", "[email protected]"]
}
} Request body (Slack example):
{
"name": "ops-slack",
"type": "slack",
"config": {
"webhook_url": "https://hooks.slack.com/services/T00/B00/XXX"
}
} Response (201):
{
"id": "ch_m1n2o3",
"name": "ops-telegram",
"type": "telegram",
"created_at": "2026-04-05T10:00:00Z"
}List channels
/channels AuthenticatedResponse (200):
{
"channels": [
{
"id": "ch_m1n2o3",
"name": "ops-telegram",
"type": "telegram",
"created_at": "2026-04-05T10:00:00Z",
"last_used": "2026-04-05T10:05:00Z"
}
]
}Get channel
/channels/:channel_id AuthenticatedResponse (200):
{
"id": "ch_m1n2o3",
"name": "ops-telegram",
"type": "telegram",
"config": {
"bot_token": "712345****",
"chat_id": "-1001234567890"
},
"created_at": "2026-04-05T10:00:00Z",
"last_used": "2026-04-05T10:05:00Z"
} Note: sensitive fields like bot tokens are partially masked in GET responses.
Update channel
/channels/:channel_id Authenticated (Admin)Update a channel's name or configuration. Send only the fields you want to change.
Request body:
{
"name": "ops-telegram-renamed",
"config": {
"chat_id": "-1009876543210"
}
} Response (200): Updated channel object.
Delete channel
/channels/:channel_id Authenticated (Admin)Response (204): No content.
Test channel
/channels/:channel_id/test AuthenticatedSend a test notification through the channel. The test message includes a timestamp and the channel name.
Response (200):
{
"success": true,
"message": "Test notification sent to ops-telegram."
} Error (502):
{
"success": false,
"error": "delivery_failed",
"message": "Telegram API returned 401: Unauthorized. Check your bot token."
}Alerts
Acknowledge alert
/alerts/:alert_id/acknowledge AuthenticatedAcknowledge an active alert. This silences notifications for the current occurrence but does not disable the alert rule. If the alert resolves and fires again later, a new notification will be sent.
Request body (optional):
{
"note": "Investigating. Backup job is running, expect high disk I/O for 2 hours."
} Response (200):
{
"id": "alt_x1y2z3",
"rule": "cpu_iowait_high",
"status": "active",
"acknowledged": true,
"acknowledged_by": "usr_a1b2c3d4",
"acknowledged_at": "2026-04-05T10:10:00Z",
"note": "Investigating. Backup job is running, expect high disk I/O for 2 hours."
}List muted rules
/servers/:server_id/mutes AuthenticatedList all muted alert rules for a server.
Response (200):
{
"server_id": "srv_a1b2c3d4",
"muted_rules": ["disk_space_high", "cpu_iowait_high"],
"updated_at": "2026-04-05T10:00:00Z"
}Update muted rules
/servers/:server_id/mutes Authenticated (Admin)Set the list of muted alert rules for a server. This replaces the entire muted list.
Request body:
{
"muted_rules": ["disk_space_high", "cpu_iowait_high"]
} Response (200):
{
"server_id": "srv_a1b2c3d4",
"muted_rules": ["disk_space_high", "cpu_iowait_high"],
"updated_at": "2026-04-05T10:10:00Z"
} Changes take effect on the next ingest cycle from the server. Pass an empty array to unmute all rules.
Rate limits
The API enforces rate limits per token:
| Endpoint group | Limit |
|---|---|
| Authentication | 10 requests/minute |
| Ingest | 120 requests/minute per server |
| All other endpoints | 60 requests/minute |
When rate limited, the API returns 429 Too Many Requests with a Retry-After header indicating seconds to wait.
Pagination
List endpoints support pagination via page and per_page query parameters. The response includes a total field with the total number of results.