Skip to content

Take the server back into service

DELETE
/servers/{server_id}/maintenance
curl --request DELETE \
--url https://api.flotera.com/api/v1/servers/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/maintenance \
--cookie __Host-erm_session=<__Host-erm_session>

Clears the declaration. A server that was not under maintenance is already in service, so the call succeeds and changes nothing.

server_id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
Idempotency-Key
string
>= 1 characters <= 128 characters /^[\x21-\x7E]{1,128}$/

Idempotency key, 1–128 ASCII, unique per (tenant_id, operation, key) for at least 24 hours. Retrying with the same body replays the stored response; a different body returns 409 idempotency_conflict.

Back in service.

Media typeapplication/json

Server detail — PG config merged with the latest telemetry read model.

object
id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
name
required
string
<= 128 characters
agent_version

Null until an agent registers on the server.

string | null
<= 32 characters
gpu_count
required
integer
gpu_model
required
string
<= 128 characters
fsm_state
required

Historical field. The server FSM it was meant to carry has no writer anywhere in the system, so this is always new; read maintenance for what the owner declared, and the marketplace link for a rental or a listing. Kept in the response so existing clients do not break.

string
<= 32 characters
maintenance
One of:

The owner’s declaration that the machine is out of service. Absent on a machine in service — a machine is not «not under maintenance», it just works, and an object of nulls would say otherwise.

object
since
required

When it was declared; repeating the declaration keeps it.

string format: date-time
reason
string | null
<= 256 characters
presence
required

Presence read model over the agent’s WebSocket session (the channel commands travel on): suspect after 60 s without a heartbeat, offline after 300 s. HTTPS-only liveness (agent hello) never makes a server online.

string
Allowed values: online suspect offline
marketplace

Active marketplace assignment; null when unassigned.

string | null
<= 32 characters
price_usd_gpu_h

Decimal string or null when unknown.

string | null
/^-?[0-9]+(\.[0-9]{1,6})?$/
health
string | null
<= 32 characters
last_seen_at

Last WebSocket heartbeat of the agent session — the same source presence is derived from, so the two never disagree. Null until the command channel comes up at least once; an agent that only reaches the core over HTTPS (hello) does not move this field.

string | null format: date-time
revenue_today_usd
required

Decimal value serialized as a string, never a JSON number.

string
/^-?[0-9]+(\.[0-9]{1,6})?$/
max_temp_core_c
number | null
max_temp_hotspot_c
number | null
max_temp_vram_c
number | null
max_fan_pct
number | null
avg_util_pct

Average GPU utilization percentage over the server’s GPUs.

string | null
/^-?[0-9]+(\.[0-9]{1,6})?$/
group_id
Any of:

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
tags
required

key or key:value tags; empty until any tag is set.

Array<string>
<= 64 items
gpu_vitals
Array<object>
<= 128 items

One per-GPU vitals sample for the fleet Temp/Fan bars, ordered by GPU index.

object
temp_c
number | null
fan_pct
number | null
tenant_id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
hostname
string | null
<= 255 characters
gpus
required
Array<object>
<= 128 items
object
id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
index
required
integer
model
required
string
<= 128 characters
vram_mb
required
integer
mem_type

Memory technology (GDDR6, GDDR6X, HBM2…) as the agent read it from the card. Null until an agent that reports it delivers a sample — the value differs between production batches of one model, so it is never inferred from the model name.

string | null
<= 16 characters
mem_vendor

Memory chip manufacturer (Samsung, Hynix, Micron…).

string | null
<= 32 characters
board_vendor

Board brand (ASUS, Palit, MSI…), resolved server-side from the raw PCI subsystem vendor id the agent reports. Null for agents that predate the field and for ids the dictionary does not know.

string | null
<= 32 characters
state
required
string
<= 32 characters
telemetry
Any of:

Latest per-GPU telemetry sample (Redis latest read model).

object
observed_at

RFC 3339 UTC with microsecond precision, or null.

string | null format: date-time
temp_core_c
number | null
temp_hotspot_c
number | null
temp_vram_c
number | null
fan_pct
number | null
fan_mode

Agent-reported fan mode: auto | managed | manual.

string | null
<= 16 characters
power_w
number | null
power_limit_w

Power limit currently in force on the card.

number | null
power_limit_default_w

Factory power limit of the card. Shown next to the one in force so that a single number cannot be mistaken for the stock value.

number | null
fan_min_pct

Lowest fan duty the card accepts. Zero is usually unreachable: zero-RPM sits below the adjustable range and the card enters it on its own rather than on command.

integer | null
fan_max_pct

Highest fan duty the card accepts.

integer | null
power_limit_min_w

Lowest power limit the card accepts. Asked of the device rather than taken from a model table: cards of the same model with different vbios differ.

number | null
power_limit_max_w

Highest power limit the card accepts. Together with the minimum it is the range set_power_limit is validated against, so a client can offer only values the hardware will take.

number | null
util_gpu_pct
number | null
util_mem_pct
number | null
vram_used_mb
integer | null
core_clock_mhz
integer | null
memory_clock_mhz
integer | null
pstate
integer | null
throttle_mask
integer | null
pcie_gen
integer | null
pcie_width
integer | null
listing
Any of:
object
id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
marketplace
required
string
<= 32 characters
state
required

rented / listed / expired. Expired means the fixed listing window has passed: the platform may still report the machine as listed while it is already absent from search results.

string
<= 32 characters
prices

Marketplace-specific price structure.

object
expires_at

End of the fixed listing window. Absent for rolling-window offers (the platform slides their end date itself) and for marketplaces without a listing deadline.

string | null format: date-time
rolling_seconds

Rolling-window length in seconds when the offer uses one; the platform keeps moving the end date, so such an offer never expires on its own.

integer | null
>= 1
host
Any of:

Latest host sample; null fields until the sensor reports. Sizes are binary MB (1 MB = 1048576 bytes) to match ram_used_mb; rates are kB/s with a decimal kilobyte (1 kB/s = 1000 bytes/s), the unit the panel converts to Mbit/s.

object
observed_at

RFC 3339 UTC with microsecond precision, or null.

string | null format: date-time
cpu_usage_pct
number | null
ram_used_mb
integer | null
ram_total_mb

Total physical RAM. Same quantity as inventory.ram_mb, but it rides every sample instead of waiting for the first hello — the fresher of the two, and the only one available before hello.

integer | null
disk_used_mb
integer | null
disk_total_mb

Capacity of the volumes the agent tracks, summed.

integer | null
net_rx_kbs

Receive rate over the interval between the two most recent samples, derived from the agent’s monotonic byte counters. Null whenever no honest delta exists: before a second sample arrives, and right after a host reboot, where the counter restarted from zero and the difference would read as a drop, not a rate.

number | null
net_tx_kbs

Transmit rate; derived exactly like net_rx_kbs.

number | null
power_w

Accounted host power draw in watts under the owner’s power source (settings.power_source): the GPU sum plus the enabled additions, as summed by the agent. It already INCLUDES the cards — never add it to the per-GPU sum. Null until an agent aware of the setting reports, or when the enabled components produced no reading.

number | null
uptime_s
integer | null
inventory
Any of:

Static hardware inventory reported at agent hello and stored verbatim. Nothing here is required and every leaf is explicitly nullable: an absent key and an explicit null say the same thing — the agent looked and could not tell. Sizes follow the vendor’s own labelling (decimal GB for drives), unlike the binary MB of HostLive.

object
cpu

Primary CPU package; cores/threads count the whole host.

object
model
string | null
<= 128 characters
cores
integer | null
threads
integer | null
ram_mb

Total physical RAM in binary MB, frozen at hello. HostLive carries the same number per sample as ram_total_mb.

integer | null
disks

Block devices enumerated at hello, in kernel device order.

Array<object> | null
<= 32 items
object
dev

Kernel device name without /dev/nvme0n1, sda.

string | null
<= 64 characters
model
string | null
<= 128 characters
size_gb

Capacity in decimal GB (1e9 bytes), as the drive is labelled.

integer | null
mounts

Mount points of the volumes that live on THIS drive — ["/", "/boot/efi"]. The panel uses them to put a usage bar under the drive it belongs to: usage arrives with telemetry and knows only its own tree, while which drive holds that tree is visible here. Guessing by matching capacity is not an option — two identical drives give two equally plausible answers.

An empty array is an answer: the drive is there and nothing on it is mounted (a fresh drive before partitioning, or a filesystem this kernel cannot mount). Absent means the agent predates the field.

Array<string> | null
<= 8 items
os

Distribution and release — Ubuntu 22.04.4 LTS.

string | null
<= 128 characters
kernel

Kernel release, uname -r.

string | null
<= 128 characters
driver

NVIDIA driver version — 550.90.07.

string | null
<= 64 characters
cuda

CUDA version the driver reports — 12.4.

string | null
<= 32 characters
net

Uplink interface — the one carrying the default route.

object
iface
string | null
<= 64 characters
speed_mbit

Negotiated link speed, not measured throughput.

integer | null
hardware_report

What the agent says about itself, as opposed to inventory, which describes the machine: the mode it runs in (native/emulator), the privileges it was granted (full/readonly), which module is currently producing GPU telemetry, and the full module registry with a reason attached to every unavailable one. The same machine with the same hardware yields a different report depending on whether the owner granted root at install time. Shape follows HardwareReport in agent-v1.yaml. The value is passed through unaltered — its shape was checked when the hello was accepted, but its vocabulary is deliberately open, because a value this control plane does not recognise means an agent newer than it, and refusing one would stop the whole fleet from checking in exactly while it is being upgraded. null means never reported. Clients must not collapse that with an empty module list: the first is fixed by upgrading the agent, the second by restarting its hardware layer.

object | null
agent_tools

Which command types this agent can execute on this host, including the ones it cannot, each carrying a reason. Shape follows ToolReport in agent-v1.yaml. A tool is not a second kind of entity next to commands; it is a registered command type plus the agent’s statement about executing it here and now. Clients render actions from this list and explain the disabled ones from the same field — an absent button and a disabled button with a reason are different messages to an operator, and only the second one tells them whether to grant privileges, replace hardware, or upgrade. null means never reported; [] means the agent answered and can execute nothing.

Array<object> | null
<= 64 items
object
platforms

Which rental platforms the owner keeps on this server, keyed by platform name. Always present: {} is a knowable state (“none added”), not missing information, which is why this field has no null. Keyed rather than a list, for two reasons that both showed up before the first line of UI. A list admits two entries for one platform and the bug surfaces as a duplicated tile; a key cannot. And a partial write addresses {vast}, a path that stays valid, where a list index shifts the moment somebody removes a platform — two operators adding platforms at once would lose one of the two writes. Order is not stored because order is presentation: the host system first, then by added_at, which each entry carries. The host system is deliberately absent here. It is not added and cannot be removed, so it has no intent to record; it appears only in platform_report, where having it in the same shape as the platforms is what lets one renderer draw both.

object
vast

One platform on one server. Deliberately thin: everything observed about the platform — is its agent installed, which version, what machine id — belongs to platform_report, because it is measured rather than decided, and re-measuring must never be able to damage what the owner decided.

object
added_at
required

RFC 3339 UTC with microsecond precision.

string format: date-time
added_by
Any of:

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
clore

One platform on one server. Deliberately thin: everything observed about the platform — is its agent installed, which version, what machine id — belongs to platform_report, because it is measured rather than decided, and re-measuring must never be able to damage what the owner decided.

object
added_at
required

RFC 3339 UTC with microsecond precision.

string format: date-time
added_by
Any of:

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
platform_report

Result of the last platform check, for the host system and for every platform on this server. Shape follows ServerPlatformReport, kept an open object here for the same reason hardware_report is: a newer agent measures more, and a control plane that rejected the extra field would break the fleet exactly while it upgrades. null means no check has ever run — not “nothing found”. Half of what the platforms require cannot be measured by anything today, so a client that renders an absent report as all-clear states as fact what nobody has looked at. The two states are stored apart and must stay apart in the UI. Kept in a column of its own, separate from platforms: an observation is reproducible by running the check again, while the intent behind it is not reproducible at all, and the two do not belong in one cell that a frequent automatic writer rewrites.

object | null
network_report

What the last network measurement saw of this host FROM THE OUTSIDE — the observed address, the dial-back outcome per port, the round trip, and throughput when it was asked for. Shape follows ServerNetworkReport, kept an open object here for the same reason the two reports beside it are. null means the network has never been measured. That is a different statement from “no public address”: a machine nobody looked at and a machine behind carrier NAT are a good host and an unusable one, and a client that renders the first as the second turns silence into a verdict. A column of its own, not a field inside platform_report: two different commands write them, and one cell shared by two independent writers can only be updated read-modify-write, which loses half of it silently when the two coincide. The truths also age differently — a host fact goes stale when the owner changes the machine, a network fact when the ISP changes something, and one checked_at for both would let a fresh filesystem check silently rejuvenate a year-old bandwidth figure.

object | null
settings
required

Applied per-server config (registry.servers settings); fields appear once applied.

object
power_limit_w
integer
fan_mode
string
<= 16 characters
power_source

Power accounting source: which components make up the host power reported in telemetry and billed as electricity. gpus (the default when absent) — GPU cards only; gpus_plus_fixed — GPU cards plus power_fixed_w; meter — an external wall-socket wattmeter (Tasmota HTTP API) on the host’s LAN, paired with power_meter_host. Under meter the wall reading is the host draw verbatim; an unreachable meter reports the sample as unmeasured, never zero. Agents older than the first meter-aware version keep reporting GPU cards only: the core does not send them the setting.

string
<= 16 characters
power_fixed_w

Fixed platform draw in watts (motherboard, CPU, drives, fans); paired with the gpus_plus_fixed power source.

integer
>= 1 <= 5000
power_meter_host

Wattmeter address on the host’s LAN as host[:port] — no scheme, no path; paired with the meter power source. The agent builds the Tasmota status URL itself and never fetches an owner-supplied URL verbatim.

string
>= 1 characters <= 64 characters /^[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]{1,5})?$/
power_meter_name

Display label of the meter — the Tasmota DeviceName captured by the panel when the address was last checked or picked from discovery (server.probe_meters). Purely informational: the agent never reads it, and it refreshes on the next check rather than tracking renames live.

string
>= 1 characters <= 64 characters
key
additional properties
any
agent_latest_version

Latest agent build offered by the control plane; null when none newer.

string | null
<= 32 characters
billing_locked
required

True while the tenant is billing-locked; live telemetry is blanked.

boolean
version
required

Monotonic resource version used for ETag/If-Match.

integer
created_at
required

RFC 3339 UTC with microsecond precision.

string format: date-time
updated_at

RFC 3339 UTC with microsecond precision, or null.

string | null format: date-time
Example
{
"presence": "online",
"revenue_today_usd": "12.340000",
"platforms": {
"vast": {
"added_at": "2026-07-25T10:15:30.123456Z"
},
"clore": {
"added_at": "2026-07-25T10:15:30.123456Z"
}
},
"created_at": "2026-07-25T10:15:30.123456Z"
}

Missing/invalid credentials (code=unauthorized).

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Authenticated but not permitted. code=forbidden — role/scope mismatch; code=feature_not_entitled — the plan does not include the capability; code=account_frozen — the account is frozen and this operation is declared x-ermeon-frozen: deny. The three are deliberately distinct: only the last one is fixed by a top-up (13 §5.6).

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Resource does not exist in this tenant. Foreign identifiers also return 404 (anti-enumeration).

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

A dependency the endpoint needs is not configured or is temporarily unavailable (code=unavailable). The rest of the API keeps working — an absent external integration must not take the service down.

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}