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

SituationWhat it usually meansGood client behavior
GET /tokens returns an empty items arrayNo strong match for the queryShow no-result UI; do not treat it as an outage
GET /tokens/:mint returns a sparse recordThe mint is known but metadata is incompleteRender fallback labels and nullable fields safely
POST /tokens/batch returns unknown entriesSome requested mints were not resolvedShow neutral rows, cache briefly, and retry later only if useful
Batch request returns {"error":"mints_required"}The request body did not include usable mintsFix the caller; do not retry the same request
Logo endpoint returns a fallback imageLogo metadata is missing, broken, or not checked yetUse the image, but keep your own local fallback too
Response status is 429The route or global public limit was hitStop 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:

  1. stop immediate retries
  2. respect Retry-After if the response includes it
  3. add exponential backoff for backend jobs
  4. debounce browser search
  5. batch mint hydration instead of looping single-mint lookups
  6. 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.