Generic cluster-keyed surface read (by registry name)
const url = 'https://crate.hosaka.fm/api/v2/surface/public.spine_artist_temporal_profile?cluster=example';const options = {method: 'GET', headers: {'X-API-Key': '<X-API-Key>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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).
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”The schema-qualified registry key, e.g. seen.radio_play_v1. GET /api/v2/surface lists every valid name.
Query Parameters
Section titled “Query Parameters ”The 64-hex identity key in this surface’s registered keyspace (case-insensitive) — see GET /api/v2/surface for which keyspace {name} expects.
Opaque keyset cursor from a previous page’s next_after. Pass back verbatim; never construct or decode it. Omit for the first page.
Rows per page, clamped to the surface’s registered cap (see GET /api/v2/surface → cap.default / cap.max).
Responses
Section titled “ Responses ”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).
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
- object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
Example
{ "object": "surface.rows", "surface": "public.spine_artist_temporal_profile", "state": "present", "keyspace": "pe-norm-v1-artist", "liveness": "populated"}Headers
Section titled “Headers ”Requests allowed in the current window.
Requests remaining in the current window.
Unix epoch (seconds) when the current window resets.
Validation failure (invalid query, malformed body, bad facet name)
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".
| code | HTTP | when thrown | fix |
|---|---|---|---|
invalid_artist_key | 400 | /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_locator | 400 | /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_key | 400 | /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_fields | 400 | /artist/{key}?fields= lists a facet that is not a valid top-level dossier field | use only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field) |
missing_locator | 400 | /resolve called with none of q/cluster/discogs/mbid | pass exactly one locator |
invalid_locator | 400 | a /resolve locator is malformed for its type | fix the format, or fall back to ?q=<name> |
invalid_query | 400 | /search ?q= missing/empty, or any Zod validation failure (details[] attached) | pass ?q=<text>; fix each details entry |
invalid_facet | 400 | an unknown facet filter name was supplied | GET /api/v2/facets for valid names + values |
rate_limited | 429 | an 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
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.
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).
Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with next.
Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.
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.
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.
Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.
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.
Echoed on master_not_found (404) — the master id that did not resolve.
Example
{ "error": "invalid_query"}Resource not found
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".
| code | HTTP | when thrown | fix |
|---|---|---|---|
invalid_artist_key | 400 | /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_locator | 400 | /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_key | 400 | /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_fields | 400 | /artist/{key}?fields= lists a facet that is not a valid top-level dossier field | use only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field) |
missing_locator | 400 | /resolve called with none of q/cluster/discogs/mbid | pass exactly one locator |
invalid_locator | 400 | a /resolve locator is malformed for its type | fix the format, or fall back to ?q=<name> |
invalid_query | 400 | /search ?q= missing/empty, or any Zod validation failure (details[] attached) | pass ?q=<text>; fix each details entry |
invalid_facet | 400 | an unknown facet filter name was supplied | GET /api/v2/facets for valid names + values |
rate_limited | 429 | an 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
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.
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).
Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with next.
Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.
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.
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.
Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.
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.
Echoed on master_not_found (404) — the master id that did not resolve.
Example
{ "error": "invalid_query"}Rate limit exceeded — see Retry-After + X-RateLimit-* headers
object
Example
{ "error": "rate_limited"}Internal server error
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".
| code | HTTP | when thrown | fix |
|---|---|---|---|
invalid_artist_key | 400 | /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_locator | 400 | /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_key | 400 | /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_fields | 400 | /artist/{key}?fields= lists a facet that is not a valid top-level dossier field | use only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field) |
missing_locator | 400 | /resolve called with none of q/cluster/discogs/mbid | pass exactly one locator |
invalid_locator | 400 | a /resolve locator is malformed for its type | fix the format, or fall back to ?q=<name> |
invalid_query | 400 | /search ?q= missing/empty, or any Zod validation failure (details[] attached) | pass ?q=<text>; fix each details entry |
invalid_facet | 400 | an unknown facet filter name was supplied | GET /api/v2/facets for valid names + values |
rate_limited | 429 | an 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
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.
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).
Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with next.
Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.
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.
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.
Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.
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.
Echoed on master_not_found (404) — the master id that did not resolve.
Example
{ "error": "invalid_query"}Database pool exhausted — retry after 5s
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".
| code | HTTP | when thrown | fix |
|---|---|---|---|
invalid_artist_key | 400 | /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_locator | 400 | /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_key | 400 | /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_fields | 400 | /artist/{key}?fields= lists a facet that is not a valid top-level dossier field | use only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field) |
missing_locator | 400 | /resolve called with none of q/cluster/discogs/mbid | pass exactly one locator |
invalid_locator | 400 | a /resolve locator is malformed for its type | fix the format, or fall back to ?q=<name> |
invalid_query | 400 | /search ?q= missing/empty, or any Zod validation failure (details[] attached) | pass ?q=<text>; fix each details entry |
invalid_facet | 400 | an unknown facet filter name was supplied | GET /api/v2/facets for valid names + values |
rate_limited | 429 | an 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
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.
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).
Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with next.
Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.
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.
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.
Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.
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.
Echoed on master_not_found (404) — the master id that did not resolve.
Example
{ "error": "invalid_query"}Request deadline (15s) or query timeout exceeded
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".
| code | HTTP | when thrown | fix |
|---|---|---|---|
invalid_artist_key | 400 | /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_locator | 400 | /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_key | 400 | /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_fields | 400 | /artist/{key}?fields= lists a facet that is not a valid top-level dossier field | use only the fields in the response valid set; omit ?fields= entirely for the full dossier (see the example field) |
missing_locator | 400 | /resolve called with none of q/cluster/discogs/mbid | pass exactly one locator |
invalid_locator | 400 | a /resolve locator is malformed for its type | fix the format, or fall back to ?q=<name> |
invalid_query | 400 | /search ?q= missing/empty, or any Zod validation failure (details[] attached) | pass ?q=<text>; fix each details entry |
invalid_facet | 400 | an unknown facet filter name was supplied | GET /api/v2/facets for valid names + values |
rate_limited | 429 | an 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
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.
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).
Actionable remediation in human terms — what to DO next, often naming the exact endpoint (a template with next.
Deep link to this code’s docs: https://crate.hosaka.fm/docs/api#error-. Auto-populated for catalogued codes.
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.
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.
Structured validation breakdown — on Zod 400s (invalid_query), an array of { path, message }, one per failed field. Present only when validation specifics are attached.
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.
Echoed on master_not_found (404) — the master id that did not resolve.
Example
{ "error": "invalid_query"}