Andamio Logo
Platform Guides/Developers

Error Handling

Recovery patterns for failed and expired Andamio transactions

Error Handling

How to handle errors, recover from failed transactions, and build resilient integrations.

State Machine States

Transactions can reach three terminal states:

StateMeaningRecovery
updatedSuccess — DB syncedNone needed
failedDB sync failed after retriesUsually auto-heals; see below
expiredNot confirmed on-chain in timeRebuild and resubmit

Common API Errors

CodeMeaningResolution
400Bad request / validation errorCheck request body against API Reference
401Invalid or expired JWTRe-authenticate user via wallet signature
403Insufficient permissionsUser lacks required role (e.g., not a teacher)
409Duplicate registrationTransaction already registered — safe to ignore
422Unprocessable entitySemantic error (e.g., module doesn't exist)
500Internal server errorRetry with exponential backoff
502Upstream service errorCardano node issue — retry after delay

Recovery Patterns

Expired Transaction

A transaction expires when it's not confirmed on-chain within the timeout window (typically 2 hours).

Before rebuilding, check if it actually confirmed:

async function handleExpired(txHash: string) {
  // Check Andamioscan to see if TX actually confirmed
  const scanRes = await fetch(
    `https://preprod.andamioscan.io/api/tx/${txHash}`
  );

  if (scanRes.ok) {
    // TX is on-chain! State machine missed it.
    // The self-healing reconciler will fix this on next read.
    console.log("TX confirmed but not detected — will auto-heal");
    return { action: "wait", reason: "auto-heal" };
  }

  // TX truly didn't confirm — safe to rebuild
  return { action: "rebuild", reason: "not-on-chain" };
}

Why this matters: If you rebuild without checking, you might submit a duplicate action (e.g., double-minting a credential).

Failed DB Sync

A failed state means the transaction confirmed on-chain but the database update failed after multiple retries.

Usually no action needed:

The Gateway has a self-healing reconciler that detects and repairs inconsistencies when related data is accessed. Your next API call that reads this data will trigger the repair.

If time-sensitive:

// Force a read to trigger reconciliation
await fetch(`${API}/course/${courseId}/commitments`, { headers });
// The reconciler will notice the on-chain state and sync it

Network Errors

For transient network issues, implement retry with exponential backoff:

async function fetchWithRetry(
  url: string,
  options: RequestInit,
  maxRetries = 3
) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const res = await fetch(url, options);
      if (res.ok || res.status < 500) return res;
    } catch (e) {
      if (i === maxRetries - 1) throw e;
    }

    // Exponential backoff: 1s, 2s, 4s
    await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
  }
}

Wallet Errors

Common wallet-related issues:

ErrorCauseResolution
User rejectedUser cancelled signingPrompt to try again
Insufficient fundsNot enough ADAShow required amount
TX too largeToo many inputs/outputsContact support
Collateral not setdApp collateral not configuredPrompt user to set collateral in wallet
try {
  const signedTx = await wallet.signTx(unsignedTx);
} catch (e) {
  if (e.message?.includes("user declined")) {
    // User cancelled — don't retry automatically
    return { error: "cancelled", retry: false };
  }
  if (e.message?.includes("insufficient")) {
    const required = parseRequiredAda(e.message);
    return { error: "insufficient_funds", required };
  }
  throw e; // Unknown error
}

Idempotency

Andamio's state machine is designed for safe retries:

  • Registration is idempotent: Registering the same tx_hash twice returns 409 Conflict but doesn't create duplicate records
  • DB syncs are idempotent: If a sync is interrupted and retried, it produces the same result
  • Draft upserts: Creating a commitment draft that already exists updates it rather than duplicating

This means if you're unsure whether an operation completed, it's safe to retry.

Debugging

Check Transaction Status

curl -H "X-API-Key: $API_KEY" \
  https://preprod.api.andamio.io/api/v2/tx/status/$TX_HASH

View Pending Transactions

curl -H "X-API-Key: $API_KEY" \
     -H "Authorization: Bearer $JWT" \
  https://preprod.api.andamio.io/api/v2/tx/pending

Check On-Chain State

For preprod:

curl https://preprod.andamioscan.io/api/tx/$TX_HASH

For mainnet:

curl https://andamioscan.io/api/tx/$TX_HASH

Best Practices

  1. Always check before rebuilding expired TXs — Avoid duplicate on-chain actions
  2. Implement SSE for real-time updates — Better UX than polling
  3. Store tx_hash immediately after submit — Enables recovery if registration fails
  4. Use draft-first flow — Provides instant feedback and safer recovery
  5. Log state transitions — Helps debug issues in production

Next Steps