Skip to content

Cluster-first canonical artist dossier (cluster_id hex OR slug)

GET
/api/v2/artist/{key}
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.

key
required
string
>= 1 characters <= 200 characters
fields

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.

string

Artist dossier contract (default-rich; trimmed when ?fields= is supplied)

Media type application/json
object
contract_version
required
string
grain
required
string
Allowed values: artist
slug
required
string
display
required
string
id
required
number | null
cluster_id
required

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.

string | null
resolved_via
required

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).

string | null
Allowed values: discogs cluster
identity
required
object
discogsArtistId
required
number | null
clusterId
required
string | null
resolvedVia
required
string
Allowed values: discogs cluster
name
required
string
realname
required
string | null
profile
required
string | null
urls
required
Array<string>
aliases
required
Array<string>
behavioral
required
object
state
required
string
signals
required
object
ownerReach
required
Any of:
object
kind
required
string
Allowed values: count
value
required
number
wantlistDemand
required
Any of:
object
kind
required
string
Allowed values: count
value
required
number
collectorCoownership
required
number | null
communityFootprint
required
number | null
travelsWith
required
Array
workCount
required
number | null
primaryGenre
required
string | null
primaryStyles
required
Array<string>
masterCountWithGenre
required
number | null
editorial
required
object
state
required
string
signals
across_the_web
required
object
count
required
number
links
required
Array<object>
object
source
required
string
sourceSubtype
required
string | null
url
required
string
title
required
string | null
description
required
string | null
confidence
required
number | null
journalism
required
object
state
required
string
signals
required
object
pressCount
required
number
reviewCount
required
number
interviewCount
required
number
featureCount
required
number
items
required
Array
citedMentions

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’.

Array<object>
object
outlet
required
string
title
required
string
url
required
string
publishedAt
required

ISO timestamp (evidence-side; never null upstream).

string
sourceRepo
required

The producer’s dedup key — which crawl lane observed the article.

string | null
connections
required
object
state
required
string
signals
required
object
count
required
number
related
required

Co-booked neighbours, strongest first — bounded at 24 rows (the carrefour#124 contract cap).

Array<object>
object
clusterId
required

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

string | null
discogsArtistId
required

Co-artist’s resolved Discogs id, or null for the unsigned tail.

integer | null
mbid
required

Co-artist’s resolved MusicBrainz id, or null.

string | null
name
required

Display name (always present) — the carrefour#124 displayName source (co_artist_name_display).

string
slug
required

Crate artist-page slug derived from the name.

string
coEventCount
required

Distinct events the two artists were booked into together.

integer
coVenueCount
required

Distinct venues across those co-events.

integer
lastCoAt
required

ISO date of the most recent co-booking, or null.

string | null
emergence
required
object
state
required
string
signals
required
object
emergenceTier
required
string
momentumTier
required
string
emergenceScore
required
number
momentumScore
required
number
establishmentDelta
required
number
eventsRecent
required
number
isFreshBreakout
required
boolean
isUnsigned
required
boolean
breakout
required
object
state
required
string
signal
required
object
emergenceTier
required
string
corroboration
required
string
pressCount
required
number
compositions
required
object
state
required
string
works
required
Array<object>
object
workMbid
required
string
workName
required
string
ownerReach
required
number
versionCollectors
required
number
versionDepth
required
number
nRecordingsGlobal
required
number
tier_presence
required
object
state
required
string
signals
required
object
spansTiers
required
boolean
playsClubs
required
boolean
playsFestivals
required
boolean
clubEvents
required
number
festivalEditions
required
number
firstClubAt
required
string | null
lastClubAt
required
string | null
firstFestivalAt
required
string | null
lastFestivalAt
required
string | null
early_demand
required
object
state
required
string
signals
required
object
ownerReach
required
Any of:
object
kind
required
string
Allowed values: count
value
required
number
wishlistDemand
required
Any of:
object
kind
required
string
Allowed values: count
value
required
number
earliestSupportedAt
required
string | null
earliestWishedAt
required
string | null
demandCoverage
required
number | null
wishedCoverage
required
number | null
alsoOwnDegree
required
number | null
bandcamp_emergence
required
object
state
required
string
signals
required
object
emergenceClass
required
string
demandLead
required
number
demandRatio
required
number | null
ownerReach
required
number | null
wishlistDemand
required
number | null
distinctReleases
required
number | null
earliestWishedAt
required
string | null
artwork
required
object
state
required
string
items
required
Array<object>
object
url
required

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.

string
source
required
string
Allowed values: bandcamp coverartarchive
grain
required
string
Allowed values: artist release
license
required
string
rehost
required

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.

boolean
width

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).

integer
height

Delivered pixel height — see width.

integer
dated_appearance
required
object
state
required
string
signals
required
object
count
required
number
sources
required
Array<object>
object
sourceKind
required
string
firstAppearanceDate
required
string | null
latestAppearanceDate
required
string | null
appearanceCount
required
number
network_position
required
object
state
required
string
signals
required
object
degree
required
number
weightedDegree
required
number
resolvedNeighbourRatio
required
number | null
recentDegree
required
number
establishmentDelta
required
number | null
airplay_breadth
required
object
state
required
string
signals
required
object
firstAirplayDate
required
string | null
latestAirplayDate
required
string | null
airplayCount
required
number
distinctStations
required
number
distinctVenues
required
number
distinctDjs
required
number
brokerage
required
object
state
required
string
signals
required
object
degree
required
number
effectiveSize
required
number | null
brokerageScore
required
number | null
brokerageTier
required
string
lead_time
required
object
state
required
string
signals
required
object
pressToAirplayDays
required
number
undergroundToBookingDays
required
number
bandcamp_tastemaker
required
object
state
required
string
signals
required
object
supporterCohortSize
required
number
aesq
required
number
aesqMedian
required
number | null
aesqRw
required
number | null
meanFirstBuyerEarliness
required
number | null
live_demand
required
object
state
required
string
signals
required
object
demandTier
required
string
demandMomentumScore
required
number | null
eventsTracked
required
number
eventsSoldOut
required
number
distinctProviders
required
number
distinctPromoters
required
number
avgFillFraction
required
number | null
maxSoldOutLeadDays
required
number | null
editorial_attention
required
object
state
required
string
signals
required
object
memberCount
required
number
sourceRepoCount
required
number
firstObservedAt
required
string | null
latestObservedAt
required
string | null
web_presence
required
object
state
required
string
signals
required
object
bandcampUrl
required
string | null
soundcloudUrl
required
string | null
instagramUrl
required
string | null
websiteUrl
required
string | null
discogsArtistIdClaimed
required
number | null
raStatus
required
string | null
followerCount
required
number | null
country
required
string | null
firstEventAt
required
string | null
spotifyUrl
required

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.

string | null
youtubeUrl
required

Same sourcing as spotifyUrl.

string | null
lastfmUrl
required

Authority-derived only. No Apple Music field — the corpus carries no Apple links (honest absence).

string | null
editorial_canon
required
object
state
required
string
signals
required
object
pickCount
required
number
picks
required
Array<object>
object
relation
required
string
polarity
required
string | null
confidence
required
number | null
assertedBy
required
string
isDirectQuote
required
boolean
speakerName
required
string | null
objectName
required
string
discography
required
object
state
required
string
signals
required
object
clusterId
required
string | null
total
required
number
shown
required
number
truncated
required
boolean
entries
required
Array<object>
object
discogsMasterId
required
number
representativeName
required
string | null
isPrimary
required
boolean
billingPosition
required
number
_links
required
object
master
required
string
overMerge
required
object
discogsArtistIdCount
required
number
folded
required
boolean
observedAt
required
string | null
provenance
required
Array<object>
object
field
required
string
producer
required
string
sourceTable
required
string
refreshCadence
required
string
tier
required
string
honestGapState
required
string
detailAnchor
string
generated_at
required
string
bandcamp_releases

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
state
required
string
Allowed values: present honest_gap
releases
required
Array<object>
object
bandcamp_item_id
required

A Bandcamp release id — the direct address for one release dossier (?item= on /bandcamp/release returns the full tracklist). WHY IT MATTERS: it is the stable per-release key you carry from a release-list row back to the full dossier fetch. GOTCHA: it is a bigint serialized AS A STRING and is OPAQUE — never numericize it (JS numbers lose precision past 2^53 and the value is an identifier, not a quantity), never do arithmetic on it, and pass it back verbatim.

string
artist
required
string | null
title
required
string | null
release_date
required
string | null
source_url
required
string | null
tags
required
Array<string>
label
required
object
name
required
string | null
url
required
string | null
rights

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
state
required
string
Allowed values: present honest_gap
signals
required
object
recordingsBridged
required

Distinct recordings reachable from the artist masters via the MB bridge.

integer
recordingsWithIsrc
required

…carrying ≥1 ISRC (identifiable recordings — the clearance prerequisite).

integer
isrcCount
required

Distinct ISRC codes across those recordings.

integer
worksBridged
required

Distinct works (compositions) reachable via the bridge.

integer
worksWithIswc
required

Works with a registered ISWC — the FACT only; the value is never exposed.

integer
ledger
required

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

Distinct works from the artist’s catalogue registered in ledger’s rights ledger (canonical_work_key).

integer
worksWithRegisteredIswc
required

…of which carry a registered ISWC.

integer
distinctCowriters
required

Distinct co-writer/songwriter person_keys credited across those works (OUTPUT count; raw keys never exposed).

integer
cowritersWithMbid
required

Distinct co-writer MB artist_mbids — the ceiling for the DEFERRED co-writer→performing-name mapping (~0 today; the ~22% path, carrefour#116).

integer
soundcloud
required

SoundCloud rights-binding coverage (seen; non-MB, cluster-keyed). null when the cluster has no SoundCloud rows (pilot-scale).

object
tracksWithIsrc
required

SoundCloud tracks carrying an ISRC.

integer
tracksBound
required

…bound to a ledger work.

integer
tracksWithRegisteredIswc
required

…whose work carries a registered ISWC.

integer
marketplace

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
state
required
string
Allowed values: present honest_gap
signals
required
object
currency
required

ISO 4217 — ‘USD’ for all live data (amounts are within-currency, USD-filtered at source).

string
latest
required
object
snapshotDate
required

Snapshot date (YYYY-MM-DD) these numbers were observed — the newest weekly snapshot.

string
pricedReleaseCount
required

Distinct releases by this artist-cluster with a marketplace price observed (catalogue/market fact, NOT a user count).

integer
totalCopiesForSale
required

Sum of copies-for-sale across the cluster’s priced releases — supply pressure.

integer
minPrice
required

Market floor: lowest listing across the cluster’s releases (USD). null = not observed.

number | null
medianSales
required

Typical sale level: median of per-release sales medians (USD). null = not observed.

number | null
maxSales
required

Historical ceiling: max of per-release sales maxima (USD). null = not observed.

number | null
series
required
object
snapshots
required

Weekly snapshots observed for this cluster (>= 1) — the depth behind latest.

integer
firstSnapshotDate
required

Earliest snapshot date (YYYY-MM-DD) — the start of this cluster’s observed series.

string
debut

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
state
required
string
Allowed values: present honest_gap
signals
required
object
firstReleaseDate
required

Earliest dated MB release-group (YYYY-MM-DD; YEAR-only granularity — month/day are Jan-1 placeholders).

string
releaseGroupCount
required

Dated release-groups crediting the artist in MusicBrainz — the catalogue span behind the date.

integer
artistMbid
required

The MB artist the debut belongs to (the exact Discogs-bound entry) — an onward MusicBrainz key.

string
mbidMatches
required

Distinct MB artists bound to this Discogs id (>1 = MB-side duplicates; the earliest is served).

integer
computedAt
required

Producer refresh timestamp (monthly MB dump cadence).

string
facts

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
state
required
string
Allowed values: present honest_gap
signals
required
object
wikidataQid
required

Wikidata self QID — provenance + onward key.

string
mbid
required

MusicBrainz artist id (unique on the surface) — onward key.

string
originCity
required

Human-readable origin city (Wikidata; dense field).

string | null
originCityQid
required
string | null
originCountry
required

Human-readable origin country.

string | null
originCountryQid
required
string | null
birthDate
required

As recorded in Wikidata (YYYY-MM-DD) — a catalogue claim, not a verified biography fact.

string | null
deathDate
required
string | null
officialWebsite
required

The artist’s own site per Wikidata P856 (link-only).

string | null
genresQids
required

RAW Wikidata Q-numbers (P136) — resolve before rendering; never display as text.

Array<string>
recordLabelsQids
required

RAW Q-numbers (P264) — same caveat.

Array<string>
influencedByQids
required

RAW Q-numbers (P737) — SPARSE + non-canonical (<5% populated); MB-native edges are canonical.

Array<string>
genres
required

Resolved en-labels for genresQids (QIDs lacking an en-label are dropped here, kept in the QID array).

Array<string>
recordLabels
required

Resolved en-labels for recordLabelsQids.

Array<string>
influencedBy
required

Resolved names for influencedByQids — same sparse/non-canonical gating caveat.

Array<string>
birthName
required

Wikidata P1477 (~8% fill).

string | null
familyName
required

Wikidata P734 surname (~48% fill).

string | null
portrait
required

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

Commons Special:FilePath URL (CC0 POINTER — the file is separately licensed). Append ?width=N for a thumbnail.

string
licence
required

Commons LicenseShortName (e.g. ‘CC BY-SA 4.0’, ‘Public domain’). null = resolution pending — do not render.

string | null
licenceUrl
required

Licence deed link — display it with the credit for non-PD licences.

string | null
credit
required

Attribution string (HTML-stripped).

string | null
displayReady
required

True = licence resolved; safe to render honoring the contract above.

boolean
lastEnrichedAt
required

Upstream Wikidata enrichment time — the honest “as of” for these facts.

string
visibility

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
state
required
string
Allowed values: present honest_gap
signals
required
object
institutionalVisibility
required

The producer’s frozen four-quadrant vocabulary.

string
Allowed values: canon_alive canon_quiet uncanonized_alive uncanonized_quiet
institutionallyInvisible
required

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.

boolean
radarOnly
required

Strict cut: converging with no authority record AND no catalogued listens — visible to nothing public except the live circuit.

boolean
hasAuthorityRecord
required
boolean
hasListenFlow
required

ListenBrainz k>=5 presence — coverage-biased lower bound; false ≠ unheard.

boolean
independentDimensions
required

Convergence depth (>=2 by universe construction).

integer | null
dimensions
required

Comma-joined independent dimension list.

string | null
firstSignal
required
string | null
latestSignal
required
string | null
spanDays
required
integer | null
inBookingCircuit
required
boolean | null
listenCount
required

Mirror’s k>=5 value verbatim; null = no k>=5 flow, never zero.

integer | null
distinctListenerCount
required
integer | null
lastListenedAt
required
string | null
lineage

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
state
required
string
Allowed values: present honest_gap
signals
required
object
asDerivative
required

The artist’s recordings that are the DERIVATIVE end (remix/sample/edit OF another recording). Graph participation, not authorship.

object
remix
required
integer
sample
required
integer
edit
required
integer
djMix
required
integer
mashup
required
integer
total
required

Sum across rel types (edges in this direction).

integer
asSource
required

The artist’s recordings that are the SOURCE end (another recording derived from them).

object
remix
required
integer
sample
required
integer
edit
required
integer
djMix
required
integer
mashup
required
integer
total
required

Sum across rel types (edges in this direction).

integer
edges
required

Capped balanced sample (≤12/direction) of concrete edges — illustrative, not the whole graph (counts are the totals).

Array<object>
object
relType
required

MB relation type, verbatim.

string
Allowed values: remix sample edit dj-mix mashup
direction
required

‘derived’ = this artist’s recording derives from the counterparty; ‘source’ = the counterparty derives from this artist’s recording.

string
Allowed values: derived source
otherRecordingMbid
required

Counterparty recording MB gid — onward key.

string
otherRecordingName
required

Counterparty recording title (MB), or null if unresolved.

string | null
otherArtistCredit
required

Counterparty AS-CREDITED billing string — descriptive only, never a join key.

string | null
artistMbidCount
required

MB artist mbids the key resolved to (>1 on the Discogs path = MB-side dupes; cluster path is gated to 1).

integer
authorship

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
state
required
string
Allowed values: present honest_gap
signals
required
object
worksWritten
required

DISTINCT MB works this cluster is credited on in any role (≥1 when present).

integer
roles
required

Per-role DISTINCT-work breakdown; parts may sum above worksWritten (a work can hold >1 role).

object
composer
required
integer
lyricist
required
integer
writer
required
integer
arranger
required
integer
recordingsOfWorks
required

DISTINCT recordings of their works (MB recordings, not commercial releases).

integer
worksWithRights
required

Works matched in ledger’s rights rollup — HONEST-SPARSE; 0 = not observed, not “no rights”.

integer
worksWithPlacement
required

Works with a film/TV placement signal — honest-sparse.

integer
placementTitleReach
required

Σ resolved screen titles across their placed works.

integer
listen_flow

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
state
required
string
Allowed values: present honest_gap
signals
required
object
listenCount
required

Total mapped listens — a lower bound, never a global play count.

integer
distinctListeners
required

≥5 by the producer k-anon floor when present.

integer
distinctRecordings
required
integer
firstListenedAt
required
string | null
lastListenedAt
required
string | null
arrival

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
state
required
string
Allowed values: present honest_gap
signals
required
object
layersPresent
required

Count of layers with a defensible first-arrival.

integer
anyAliasBridged
required
boolean | null
listenFirstAt
required
string | null
bandcampFirstAt
required
string | null
attentionFirstAt
required
string | null
authorityFirstAt
required
string | null
seenEventFirstAt
required

MIN over ALL seen:* kinds — mixes stated with evidence-class; never rank against a single-class column.

string | null
seenKnownSince
required

DETECTION clock — when the fleet first detected, not when the event happened.

string | null
editorialFirstAt
required
string | null
catalogueFirstAt
required

YEAR-grain Jan-1 placeholder — sub-year comparison forbidden.

string | null
placementFirstAt
required

YEAR-grain Jan-1 placeholder — sub-year comparison forbidden.

string | null
aura

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
state
required
string
Allowed values: present honest_gap
signals
required
object
convergenceDimCount
required
integer
dimensions
required
Array<string>
breakOdds
required

Base rate at this convergence depth — not a per-artist forecast.

number | null
firstSignalAt
required
string
latestAppearanceAt
required
string
hasBreakEvent
required
boolean
breakEventAt
required
string | null
dj_champion

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
state
required
string
Allowed values: present honest_gap
signals
required
object
championCount
required

Distinct championing DJs (full total).

integer
earlyChampionCount
required
integer
repeatChampionCount
required
integer
topChampions
required

Capped sample (≤8) by playCount.

Array<object>
object
djClusterId
required

The championing DJ’s 64-hex cluster_id — an onward key.

string
playCount
required
integer
playEventCount
required
integer
firstPlayedAt
required
string | null
latestPlayedAt
required
string | null
isEarlyChampion
required
boolean
isRepeatChampion
required
boolean
radio_play

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
state
required
string
Allowed values: present honest_gap
signals
required
object
recentPlays
required

Latest dated plays (≤10, playedOn DESC).

Array<object>
object
playedOn
required
string
stationKey
required
string
djName
required
string | null
title
required

Tracklist-extracted; null = untitled play row.

string | null
remix
required
string | null
sourceType
required
string
credits

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
state
required
string
Allowed values: present honest_gap
signals
required
object
writing
required

Songwriting credits, strongest-first (≤12).

Array<object>
object
name
required

MB-canonical display name (null only when MB holds no name row).

string | null
mbid
required

Credited person’s MusicBrainz id.

string
discogsArtistId
required

Onward Discogs id when MB binds one.

integer | null
clusterId
required

Onward 64-hex fleet cluster_id when bound — feed straight to /api/v2/artist/{key}.

string | null
role
required

Role-family@1 string (composer/lyricist/writer/… | producer/engineer/mix/…).

string
count
required

Distinct bridged works (writing) or recordings (production) carrying the credit.

integer
production
required

Production-desk credits, strongest-first (≤12).

Array<object>
object
name
required

MB-canonical display name (null only when MB holds no name row).

string | null
mbid
required

Credited person’s MusicBrainz id.

string
discogsArtistId
required

Onward Discogs id when MB binds one.

integer | null
clusterId
required

Onward 64-hex fleet cluster_id when bound — feed straight to /api/v2/artist/{key}.

string | null
role
required

Role-family@1 string (composer/lyricist/writer/… | producer/engineer/mix/…).

string
count
required

Distinct bridged works (writing) or recordings (production) carrying the credit.

integer
placements

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
state
required
string
Allowed values: present honest_gap
signals
required
object
releaseGroupsLinked
required

Distinct MB release-groups reached (the measurement base).

integer
soundtracks
required

Release-groups typed Soundtrack — the placement signal.

integer
compilations
required
integer
remixes
required
integer
liveReleases
required
integer
earliestSoundtrack
required

Earliest MB first-release date among soundtrack groups (year-accurate).

string | null
geography

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
state
required
string
Allowed values: present honest_gap
signals
required
object
city
required

Primary city, producer-resolved (e.g. Bristol).

string | null
region
required

Primary region/state, or null.

string | null
country
required

Primary country, or null.

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

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