Skip to content

How market prices moved over the window

GET
/marketplaces/market/history
curl --request GET \
--url 'https://api.flotera.com/api/v1/marketplaces/market/history?days=7' \
--cookie __Host-erm_session=<__Host-erm_session>

Percentile series per GPU model and marketplace, from the shared hourly market history. Answers “is my price still right”, which the current-snapshot overview cannot: a snapshot shows where the market is, never where it was going.

step says what a point averages — hour for windows up to two days, day beyond that. The choice follows the window, not a setting: a day plotted hourly is two points, a month plotted hourly is seven hundred.

buckets says how many hourly samples went into a point. A point built from one sample and a point built from twenty-four describe different things, and the price alone does not distinguish them.

rented_share is null when the marketplace does not report occupancy — as on Vast, whose sample is filtered to free machines. Null and zero are different answers. For such marketplaces reported_usage_share carries the platform’s own model-wide busy share instead; the two series measure different populations and are kept in separate fields so the provenance stays visible.

Read sources[].price_sample before comparing marketplaces on one chart: a median over free machines and a median over the whole market are not the same measurement.

days
integer
default: 7 >= 1 <= 90

Window length in days, inclusive of today.

gpu_model
string
<= 128 characters

Narrow the series to one card class from the catalog ∪ fleet set (see getMarketOverview). Spellings are normalized server-side; a model outside the set is a 422.

Market history.

Media typeapplication/json
object
gpu_models
required
Array<string>
<= 64 items
days
required
integer
>= 1 <= 90
step
required

What one point averages. Follows the window, not a setting.

string
Allowed values: hour day
points
required
Array<object>
<= 8192 items
object
gpu_model
required
string
<= 128 characters
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
at
required

Start of the bucket, UTC.

string format: date-time
buckets
required

How many hourly samples this point averages. A point from one sample and a point from twenty-four describe different things.

integer
>= 1
offers

Decimal string or null when unknown.

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

Decimal string or null when unknown.

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

Decimal string or null when unknown.

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

Decimal string or null when unknown.

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

Decimal string or null when unknown.

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

Busy share by the marketplace’s OWN model-wide aggregate, 0..1. A different population than rented_share: that one is our count over the sampled offers, this one is the platform’s figure for its whole market of the model — which is how Vast occupancy enters the series at all, since its offer sample lists free machines only. Null when the marketplace publishes no such aggregate.

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

At least one hourly sample in this point hit the marketplace’s page limit, so its percentiles describe a slice rather than the market.

boolean
sources
required

Same sampling caveat as the overview: price_sample must be read before two marketplaces are compared on one chart.

Array<object>
<= 8 items
object
marketplace
required

A market DATA source — a superset of MarketplaceName. Tier-B venues (tensordock, runpod, akash) feed the price index only: no accounts, no listings, no rentals — see infrastructure/26-market-intelligence.md §3. Execution paths (accounts, links, offers) keep MarketplaceName; a tier-B name is never valid there.

string
Allowed values: vast clore tensordock runpod akash
last_sampled_at

RFC 3339 UTC with microsecond precision, or null.

string | null format: date-time
price_sample

Which population of machines the prices describe. all includes rented machines; available_only means the query filtered to machines free to rent; reported means there is no sample of ours at all — the venue publishes its own aggregate figures (tier-B sources), computed by an unpublished method.

Without this, comparing marketplaces is misleading. Vast is queried with rentable = true, so its median describes what was NOT taken and skews high; Clore returns the whole market including the attractively-priced machines already rented, and skews low. On live data the gap is a consistent 1.8-2.4x across every card, and how much of it is real cannot be stated. A UI that presents that gap as measured pushes a fleet owner off a marketplace on the strength of a sampling artefact.

string
Allowed values: all available_only reported
last_fetch_status

How the LAST attempt ended, separate from when data was last fresh: an hour-old market looks identical whether the fetcher is healthy (freshness window) or broken (a run of failures).

rate_limited is not folded into failed — hitting a rate limit is normal traffic shaping against someone else’s API, not a fault, and confusing the two sends the operator to reissue a working key.

string
nullable
Allowed values: ok rate_limited failed
Example
{
"step": "hour",
"points": [
{
"marketplace": "vast"
}
],
"sources": [
{
"marketplace": "vast",
"price_sample": "all",
"last_fetch_status": "ok"
}
]
}

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