Skip to content

Create a browser session

POST
/auth/login
curl --request POST \
--url https://api.flotera.com/api/v1/auth/login \
--header 'Content-Type: application/json' \
--data '{ "email": "hello@example.com", "password": "example", "turnstile_token": "example" }'

Public endpoint protected by distributed rate limits (IP prefix + normalized account key). Responses do not distinguish unknown user from bad password. On success sets the __Host-erm_session cookie (Secure; HttpOnly; SameSite=Lax; Path=/).

When GET /auth/config reports turnstile.enabled, turnstile_token is mandatory: the server verifies it against Cloudflare siteverify after the rate-limit check but before reading the user row or running Argon2, so a flood of guesses costs the attacker a challenge rather than costing the server a password hash. Verification is fail closed — an unreachable or malformed siteverify response rejects the request (05 §4.3 treats a failing protection store the same way). A rejected challenge returns 403 with the same body regardless of whether the account exists, and the token is single-use: clients must reset the widget before retrying.

Media typeapplication/json
object
email
required
string format: email
<= 254 characters
password
required
string
>= 8 characters <= 256 characters
turnstile_token

Cloudflare Turnstile response token from the widget. Optional in the schema because the challenge is deployment-configurable, but required in practice whenever GET /auth/config reports turnstile.enabled; omitting it then returns 403. Single-use.

string | null
<= 2048 characters
Examplegenerated
{
"email": "hello@example.com",
"password": "example",
"turnstile_token": "example"
}

Session created; profile returned.

Media typeapplication/json
object
user_id
required

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

string format: uuid
tenant_id
required

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

string format: uuid
email
required
string format: email
tenant_name
required

Display name of the tenant shown in the panel header.

string
<= 80 characters
roles
Array<string>
<= 8 items
preferences
object
language
string
<= 16 characters
theme
string
<= 16 characters
timezone

IANA timezone, used for quiet hours and daily boundaries in the UI.

string
<= 64 characters
tenant_settings
object
electricity_usd_per_kwh

Electricity price in USD per kWh as a decimal string, never a JSON number: it multiplies into money. The server additionally rejects zero (free electricity is an assertion, and an empty input field is not one) and anything above 100 USD/kWh. Send null to clear the price: the settings object is merged server-side, so a key can be reset but never deleted.

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

What Vast withholds from rental revenue, as a percentage: the decimal string “20.000000” means 20 %, not 0.2. Zero is a valid rate, not an empty field — Vast removed its host fee in June 2024, so zero is the truthful value there. Must be below 100. Send null to clear.

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

LEGACY. What Clore withholds from rental revenue, as a single rate for the whole marketplace. Superseded by the per-currency keys below, which the form now writes instead. Still read, and still accepted here, as the fallback for a currency that has no rate of its own: an owner who once stated Clore’s cut must not silently lose that statement.

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

What Clore withholds when the renter pays in CLORE. On-demand base host fee only — paying in CLORE carries no extra fee. Store the total you actually see, not the breakdown: spot base is 1.25 % and PoH/MFP tiers move it.

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

What Clore withholds when the renter pays in bitcoin: the base host fee plus the 15 % extra host fee for paying outside CLORE.

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

What Clore withholds when the renter pays in a dollar stablecoin (USDT/USDC). Also applies to a rental we could only price from the machine’s dollar figure, because that estimate rides the same rail.

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

What the marketplace adds ON TOP of your price for the renter. This is NOT your expense — it is the conversion factor between the catalogue price and your own. Vast states that “live GPU prices are typically about 25% above what hosts earn”, so a listing at $1.00 corresponds to $0.80 of yours. Without it, advising you to match the market median would set your price at the median and show renters a quarter above it. Convert by dividing by (1 + markup), never by subtracting the percentage. Must not exceed 500.

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

Same as vast_renter_markup_percent, for Clore, which splits its 10 % base fee 50/50 — the renter’s half sits on top of your price.

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

Owner’s operating expenses in USD per month for the whole fleet — premises, connectivity, maintenance. Fleet-wide rather than per-server because these costs do not divide by hardware naturally; consumers spread the amount across the month’s billable managed-server-days, the same proration the subscription itself uses, which makes it comparable with per-server electricity. Zero is rejected; send null to clear. Must not exceed 1000000.

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

Monotonic resource version used for ETag/If-Match.

integer
Example
{
"tenant_settings": {
"electricity_usd_per_kwh": "0.083000",
"vast_commission_percent": "0.000000",
"clore_commission_percent": "20.000000",
"clore_commission_clore_percent": "5.000000",
"clore_commission_btc_percent": "20.000000",
"clore_commission_usd_percent": "20.000000",
"vast_renter_markup_percent": "25.000000",
"clore_renter_markup_percent": "5.000000",
"operational_usd_per_month": "300.000000"
}
}
Set-Cookie
string

__Host-erm_session=<opaque>; Secure; HttpOnly; SameSite=Lax; Path=/

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"
}

Turnstile challenge missing, rejected by Cloudflare, or impossible to verify (fail closed). code=forbidden, detail="captcha verification failed". The body never depends on the submitted credentials; the token is single-use, so the widget must be reset before retrying.

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"
}

Request failed validation (code=validation_failed), with per-field errors.

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"
}

Rate limit exceeded (code=rate_limited).

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"
}
Retry-After
integer

Seconds to wait before retrying.