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:
| State | Meaning | Recovery |
|---|---|---|
updated | Success — DB synced | None needed |
failed | DB sync failed after retries | Usually auto-heals; see below |
expired | Not confirmed on-chain in time | Rebuild and resubmit |
Common API Errors
| Code | Meaning | Resolution |
|---|---|---|
400 | Bad request / validation error | Check request body against API Reference |
401 | Invalid or expired JWT | Re-authenticate user via wallet signature |
403 | Insufficient permissions | User lacks required role (e.g., not a teacher) |
409 | Duplicate registration | Transaction already registered — safe to ignore |
422 | Unprocessable entity | Semantic error (e.g., module doesn't exist) |
500 | Internal server error | Retry with exponential backoff |
502 | Upstream service error | Cardano 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 itNetwork 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:
| Error | Cause | Resolution |
|---|---|---|
| User rejected | User cancelled signing | Prompt to try again |
| Insufficient funds | Not enough ADA | Show required amount |
| TX too large | Too many inputs/outputs | Contact support |
| Collateral not set | dApp collateral not configured | Prompt 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_hashtwice returns409 Conflictbut 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_HASHView Pending Transactions
curl -H "X-API-Key: $API_KEY" \
-H "Authorization: Bearer $JWT" \
https://preprod.api.andamio.io/api/v2/tx/pendingCheck On-Chain State
For preprod:
curl https://preprod.andamioscan.io/api/tx/$TX_HASHFor mainnet:
curl https://andamioscan.io/api/tx/$TX_HASHBest Practices
- Always check before rebuilding expired TXs — Avoid duplicate on-chain actions
- Implement SSE for real-time updates — Better UX than polling
- Store
tx_hashimmediately after submit — Enables recovery if registration fails - Use draft-first flow — Provides instant feedback and safer recovery
- Log state transitions — Helps debug issues in production
Next Steps
- API Integration — Complete transaction flow
- Transaction State Machine — State transition details
- API Reference — Endpoint documentation