Skip to content

Market prices for the GPU models this tenant actually runs

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

Per GPU model and marketplace: how many offers, and the min/median/max price per GPU-hour in USD.

The card list comes from the tenant’s own telemetry, so the answer is about their fleet and nothing else. Prices come from a shared cache — public offers are identical for everyone, and fetching them per tenant would multiply the load on the marketplace by the number of customers.

rented_share is null when the marketplace did not report occupancy in that sample. Null and zero are different answers and must not be collapsed: zero would claim the market has stopped.

gpu_model narrows the answer to one card class from catalog_gpu_models — the popular-card catalog the index polls regardless of the fleet, plus the fleet’s own classes. This is how the panel calculator asks about a card the tenant does not own yet. A model outside that set is rejected with 422: it would produce an honest but forever-empty answer, so such a request is a typo, not a question.

gpu_model
string
<= 128 characters

Normalized card class to ask about instead of the whole fleet. Accepts marketplace spellings too — the server normalizes («RTX 4090» → «rtx 4090»).

Market overview.

Media typeapplication/json
object
sources
required

When each marketplace’s offers were last sampled. null means it has never been sampled, and then a missing row for a card says “we have not looked yet” — NOT “nobody offers this card”. The two answers lead a fleet owner to opposite decisions, so the UI must not collapse them.

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
gpu_models
required

Card classes the rows cover — the tenant’s fleet (from its own telemetry) by default, the single selected class when gpu_model was passed. Whether a missing row means “not sampled yet” or “not offered” is answered by sources.

Array<string>
<= 64 items
catalog_gpu_models

Everything the gpu_model filter accepts: the popular-card catalog the market index polls regardless of the fleet, plus this fleet’s own classes, sorted. The panel calculator builds its card selector from this list. Optional — APIs older than the filter do not send it.

Array<string>
<= 128 items
rows
required
Array<object>
<= 256 items
object
gpu_model
required

Normalized card name, comparable across marketplaces.

string
<= 128 characters
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
offers
required

Offers WE observed in our own sample. Tier-B sources publish no order book, so their rows are reported-only: offers is 0, every sample-side price is null, and only the reported_* block is populated.

integer
min_usd_per_gpu_hour

Decimal string or null when unknown.

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

Lower quartile — what undercutting the market costs. The minimum alone is a single outlier and is useless as a target.

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

Upper quartile — what asking above the market means.

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

Decimal string or null when unknown.

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

Share of listed machines currently rented, 0..1. null means the marketplace does not report occupancy in this sample — which is NOT zero. Vast is null: its rented field reads false on every offer, even for a query that explicitly asks for rented ones.

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

The marketplace’s response hit our page limit, so the quartiles above describe a slice of the market rather than the market. null means no sample has been recorded yet — not “complete”.

boolean
nullable
observed_at

RFC 3339 UTC with microsecond precision, or null.

string | null format: date-time
reported_usage_share

Share of ALL cards of this model rented on the marketplace right now, 0..1 — the marketplace’s OWN market-wide figure (Vast’s gpu metrics aggregate), not our sample. Deliberately separate from rented_share: the two describe different populations measured by different parties, and merging them would make the row lie about its provenance. null — the marketplace does not publish this figure.

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

The same figure averaged over the marketplace’s own 30-day window.

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

Cards of this model listed on the whole marketplace.

integer
nullable
reported_supply_available

Of them, currently free to rent.

integer
nullable
reported_min_usd_per_gpu_hour

The marketplace’s own market floor for this model. Vast does not publish it; for RunPod the minimum is the ONLY figure the venue publishes at all, so without this field its row would be empty.

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

The marketplace’s own market ceiling for this model.

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

The marketplace’s own median price for this model. Its sampling method is not published, so this is a cross-check next to our median_usd_per_gpu_hour, never a substitute for it.

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

When the marketplace’s aggregate was last read.

string | null format: date-time
Example
{
"sources": [
{
"marketplace": "vast",
"price_sample": "all",
"last_fetch_status": "ok"
}
],
"rows": [
{
"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"
}