@stacks/token-metadata-api-client
v2.2.4
Published
Client for the Stacks Token Metadata API
Readme
@stacks/token-metadata-api-client
A fully typed TypeScript client for the Token Metadata API. Built on openapi-fetch, it provides autocompletion for every endpoint path, query parameter, and response type with zero runtime overhead.
Features
- Full TypeScript autocompletion for paths, parameters, and responses
- Supports SIP-010 Fungible Tokens, SIP-009 Non-Fungible Tokens, and SIP-013 Semi-Fungible Tokens
- Works in Node.js and browsers (UMD bundle included)
- Configurable base URL and fetch implementation
Installation
npm install @stacks/token-metadata-api-clientQuick start
import { createClient } from '@stacks/token-metadata-api-client';
const client = createClient();By default the client points to https://api.mainnet.hiro.so. You can override this (or any other
openapi-fetch client option) when
creating the client:
const client = createClient({
baseUrl: 'http://localhost:3000',
});Every method returns { data, error, response } where data is the typed success body and error
is the typed error body. The raw Response object is also available.
API status
Check if the API is ready and which chain tip it has indexed.
const { data, error } = await client.GET('/metadata/v1/');
if (data) {
console.log(data.server_version); // e.g. "token-metadata-api v0.0.1 (master:a1b2c3)"
console.log(data.status); // e.g. "ready"
console.log(data.chain_tip); // { block_height: 150000, index_block_hash: "0x..." } | null
}Fungible Tokens (SIP-010)
List all fungible tokens
Retrieve a paginated list of all indexed fungible tokens. Supports filtering by name, symbol,
or deployer address, and ordering results.
const { data } = await client.GET('/metadata/v1/ft', {
params: {
query: {
limit: 10,
offset: 0,
order_by: 'name',
order: 'asc',
},
},
});
if (data) {
console.log(`Total tokens: ${data.total}`);
for (const token of data.results) {
console.log(`${token.symbol} - ${token.name} (${token.contract_principal})`);
}
}Filter by name or symbol:
const { data } = await client.GET('/metadata/v1/ft', {
params: {
query: { name: 'Bitcoin' },
},
});Filter by deployer address:
const { data } = await client.GET('/metadata/v1/ft', {
params: {
query: { address: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9' },
},
});Return only tokens with valid SIP-016 metadata:
const { data } = await client.GET('/metadata/v1/ft', {
params: {
query: { valid_metadata_only: true },
},
});Get metadata for a specific fungible token
const { data, error } = await client.GET('/metadata/v1/ft/{principal}', {
params: {
path: {
principal: 'SP32XCD69XPS3GKDEXAQ29PJRDSD5AR643GNEEBXZ.fari-token',
},
},
});
if (data) {
console.log(data.name); // "Fari Token"
console.log(data.symbol); // "FARI"
console.log(data.decimals); // 8
console.log(data.total_supply); // "9999980000000"
console.log(data.description);
console.log(data.image_uri);
// SIP-016 metadata (when available)
if (data.metadata) {
console.log(data.metadata.sip); // 16
console.log(data.metadata.attributes); // [{ trait_type, value, display_type? }]
console.log(data.metadata.properties); // { collection: "...", total_supply: "..." }
}
}Request a specific locale (SIP-016 localization):
const { data } = await client.GET('/metadata/v1/ft/{principal}', {
params: {
path: { principal: 'SP32XCD69XPS3GKDEXAQ29PJRDSD5AR643GNEEBXZ.fari-token' },
query: { locale: 'jp' },
},
});Non-Fungible Tokens (SIP-009)
Retrieve metadata for a specific NFT by its contract principal and token ID.
const { data, error } = await client.GET('/metadata/v1/nft/{principal}/{token_id}', {
params: {
path: {
principal: 'SP497E7RX3233ATBS2AB9G4WTHB63X5PBSP5VGAQ.boomboxes-cycle-12',
token_id: 35,
},
},
});
if (data) {
console.log(data.token_uri);
if (data.metadata) {
console.log(data.metadata.name); // "Boombox #35"
console.log(data.metadata.description);
console.log(data.metadata.image); // Original image URI (e.g. ipfs://...)
console.log(data.metadata.cached_image); // Cached HTTP URL ready to display
// Attributes (traits)
for (const attr of data.metadata.attributes ?? []) {
console.log(`${attr.trait_type}: ${attr.value}`);
}
// Localization info
if (data.metadata.localization) {
console.log(data.metadata.localization.default); // "en"
console.log(data.metadata.localization.locales); // ["en", "jp"]
}
}
}Request a localized version:
const { data } = await client.GET('/metadata/v1/nft/{principal}/{token_id}', {
params: {
path: {
principal: 'SP497E7RX3233ATBS2AB9G4WTHB63X5PBSP5VGAQ.boomboxes-cycle-12',
token_id: 35,
},
query: { locale: 'jp' },
},
});Semi-Fungible Tokens (SIP-013)
Retrieve metadata for a specific SFT by its contract principal and token ID. The response also
includes decimals and total_supply for the given token ID.
const { data, error } = await client.GET('/metadata/v1/sft/{principal}/{token_id}', {
params: {
path: {
principal: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.key-alex-autoalex-v1',
token_id: 1,
},
},
});
if (data) {
console.log(data.decimals); // 6
console.log(data.total_supply); // "250"
console.log(data.token_uri);
if (data.metadata) {
console.log(data.metadata.name);
console.log(data.metadata.description);
console.log(data.metadata.cached_image);
}
}Error handling
All endpoints return { data, error }. When a request fails, data will be undefined and
error will contain a typed error body. You can also inspect the raw response for the HTTP status
code.
const { data, error, response } = await client.GET('/metadata/v1/ft/{principal}', {
params: {
path: { principal: 'SP000000000000000000000.nonexistent' },
},
});
if (error) {
// `error` is typed — possible values depend on the status code
console.error(`HTTP ${response.status}: ${error.error}`);
// 404 → "Token not found" | "Contract not found"
// 422 → "Token metadata fetch in progress" | "Locale not found" | "Token error"
}You can also handle specific status codes:
if (response.status === 404) {
console.log('Token does not exist');
} else if (response.status === 422) {
console.log('Metadata is still being indexed, try again later');
}Using a custom fetch implementation
You can pass any custom fetch function (e.g. for adding auth headers, logging, or using a
different HTTP library):
const client = createClient({
baseUrl: 'https://api.mainnet.hiro.so',
fetch: async (input, init) => {
console.log(`→ ${init?.method ?? 'GET'} ${input}`);
const response = await fetch(input, init);
console.log(`← ${response.status}`);
return response;
},
});Browser usage
The package ships a UMD bundle at lib/index.umd.js that can be loaded via a <script> tag or
any UMD-compatible loader:
<script src="https://unpkg.com/@stacks/token-metadata-api-client/lib/index.umd.js"></script>
<script>
const client = TokenMetadataApiClient.createClient();
client.GET('/metadata/v1/ft/{principal}', {
params: { path: { principal: 'SP32XCD69XPS3GKDEXAQ29PJRDSD5AR643GNEEBXZ.fari-token' } },
}).then(({ data }) => console.log(data));
</script>TypeScript
All request parameters and response types are inferred automatically from the OpenAPI schema. No
manual type imports are needed — just use the data and error objects returned by each call and
your editor will provide full autocompletion.
If you need to reference the types directly, you can import the generated schema:
import type { operations } from '@stacks/token-metadata-api-client/lib/generated/schema';
type FtMetadataResponse =
operations['getFtMetadata']['responses']['200']['content']['application/json'];