Skip to Content

Error Handling

Every error response from the Burn & Claim API follows a consistent JSON format. This page covers the error shape, common error codes, and strategies for handling them.

Error response shape

interface ApiError { statusCode: number message: string error?: string // HTTP status text }

Example:

{ "statusCode": 400, "message": "items must contain at least 1 element", "error": "Bad Request" }

HTTP status codes

StatusMeaningRetry?
400Bad Request. The request body or query parameters are invalid.No. Fix the request.
401Unauthorized. API key is invalid/revoked, or session token is expired/malformed.Recreate session; do not retry with same credentials.
403Forbidden. The session token lacks the required scope for this endpoint.No. Create a session with the correct scopes.
429Too Many Requests. Rate limit or monthly quota exceeded.Yes, after Retry-After seconds.
500Internal Server Error. An unexpected server-side failure.Yes, with exponential backoff.
502Bad Gateway. Upstream service unavailable.Yes, with exponential backoff.
503Service Unavailable. The API is temporarily down.Yes, with exponential backoff.

Common error messages

Authentication

MessageCauseFix
Invalid API keyThe X-Api-Key value is not recognizedCheck the key in your dashboard
API key revokedThe key was deleted from the dashboardCreate a new key
Session expiredThe session token’s TTL has elapsedCreate a new session from your backend
Invalid session tokenThe token is malformed or was tampered withCreate a new session
Wallet mismatchThe session token is bound to a different walletCreate a session for the correct wallet

Validation

MessageCause
items must contain at least 1 elementThe items array in buildTransactions is empty
Invalid wallet addressThe :address path parameter is not a valid Solana public key
ttlSeconds must not be greater than 3600Session TTL exceeds the 1-hour maximum
Unknown action for item typeThe requested action is not valid for the item’s type

Submission

CodeMessageFix
TICKET_EXPIREDThe submission ticket has expiredCall buildTransactions again to get fresh tickets
HASH_MISMATCHThe signed tx message doesn’t match the built txSign the exact transaction returned by buildTransactions without modifying it
RPC_ERRORSolana RPC rejected the transactionCheck if the blockhash expired or if the wallet has enough SOL for fees

Retry strategy

async function callWithRetry<T>( fn: () => Promise<T>, options: { maxRetries?: number; onRetry?: (attempt: number, delay: number) => void } = {} ): Promise<T> { const { maxRetries = 3, onRetry } = options for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn() } catch (err: unknown) { const response = extractResponse(err) if (!response) throw err const status = response.status // Never retry client errors (except 429) if (status >= 400 && status < 500 && status !== 429) { throw err } if (attempt === maxRetries) throw err let delay: number if (status === 429) { const retryAfter = response.headers.get('Retry-After') delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000 } else { // Exponential backoff for 5xx: 1s, 2s, 4s... delay = Math.min(1000 * 2 ** attempt, 15000) } // Add jitter delay += Math.random() * 500 onRetry?.(attempt + 1, delay) await new Promise((r) => setTimeout(r, delay)) } } throw new Error('Unreachable') } function extractResponse(err: unknown): Response | null { if (err && typeof err === 'object' && 'response' in err) { return (err as { response: Response }).response } return null }

Full error handling example

import { BurnerClient } from '@burnandclaim/sdk' async function burnWalletAssets( client: BurnerClient, walletAddress: string ) { // Step 1: Get assets (retryable) const { items } = await callWithRetry(() => client.getWalletAssets(walletAddress) ) if (items.length === 0) { console.log('No burnable items found') return } // Step 2: Build transactions (retryable) const { transactions } = await callWithRetry(() => client.buildTransactions(walletAddress, { items: items.map((item) => ({ type: item.type, address: item.address, action: item.actions[0], })), }) ) // Step 3: Sign (wallet adapter -- not retryable, user interaction) const signed = await signTransactions(transactions) // Step 4: Submit (retryable, idempotent via ticket dedup) const { results } = await callWithRetry(() => client.submitTransactions(walletAddress, { signed }) ) // Step 5: Report results for (const result of results) { switch (result.status) { case 'submitted': console.log(`Burned: ${result.signature}`) break case 'duplicate': console.log(`Already submitted: ${result.signature}`) break case 'failed': console.error(`Failed: ${result.error?.code} -- ${result.error?.message}`) break } } }
Last updated on