Canonical label dossier by cluster_id hex or slug
const url = 'https://crate.0xhoneyjar.xyz/api/v2/label/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.0xhoneyjar.xyz/api/v2/label/example \ --header 'X-API-Key: <X-API-Key>'The cluster-first canonical label address. {key} is a 64-hex label_cluster_id (resolves DIRECTLY, OBSERVED tier, never re-anchors) OR a name-slug. discogs:/mbid: → 400. Keyed; unresolved → 200 identity:null (honest-gap), not 404.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Responses
Section titled “ Responses ”Label dossier contract
object
Resolved Discogs label id (the LEAF coordinate); null = cluster-only / unresolved.
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
Cycle-L1: cross-source identity binds from label_identity_v1. bandcamp_url is a LINK only (rehost:false, never fetched).
object
The label’s self-declared Bandcamp page URL from the cross-source identity spine (a LINK, never fetched — same rehost:false posture as artwork). WHY IT MATTERS: it is the canonical ‘listen/buy on Bandcamp’ address for the label, and the bind that lets a Bandcamp-native (cluster-only) label still resolve. GOTCHA: it is a page link, not an API or audio endpoint; it is nullable (no Bandcamp bind for this cluster yet) — null is an honest gap, not an error.
Cycle-L1: which source planes this cluster is observed in (presence flags, not measured counts).
object
Cycle-L1: present ONLY when >1 raw Discogs label id folded into this cluster (over-merge honesty) — the dossier is then forced to the OBSERVED tier.
object
OVER-MERGE observability: how many distinct raw Discogs label ids folded into this one label_cluster_id. WHY IT MATTERS: a label cluster is name-derived, so distinct Discogs labels that share a normalized name can collapse together; this count is crate’s honesty signal that the cluster may conflate more than one real label. GOTCHA: when it is >1 the dossier is forced to the OBSERVED tier (resolved_via:‘cluster’) and surfaces an over_merge block — do NOT present such a cluster’s identity or signals as authoritative for a single label. 1 (or absent) means a clean single-label cluster.
object
Cycle-L1: Bandcamp co-ownership adjacency (mirror.bandcamp_label_travels_v1, cluster-keyed, k-anon k>=5). Each entry links onward to the label by its cluster_id (GET /api/v2/label/{cluster_id}).
object
object
The rarity-weighted normalized pointwise mutual information of a Bandcamp co-ownership edge — how much more often two labels are co-collected than chance, downweighting ubiquitous labels. WHY IT MATTERS: it ranks the travels adjacency (higher = a stronger, more distinctive co-ownership tie) so you can take the top few as the label’s nearest neighbors. GOTCHA: it is a relative affinity score, NOT a count or a probability — don’t sum it, threshold it absolutely, or show it as a percentage; use it only to order edges. Pair it with support (the distinct-supporter count behind the edge, k-anon floor 5).
object
Cycle-L2: per-label DJ-set momentum (seen.label_djset_momentum, cluster-keyed) — which labels are breaking in DJ sets. Lead with playsLast365d (recency) + distinctArtists (roster breadth); velocity = playsLast365d/playCount. honest_gap when the label has no DJ plays.
object
object
How many times the label’s roster was played in DJ sets in the last 365 days — the RECENCY signal of label DJ-momentum (seen.label_djset_momentum). WHY IT MATTERS: it is the lead ‘which labels are breaking right now’ number; pair it with distinctArtists (roster breadth) to separate a one-hit label from a deep accelerating roster. GOTCHA: it is a raw count over public DJ tracklists (no per-fan data), not a rate — divide by playCount to get velocity. A label with no DJ plays has no row at all (dj_momentum.state=‘honest_gap’), not a zero.
The share of a label’s all-time DJ-set plays that happened in the last 365 days: playsLast365d / playCount ∈ [0,1]. WHY IT MATTERS: it normalizes momentum so a young breaking label (near 1.0) is distinguishable from a long-established catalogue label with the same recent count but a huge back-catalogue (near 0). GOTCHA: it is null when playCount is 0 (no all-time plays — avoid div-by-zero); it is a ratio, not a percentage or a count — don’t sum it across labels.
Example
{ "grain": "label", "cluster_id": "a3f9c1e84b2d70f6a3f9c1e84b2d70f6a3f9c1e84b2d70f6a3f9c1e84b2d70f6", "resolved_via": "discogs", "binds": { "bandcamp_url": "https://labelname.bandcamp.com" }, "over_merge": { "discogs_label_id_count": "1", "folded": true }, "travels": { "related": [ { "npmi": "0.42" } ] }, "dj_momentum": { "signals": { "playsLast365d": "342", "velocity": "0.61" } }}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.0xhoneyjar.xyz/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.0xhoneyjar.xyz/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.0xhoneyjar.xyz/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.0xhoneyjar.xyz/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"}