Cluster-first canonical artist dossier (cluster_id hex OR slug)
const url = 'https://crate.hosaka.fm/api/v2/artist/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/artist/example \ --header 'X-API-Key: <X-API-Key>'Cycle-2b — the v2 cluster-first headline resource: the same exhaustive artist dossier as v1, addressed by the canonical cluster_id (64-hex, prime key) OR a human slug, plus the v2 ?fields= sparse-fieldset. Default = the full dossier in ONE round-trip; ?fields= opts OUT to trim to the named top-level facets (unknown field → 400 invalid_fields with the valid set + a copy-pasteable example). A 64-hex key resolves identity DIRECTLY from the cluster_id (OBSERVED tier, never re-anchors onto a same-name Discogs row). discogs:/mbid: locators are not a canonical address (→ 400 here) → resolve them to a cluster_id first via GET /api/v2/resolve (the response next field is the ready-to-call URL), then call /api/v2/artist/{cluster_id}. Keyed (X-API-Key); unresolved → 200 identity:null (honest-gap), not 404.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Query Parameters
Section titled “Query Parameters ”Comma-separated top-level facets to KEEP (opt-out trim). Omit for the full dossier (one round-trip). An unknown field → 400 invalid_fields with the valid set + an example.
Responses
Section titled “ Responses ”Artist dossier contract (default-rich; trimmed when ?fields= is supplied)
object
Crate’s CANONICAL artist identity — a pe-norm-v1 hex string. The SAME artist across Discogs, MusicBrainz and Bandcamp collapses to ONE cluster_id, so this is the key you store and the key you address every artist surface off of (/artist/{key} takes a 64-hex cluster_id directly). WHY IT MATTERS: it is crate’s prime IP — the non-Discogs long-tail join key; discogs_artist_id / mbid are mere leaf coordinates onto it. GOTCHA: it is an OPAQUE string — pass it through verbatim, NEVER numericize, parse, or compare it as a number. null is an HONEST GAP (the name/link couldn’t be resolved to a cluster), NOT an error — you still get HTTP 200. A 64-hex cluster_id always resolves at OBSERVED tier (resolved_via:‘cluster’), never re-anchored onto a same-name Discogs row.
The binding TIER — how trustworthy the identity match is. ‘discogs’ = canonical, Discogs-bound (verified). ‘cluster’ = OBSERVED/UNVERIFIED identity inferred from the seen booking graph with no Discogs bind. null = the lookup did not resolve at all (honest gap). WHY IT MATTERS: it is a trust signal you must respect — a ‘cluster’ result is crate showing you what it can SEE in the booking graph, not what it has verified. GOTCHA: surface a ‘cluster’ result as flagged/unverified, NEVER as canonical truth; do not silently merge a ‘cluster’ artist with a verified one. A bare 64-hex key always comes back ‘cluster’ by design (it skips the cc0_artists lookup so a hex address never re-anchors onto a same-name Discogs row).
object
object
object
object
object
object
object
object
Cited press mentions via the CLUSTER spine (archive_api_v1.artist_press_mentions, cycle-107) — outlet/title/url/publishedAt/sourceRepo, newest first, ≤10, deduped by url. Serves BOTH entry lanes (the typed items[] remain the Discogs-entity path). UNFILTERED by design: the producer’s extraction_confidence is a constant (no working precision signal); precision posture = gazetteer+LLM extraction across 38 outlets, ~13.6% article coverage — absence is COVERAGE, never ‘no press’.
object
ISO timestamp (evidence-side; never null upstream).
The producer’s dedup key — which crawl lane observed the article.
object
object
Co-booked neighbours, strongest first — bounded at 24 rows (the carrefour#124 contract cap).
object
Co-artist’s 64-hex cluster_id (the fleet identity-spine join key; carrefour#124) — null when the co-booking edge carries no cluster bind (consumers filter). Feed it straight to /api/v2/artist/{key}.
Co-artist’s resolved Discogs id, or null for the unsigned tail.
Co-artist’s resolved MusicBrainz id, or null.
Display name (always present) — the carrefour#124 displayName source (co_artist_name_display).
Crate artist-page slug derived from the name.
Distinct events the two artists were booked into together.
Distinct venues across those co-events.
ISO date of the most recent co-booking, or null.
object
object
object
object
object
object
object
object
object
object
object
object
object
object
Hotlink-only artwork URL: a Bandcamp CDN string, or a deterministic Cover Art Archive release-group URL. crate never fetches or re-hosts the bytes; a CAA url is best-effort and may 404 if no cover exists.
A const literal — always the boolean false — declaring crate’s link-only artwork posture. WHY IT MATTERS: it is a contractual promise that the url on the artwork item is a HOTLINK the caller dereferences directly (a Bandcamp CDN url or a deterministic Cover Art Archive url); crate never fetches, caches, or re-hosts the image bytes. GOTCHA: because crate never dereferences the url, it can be stale or 404 (a CAA url is best-effort and may not exist) — your client must handle a broken image gracefully. Treat rehost:false as a fixed flag, not a toggle; it will never be true.
Delivered pixel width of the hotlinked image. For bandcamp these are the _16 locator’s render dims (700×700 square) — exactly the box the hotlink renders at, NOT the original artwork’s source aspect (HEAD the url for non-square edge cases). Absent when the source carries no dims (coverartarchive).
Delivered pixel height — see width.
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
Authority-derived (mirror.external_artist_links, keyed by the CANONICAL discogs id — never the RA-claimed one) with the RA self-declared field as fallback.
Same sourcing as spotifyUrl.
Authority-derived only. No Apple Music field — the corpus carries no Apple links (honest absence).
object
object
object
object
object
object
object
object
object
Per-artist Bandcamp release list (cluster-first re-entry of the release grain demoted in the v2 cut). Populated whenever the artist resolves to a cluster identity (64-hex entry, or a slug that falls back to the booking-graph cluster); honest_gap otherwise. Summary rows only — fetch the full tracklist+durations+artwork+economics at GET /api/v2/artist/{key}/bandcamp/{item}.
object
object
A Bandcamp release id — the direct address for one release dossier (?item=
object
Rights-readiness for sync clearance (v2-only). Two provenance dimensions, both counts-only (no ISRC/ISWC values cross the wire). signals (cycle-088, MB proxy; entry re-anchored cycle-105): how identifiable (ISRC) and how registered (ISWC — the FACT) the artist’s MB-bridged catalogue is; the catalogue entry is the UNION of the Discogs-master bridge and the artist’s own MB credits (mirror.mb_recording_artist_credits primary credits + credited works) — cluster-native, served on BOTH lanes; a superset of the pre-2.44.0 masters-only cut. ledger (cycle-101, CLUSTER-anchored): what the catalogue has been REGISTERED as in ledger’s rights ledger, and who wrote it — registered works, works with a registered ISWC, distinct co-writers, plus a SoundCloud non-MB leg. MB-bridged (reach == the MB proxy — MB recording coverage gates entry). COVERAGE, measured 2026-07-16 against ledger’s full 59.8M-row isrc_work_crosswalk (daily delta; the ship-time 2.88M was a stalled slice): 53.1% of corpus ISRCs match (n=121,548) and matched works carry writer identity at 100% (measured n=230,405, both iswc:/prov: bridge modes). A leg that reaches nothing still serves an honest gap; the SoundCloud leg remains pilot-scale. Co-writer → performing-artist NAME is a deferred v2 (cowritersWithMbid marks the ceiling).
object
object
Distinct recordings reachable from the artist masters via the MB bridge.
…carrying ≥1 ISRC (identifiable recordings — the clearance prerequisite).
Distinct ISRC codes across those recordings.
Distinct works (compositions) reachable via the bridge.
Works with a registered ISWC — the FACT only; the value is never exposed.
Rights-REGISTRATION dimension (cycle-101; entry re-anchored cycle-103), cluster-anchored via the UNION of the artist’s PRIMARY-credit recordings (mirror.mb_recording_artist_credits — cluster-native, reaches artists with no Discogs presence) and the recordings on their own Discogs masters, → ISRCs → ledger’s ISRC↔work crosswalk → work-writer identity. The union is a superset of the pre-2.42.0 master-anchored cut for every artist. null = neither the ledger nor the SoundCloud leg reached anything (honest gap). Counts only — no writer_person_key / ISRC / ISWC values.
object
Distinct works from the artist’s catalogue registered in ledger’s rights ledger (canonical_work_key).
…of which carry a registered ISWC.
Distinct co-writer/songwriter person_keys credited across those works (OUTPUT count; raw keys never exposed).
Distinct co-writer MB artist_mbids — the ceiling for the DEFERRED co-writer→performing-name mapping (~0 today; the ~22% path, carrefour#116).
SoundCloud rights-binding coverage (seen; non-MB, cluster-keyed). null when the cluster has no SoundCloud rows (pilot-scale).
object
SoundCloud tracks carrying an ISRC.
…bound to a ledger work.
…whose work carries a registered ISWC.
Discogs MARKETPLACE supply/price for the artist’s catalogue (v2-only; mirror.marketplace_signal_by_cluster_v1, cluster-anchored — both entry paths reach it). The demand model’s third leg beside collection reach (signals.ownerReach) and wantlist level: how many releases are priced on the market, total copies for sale (supply pressure), the market floor, and typical/ceiling sale levels. latest is the newest weekly snapshot (the producer-contracted current state); series tells you how much history stands behind it. PUBLIC release-level marketplace facts deduped to release before rollup — not collector behaviour. Amounts within-currency (USD — all live data). null signals = no marketplace data observed for the cluster (honest gap, never zeros).
object
object
ISO 4217 — ‘USD’ for all live data (amounts are within-currency, USD-filtered at source).
object
Snapshot date (YYYY-MM-DD) these numbers were observed — the newest weekly snapshot.
Distinct releases by this artist-cluster with a marketplace price observed (catalogue/market fact, NOT a user count).
Sum of copies-for-sale across the cluster’s priced releases — supply pressure.
Market floor: lowest listing across the cluster’s releases (USD). null = not observed.
Typical sale level: median of per-release sales medians (USD). null = not observed.
Historical ceiling: max of per-release sales maxima (USD). null = not observed.
object
Weekly snapshots observed for this cluster (>= 1) — the depth behind latest.
Earliest snapshot date (YYYY-MM-DD) — the start of this cluster’s observed series.
First-release DEBUT anchor (v2-only; mirror.artist_first_release_v1 — MusicBrainz’s earliest dated release-group crediting the artist, plus the dated-release-group count behind it). An “in the MusicBrainz index” catalogue claim, never a biography fact. Served via the EXACT Discogs↔MB bind only (Discogs-resolved artists; cluster-path artists read honest_gap — cluster/name keying was measured to graft homonym facts and is deliberately refused). Dates are YEAR-only granularity: month/day are Jan-1 placeholders. Rare MB source-date errors exist at the extremes — treat implausible years as source noise, not fact. null signals = no dated MB release or no Discogs↔MB bind (honest gap).
object
object
Earliest dated MB release-group (YYYY-MM-DD; YEAR-only granularity — month/day are Jan-1 placeholders).
Dated release-groups crediting the artist in MusicBrainz — the catalogue span behind the date.
The MB artist the debut belongs to (the exact Discogs-bound entry) — an onward MusicBrainz key.
Distinct MB artists bound to this Discogs id (>1 = MB-side duplicates; the earliest is served).
Producer refresh timestamp (monthly MB dump cadence).
Structured Wikidata biographical facts (v2-only; mirror.artist_facts_v1, CC0 — re-publishable, no attribution): origin city/country (human-readable + QIDs), birth/death dates, official website, and genre/label/influence QID arrays. As-recorded-in-Wikidata claims, not verified biography. Discogs-resolved artists join 1:1 on the producer’s PK; cluster-path artists are served only when EXACTLY ONE facts row binds the cluster (the surface’s cluster key is name-minted and homonyms co-merge by design — an ambiguous bind reads honest_gap, never a guess). RESOLVED NAMES ship alongside the QIDs (genres/recordLabels/influencedBy — render the names; QIDs are exact-join keys); influencedBy(_Qids) is SPARSE and non-canonical (gate any influence UI; MusicBrainz-native edges are the canonical lineage source). PORTRAIT: portrait.url is a Wikidata P18 → Commons FilePath pointer (append ?width=N for a thumbnail) whose FILE carries its own per-file licence, resolved in-surface — CONTRACT: render ONLY when portrait.displayReady is true; licence ‘Public domain’ needs no attribution, any other licence must display portrait.credit and link portrait.licenceUrl. displayReady=false = licence resolution pending. null signals = not-yet-enriched or ambiguous bind (absent == unknown).
object
object
Wikidata self QID — provenance + onward key.
MusicBrainz artist id (unique on the surface) — onward key.
Human-readable origin city (Wikidata; dense field).
Human-readable origin country.
As recorded in Wikidata (YYYY-MM-DD) — a catalogue claim, not a verified biography fact.
The artist’s own site per Wikidata P856 (link-only).
RAW Wikidata Q-numbers (P136) — resolve before rendering; never display as text.
RAW Q-numbers (P264) — same caveat.
RAW Q-numbers (P737) — SPARSE + non-canonical (<5% populated); MB-native edges are canonical.
Resolved en-labels for genresQids (QIDs lacking an en-label are dropped here, kept in the QID array).
Resolved en-labels for recordLabelsQids.
Resolved names for influencedByQids — same sparse/non-canonical gating caveat.
Wikidata P1477 (~8% fill).
Wikidata P734 surname (~48% fill).
Commons portrait with its PER-FILE licence resolved in-surface. Render ONLY when displayReady; ‘Public domain’ needs no attribution, else show credit + link licenceUrl. null = no image.
object
Commons Special:FilePath URL (CC0 POINTER — the file is separately licensed). Append ?width=N for a thumbnail.
Commons LicenseShortName (e.g. ‘CC BY-SA 4.0’, ‘Public domain’). null = resolution pending — do not render.
Licence deed link — display it with the credit for non-PD licences.
Attribution string (HTML-stripped).
True = licence resolved; safe to render honoring the contract above.
Upstream Wikidata enrichment time — the honest “as of” for these facts.
Institutional-visibility badge (v2-only; seen.radar_only_artists_v1): every dimension-converging artist (>=2 independent live-circuit dimensions in 18 months) classed canon_alive | canon_quiet | uncanonized_alive | uncanonized_quiet. institutionallyInvisible (= NOT institutionally catalogued: no authority record AND no seen-side Discogs bind — the mig-0331 CORRECTED cut; the old authority-only cut over-stated invisibility by up to ~21.4%) is the producer’s badge cut; radarOnly (uncanonized_quiet) is the strict cut. PRODUCER CAVEATS, verbatim: ‘alive’ = ListenBrainz-corpus presence — a coverage-biased LOWER bound (Western/indie/electronic lean; sub-k-anon clusters read as no-flow) — quiet ≠ dead; some uncanonized rows are name-variant/persona sibling mints of catalogued artists; composition not timing — no lead/lag claim. Flow magnitudes are mirror’s k>=5 values verbatim (null = no k>=5 catalogued flow, never zero). null signals = not dimension-converging (universe floor), not a verdict on the artist.
object
object
The producer’s frozen four-quadrant vocabulary.
The badge cut (mig-0331 corrected): NOT institutionally catalogued — no authority record AND no seen-side Discogs bind. The previous authority-only cut over-stated invisibility by up to ~21.4%. The seen-side Discogs bind is heuristic-not-truth (~0.3% internal mbid<->discogs inconsistency, the same resolver class as the authority record); mb_only clusters deliberately not folded.
Strict cut: converging with no authority record AND no catalogued listens — visible to nothing public except the live circuit.
ListenBrainz k>=5 presence — coverage-biased lower bound; false ≠ unheard.
Convergence depth (>=2 by universe construction).
Comma-joined independent dimension list.
Mirror’s k>=5 value verbatim; null = no k>=5 flow, never zero.
Creative-LINEAGE facet (v2-only; MusicBrainz’s recording→recording derivation graph — mirror.mb_recording_relations, ~238K remix/sample/edit/dj-mix/mashup edges — made per-artist-addressable through the mb_recording_artist_credits bridge). DIRECTION is verified against live titles: in an edge src→dst, src is the DERIVATIVE and dst is the SOURCE. So asDerivative counts the artist’s recordings that remix/sample/edit another recording; asSource counts recordings another recording derived from. IMPORTANT billing-grain caveat: MB credits a remix recording to the ORIGINAL artist (a Photomachine remix of a Four Tet track is credited “Four Tet”), so asDerivative largely reflects OTHERS remixing this artist’s work — read these as GRAPH PARTICIPATION, never as authorship (“this artist remixed N others” is NOT a valid reading). Discogs-resolved artists aggregate their MB-duplicate mbids; cluster-path artists are served only when EXACTLY ONE mb_artists row binds the cluster (name-minted cluster keys co-merge homonyms — an ambiguous bind reads honest_gap). edges is a capped, deterministic, balanced sample (≤12 per direction) — the counts are the full totals. otherArtistCredit is the AS-CREDITED billing string (never a join key). null signals = no edges in the graph (honest gap; ~97% of artists).
object
object
The artist’s recordings that are the DERIVATIVE end (remix/sample/edit OF another recording). Graph participation, not authorship.
object
Sum across rel types (edges in this direction).
The artist’s recordings that are the SOURCE end (another recording derived from them).
object
Sum across rel types (edges in this direction).
Capped balanced sample (≤12/direction) of concrete edges — illustrative, not the whole graph (counts are the totals).
object
MB relation type, verbatim.
‘derived’ = this artist’s recording derives from the counterparty; ‘source’ = the counterparty derives from this artist’s recording.
Counterparty recording MB gid — onward key.
Counterparty recording title (MB), or null if unresolved.
Counterparty AS-CREDITED billing string — descriptive only, never a join key.
MB artist mbids the key resolved to (>1 on the Discogs path = MB-side dupes; cluster path is gated to 1).
AUTHORSHIP facet (v2-only; mirror.songwriter_signals_v1, cluster-grain — one row per pe-norm-v1 cluster credited on ≥1 MusicBrainz work). This is the “who WROTE it” axis, distinct from rights (what the recorded catalogue is registered as + its co-writers). worksWritten = DISTINCT MB works credited in ANY role; roles is the per-role DISTINCT-work breakdown (composer/lyricist/writer/arranger — a work can hold >1 role, so the parts may exceed the whole). recordingsOfWorks = DISTINCT recordings of those works (catalogue realisation, not commercial releases). The enrichment legs are HONEST-SPARSE (~10% of works): worksWithRights = works matched in ledger’s rights rollup; worksWithPlacement + placementTitleReach = film/TV placement reach. IMPORTANT coverage caveat: MusicBrainz authorship coverage is UNEVEN — strong for classical/composed repertoire, THIN for producer/electronic credits MB records as performance rather than authorship (a well-known electronic artist can read worksWritten=3 here while carrying dozens of recorded-catalogue registered works on the rights facet). A 0 on an enrichment leg is “not observed”, never “no rights”. Cluster-keyed on both entry paths (Discogs resolves discogs→cluster); a homonym-merged cluster blends its co-merged artists’ authorship (a spine property, not re-adjudicated here). null signals = the cluster is credited on no MB work (honest gap — the norm for performers who aren’t recorded writers). Counts only; non-user-derived at source.
object
object
DISTINCT MB works this cluster is credited on in any role (≥1 when present).
Per-role DISTINCT-work breakdown; parts may sum above worksWritten (a work can hold >1 role).
object
DISTINCT recordings of their works (MB recordings, not commercial releases).
Works matched in ledger’s rights rollup — HONEST-SPARSE; 0 = not observed, not “no rights”.
Works with a film/TV placement signal — honest-sparse.
Σ resolved screen titles across their placed works.
LISTEN-FLOW facet (v2-only; mirror.listen_flow_by_cluster_v1 — ListenBrainz streaming attention per cluster). listenCount + distinctListeners + distinctRecordings + first/lastListenedAt. K-ANON’D UPSTREAM (distinctListeners ≥ 5 when the row exists) — presence is a LOWER BOUND on aliveness; absence NEVER means no listeners (below-floor or MBID-unmapped: ~29% of listens carry no MBID upstream and never reach any cluster; the k≥5 floor suppresses ~44% of listened clusters). null signals = honest gap.
object
object
Total mapped listens — a lower bound, never a global play count.
≥5 by the producer k-anon floor when present.
ARRIVAL facet (v2-only; wintermute.cluster_arrival_wide_v1 — per-substrate-layer first-arrival pivot). layersPresent + per-layer *FirstAt timestamps + anyAliasBridged. CLOCK CAVEATS ARE LOAD-BEARING: catalogueFirstAt + placementFirstAt are YEAR-grain Jan-1 placeholders (sub-year comparison forbidden); implausibly-old values (e.g. 1970-01-01) are source sentinels, never facts; seenKnownSince is a DETECTION clock — never rank it against the event columns. NULL column = no defensible arrival in that layer, never imputed. null signals = no arrival row (honest gap).
object
object
Count of layers with a defensible first-arrival.
MIN over ALL seen:* kinds — mixes stated with evidence-class; never rank against a single-class column.
DETECTION clock — when the fleet first detected, not when the event happened.
YEAR-grain Jan-1 placeholder — sub-year comparison forbidden.
YEAR-grain Jan-1 placeholder — sub-year comparison forbidden.
AURA facet (v2-only; seen.artist_substrate_signal_v1 — the /api/v2/aura signal folded per-cluster). convergenceDimCount + dimensions[] + breakOdds + firstSignalAt/latestAppearanceAt + break-event flags. DEEP not GLOBAL: convergence across the signals hosaka observes, not a popularity meter. breakOdds is the measured BASE RATE of past artists at that convergence depth breaking within 12 months — NEVER a per-artist forecast. ~11K clusters converge; null signals = not converging (honest gap — the norm).
object
object
Base rate at this convergence depth — not a per-artist forecast.
DJ-CHAMPION facet (v2-only; seen.dj_champion — WHICH DJs championed this artist). championCount/earlyChampionCount/repeatChampionCount are FULL totals; topChampions[] is a capped sample (≤8, playCount DESC) with each champion’s dj clusterId as an onward key (/api/v2/artist/{hex}). isEarlyChampion = played within the early window after the artist’s first corpus play. The full edge list pages via GET /api/v2/surface/seen.dj_champion. null signals = no championing DJs observed (honest gap).
object
object
Distinct championing DJs (full total).
Capped sample (≤8) by playCount.
object
The championing DJ’s 64-hex cluster_id — an onward key.
RADIO-PLAY facet (v2-only; seen.radio_play_v1 — the LATEST dated titled plays, ≤10, quarantined/out-of-sanity-window excluded). The dossier’s current-signal detail: playedOn + stationKey + djName + extracted title/remix + sourceType. airplay_breadth carries the full rollup counts; the full play archive pages via GET /api/v2/surface/seen.radio_play_v1. Titles are tracklist-extracted — many plays carry NO title (title null, honest by design). null signals = no dated in-window plays (honest gap).
object
object
Latest dated plays (≤10, playedOn DESC).
object
Tracklist-extracted; null = untitled play row.
WHO wrote / WHO produced, with names (v2-only; cycle-094, entry re-anchored cycle-104): the artist’s catalogue joined to the MusicBrainz credit tables (mirror MB Phase-3), names + onward keys via mb_artists. The catalogue entry is the UNION of the Discogs-master bridge and the artist’s own MB credits (mirror.mb_recording_artist_credits primary credits + credited works) — cluster-native, so artists with no Discogs presence are served; a superset of the pre-2.43.0 masters-only cut. Complements the rights facet’s counts with the diligence NAMES. Performance role families (performer/vocal/instrument) are deliberately excluded — the desk, not the lineup. The subject is NOT excluded (self-written/self-produced is signal).
object
object
Songwriting credits, strongest-first (≤12).
object
MB-canonical display name (null only when MB holds no name row).
Credited person’s MusicBrainz id.
Onward Discogs id when MB binds one.
Onward 64-hex fleet cluster_id when bound — feed straight to /api/v2/artist/{key}.
Role-family@1 string (composer/lyricist/writer/… | producer/engineer/mix/…).
Distinct bridged works (writing) or recordings (production) carrying the credit.
Production-desk credits, strongest-first (≤12).
object
MB-canonical display name (null only when MB holds no name row).
Credited person’s MusicBrainz id.
Onward Discogs id when MB binds one.
Onward 64-hex fleet cluster_id when bound — feed straight to /api/v2/artist/{key}.
Role-family@1 string (composer/lyricist/writer/… | producer/engineer/mix/…).
Distinct bridged works (writing) or recordings (production) carrying the credit.
Placement/type history rollup (v2-only; cycle-094): the bridged catalogue’s MusicBrainz release-groups typed by secondary_types — “has this artist been synced/soundtracked before”. COUNTS ONLY. null signals = the bridge reached no release-groups (unmeasured); a present row with soundtracks=0 is an answer (“measured — no placements”).
object
object
Distinct MB release-groups reached (the measurement base).
Release-groups typed Soundtrack — the placement signal.
Earliest MB first-release date among soundtrack groups (year-accurate).
Primary geography at the artist grain (v2-only; cycle-092): the producer-resolved scene location (seen.artist_primary_geography, cluster_id-keyed via the performing-entity spine — unsigned/no-Discogs artists carry it too). honest_gap when the producer holds no location for the cluster.
object
object
Primary city, producer-resolved (e.g. Bristol).
Primary region/state, or null.
Primary country, or null.
Example
{ "grain": "artist", "cluster_id": "a3f9c1e84b2d70f6a3f9c1e84b2d70f6a3f9c1e84b2d70f6a3f9c1e84b2d70f6", "resolved_via": "discogs", "identity": { "resolvedVia": "discogs" }, "behavioral": { "signals": { "ownerReach": { "kind": "count" }, "wantlistDemand": { "kind": "count" } } }, "early_demand": { "signals": { "ownerReach": { "kind": "count" }, "wishlistDemand": { "kind": "count" } } }, "artwork": { "items": [ { "source": "bandcamp", "grain": "artist", "rehost": false } ] }, "bandcamp_releases": { "state": "present", "releases": [ { "bandcamp_item_id": "1234567890" } ] }, "rights": { "state": "present" }, "marketplace": { "state": "present" }, "debut": { "state": "present" }, "facts": { "state": "present" }, "visibility": { "state": "present", "signals": { "institutionalVisibility": "canon_alive" } }, "lineage": { "state": "present", "signals": { "edges": [ { "relType": "remix", "direction": "derived" } ] } }, "authorship": { "state": "present" }, "listen_flow": { "state": "present" }, "arrival": { "state": "present" }, "aura": { "state": "present" }, "dj_champion": { "state": "present" }, "radio_play": { "state": "present" }, "credits": { "state": "present" }, "placements": { "state": "present" }, "geography": { "state": "present" }}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"}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"}