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

POST /auth/register Public

Create 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

POST /auth/login Public

Authenticate 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

POST /auth/logout Authenticated

Invalidate the current session token.

Response (204): No content.

Get current user

GET /auth/me Authenticated

Returns 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

POST /auth/verify Public

Confirm 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

POST /servers Authenticated

Register 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

GET /servers Authenticated

List all servers in the account.

Query parameters:

ParamTypeDescription
tagstringFilter by tag. Repeat for multiple tags.
statusstringFilter by status: healthy, warning, critical, offline.
searchstringSearch by name or hostname.
pageintPage number (default: 1).
per_pageintResults 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

GET /servers/:server_id Authenticated

Get 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

DELETE /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

POST /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_cores is only present when per-core monitoring is enabled (Crucible 0.3.0+).
  • inodes_total and inodes_used are used by the inode_high alert rule.
  • options contains mount options; used by the filesystem_readonly rule to detect read-only remounts.

Health

Get server health

GET /servers/:server_id/health Authenticated

Get 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

GET /servers/:server_id/health/history Authenticated

Get time-series metric data for a server.

Query parameters:

ParamTypeDescription
metricstringMetric name: cpu, memory, disk, network, temperature.
fromISO 8601Start time (default: 1 hour ago).
toISO 8601End time (default: now).
resolutionstringData 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

GET /servers/:server_id/health/alerts Authenticated

List active and historical alerts for a server.

Query parameters:

ParamTypeDescription
statusstringactive, resolved, or all (default: all).
severitystringFilter: critical, warning, info.
fromISO 8601Start time for history.
toISO 8601End time for history.
pageintPage 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

POST /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

GET /channels Authenticated

Response (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

GET /channels/:channel_id Authenticated

Response (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

PUT /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

DELETE /channels/:channel_id Authenticated (Admin)

Response (204): No content.

Test channel

POST /channels/:channel_id/test Authenticated

Send 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

POST /alerts/:alert_id/acknowledge Authenticated

Acknowledge 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

GET /servers/:server_id/mutes Authenticated

List 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

PUT /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 groupLimit
Authentication10 requests/minute
Ingest120 requests/minute per server
All other endpoints60 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.