Failures are typed, bounded, and safe to correlate.
Every valid API failure uses casset.error.v1. The SDK converts that contract into a CassetApiError subclass with a sanitized message, HTTP status, request ID, retryability, and bounded retry delay.
Handle behavior from code and retryable. Show users a calm product message. Log the request ID—not the credential, Authorization header, response body, or private application state.
Error envelope
HTTP/1.1 429 Too Many Requests
Cache-Control: private, no-store
Content-Type: application/json
Retry-After: 17
X-Request-Id: errors-rate-001
{
"schema": "casset.error.v1",
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Try again after the retry window.",
"requestId": "errors-rate-001",
"retryable": true
}
}| Field | Meaning | Client behavior |
|---|---|---|
schema | Exact contract name: casset.error.v1 | Unknown schemas fail strict parsing. |
error.code | Stable machine-readable error category | Mapped to a typed CassetApiError subclass. |
error.message | Safe API explanation for this response | The SDK does not expose it as the thrown message; it uses a fixed sanitized message. |
error.requestId | Correlation ID for the request | Must match the x-request-id response header exactly. |
error.retryable | Whether this specific failure may be retried | A transient HTTP status is retried only when this is true, or when no valid envelope exists. |
Complete error reference
| Code / SDK class | HTTP | Meaning and likely causes | Retry? | Developer action | User-facing recommendation |
|---|---|---|---|---|---|
INVALID_REQUESTInvalidRequest | 400; SDK also maps 422. Sandbox conflict uses 409. | The handle, client configuration, request ID, body, or console action does not match the contract. | No | Fix the input. Validate handles before accepting them and treat configuration errors as build-time defects. | “This request could not be completed. Check the artist address and try again.” |
INVALID_CREDENTIALInvalidCredential | 401 | The local sandbox Bearer credential is missing, malformed, unknown, or revoked. Public v1 does not require a credential. | No | Remove credentials from public v1 calls. For local sandbox calls, issue a new one-time credential and keep it server-side. | “This local connection is no longer authorized.” |
PERMISSION_DENIEDPermissionDenied | 403 | The authenticated sandbox actor does not own or cannot access the requested sandbox resource. It is also an exact mock state. | No | Check local App ownership and the action being rehearsed. Do not retry with the same authorization state. | “This integration does not have access to that resource.” |
ENTITLEMENT_REQUIREDEntitlementRequired | 402 | The SDK and exact mock vocabulary can represent an entitlement gate. No current public v1 or local sandbox Artist/Catalog route emits it. | No | Treat it as an unavailable gated resource. Do not imply that a production entitlement purchase flow exists. | “This resource is not available for this integration.” |
RESOURCE_UNAVAILABLEResourceUnavailable | 404; SDK fallback also maps 503 and 504 | The artist is unknown, private, or unpublished; the local sandbox is disabled; or a service dependency is unavailable. | 404: no. 503/504: yes by SDK fallback. Always follow the envelope. | For 404, stop and render no data. For a retryable service failure, allow the SDK policy to finish before showing a fallback. | 404: “Artist unavailable.” Temporary: “Casset is temporarily unavailable. Try again shortly.” |
RATE_LIMITEDRateLimited | 429 | Public v1 exceeded 60 requests per IP in a 60-second window, or an exact mock is exercising rate-limit behavior. | Yes | Use the SDK retry policy, respect the bounded Retry-After value, reduce duplicate reads, and avoid immediate manual loops. | “Too many requests. Please wait a moment and try again.” |
INTEGRATION_FAILUREIntegrationFailure | 502 sandbox; 503 public; SDK fallback for other failures | Transport, timeout, service, malformed JSON, schema mismatch, or request-ID protocol failure. | Depends. Transport and service failures default to yes; contract and request-ID failures are no. | Inspect status, retryable, and requestId. Fix non-retryable contract failures before retrying. | Retryable: “Casset is temporarily unavailable.” Non-retryable: “This integration could not read the response.” |
Parser error
CassetContractError is not an HTTP error code. Direct calls toparsePublicArtistResponse, parsePublicCatalogResponse, or parseHeadlessApiErrorEnvelope throw it when a value violates the exact schema. Its issues array contains only field paths and bounded validation messages. When the HTTP client encounters the same problem, it throws a non-retryable IntegrationFailure instead.
Handle errors in TypeScript
import {
CassetApiError,
InvalidCredential,
InvalidRequest,
RateLimited,
ResourceUnavailable,
} from "@casset/apps"
try {
const catalog = await casset.getCatalog("connor")
return catalog.tracks
} catch (error) {
if (error instanceof InvalidRequest) {
throw new Error("Invalid integration input")
}
if (error instanceof InvalidCredential) {
return { state: "reauthorize-local-sandbox" }
}
if (error instanceof ResourceUnavailable && error.status === 404) {
return { state: "artist-unavailable" }
}
if (error instanceof RateLimited) {
return { state: "try-later", requestId: error.requestId }
}
if (error instanceof CassetApiError) {
console.info("Casset request failed", {
code: error.code,
status: error.status,
retryable: error.retryable,
requestId: error.requestId,
})
return { state: "temporarily-unavailable" }
}
throw error
}Request IDs and correlation
Casset uses one correlation value: x-request-id. There is no separate public correlation-ID header. If you send a valid ID, the server mirrors it in the response header and error body. Otherwise, the server generates a req_* value. The SDK generates one sdk-*value and reuses it across all retry attempts.
- Accepted length: 8–120 characters.
- Accepted characters: letters, digits, period, underscore, colon, and hyphen.
- Secret-like prefixes, JWT-shaped values, control characters, and whitespace are rejected.
- A response header/body mismatch or a response ID that differs from the request becomes a non-retryable integration failure.
Debugging workflow
- Capture
code,status,retryable, andrequestId. - Confirm whether the request targeted public v1, the local sandbox, or an exact mock.
- For 400/401/403/404, fix state or render the appropriate terminal fallback; do not loop.
- For 429 or retryable 5xx, let the SDK finish its bounded attempts before adding application-level recovery.
- For non-retryable
IntegrationFailure, compare the exact response schema and request-ID header. Treat drift as an integration defect. - Share the request ID and sanitized metadata with the Casset maintainer. Never share the Bearer value or webhook signing secret.
See Retries for exact attempt and delay behavior, or Observability for the full safe-logging boundary.