Carbium Data Errors and Limits
Handle Carbium Data token search misses, unknown mints, invalid batch requests, cached logo fallbacks, route limits, and HTTP 429 responses without breaking a wallet, bot, dashboard, or token selector.
Carbium Data Errors and Limits
Carbium Data is designed to be easy to call from a wallet, dashboard, agent, or backend job. The part that matters in production is how your app behaves when token metadata is incomplete, a mint is unknown, a logo is missing, or the public route limits push back.
This page is the error-handling companion to Token API Calls. Use it when you are wiring the public Token Index API at https://tokens.carbium.io into a real product.
Fast decision table
| Situation | What it usually means | Good client behavior |
|---|---|---|
| GET /tokens returns an empty items array | No strong match for the query | Show no-result UI; do not treat it as an outage |
| GET /tokens/:mint returns a sparse record | The mint is known but metadata is incomplete | Render fallback labels and nullable fields safely |
| POST /tokens/batch returns unknown entries | Some requested mints were not resolved | Show neutral rows, cache briefly, and retry later only if useful |
| Batch request returns {"error":"mints_required"} | The request body did not include usable mints | Fix the caller; do not retry the same request |
| Logo endpoint returns a fallback image | Logo metadata is missing, broken, or not checked yet | Use the image, but keep your own local fallback too |
| Response status is 429 | The route or global public limit was hit | Stop immediate retries, back off, and batch/cache more aggressively |
The common pattern: missing token metadata is a normal data-quality state. A rate limit is a traffic-shaping state. Neither should crash the app.
Empty search is not an error
Token search is user-input driven. A user can type a ticker that does not exist, paste a partial mint, or search for a spam token that is not indexed.
type TokenSearchResponse = {
items?: TokenRow[];
page?: number;
limit?: number;
total?: number | null;
};
async function searchTokens(query: string) {
const url = new URL("/tokens", "https://tokens.carbium.io");
url.searchParams.set("q", query);
url.searchParams.set("limit", "20");
const response = await fetch(url);
if (response.status === 429) {
throw new Error("rate_limited");
}
if (!response.ok) {
throw new Error("token_search_http_" + response.status);
}
const data = (await response.json()) as TokenSearchResponse;
return data.items ?? [];
}
For selectors, treat an empty array as a UI result. Show a no-match row, let the user paste an exact mint, and avoid repeating the same query on every render.
Sparse token records are normal
Solana token metadata is uneven. A token can be visible by mint before every enrichment field is present. Your display code should tolerate:
- empty or missing symbol
- empty or missing name
- logo_url: null
- logo_status values such as missing, broken, or unchecked
- nullable authority, Metaplex, description, external URL, extension, and tag fields
Use the mint as the durable identifier. Treat symbol and name as display hints.
function tokenLabel(token: TokenRow) {
if (token.symbol && token.name) return token.symbol + " - " + token.name;
if (token.symbol) return token.symbol;
if (token.name) return token.name;
return token.mint.slice(0, 4) + "..." + token.mint.slice(-4);
}
Do not block a route preview, portfolio row, or watchlist item just because optional display metadata is missing.
Batch errors usually point to caller shape
Use POST /tokens/batch when your app already has multiple mint addresses.
curl -X POST 'https://tokens.carbium.io/tokens/batch' \
-H 'content-type: application/json' \
-d '{
"mints": [
"So11111111111111111111111111111111111111112",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
]
}'
If the API returns {"error":"mints_required"}, fix the request body. The caller sent an empty or unusable mints payload, so retrying the same request only burns route budget.
If the response includes unknown, keep the successful records and handle the missing ones separately:
async function hydrateTokens(mints: string[]) {
const response = await fetch("https://tokens.carbium.io/tokens/batch", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ mints: [...new Set(mints)].slice(0, 500) }),
});
if (response.status === 429) throw new Error("rate_limited");
if (!response.ok) throw new Error("batch_lookup_http_" + response.status);
const data = await response.json();
return {
byMint: new Map((data.items ?? []).map((token: TokenRow) => [token.mint, token])),
unknown: data.unknown ?? [],
};
}
Do not turn one unresolved mint into a failed table. Render known rows, mark unknown rows clearly, and retry unknowns later only when the mint may be newly issued.
Logo fallback behavior
Use GET /img/:mint when you want a Carbium-hosted token-logo URL:
<img
src="https://tokens.carbium.io/img/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
alt="USDC logo"
width="32"
height="32"
/>
The image route can return a cached token logo or a fallback image for missing/unknown logo state. Keep a local fallback in your own UI anyway, because image requests can still fail at the browser, network, or CDN layer.
Good defaults:
- set fixed image dimensions so rows do not jump
- use loading="lazy" in long lists
- avoid cache-busting query strings
- keep a neutral app-side fallback icon
- do not treat a missing logo as a token-safety verdict
429 handling
The public Token Index API is free and route-limited. Current public limits are documented in Data Rate Limits.
When you receive 429 Too Many Requests:
- stop immediate retries
- respect Retry-After if the response includes it
- add exponential backoff for backend jobs
- debounce browser search
- batch mint hydration instead of looping single-mint lookups
- cache token records that your app reads repeatedly
async function fetchWithBackoff(url: string, init?: RequestInit) {
const response = await fetch(url, init);
if (response.status !== 429) return response;
const retryAfter = Number(response.headers.get("retry-after") ?? "0");
const waitMs = retryAfter > 0 ? retryAfter * 1000 : 1500;
await new Promise((resolve) => setTimeout(resolve, waitMs));
return fetch(url, init);
}
Use this pattern sparingly. If a flow regularly hits 429, the fix is usually better caching, batching, or a higher-throughput Carbium data access path, not more retries.
What to include in support requests
If a Carbium Data issue looks like a service problem, include:
- endpoint and method
- UTC timestamp
- whether the request was browser or backend traffic
- response status and short error body
- example mint or query
- whether the same issue appears on tokens.carbium.io/endpoints
- whether your app recently changed debounce, cache, batch size, or retry behavior
Never include private keys, API keys, wallet seed phrases, cookies, bearer tokens, customer PII, or unpublished customer data in support channels.
Use Carbium Data for token identity and display metadata. Use Swap API quote responses for price, route, output, and transaction decisions. Start from [carbium.io](https://www.carbium.io) when you need the full RPC, Swap API, Data, and DEX platform context.