Skip to content

Generic cluster-keyed surface read (by registry name)

GET
/api/v2/surface/{name}
curl --request GET \
--url 'https://crate.hosaka.fm/api/v2/surface/public.spine_artist_temporal_profile?cluster=example' \
--header 'X-API-Key: <X-API-Key>'

The generic read behind every row in GET /api/v2/surface: one operation serves all 44 registered surfaces (public.spine_artist_temporal_profile, seen.radio_play_v1, seen.dj_champion, seen.radio_co_play, seen.song_station_journey, mirror.wantlist_demand_by_cluster_v1, seen.master_propagation_timeline_cluster, seen.dj_tastemaker_score, seen.performing_entity, seen.artist_dossier, seen.artist_momentum, seen.artist_network_position, seen.artist_tier_presence, seen.artist_festival_co_appearance, seen.artist_cross_tier_network, seen.artist_brokerage, seen.artist_emergence_narrative_public, seen.artist_airplay_first_appearance, seen.artist_djset_first_appearance, seen.artist_emergence_lead_time, seen.artist_dated_appearance, seen.artist_primary_geography, seen.artist_identity_bridge, seen.live_demand, seen.bandcamp_artist_gravity, seen.bandcamp_artist_tastemaker_quality, seen.artist_djset_scout_signal, seen.label_djset_momentum, seen.artist_signal_passport, seen.artist_sc_rights_rollup_v1, archive_api_v1.artist_mention_daily, public.spine_artist_name_published_view, archive_api_v1.artist_press_mentions, seen.artist_signal_known_since, mirror.cluster_authority_ids_v1, sync.placement_claim_by_cluster_v1, deadwax.pressing_provenance_depth_by_cluster_v1, mirror.listen_flow_by_cluster_v1, mirror.cluster_alias_v1, wintermute.cluster_arrival_ledger_v1, seen.radar_only_artists_v1, sync.placement_arrival_by_cluster_v1, wintermute.cluster_arrival_wide_v1, mirror.market_vs_flow_divergence_v1). {name} is the schema-qualified registry key — GET /api/v2/surface for the live list + each name’s shape. ?cluster= is the 64-hex identity key in THAT surface’s registered keyspace (a key from the wrong keyspace fails soft as an empty honest_gap, not an error — see the index for which keyspace {name} expects). cluster-row grain surfaces (cap 1/1) ignore ?after/?limit and answer with 0 or 1 rows; cluster-multirow/cluster-edge-list grains keyset-paginate via the opaque ?after cursor from a prior page’s next_after (never OFFSET — pass it back verbatim, never construct or decode it). ?limit clamps to the surface’s registered cap. Unknown {name} → 400 with a hint listing every valid name + doc_url + next (the index call). A per-row crate-side kill (registry enabled:false) → 404; the master kill (env CRATE_SURFACE_ENABLED=false) → 503. state:‘degraded’ (still HTTP 200, rows:[]) means the dedicated crate_surface_reader read pool is unconfigured or not yet granted on the replica — fail-closed: the code ships ahead of the DB role landing. Cursor durability: cursors are page-iteration handles, NOT bookmarks — some surfaces build them from producer-internal columns that can change across producer re-crawls (seen.radio_play_v1’s play_key today), so a stored cursor may silently skip or repeat rows after a re-crawl; re-start from the first page for a fresh read (each surface’s coverage_note in GET /api/v2/surface carries the current specifics).

name
required

The schema-qualified registry key, e.g. seen.radio_play_v1. GET /api/v2/surface lists every valid name.

string
Allowed values: public.spine_artist_temporal_profile seen.radio_play_v1 seen.dj_champion seen.radio_co_play seen.song_station_journey mirror.wantlist_demand_by_cluster_v1 seen.master_propagation_timeline_cluster seen.dj_tastemaker_score seen.performing_entity seen.artist_dossier seen.artist_momentum seen.artist_network_position seen.artist_tier_presence seen.artist_festival_co_appearance seen.artist_cross_tier_network seen.artist_brokerage seen.artist_emergence_narrative_public seen.artist_airplay_first_appearance seen.artist_djset_first_appearance seen.artist_emergence_lead_time seen.artist_dated_appearance seen.artist_primary_geography seen.artist_identity_bridge seen.live_demand seen.bandcamp_artist_gravity seen.bandcamp_artist_tastemaker_quality seen.artist_djset_scout_signal seen.label_djset_momentum seen.artist_signal_passport seen.artist_sc_rights_rollup_v1 archive_api_v1.artist_mention_daily public.spine_artist_name_published_view archive_api_v1.artist_press_mentions seen.artist_signal_known_since mirror.cluster_authority_ids_v1 sync.placement_claim_by_cluster_v1 deadwax.pressing_provenance_depth_by_cluster_v1 mirror.listen_flow_by_cluster_v1 mirror.cluster_alias_v1 wintermute.cluster_arrival_ledger_v1 seen.radar_only_artists_v1 sync.placement_arrival_by_cluster_v1 wintermute.cluster_arrival_wide_v1 mirror.market_vs_flow_divergence_v1
cluster
required

The 64-hex identity key in this surface’s registered keyspace (case-insensitive) — see GET /api/v2/surface for which keyspace {name} expects.

string
/^[0-9a-f]{64}$/
after

Opaque keyset cursor from a previous page’s next_after. Pass back verbatim; never construct or decode it. Omit for the first page.

string
limit

Rows per page, clamped to the surface’s registered cap (see GET /api/v2/surface → cap.default / cap.max).

integer
>= 1

Surface rows (state:‘present’), an honest_gap (present:false, rows:[] — never 404), or a degraded read (state:‘degraded’, the crate_surface_reader pool is unconfigured/ungranted).

Media type application/json
Any of:
object
object
required
string
Allowed values: surface.rows
surface
required
string
Allowed values: public.spine_artist_temporal_profile
present
required
boolean
state
required
string
Allowed values: present honest_gap degraded
keyspace
required
string
Allowed values: pe-norm-v1-artist pe-norm-v1-recording pe-norm-v1-label
liveness
required
string
Allowed values: populated advisory empty
rows
required
Array<object>
object
cluster_id_hex
required
string
/^[0-9a-f]{64}$/
resolved_artist_discogs_id
required
number | null
master_count_with_year
required
number | null
earliest_release_year
required
number | null
latest_release_year
required
number | null
active_decades
required
Array<number | null>
year_span
required
number | null
next_after
required
string | null
coverage_note
required
string
degraded_reason
required
string | null
generated_at
required
string
Example
{
"object": "surface.rows",
"surface": "public.spine_artist_temporal_profile",
"state": "present",
"keyspace": "pe-norm-v1-artist",
"liveness": "populated"
}
X-RateLimit-Limit
integer

Requests allowed in the current window.

X-RateLimit-Remaining
integer

Requests remaining in the current window.

X-RateLimit-Reset
integer

Unix epoch (seconds) when the current window resets.

Validation failure (invalid query, malformed body, bad facet name)

Media type application/json

The error envelope for all 4xx/5xx responses. error is the only guaranteed field — branch on it, never on HTTP status alone. An unresolved/empty lookup is NOT an error: it returns HTTP 200 with present:false / a null field / state:"honest_gap".

codeHTTPwhen thrownfix
invalid_artist_key400/artist/{key} key is not a 64-hex cluster_id, discogs:<id>, or mbid:<uuid>resolve by name first: GET /api/v2/resolve?q=<name>, then call /artist/{cluster_id}
use_resolve_for_locator400/artist/{key} given a discogs:/mbid: locator (not a canonical address)GET /api/v2/resolve?discogs=<id> (or ?mbid=), then use the returned cluster_id (the response next field is the ready-to-call URL)
invalid_label_key400/label/{key} key is not a 64-hex label_cluster_id or name-slug (e.g. a discogs:/mbid: locator — not resolvable for labels yet)address a label by its 64-hex label_cluster_id or its name-slug (e.g. /api/v2/label/warp)
invalid_fields400/artist/{key}?fields= lists a facet that is not a valid top-level dossier fielduse only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field)
missing_locator400/resolve called with none of q/cluster/discogs/mbidpass exactly one locator
invalid_locator400a /resolve locator is malformed for its typefix the format, or fall back to ?q=<name>
invalid_query400/search ?q= missing/empty, or any Zod validation failure (details[] attached)pass ?q=<text>; fix each details entry
invalid_facet400an unknown facet filter name was suppliedGET /api/v2/facets for valid names + values
rate_limited429an IP/key/tier rate or concurrency cap was exceeded (retry_after_seconds + Retry-After + X-RateLimit-* set)back off retry_after_seconds (or until X-RateLimit-Reset), then retry
object
error
required

Machine-readable error code (stable lowercase snake_case). The ONLY field guaranteed on every error body — switch on it programmatically, never on HTTP status alone (several codes share a status). See the code table in this schema’s description.

string
message

One-sentence human-readable statement of WHAT is wrong (developer-facing). Present only for catalogued codes. Describes the violated rule — not a fix (see hint/next).

string
hint

Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with ). The human counterpart to the machine-actionable next.

string
doc_url

Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.

string
param

The specific request parameter that caused the failure (e.g. “key”, “q”), so a client can point at the offending input. Present only when the code declares one.

string
next

A copy-pasteable, fully-formed corrected call (a concrete URL, NOT a template) an agent can fire verbatim to recover — the machine-actionable counterpart to hint. Present only when a handler supplies one.

string
details

Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.

Array
retry_after_seconds

On a 429 rate_limited response, seconds to wait before retrying (mirrors the Retry-After header). Sleep at least this long, then re-issue the identical request.

number
master_id

Echoed on master_not_found (404) — the master id that did not resolve.

number
Example
{
"error": "invalid_query"
}

Resource not found

Media type application/json

The error envelope for all 4xx/5xx responses. error is the only guaranteed field — branch on it, never on HTTP status alone. An unresolved/empty lookup is NOT an error: it returns HTTP 200 with present:false / a null field / state:"honest_gap".

codeHTTPwhen thrownfix
invalid_artist_key400/artist/{key} key is not a 64-hex cluster_id, discogs:<id>, or mbid:<uuid>resolve by name first: GET /api/v2/resolve?q=<name>, then call /artist/{cluster_id}
use_resolve_for_locator400/artist/{key} given a discogs:/mbid: locator (not a canonical address)GET /api/v2/resolve?discogs=<id> (or ?mbid=), then use the returned cluster_id (the response next field is the ready-to-call URL)
invalid_label_key400/label/{key} key is not a 64-hex label_cluster_id or name-slug (e.g. a discogs:/mbid: locator — not resolvable for labels yet)address a label by its 64-hex label_cluster_id or its name-slug (e.g. /api/v2/label/warp)
invalid_fields400/artist/{key}?fields= lists a facet that is not a valid top-level dossier fielduse only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field)
missing_locator400/resolve called with none of q/cluster/discogs/mbidpass exactly one locator
invalid_locator400a /resolve locator is malformed for its typefix the format, or fall back to ?q=<name>
invalid_query400/search ?q= missing/empty, or any Zod validation failure (details[] attached)pass ?q=<text>; fix each details entry
invalid_facet400an unknown facet filter name was suppliedGET /api/v2/facets for valid names + values
rate_limited429an IP/key/tier rate or concurrency cap was exceeded (retry_after_seconds + Retry-After + X-RateLimit-* set)back off retry_after_seconds (or until X-RateLimit-Reset), then retry
object
error
required

Machine-readable error code (stable lowercase snake_case). The ONLY field guaranteed on every error body — switch on it programmatically, never on HTTP status alone (several codes share a status). See the code table in this schema’s description.

string
message

One-sentence human-readable statement of WHAT is wrong (developer-facing). Present only for catalogued codes. Describes the violated rule — not a fix (see hint/next).

string
hint

Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with ). The human counterpart to the machine-actionable next.

string
doc_url

Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.

string
param

The specific request parameter that caused the failure (e.g. “key”, “q”), so a client can point at the offending input. Present only when the code declares one.

string
next

A copy-pasteable, fully-formed corrected call (a concrete URL, NOT a template) an agent can fire verbatim to recover — the machine-actionable counterpart to hint. Present only when a handler supplies one.

string
details

Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.

Array
retry_after_seconds

On a 429 rate_limited response, seconds to wait before retrying (mirrors the Retry-After header). Sleep at least this long, then re-issue the identical request.

number
master_id

Echoed on master_not_found (404) — the master id that did not resolve.

number
Example
{
"error": "invalid_query"
}

Rate limit exceeded — see Retry-After + X-RateLimit-* headers

Media type application/json
object
error
required
string
Allowed values: rate_limited
retry_after_seconds
required
number
Example
{
"error": "rate_limited"
}

Internal server error

Media type application/json

The error envelope for all 4xx/5xx responses. error is the only guaranteed field — branch on it, never on HTTP status alone. An unresolved/empty lookup is NOT an error: it returns HTTP 200 with present:false / a null field / state:"honest_gap".

codeHTTPwhen thrownfix
invalid_artist_key400/artist/{key} key is not a 64-hex cluster_id, discogs:<id>, or mbid:<uuid>resolve by name first: GET /api/v2/resolve?q=<name>, then call /artist/{cluster_id}
use_resolve_for_locator400/artist/{key} given a discogs:/mbid: locator (not a canonical address)GET /api/v2/resolve?discogs=<id> (or ?mbid=), then use the returned cluster_id (the response next field is the ready-to-call URL)
invalid_label_key400/label/{key} key is not a 64-hex label_cluster_id or name-slug (e.g. a discogs:/mbid: locator — not resolvable for labels yet)address a label by its 64-hex label_cluster_id or its name-slug (e.g. /api/v2/label/warp)
invalid_fields400/artist/{key}?fields= lists a facet that is not a valid top-level dossier fielduse only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field)
missing_locator400/resolve called with none of q/cluster/discogs/mbidpass exactly one locator
invalid_locator400a /resolve locator is malformed for its typefix the format, or fall back to ?q=<name>
invalid_query400/search ?q= missing/empty, or any Zod validation failure (details[] attached)pass ?q=<text>; fix each details entry
invalid_facet400an unknown facet filter name was suppliedGET /api/v2/facets for valid names + values
rate_limited429an IP/key/tier rate or concurrency cap was exceeded (retry_after_seconds + Retry-After + X-RateLimit-* set)back off retry_after_seconds (or until X-RateLimit-Reset), then retry
object
error
required

Machine-readable error code (stable lowercase snake_case). The ONLY field guaranteed on every error body — switch on it programmatically, never on HTTP status alone (several codes share a status). See the code table in this schema’s description.

string
message

One-sentence human-readable statement of WHAT is wrong (developer-facing). Present only for catalogued codes. Describes the violated rule — not a fix (see hint/next).

string
hint

Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with ). The human counterpart to the machine-actionable next.

string
doc_url

Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.

string
param

The specific request parameter that caused the failure (e.g. “key”, “q”), so a client can point at the offending input. Present only when the code declares one.

string
next

A copy-pasteable, fully-formed corrected call (a concrete URL, NOT a template) an agent can fire verbatim to recover — the machine-actionable counterpart to hint. Present only when a handler supplies one.

string
details

Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.

Array
retry_after_seconds

On a 429 rate_limited response, seconds to wait before retrying (mirrors the Retry-After header). Sleep at least this long, then re-issue the identical request.

number
master_id

Echoed on master_not_found (404) — the master id that did not resolve.

number
Example
{
"error": "invalid_query"
}

Database pool exhausted — retry after 5s

Media type application/json

The error envelope for all 4xx/5xx responses. error is the only guaranteed field — branch on it, never on HTTP status alone. An unresolved/empty lookup is NOT an error: it returns HTTP 200 with present:false / a null field / state:"honest_gap".

codeHTTPwhen thrownfix
invalid_artist_key400/artist/{key} key is not a 64-hex cluster_id, discogs:<id>, or mbid:<uuid>resolve by name first: GET /api/v2/resolve?q=<name>, then call /artist/{cluster_id}
use_resolve_for_locator400/artist/{key} given a discogs:/mbid: locator (not a canonical address)GET /api/v2/resolve?discogs=<id> (or ?mbid=), then use the returned cluster_id (the response next field is the ready-to-call URL)
invalid_label_key400/label/{key} key is not a 64-hex label_cluster_id or name-slug (e.g. a discogs:/mbid: locator — not resolvable for labels yet)address a label by its 64-hex label_cluster_id or its name-slug (e.g. /api/v2/label/warp)
invalid_fields400/artist/{key}?fields= lists a facet that is not a valid top-level dossier fielduse only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field)
missing_locator400/resolve called with none of q/cluster/discogs/mbidpass exactly one locator
invalid_locator400a /resolve locator is malformed for its typefix the format, or fall back to ?q=<name>
invalid_query400/search ?q= missing/empty, or any Zod validation failure (details[] attached)pass ?q=<text>; fix each details entry
invalid_facet400an unknown facet filter name was suppliedGET /api/v2/facets for valid names + values
rate_limited429an IP/key/tier rate or concurrency cap was exceeded (retry_after_seconds + Retry-After + X-RateLimit-* set)back off retry_after_seconds (or until X-RateLimit-Reset), then retry
object
error
required

Machine-readable error code (stable lowercase snake_case). The ONLY field guaranteed on every error body — switch on it programmatically, never on HTTP status alone (several codes share a status). See the code table in this schema’s description.

string
message

One-sentence human-readable statement of WHAT is wrong (developer-facing). Present only for catalogued codes. Describes the violated rule — not a fix (see hint/next).

string
hint

Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with ). The human counterpart to the machine-actionable next.

string
doc_url

Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.

string
param

The specific request parameter that caused the failure (e.g. “key”, “q”), so a client can point at the offending input. Present only when the code declares one.

string
next

A copy-pasteable, fully-formed corrected call (a concrete URL, NOT a template) an agent can fire verbatim to recover — the machine-actionable counterpart to hint. Present only when a handler supplies one.

string
details

Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.

Array
retry_after_seconds

On a 429 rate_limited response, seconds to wait before retrying (mirrors the Retry-After header). Sleep at least this long, then re-issue the identical request.

number
master_id

Echoed on master_not_found (404) — the master id that did not resolve.

number
Example
{
"error": "invalid_query"
}

Request deadline (15s) or query timeout exceeded

Media type application/json

The error envelope for all 4xx/5xx responses. error is the only guaranteed field — branch on it, never on HTTP status alone. An unresolved/empty lookup is NOT an error: it returns HTTP 200 with present:false / a null field / state:"honest_gap".

codeHTTPwhen thrownfix
invalid_artist_key400/artist/{key} key is not a 64-hex cluster_id, discogs:<id>, or mbid:<uuid>resolve by name first: GET /api/v2/resolve?q=<name>, then call /artist/{cluster_id}
use_resolve_for_locator400/artist/{key} given a discogs:/mbid: locator (not a canonical address)GET /api/v2/resolve?discogs=<id> (or ?mbid=), then use the returned cluster_id (the response next field is the ready-to-call URL)
invalid_label_key400/label/{key} key is not a 64-hex label_cluster_id or name-slug (e.g. a discogs:/mbid: locator — not resolvable for labels yet)address a label by its 64-hex label_cluster_id or its name-slug (e.g. /api/v2/label/warp)
invalid_fields400/artist/{key}?fields= lists a facet that is not a valid top-level dossier fielduse only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field)
missing_locator400/resolve called with none of q/cluster/discogs/mbidpass exactly one locator
invalid_locator400a /resolve locator is malformed for its typefix the format, or fall back to ?q=<name>
invalid_query400/search ?q= missing/empty, or any Zod validation failure (details[] attached)pass ?q=<text>; fix each details entry
invalid_facet400an unknown facet filter name was suppliedGET /api/v2/facets for valid names + values
rate_limited429an IP/key/tier rate or concurrency cap was exceeded (retry_after_seconds + Retry-After + X-RateLimit-* set)back off retry_after_seconds (or until X-RateLimit-Reset), then retry
object
error
required

Machine-readable error code (stable lowercase snake_case). The ONLY field guaranteed on every error body — switch on it programmatically, never on HTTP status alone (several codes share a status). See the code table in this schema’s description.

string
message

One-sentence human-readable statement of WHAT is wrong (developer-facing). Present only for catalogued codes. Describes the violated rule — not a fix (see hint/next).

string
hint

Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with ). The human counterpart to the machine-actionable next.

string
doc_url

Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.

string
param

The specific request parameter that caused the failure (e.g. “key”, “q”), so a client can point at the offending input. Present only when the code declares one.

string
next

A copy-pasteable, fully-formed corrected call (a concrete URL, NOT a template) an agent can fire verbatim to recover — the machine-actionable counterpart to hint. Present only when a handler supplies one.

string
details

Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.

Array
retry_after_seconds

On a 429 rate_limited response, seconds to wait before retrying (mirrors the Retry-After header). Sleep at least this long, then re-issue the identical request.

number
master_id

Echoed on master_not_found (404) — the master id that did not resolve.

number
Example
{
"error": "invalid_query"
}