Typed ESM client, Zod schemas, and response types for the Miso read API. Every successful response is validated before it reaches application code, and u64/u128 values stay decimal strings so JavaScript never silently loses precision.
npm install @misofm/api-clientThe package ships JavaScript and declarations and works directly in modern Node,
browsers, Bun, and Workers. It requires a global fetch, or an injected compatible
implementation.
import { createMisoApiClient } from "@misofm/api-client";
const api = createMisoApiClient({
baseUrl: "https://api.testnet.miso.fm",
});
const pressing = await api.getPressing(pressingId);
if (pressing) {
console.log(pressing.releaseId, pressing.edition, pressing.supply);
}Reads for resources that may legitimately be absent return null on 404.
Collection and required wallet reads return their body or throw.
Singular resources use get…; collections use list…:
| Area | Canonical methods |
|---|---|
| Catalog | getPressing, getPressingListing, getRelease, getRecordAlbum |
| Artists | getArtist, listArtists |
| Wallet | listWalletRecords, listWalletParties, listWalletPendingMemberships, listWalletWorks, listWalletWorkDetails, getWalletBalance, getWalletOwnership, getWalletPartyOwnership, getWalletRecordOwnership |
| Works and receipts | getWork, getPurchaseReceipt |
Older names (getListing, getArtists, getWalletRecords,
getWalletParties, getPendingMemberships, getWalletWorks, getBalance,
ownsParty, and ownsRecord) remain backward-compatible aliases and are marked
deprecated for editor-assisted migration. getReceipt(pressingId, txDigest) is
the deprecated compatibility receipt lookup; use
getPurchaseReceipt(txDigest, recordId) to select one exact Record. The legacy
method throws HTTP 409 when one transaction bought multiple Records from the
same Pressing.
getWalletWorkDetails is the deprecated compatibility alias for
listWalletWorkDetails. Both make one private request to return all three work
kinds in the typed WorkDetail[] shape.
Relationship expansions live in the method options:
const release = await api.getRelease(releaseId, {
include: ["trackCredits"],
});
const album = await api.getRecordAlbum(recordId, {
include: ["release", "trackCredits"],
});Every method accepts per-call request options. Methods with relationship options
use the same options object; getWalletBalance keeps its existing optional coin
type argument and accepts request options third.
const controller = new AbortController();
const profile = await api.getArtist(partyId, {
include: ["roles"],
signal: controller.signal,
headers: { "X-Request-ID": crypto.randomUUID() },
});
const balance = await api.getWalletBalance(address, coinType, {
signal: AbortSignal.timeout(5_000),
});API failures throw MisoApiError. Contract mismatches throw
MisoApiContractError, which generally means the deployed API and client package
need to be brought to compatible versions.
import { MisoApiContractError, MisoApiError } from "@misofm/api-client";
try {
await api.getWalletBalance(address);
} catch (error) {
if (error instanceof MisoApiError) {
console.error(error.status, error.code, error.message);
console.error("request", error.requestId);
if (error.retryAfter !== undefined) {
console.error(`retry after ${error.retryAfter} seconds`);
}
} else if (error instanceof MisoApiContractError) {
console.error(error.issues);
} else {
throw error;
}
}There is no global purge layer. Applications that have just completed a write can bump one long-lived client so its next mutable public read bypasses a pre-write edge entry:
import { cacheBuster, createMisoApiClient } from "@misofm/api-client";
let version: string | undefined;
const api = createMisoApiClient({
baseUrl: "https://api.testnet.miso.fm",
version: () => version,
});
await submitWrite();
version = cacheBuster();
const updated = await api.getArtist(partyId);The v parameter is intentionally resource-scoped. It is added to mutable public
reads such as artists, pressings, listings, releases, and expanded record albums.
It is not added to private wallet reads, immutable purchase receipts, or the bare
record-to-release relation, preventing useless cache namespaces.
Use cacheBuster() rather than inventing a version string. The gateway accepts
its timestamp-plus-random-nonce token through the longest public cache window,
then safely returns to the unversioned entry. Generate it once per completed
write and reuse it for that post-write read burst. Arbitrary strings used by
0.5.1 clients remain compatible through one shared no-store namespace, but do
not create attacker-controlled cache keys.
For TanStack Query, use the shared cache helpers:
import {
READ_CACHE_CLASS,
queryPolicy,
recordAlbumQueryPolicy,
workCacheClass,
} from "@misofm/api-client/cache";
queryPolicy(READ_CACHE_CLASS.getPressing);
queryPolicy(workCacheClass(release?.state));
recordAlbumQueryPolicy(album, { include: ["release"] });READ_CACHE_CLASS.getRecordAlbum is retained for compatibility and describes
only an unexpanded response. Expanded release metadata is response-dependent, so
use recordAlbumQueryPolicy for that case.
All documented subpaths are stable package exports:
import { schemas } from "@misofm/api-client";
import { pressingViewSchema } from "@misofm/api-client/schemas";
import { browserCacheControl, cacheControl } from "@misofm/api-client/cache";
import type { PressingView } from "@misofm/api-client/types";
const parsed: PressingView = pressingViewSchema.parse(payload);
schemas.pressingViewSchema.parse(parsed);
cacheControl("sale");
browserCacheControl("sale"); // pair with the CDN-only policy in edge runtimesThe schemas are the source of truth shared by the read service, generated API
description, and consumers. The package is maintained in the
misofm/api-client repository and
licensed under Apache-2.0.