Headless API v1 reference.
Two unauthenticated GET routes: one public Artist and one public Catalog.
These routes are deployed, but they are not currently available for supported production use. Requests may intentionally fail closed with 503 ResourceUnavailable. No uptime, availability, or production-use commitment is offered.
Endpoints
GET /api/v1/artists/{handle}
GET /api/v1/artists/{handle}/catalogBefore you call the API
| Concern | v1 behavior |
|---|---|
| Base URL | The deployed /api/v1 route family is unsupported for production use and may fail closed with 503. Use exact mocks or a local Casset origin for reliable evaluation. |
| Method | GET only. The SDK exposes no write operation. |
| Authentication | None on /api/v1. A Bearer credential is required only on /api/sandbox/v1 counterparts. |
| Handle | 3–30 lowercase letters, digits, underscores, or hyphens: ^[a-z0-9_-]{3,30}$. |
| Content type | application/json |
| Success cache | public, max-age=0, must-revalidate |
| Error cache | private, no-store |
| Rate limit | 60 requests per IP per 60 seconds. The check runs before handle validation, so 429—or a production limiter 503—can preempt 400. 429 includes Retry-After. |
| Correlation | Send x-request-id. Casset mirrors a valid value or generates one. |
Get public artist
GET /api/v1/artists/{handle}Returns bounded public identity for one published, non-private Profile World. It never returns an internal artist or user ID.
curl
curl --include \
--header "Accept: application/json" \
--header "X-Request-Id: reference-artist-001" \
http://127.0.0.1:3000/api/v1/artists/connorTypeScript
const artist = await casset.getArtist("connor", {
requestId: "reference-artist-001",
})
console.log(artist.artist.displayName)
console.log(artist.links.catalog)200 response
HTTP/1.1 200 OK
Cache-Control: public, max-age=0, must-revalidate
Content-Type: application/json
X-Request-Id: reference-artist-001
{
"schema": "casset.public-artist.v1",
"artist": {
"handle": "connor",
"displayName": "Connor James",
"bio": "Multi-instrumentalist, singer/songwriter, and producer.",
"verified": true
},
"links": {
"profile": "http://127.0.0.1:3000/connor",
"catalog": "http://127.0.0.1:3000/api/v1/artists/connor/catalog"
}
}Empty response
Fields
| Field | Type | Meaning | Limit |
|---|---|---|---|
schema | literal | casset.public-artist.v1 | Exact |
artist.handle | string | Canonical public handle | 3–30 |
artist.displayName | string | Public display name | 160 |
artist.bio | string | null | Public biography when present | 2,000 |
artist.verified | boolean | Public verified state | Exact |
links.profile | HTTPS, loopback HTTP, or root-relative link | Canonical Profile World | 2,048 |
links.catalog | HTTPS, loopback HTTP, or root-relative link | Canonical v1 Catalog read | 2,048 |
Get public Catalog
GET /api/v1/artists/{handle}/catalogReturns one bounded page of active public Track metadata. It contains no cursor, filter, audio, artwork, lyric, storage, or playback field.
curl
curl --include \
--header "Accept: application/json" \
--header "X-Request-Id: reference-catalog-001" \
http://127.0.0.1:3000/api/v1/artists/connor/catalogTypeScript
const catalog = await casset.getCatalog("connor", {
requestId: "reference-catalog-001",
})
for (const track of catalog.tracks) {
console.log(track.title, track.kind, track.durationSec)
}200 response
HTTP/1.1 200 OK
Cache-Control: public, max-age=0, must-revalidate
Content-Type: application/json
X-Request-Id: reference-catalog-001
{
"schema": "casset.public-catalog.v1",
"artist": {
"handle": "connor",
"displayName": "Connor James"
},
"tracks": [
{
"title": "Baby Blue",
"kind": "ORIGINAL",
"durationSec": 213
}
],
"page": { "count": 1 }
}Empty 200 response
HTTP/1.1 200 OK
Cache-Control: public, max-age=0, must-revalidate
Content-Type: application/json
X-Request-Id: reference-catalog-empty-001
{
"schema": "casset.public-catalog.v1",
"artist": {
"handle": "connor",
"displayName": "Connor James"
},
"tracks": [],
"page": { "count": 0 }
}Fields
| Field | Type | Meaning | Limit |
|---|---|---|---|
schema | literal | casset.public-catalog.v1 | Exact |
artist.handle | string | Canonical public handle | 3–30 |
artist.displayName | string | Public display name | 160 |
tracks | array | Active public Track metadata | 0–500 |
tracks[].title | string | Public Track title | 240 |
tracks[].kind | ORIGINAL | GENERATION | Public provenance kind | Exact |
tracks[].durationSec | number | null | Duration in whole or fractional seconds when valid | 0–86,400 |
page.count | number | Must equal tracks.length | 0–500 |
Failure matrix for both endpoints
The following responses apply to both public paths. Their sandbox read counterparts add credential failure and return the same error schema; ownership failures belong to the separate sandbox console.
| Status | Public /api/v1 | Local /api/sandbox/v1 | Retry |
|---|---|---|---|
| 400 | Malformed handle | Malformed handle or request | No |
| 401 | Not emitted; public v1 has no credential | Missing, invalid, or revoked sandbox credential | No |
| 403 | Not emitted by current public reads | Not emitted by the two sandbox reads; authenticated console ownership/session actions can emit it | No |
| 404 | Unknown, private, or unpublished artist | Unavailable sandbox or missing public artist | No |
| 429 | Per-IP public rate limit | Not emitted by the two sandbox reads | Yes when the envelope says retryable |
| 502 | Not emitted | Sandbox integration failure | Follow envelope.retryable |
| 503 | Public data could not be prepared, or the production rate limiter is unavailable | Not emitted by the two sandbox reads | Yes when the envelope says retryable |
| 5xx other | Not currently emitted by these public routes | Not currently emitted by these sandbox reads | SDK retries only 500–599 when retryable is true or no valid envelope exists |
400 — invalid handle
HTTP/1.1 400 Bad Request
Cache-Control: private, no-store
Content-Type: application/json
X-Request-Id: reference-invalid-001
{
"schema": "casset.error.v1",
"error": {
"code": "INVALID_REQUEST",
"message": "A valid artist handle is required.",
"requestId": "reference-invalid-001",
"retryable": false
}
}404 — unknown, private, or unpublished
HTTP/1.1 404 Not Found
Cache-Control: private, no-store
Content-Type: application/json
X-Request-Id: reference-not-found-001
{
"schema": "casset.error.v1",
"error": {
"code": "RESOURCE_UNAVAILABLE",
"message": "Artist not found.",
"requestId": "reference-not-found-001",
"retryable": false
}
}401 — local sandbox only
HTTP/1.1 401 Unauthorized
Cache-Control: no-store, private
Content-Type: application/json
X-Request-Id: sandbox-auth-001
{
"schema": "casset.error.v1",
"error": {
"code": "INVALID_CREDENTIAL",
"message": "A valid Bearer sandbox credential is required.",
"requestId": "sandbox-auth-001",
"retryable": false
}
}403 — local sandbox console permission failure only
HTTP/1.1 403 Forbidden
Cache-Control: no-store, private
Content-Type: application/json
X-Request-Id: sandbox-permission-001
{
"schema": "casset.error.v1",
"error": {
"code": "PERMISSION_DENIED",
"message": "The app belongs to a different sandbox developer.",
"requestId": "sandbox-permission-001",
"retryable": false
}
}429 — public rate limit
HTTP/1.1 429 Too Many Requests
Cache-Control: private, no-store
Content-Type: application/json
Retry-After: 17
X-Request-Id: reference-rate-001
{
"schema": "casset.error.v1",
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Try again after the retry window.",
"requestId": "reference-rate-001",
"retryable": true
}
}503 — public service failure
HTTP/1.1 503 Service Unavailable
Cache-Control: private, no-store
Content-Type: application/json
X-Request-Id: reference-service-001
{
"schema": "casset.error.v1",
"error": {
"code": "INTEGRATION_FAILURE",
"message": "The public catalog service is temporarily unavailable.",
"requestId": "reference-service-001",
"retryable": true
}
}Public media and Hooks · local extension
Enable CASSET_PUBLIC_MEDIA_ENABLED=true only in your reviewed local server process. This independent, default-off flag does not enable audio playback. The extension reads existing public assets and selected Hook visuals; it creates no upload, storage, rendering or SDK system. The full contract is in docs/api/public-media.md.
GET /api/v1/artists/{handle}/media?source=profile&limit=25
GET /api/v1/artists/{handle}/media?source=bonus&type=IMAGE
GET /api/v1/artists/{handle}/track/media?title=...&kind=...&order=...
GET /api/v1/artists/{handle}/track/hooks?title=...&kind=...&order=...
GET /api/v1/artists/{handle}/hook?title=...&kind=...&order=...Use exact public title, provenance kind and stored sequence order from the existing playback selector. Artist and Track IDs remain server-side. Casset stores one canonical Hook window per Track; follow the Hook’s returnedhref for detail. Renames/reorders invalidate old links.
| Concern | Behavior |
|---|---|
| Schemas | casset.public-media.v1, casset.public-hooks.v1, casset.public-hook.v1; nested casset.public-visualizer.v1 |
| Media | type, category, url, thumbnailUrl, title, width, height, durationSec, createdAt; unknown values are null, IDs and raw metadata are omitted |
| Sources | profile: standalone same-owner COVER/BTS; bonus: existing ArtistMedia IMAGE/VIDEO, category BONUS. No invented Track relationship or bonus-audio rights. |
| Filters | source=profile|bonus, type=IMAGE|VIDEO; category=COVER|BTS only for profile |
| Pagination | offset=0 and limit=25 by default, limit 1–50; follow page.nextOffset through candidate pages until null. Track lists also accept offset/limit. |
| Hooks | href, track selector, startSec/endSec on original audio clock, audioPreviewPath, media, videoWindow and visualizer. Preview path is null unless existing playback separately allows it. |
| Visualizer | Existing selected shader/assignment metadata only: engine, source, mood, presetId, lyricSync and bounded parameters. No raw config, nested media URL or new renderer. |
| Access | Published non-private artist, existing anonymous access and exact Track rights, paid, token, Vault, prerelease and ownership checks; selected primary visuals/READY renders only |
| Delivery | Existing public Blob CDN URLs, private/no-store discovery responses. No video proxy; clients handle unavailable CDN objects. |
| Rate limit | Shared Headless 60/IP/minute; distributed limiter required in production independently of playback WAF attestation |
| Errors | casset.error.v1: 400 invalid query, 403 Origin denied, 404 resource unavailable, 429 rate limited, 503 flag/provider unavailable; Retry-After for retryable responses |
const response = await fetch(cassetOrigin + "/api/v1/artists/connor/media?category=BTS", {
credentials: "omit", cache: "no-store"
});
if (!response.ok) throw new Error("Casset media: " + response.status);
const page = await response.json();
// Use media.url directly in <img> / <video>; thumbnailUrl is the saved poster.
// Continue with page.nextOffset until null.Send an approved Origin (browsers supply it automatically), without cookies or credentials. Local inspection found no published Starlight visual: Track media correctly returns an empty list and visualizer is null. No example asset is presented as production data. This extension has not been deployed by this work.
Privacy behavior
- Private, unpublished, and unknown artists are indistinguishable at the HTTP boundary.
- Catalog reads select only active Track title, kind, and duration.
- Strict SDK parsers reject unknown response fields instead of silently accepting data expansion.
- Error responses use
private, no-store; sandbox responses useno-store, private.
Read Errors for the full typed-error matrix and Retries before implementing custom transport behavior.