Machine contract · API-first

For AI agents — the machine contract.

The crate API is designed to be consumed without a browser. Keyless entry for evaluation, a self-describing index, teaching errors with copy-pasteable fixes, and honest-gap semantics so agents never parse an HTML error page.

For the umbrella hosaka story (substrate, emergence model, the fleet): hosaka.fm/agents →

Cold-start in three calls

No API key needed for the first call. 100 req/hr/IP. CORS *. Fails closed on 503 when the substrate is busy — handle it explicitly, never silently.

01 · preview (keyless)

Try it without a key

Artist name → a capped preview dossier. Enough to evaluate the shape, test your parsing, and confirm the cluster resolves.

GET /api/v2/preview/artist?q={name}
02 · resolve (keyed)

Name or link → cluster_id

Accepts a name, Discogs/MusicBrainz id, or any artist URL (Bandcamp, SoundCloud, Spotify, a website). Returns the canonical cluster_id + slug + locators.

GET /api/v2/resolve?q={name}
03 · dossier (keyed)

Full picture in one call

Discography, bookings, press, emergence signals, rights, credits, placements — assembled server-side. Add ?fields=identity,discography to trim to facets you need.

GET /api/v2/artist/{cluster_id}
keyless-proof.sh
# no key needed — 100 req/hr/IP
curl -s 'https://crate.hosaka.fm/api/v2/preview/artist?q=four+tet'

# authentic response (2026-07-08) — arc_truncated:true from the API itself
{
  "object": "artist.preview",
  "present": true,
  "display": "Four Tet",
  "cluster_id": null,
  "resolved_via": "discogs",
  "emergence_tier": "steady",
  "arc": [
    { "year": 1996, "tier": "press",    "label": "first press mention" },
    { "year": 1998, "tier": "bandcamp", "label": "first release" },
    { "year": 2001, "tier": "radio",    "label": "first radio airplay" },
    { "year": 2001, "tier": "booking",  "label": "first festival" },
    { "year": 2013, "tier": "soundcloud","label":"first SoundCloud repost" },
    { "year": 2013, "tier": "booking",  "label": "booked w/ Bill Patrick" }
  ],
  "arc_truncated": true,
  "booked_with": ["Ben UFO", "Floating Points", "Pearson Sound"],
  "press_count": 50,
  "note": "Education preview — a capped subset of the artist dossier. The full ~24-facet contract lives at GET /api/v2/artist/{key} (keyed).",
  "generated_at": "2026-07-07T17:02:12.866Z"
}

cluster_id is nullable — but off-master artists (no Discogs/MusicBrainz identity) now resolve too: the cluster is name-minted deterministically (matched_on: name_mint). Only name-dark inputs stay null, never an error — handle that case.

Self-describing index — GET /api/v2

The root index is public and keyless. It returns the cold-start recipe, 22 documented resources (each with an eli5, auth notes, and a copy-pasteable example), 15 catalogued errors, and 7 task-oriented recipes. An agent can bootstrap its understanding of the API surface from a single call before acquiring a key.

22documented resources

Each resource carries an eli5, auth requirement, and a verbatim example call with expected response shape.

15catalogued errors

Catalogued codes carry hint and doc_url; param when the code declares a source parameter; next when the handler can build a corrected call. error is the only guaranteed field.

7task-oriented recipes

Stepwise recipes for common agent tasks: name → dossier, genre discovery, Bandcamp tracklist, track-title search, tastemaker lookup, and more.

OpenAPI 3.1 spec: /api/v2/openapi.json (public, no key). Generate typed client in one line:

types.sh
npx openapi-typescript https://crate.hosaka.fm/api/v2/openapi.json -o crate-api.d.ts

Teaching errors

Error bodies are designed to be machine-actionable. error is the only field guaranteed on every error body. Catalogued codes (those in the ERROR_CATALOG) also carry:

  • hint — what to do next, in human terms
  • doc_url — deep link to this code's docs page
  • param — the specific request parameter that caused the failure (only when the code declares one)
  • next — a copy-pasteable, fully-formed corrected call (only when the handler can build one)

Live example — a keyed endpoint with no key (curl -s 'https://crate.hosaka.fm/api/v2/artist/x'):

missing-key.sh
# no X-API-Key header → teaching error (authentic, 2026-07-08)
curl -s 'https://crate.hosaka.fm/api/v2/artist/x'

{"error":"missing_api_key"}

Branch on error (lowercase snake_case), never on HTTP status alone — several codes share a status code. The error field is the only field guaranteed on every error body.

Honest-gap semantics

Unresolved lookups return HTTP 200 with present:false or a field with state:"honest_gap" — never a 404. Agents never need to parse error pages or handle unexpected status codes for "no data found" conditions.

honest-gap.json
// unresolved lookup: HTTP 200, not 404
{
  "object": "artist.preview",
  "present": false,
  "note": "No signal on file for that query. Try a fuller name, or /api/v2/tracks for title search (?fuzzy=true for substring)."
}

Dossier facets carry a per-facet state — present, honest_gap, or degraded — along with the producer and refresh cadence behind it. Your agent knows what it can rely on and at what freshness, without guessing.

Rate limits

Every keyed 2xx response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. 429 responses carry Retry-After. Calls that return an error or 429 are never billed.

TierBurst/minConcurrencyIncluded/mo
free30110,000
indie60215,000
studio3003100,000
scale6005500,000
sync1,000102,000,000

The keyless preview endpoint is IP-scoped at 100 req/hr regardless of tier. Overage is dark behind CRATE_OVERAGE_ENABLED — until flipped, the hard cap is a 429 (no surprise bills). See /pricing for the full billing model.

crate-cli robot contract

npm install -g @hosaka-fm/crate-cli (v0.3.7). stdout = untransformed API JSON. stderr = diagnostics. These channels never mix. Pipe stdout to jq, redirect stderr to a log — they will never cross.

agent-commands.sh
# Confirm auth and substrate health before a batch run
crate triage

# Pull the robot handbook into a system prompt
crate robot-docs

# Query without a key
crate preview 'four tet'

# Machine-readable capability list
crate capabilities

Exit code dictionary

CodeMeaningNotes
0oksuccess — including honest-gap (present:false) bodies; never exit 1 for "no results"
1usage errorbad args/flags; the error names the corrected invocation
2auth errormissing/invalid API key (HTTP 401/402)
3invalid inputthe API rejected the input (HTTP 400); its hint is passed through
4rate-limitedHTTP 429 — Retry-After surfaced on stderr; the CLI never auto-retries, the agent decides
5server errorupstream server error (HTTP 5xx)
6network errorDNS failure, timeout, connection refused
7config errorconfig file unreadable or corrupt

Machine-readable surfaces

crate publishes a set of surfaces designed for agent consumption:

  • /llms.txt — site one-liner, cold-start recipe, endpoint facts, tier summary, SDK/CLI installs
  • /api/v2/openapi.json — OpenAPI 3.1 spec (public, no key); generate types with npx openapi-typescript
  • /sitemap.xml — canonical page list with real lastModified dates
  • crate robot-docs — paste-ready agent handbook for pasting into a system prompt
  • crate capabilities — machine-contract JSON listing every available command and flag

TypeScript SDK

npm install @hosaka-fm/crate (v1.16.0) — a typed wrapper over the same REST API, generated straight from the OpenAPI spec. Full docs at crate-sdk.hosaka.fm.

As of 2026-08-04, the crate API (v2=2.47.0) exposes 22 documented resources on the self-describing index (GET /api/v2, public) — including title-first track search (GET /api/v2/tracks, with ?fuzzy and ?source=mb|bandcamp|all) — 15 catalogued errors — each carrying hint and doc_url; plus param when the code declares a source parameter and next when the handler can build a corrected call. error is the only guaranteed field. Two keyless surfaces are available without an API key: the preview endpoint (GET /api/v2/preview/artist?q=, 100 req/hr/IP, fails closed on 503) and the OpenAPI spec (GET /api/v2/openapi.json). Unresolved lookups return HTTP 200 with present:false — never 404. v1 is frozen (deprecation shim). Calls are billed on successful 2xx only; errors and 429s are never billed.