Skip to content

Where a price stands in its card class, and what is rented around it

GET
/marketplaces/market/ladder
curl --request GET \
--url 'https://api.flotera.com/api/v1/marketplaces/market/ladder?marketplace=vast&gpu_model=example&price=example' \
--cookie __Host-erm_session=<__Host-erm_session>

The spread between hosts at one moment is an order of magnitude wider than the median’s drift over time: the middle half of RTX 4090 on Vast spans 40%, RTX 3060 on Clore 70%, while the median moved 7–22% over nine days. So “the median is X” answers the wrong question, and “you are at p85 of your class” answers the right one.

The position is COUNTED over live offers — how many are strictly cheaper and how many exist. It is never interpolated between the quartiles of the overview: prices are distributed arbitrarily inside a quartile, and an interpolated “about p78” is invented precision that a reader will take for a measurement and price against.

bands splits the ladder into fifths and reports, per rung, how many offers there are and how many are rented right now. rented_known is how many of them said anything about being rented at all: zero means the marketplace does not report occupancy, and the share is then unknowable — not zero. Bands are absent below min_offers_for_bands.

This is a MEASUREMENT of the market as it stands, never a probability of being rented. On live data RTX 3080 runs 96 → 71 → 26 → 4 → 0 across the rungs while RTX 3090 runs 66 → 31 → 37 → 77 → 21 — the fourth rung busier than the second. Reliability, bandwidth and a single renter taking ten neighbours at once all decide it; the price does not.

reference carries the same rungs measured on the OTHER marketplace, and only when this one reports no occupancy at all — as on Vast, where the sample is filtered to free machines and not one offer carries the flag. What transfers is the POSITION, not the dollar: the same price sits on different rungs on different marketplaces. The field names its marketplace because that measurement must not be merged with ours.

Read price_sample before reading the position: on Vast the ladder is built from free machines only — which is to say, from exactly the machines competing for the next renter.

marketplace
required

Supported marketplace. The database constrains the same two values, so a third one is a migration rather than a new string.

string
Allowed values: vast clore
gpu_model
required
string
>= 1 characters <= 128 characters

Card class; normalised the same way market sampling normalises it.

price
required
string
<= 32 characters /^[0-9]+(\.[0-9]+)?$/

The price whose position is asked, $/GPU-hour, as a decimal string. Money never travels as a JSON number.

The ladder and this price’s place on it.

Media typeapplication/json
object
marketplace
required

Supported marketplace. The database constrains the same two values, so a third one is a migration rather than a new string.

string
Allowed values: vast clore
gpu_model
required
string
price_sample
required

Which population the ladder is built from. On Vast it is free machines only — the ones a renter can take instead of you.

string
Allowed values: all available_only
offers
required
integer
offers_below
required

Offers strictly cheaper than the asked price.

integer
percentile
required

Place of the price, 0..100. Null when there are no offers at all — which is not “cheaper than everyone”, and leads elsewhere.

integer | null
<= 100
band_index
required

Rung the price falls on, 0-based. Null on a thin market.

integer | null
bands
required
Array<object>
<= 32 items
object
from_usd_per_gpu_hour
required

Cheapest price on this rung. Adjacent rungs MAY share a bound: the ladder is cut by count, so equal prices land on two rungs.

string | null
to_usd_per_gpu_hour
required
string | null
offers
required
integer
rented_known
required

How many offers on this rung said anything about being rented. Zero means the marketplace does not report occupancy at all — the share is then unknowable, and reading it as “none rented” is the error this field exists to prevent.

integer
rented
required
integer
reference
required
One of:

The same rungs measured on the other marketplace, present only when this one reports no occupancy. Position transfers, price does not.

object
marketplace
required

Supported marketplace. The database constrains the same two values, so a third one is a migration rather than a new string.

string
Allowed values: vast clore
price_sample
required
string
Allowed values: all available_only
band_index
required
integer | null
bands
required
Array<object>
<= 32 items
object
from_usd_per_gpu_hour
required

Cheapest price on this rung. Adjacent rungs MAY share a bound: the ladder is cut by count, so equal prices land on two rungs.

string | null
to_usd_per_gpu_hour
required
string | null
offers
required
integer
rented_known
required

How many offers on this rung said anything about being rented. Zero means the marketplace does not report occupancy at all — the share is then unknowable, and reading it as “none rented” is the error this field exists to prevent.

integer
rented
required
integer
history
required
One of:

NOT a probability model and not a fitted curve — exactly the counters of bands, summed over the window. Persistent humps stay visible; only single-hour swings vanish. A machine that sat rented for a day honestly weighs 24 observations: rung occupancy over time is what is summed, not the fates of individual offers. Always measured on Clore, the one marketplace with a truthful per-offer rented; position transfers across marketplaces, dollars do not — never merge these numbers with the venue’s own.

object
marketplace
required

Supported marketplace. The database constrains the same two values, so a third one is a migration rather than a new string.

string
Allowed values: vast clore
window_days
required
integer
>= 1 <= 90
bands
required
Array<object>
<= 32 items
object
band_index
required

Rung 1..5, cheap to expensive — storage numbering.

integer
>= 1 <= 5
offers
required

Offer-hours observed on the rung over the window.

integer
rented_known
required
integer
rented
required
integer
rented_share
required

Rented share among offers whose occupancy the venue reported, 4 decimal places. Null when it reported none — which is not zero.

string | null
/^-?[0-9]+(\.[0-9]{1,6})?$/
observed_at
required
string | null format: date-time
min_offers_for_bands
required

Below this many offers the ladder has no rungs.

integer
>= 1
Example
{
"marketplace": "vast",
"price_sample": "all",
"reference": {
"marketplace": "vast",
"price_sample": "all"
},
"history": {
"marketplace": "vast"
}
}

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

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

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